Internationalization(๊ตญ์ ํ)
Next.js๋ฅผ ์ฌ์ฉํ๋ฉด ์ฌ๋ฌ ์ธ์ด๋ฅผ ์ง์ํ๋๋ก ์ฝํ ์ธ ๋ผ์ฐํ ๋ฐ ๋ ๋๋ง์ ๊ตฌ์ฑํ ์ ์์ต๋๋ค. ์ฌ์ดํธ๋ฅผ ๋ค์ํ ๋ก์บ์ ๋ง๊ฒ ์กฐ์ ํ๋ ค๋ฉด ๋ฒ์ญ๋ ์ฝํ ์ธ (๋ก์ปฌ๋ผ์ด์ ์ด์ )์ ๊ตญ์ ํ๋ ๊ฒฝ๋ก๊ฐ ํฌํจ๋ฉ๋๋ค.
1. Terminology(์ฉ์ด)
Locale(ํ์ง์): ์ธ์ด ๋ฐ ์์ ๊ธฐ๋ณธ ์ค์ ์งํฉ์ ๋ํ ์๋ณ์์
๋๋ค. ์ฌ๊ธฐ์๋ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ์๊ฐ ์ ํธํ๋ ์ธ์ด์ ํด๋น ์ง์ญ์ ์ธ์ด๊ฐ ํฌํจ๋ฉ๋๋ค.
en-US: ๋ฏธ๊ตญ์์ ์ฌ์ฉ๋๋ ์์ดnl-NL: ๋ค๋๋๋์์ ์ฌ์ฉ๋๋ ๋ค๋๋๋์ดnl: ๋ค๋๋๋์ด, ํน์ ์ง์ญ ์์
2. Routing Overview
๋ธ๋ผ์ฐ์ ์์ ์ฌ์ฉ์์ ์ธ์ด ๊ธฐ๋ณธ ์ค์ ์ ์ฌ์ฉํ์ฌ ์ฌ์ฉํ Locale์ ์ ํํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
๊ธฐ๋ณธ ์ธ์ด๋ฅผ ๋ณ๊ฒฝํ๋ฉด ์ ํ๋ฆฌ์ผ์ด์
์ผ๋ก ๋ค์ด์ค๋ Accept-Language ํค๋๊ฐ ์์ ๋ฉ๋๋ค.
์๋ฅผ ๋ค์ด, ๋ค์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด ์์ ์์ฒญ์ ์ดํด๋ณด๊ณ ํค๋, ์ง์ํ๋ ค๋ Locale ๋ฐ ๊ธฐ๋ณธ Locale์ ๋ฐ๋ผ ์ ํํ Locale์ ๊ฒฐ์ ํ ์ ์์ต๋๋ค.
1import { match } from '@formatjs/intl-localematcher'2import Negotiator from 'negotiator'34let headers = { 'accept-language': 'en-US,en;q=0.5' }5let languages = new Negotiator({ headers }).languages()6let locales = ['en-US', 'nl-NL', 'nl']7let defaultLocale = 'en-US'89match(languages, locales, defaultLocale) // -> 'en-US'
๋ผ์ฐํ
์ ํ์ ๊ฒฝ๋ก(/fr/products) ๋๋ ๋๋ฉ์ธ(my-site.fr/products)์ ํตํด ๊ตญ์ ํํ ์ ์์ต๋๋ค.
์ด ์ ๋ณด๋ฅผ ํตํด ์ด์ ๋ฏธ๋ค์จ์ด ๋ด๋ถ์ Locale์ ๋ฐ๋ผ ์ฌ์ฉ์๋ฅผ ๋ฆฌ๋๋ ์
ํ ์ ์์ต๋๋ค.
12let locales = ['en-US', 'nl-NL', 'nl']34// Get the preferred locale, similar to the above or using a library5function getLocale(request) { ... }67export function middleware(request) {8// Check if there is any supported locale in the pathname9const { pathname } = request.nextUrl10const pathnameHasLocale = locales.some(11(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`12)1314if (pathnameHasLocale) return1516// Redirect if there is no locale17const locale = getLocale(request)18request.nextUrl.pathname = `/${locale}${pathname}`19// e.g. incoming request is /products20// The new URL is now /en-US/products21return Response.redirect(request.nextUrl)22}2324export const config = {25matcher: [26// Skip all internal paths (_next)27'/((?!_next).*)',28// Optional: only run on root (/) URL29// '/'30],31}
๋ง์ง๋ง์ผ๋ก app/ ๋ด๋ถ์ ๋ชจ๋ ํน์ ํ์ผ์ด app/[lang] ์๋์ ์ค์ฒฉ๋๋๋ก ํฉ๋๋ค.
์ด๋ ๊ฒ ํ๋ฉด Next.js ๋ผ์ฐํฐ๊ฐ ๋ผ์ฐํธ์์ ๋ค์ํ Locale์ ๋์ ์ผ๋ก ์ฒ๋ฆฌํ๊ณ , ๋ชจ๋ ๋ ์ด์์๊ณผ ํ์ด์ง์ lang ๋งค๊ฐ ๋ณ์๋ฅผ ์ ๋ฌํ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด:
1// You now have access to the current locale2// e.g. /en-US/products -> `lang` is "en-US"3export default async function Page({ params: { lang } }) {4return ...5}
๋ฃจํธ ๋ ์ด์์์ ์ ํด๋์ ์ค์ฒฉํ ์๋ ์์ต๋๋ค. (e.g. app/[lang]/layout.js)
3. Localization(์ง์ญํ)
์ฌ์ฉ์๊ฐ ์ ํธํ๋ Locale ๋๋ ํ์งํ์ ๋ฐ๋ผ ํ์๋๋ ์ฝํ ์ธ ๋ฅผ ๋ณ๊ฒฝํ๋ ๊ฒ์ Next.js์๋ง ๊ตญํ๋ ๊ฒ์ด ์๋๋๋ค. ์๋์ ์ค๋ช ๋ ํจํด์ ๋ชจ๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋์ผํ๊ฒ ์๋ํฉ๋๋ค.
์ ํ๋ฆฌ์ผ์ด์ ๋ด์์ ์์ด์ ๋ค๋๋๋์ด ์ฝํ ์ธ ๋ฅผ ๋ชจ๋ ์ง์ํ๋ค๊ณ ๊ฐ์ ํด ๋ณด๊ฒ ์ต๋๋ค. ์ผ๋ถ ํค์์ ํ์งํ๋ ๋ฌธ์์ด๋ก์ ๋งคํ์ ์ ๊ณตํ๋ ๊ฐ์ฒด์ธ ๋ ๊ฐ์ ์๋ก ๋ค๋ฅธ โ์ฌ์ (dictionaries)โ์ ์ ์งํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด:
1{2"products": {3"cart": "Add to Cart"4}5}
1{2"products": {3"cart": "Toevoegen aan Winkelwagen"4}5}
๊ทธ๋ฐ ๋ค์ getDictionary ํจ์๋ฅผ ๋ง๋ค์ด ์์ฒญ๋ locale์ ๋ํ ๋ฒ์ญ์ ๋ก๋ํ ์ ์์ต๋๋ค:
1import 'server-only'23const dictionaries = {4en: () => import('./dictionaries/en.json').then(module => module.default),5nl: () => import('./dictionaries/nl.json').then(module => module.default),6}78export const getDictionary = async locale => dictionaries[locale]()
ํ์ฌ ์ ํ๋ ์ธ์ด๊ฐ ์ฃผ์ด์ง๋ฉด, ๋ ์ด์์์ด๋ ํ์ด์ง ๋ด์์ ์ฌ์ ์ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.
1import { getDictionary } from './dictionaries'23export default async function Page({ params: { lang } }) {4const dict = await getDictionary(lang) // en5return <button>{dict.products.cart}</button> // Add to Cart6}
app/ ๋๋ ํ ๋ฆฌ์ ๋ชจ๋ ๋ ์ด์์๊ณผ ํ์ด์ง๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์๋ฒ ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ๋ฏ๋ก,
๋ฒ์ญ ํ์ผ์ ํฌ๊ธฐ๊ฐ ํด๋ผ์ด์ธํธ ์ธก JavaScript ๋ฒ๋ค ํฌ๊ธฐ์ ์ํฅ์ ๋ฏธ์น๋ ๊ฒ์ ๋ํด ๊ฑฑ์ ํ ํ์๊ฐ ์์ต๋๋ค.
์ด ์ฝ๋๋ ์๋ฒ์์๋ง ์คํ๋๋ฉฐ ๊ฒฐ๊ณผ HTML๋ง ๋ธ๋ผ์ฐ์ ๋ก ์ ์ก๋ฉ๋๋ค.
4. Static Generation(์ ์ ์์ฑ)
์ง์ ๋ locales ์งํฉ์ ๋ํ ์ ์ ๊ฒฝ๋ก๋ฅผ ์์ฑํ๋ ค๋ฉด ํ์ด์ง ๋๋ ๋ ์ด์์์ generateStaticParams๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด ๋ฃจํธ ๋ ์ด์์์์ ์ ์ญ์ผ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค:
1export async function generateStaticParams() {2return [{ lang: 'en-US' }, { lang: 'de' }]3}45export default function Root({ children, params }) {6return (7<html lang={params.lang}>8<body>{children}</body>9</html>10)11}