국제화 (다국어 지원)
Next.js를 사용하면 여러 언어를 지원하도록 콘텐츠 라우팅 및 렌더링을 구성할 수 있습니다. 사이트를 다양한 locale에 맞게 조정하려면 번역된 콘텐츠(로컬라이제이션)와 국제화된 경로가 포함됩니다.
용어
Locale: 언어 및 서식 기본 설정 집합에 대한 식별자입니다. 여기에는 일반적으로 사용자가 선호하는 언어와 해당 지역이 포함됩니다.
- en-US: 미국에서 사용되는 영어
- nl-NL: 네덜란드에서 사용되는 네덜란드어
- nl: 네덜란드어, 특정 지역 없음
라우팅 개요
브라우저에서 사용자의 언어 기본 설정을 사용하여 사용할 locale을 선택하는 것이 좋습니다. 기본 언어를 변경하면 애플리케이션으로 들어오는 Accept-Language 헤더가 수정됩니다.
예를 들어 다음 라이브러리를 사용하면 수신 Request을 살펴보고, Headers, 지원하려는 locales 및 기본 locale에 따라 선택할 locale을 결정할 수 있습니다.
import { match } from '@formatjs/intl-localematcher';
import Negotiator from 'negotiator';
let headers = { 'Accept-Language': 'en-US,en;q=0.5' };
let languages = new Negotiator(headers).languages();
let locales = ['en-US', 'nl-NL', 'nl'];
let defaultLocale = 'en-US';
match(languages, locales, defaultLocale); // -> 'en-US'
라우팅은 하위 경로(/fr/products) 또는 도메인(my-site.fr/products)을 통해 국제화할 수 있습니다. 이 정보를 사용하면 이제 미들웨어 내부의 locale에 따라 사용자를 리디렉션할 수 있습니다.
// middleware.js
import { NextResponse } from 'next/server'
let locales = ['en-US', 'nl-NL', 'nl']
// Get the preferred locale, similar to above or using a library
function getLocale(request) { ... }
export function middleware(request) {
// Check if there is any supported locale in the pathname
const pathname = request.nextUrl.pathname
const pathnameIsMissingLocale = locales.every(
(locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
)
// Redirect if there is no locale
if (pathnameIsMissingLocale) {
const locale = getLocale(request)
// e.g. incoming request is /products
// The new URL is now /en-US/products
return NextResponse.redirect(
new URL(`/${locale}/${pathname}`, request.url)
)
}
}
export const config = {
matcher: [
// Skip all internal paths (_next)
'/((?!_next).*)',
// Optional: only run on root (/) URL
// '/'
],
}
마지막으로 app/ 내의 모든 특수 파일이 app/[lang] 아래에 중첩되도록 합니다. 이렇게 하면 Next.js 라우터가 경로에서 다양한 locale을 동적으로 처리하고 모든 레이아웃과 페이지에 lang 매개 변수를 전달할 수 있습니다. 예를 들어
// app/[lang]/page.js
// 이제 현재 locale에 액세스할 수 있습니다.
// e.g. /en-US/products -> `lang` is "en-US"
export default async function Page({ params: { lang } }) {
return ...
}
루트 레이아웃은 새 폴더(예: app/[lang]/layout.js)에 중첩할 수도 있습니다.
현지화
사용자가 선호하는 locale에 따라 표시되는 콘텐츠를 변경하는 것, 즉 로컬라이제이션은 Next.js에만 국한된 것이 아닙니다. 아래에 설명된 패턴은 모든 웹 애플리케이션에서 동일하게 작동합니다.
애플리케이션 내에서 영어와 네덜란드어 콘텐츠를 모두 지원하고자 한다고 가정해 보겠습니다. 일부 키에서 현지화된 문자열로의 매핑을 제공하는 객체인 두 개의 서로 다른 "사전"을 유지할 수 있습니다. 예를 들어
dictionaries/en.json
{
"products": {
"cart": "Add to Cart"
}
}
dictionaries/nl.json
{
"products": {
"cart": "Toevoegen aan Winkelwagen"
}
}
그런 다음 요청된 locale에 대한 번역을 로드하는 getDictionary 함수를 만들 수 있습니다:
// app/[lang]/dictionaries.js
import 'server-only';
const dictionaries = {
en: () => import('./dictionaries/en.json').then((module) => module.default),
nl: () => import('./dictionaries/nl.json').then((module) => module.default),
};
export const getDictionary = async (locale) => dictionaries[locale]();
현재 선택된 언어가 주어지면 레이아웃이나 페이지 내에서 사전을 가져올 수 있습니다.
import { getDictionary } from './dictionaries';
export default async function Page({ params: { lang } }) {
const dict = await getDictionary(lang); // en
return <button>{dict.products.cart}</button>; // Add to Cart
}
app/ 디렉토리의 모든 레이아웃과 페이지는 기본적으로 서버 컴포넌트를 사용하므로 번역 파일의 크기가 클라이언트 측 JavaScript 번들 크기에 영향을 미치는 것에 대해 걱정할 필요가 없습니다. 이 코드는 서버에서만 실행되며 결과 HTML만 브라우저로 전송됩니다.
정적 생성
특정 locale 집합에 대한 정적 경로를 생성하려면 page 또는 layout에 generateStaticParams를 사용할 수 있습니다. 예를 들어 루트 레이아웃에서 전역으로 사용할 수 있습니다:
// app/[lang]/layout.js
export async function generateStaticParams() {
return [{ lang: 'en-US' }, { lang: 'de' }];
}
export default function Root({ children, params }) {
return (
<html lang={params.lang}>
<body>{children}</body>
</html>
);
}
셀프 실습
정적생성 테스트를 하는데 이해가 안되는 부분이 있었다.
params.lang 콘솔을 찍어보면 en-US를 잘 가져오는데 왜 안되는걸까?
한참을 찾다가 콘솔창을 봤더니 Prop `lang` did not match. Server: "en" Client: "en-US"...
원인은 최상단 루트의 layout에서 'en'으로 지정했기 때문이였다...
근데 이렇게 되면, 루트의 layout.js가 존재해도, lang이 정의되지않았다면 되지 않을까? 싶어서 테스트를 해봤지만 역시나 클라이언트의 lang과 서버의 lang이 맞지 않는다는 오류가 뜬다..
generateStaticParams 는 가능한 빨리 데이터를 가져와서 웹사이트 속도를 높이는데 쓰인다. 예를들어 매번 API 요청을 통해 목록을 가져오는것이 아니라, 항목이 정해져있고 자주 변하지 않는것이라면, 웹사이트를 처음 로딩할때 한번만 호출할때 쓰는 목적으로 쓰인다. 여기서 예시로 든 '언어 목록' 처럼.