🇰🇷

Nextjs + i18n

Tags
Nextjs
i18n
internationalize
Date
2021/03/15

개요

nextjs 를 global service 출시를 해보면서 정리한 내용 + 경험을 정리함.

Nextjs 세팅

nextjs 10.0.0 버젼 이후로 내부적으로 i18n 지원 기능이 있다. locale list, default locale, 등을 제공하고 설정을 추가하면 nextjs 에서 자동으로 라우팅이 된다. (i18n routing)

next.config.js

next.config.js 를 설정한 것만으로 internationalized routing 을 구현할 수 있다.

Locale Identifier

일반적으로 locale identifier 은 language, region, script 로 구성된다 (language-region-script). 하지만 region, script 는 optional
en-US - English as spoken in the United States
nl-NL - Dutch as spoken in the Netherlands
nl - Dutch, no specific region

sub path

module.exports = { i18n: { locales: ['en-US', 'fr', 'nl-NL'], defaultLocale: 'en-US', }, }
JavaScript
위와 같이 설정했을 때, en-US 는 default path 로 설정될 것이고, fr, nl-nl 은 locale 이 prefix 된 path 를 추가해야 적용될 것이다. 예를들어, pages/blog.js 로 라우팅 된 경로는 다음과 같이 적용될 것이다
/blog - en-US
/fr/blog
/nl-nl/blog

domain

locale 별로 다른 도메인을 설정할 수 있다.
// next.config.js module.exports = { i18n: { locales: ['en-US', 'fr', 'nl-NL', 'nl-BE'], defaultLocale: 'en-US', domains: [ { domain: 'example.com', defaultLocale: 'en-US', }, { domain: 'example.fr', defaultLocale: 'fr', }, { domain: 'example.nl', defaultLocale: 'nl-NL', // specify other locales that should be redirected // to this domain locales: ['nl-BE'], }, ], }, }
JavaScript
example.com/blog
example.fr/blog
example.nl/blog
example.nl/nl-BE/blog

Automatic locale detection

Accept-Language 헤더를 기준으로 locale 을 자동으로 감지 가능
만약 domain routing 을 적용하고 Accept-Languagefr;q=0.9 유저가 example.com 에 방문하면 example.kr 로 redirect 된다.
다음 설정을 통해서 automatic locale detection 을 설정 끌 수 있다
// next.config.js module.exports = { i18n: { localeDetection: false, }, }
Plain Text
위 설정을 끄면 자동으로 locale 을 감지하고 redirect 하지 않고, 설정한 routing 을 통해서 locale 값을 반환한다.

Accessing the locale information

다음 방법을 통해서 locale 정보에 접근할 수 있다.
useRouter() 를 사용
const router = useRouter(); const { locale, // current locale locales, // all configured locales defaultLocale, } = router;
JavaScript
getStaticProps , getServerSideProps 에서 context (참고)

SEO

nextjs 가 lang attribute 를 자동으로 html 태그에 추가시킴
hreflang meta tag 를 추가하는 것은 추가 작업 해야함 (참고) (일반적으로는 언어별 또는 지역별 페이지를 명시적으로 지정하는 것이 좋다.)

Edge Network

vercel 을 통해서 배포하면 Edge Network 를 사용해 sub path 나 domain 을 통해서 dynamic routing 을 구현한다. Edge Network 는 다음 의 링크에 자세히 보자

global hitple project 적용

일본, 대만, 베트남 타겟
app 단위의 size 가 아니라 Product Detail Page 단일 페이지
각 국가별로 마케팅 컨텐츠 및 링크 사용
도메인 기반 locale 사용
auto locale detection off
// next.config.js module.exports = { ... i18n: { localeDetection: false, locales: ["ja", "ko", "zh-tw", "vi"], defaultLocale: "ko", domains: [ { domain: "vn.hitple.com", defaultLocale: "vi", }, { domain: "tw.hitple.com", defaultLocale: "zh-tw", }, { domain: "jp.hitple.com", defaultLocale: "ja", }, { domain: "kr.hitple.com", defaultLocale: "ko", }, ], }, ... }
JavaScript

i18next, react-i18next 를 사용

// _app.tsx ... import i18n from "i18next"; import { initReactI18next } from "react-i18next"; import { useRouter } from "next/router"; import Korean from "src/locales/ko.json"; // {title: '안녕하세요'} import Japanese from "src/locales/ja.json"; // {title: '곤니치와'} import Taiwan from "src/locales/tw.json"; // {title: 'hello'} import Vietnamse from "src/locales/vi.json"; export default function MyApp({ Component, pageProps }: AppProps) { const { locale } = useRouter(); i18n.use(initReactI18next).init({ resources: { ko: { translation: Korean, }, ja: { translation: Japanese, }, vi: { translation: Vietnamse, }, "zh-tw": { translation: Taiwan, }, }, lng: locale ?? Locale.KOREAN, fallbackLng: Locale.KOREAN, interpolation: { escapeValue: false, }, }); ... return ( ... ); }
JavaScript
// Title.tsx import React from "react"; import styled from "styled-components"; import { useTranslation } from "react-i18next"; interface Props { className?: string; } const Title = styled.h1``; const Title = ({ className, }: Props) => { const { t } = useTranslation(); return ( <Title className={className}> {t("title")} </Title> ); }; export default ReviewCardImageList;
JavaScript

각 국가별로 필요한 소스 import (리소스 최소화를 위해서)

import React from "react"; import { useRouter } from "next/router"; import { Locale } from "src/interfaces/common"; import LazyloadCSS from "src/components/common/LazyLoadCSS"; const FontStyleSheetImport = () => { const { locale } = useRouter(); return ( <> {locale === Locale.JAPANESE ? ( <> <link rel="preconnect" href="https://fonts.gstatic.com" /> <LazyloadCSS href="/styles/NotoSansJP.min.css" /> <LazyloadCSS href="/styles/ja.css" /> </> ) : locale === Locale.TAIWAN ? ( <> <link rel="preconnect" href="https://fonts.gstatic.com" /> <LazyloadCSS href="/styles/NotoSansTC.min.css" /> <LazyloadCSS href="/styles/tw.css" /> </> ) : locale === Locale.VIETNAMSE ? ( <> <link rel="preconnect" href="https://fonts.gstatic.com" /> <LazyloadCSS href="/styles/NotoSans.min.css" /> <LazyloadCSS href="/styles/vi.css" /> </> ) : ( // 한국의 경우 내부적으로 사용하고 있는 폰트를 파일화해서 사용 <> <link rel="preload" href="/NotoSansKR.otf" as="font" type="font/otf" crossOrigin="" /> <LazyloadCSS href="/styles/ko.css" /> </> )} </> ); }; export default FontStyleSheetImport;
JavaScript

부록

참고