Try   HackMD

Front-End React Next.js Router

【學習筆記】Next.js 實現多國語系:react-i18next & next-i18next

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

前言

在開發網站時,多國語系功能(i18n)是很常見的需求,能根據使用者需求切換網站顯示的語言。這陣子在 Next.js 專案中意外踩了幾個坑,寫下學習筆記作為紀錄。

本篇分為以下幾個段落:

  • What is i18n?
  • 如何實作
    • Next.js 內建:路由層級
    • 選擇套件:react-i18next & next-i18next
  • 使用範例
    • SSG/SSR:使用 next-i18next
    • SPA:使用 react-i18next

What is i18n?

i18n 是由 internationalization(國際化)英文縮寫而來,18 代表 i 到 n 之間的字母數量。

透過 i18n 多國語系功能,能夠讓不同語系的使用者,根據需求選定顯示語言和格式,減少在地化的時間成本,達到國際化的目的。

如何實作

Next.js 內建:路由層級

在介紹套件之前,先介紹 Next.js 官方內建 i18n 功能,透過路由層級(routing)設定不同語言轉至不同路徑,設定範例如下:

// next.config.js module.exports = { i18n: { locales: ['en', 'zh', 'jp'], defaultLocale: 'en', }, }

根據上述配置,即可搭配 next/linknext/router,根據路由顯示對應的語言,路徑如下:

  • /posts:預設為 en
  • /zh/blog
  • /jp/blog

其中需注意 Page RouterApp Router 在路由設定上有所不同,詳細可參考官方提供的範例,以下介紹常見的 i18n 套件作為範例。

關於 Page router 和 App router 的差別,可參考之前的筆記:【學習筆記】Next.js 路由系統:App Router vs Page Router

選擇套件:react-i18next & next-i18next

可參考上述兩種套件的 npm 下載數:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

使用範例

SSR:使用 next-i18next

(1) 首先是安裝套件:

npm install next-i18next --save

(2) 在根目錄新增 next-i18next.config.js 設定檔:

// next-i18next.config.js module.exports = { i18n: { locales: ['en', 'jp', 'zh'], defaultLocale: 'en', }, fallbackLng: { default: ['en'], }, };

(3) 在 next.config.js 設定檔引入使用 next-i18next

// next.config.js const { i18n } = require('./next-i18next.config') module.exports = { i18n, }

(4) 修改 src/pages/_app.tsx 檔案,以 appWithTranslation 這個 HOC(高階組件)包住整個 App:

// src/pages/_app.tsx import { appWithTranslation } from 'next-i18next' const MyApp = ({ Component, pageProps }) => ( <Component {...pageProps} /> ) export default appWithTranslation(MyApp)

【補充】Higher-Order Components(HOC,高階組件)

  • HOC 並不是 React 提供的 API,而是和 JavsScript 中的 Higher Order Function(高階函式)類似的一個函式,高階函式可代入另一個函式作為參數,最終回傳一個函式作為結果
  • 而 HOC 則是可代入元件(Component)作為參數,並回傳一個新的元件
  • 目的是將共用邏輯放在 HOC 中,變動的部分由 Component 的 props 和 state 傳入

(5) 在 public/locales/<locale>/<namespace>.json 路徑加入多國語系檔案,架構參考如下:

.
└── public
    └── locales
        ├── en
        |   ├── posts.json
        |   └── common.json
        ├── jp
        |   ├── posts.json
        |   └── common.json
        └── zh
            ├── posts.json
            └── common.json

(6) 在頁面引入語系檔案:

import { serverSideTranslations } from 'next-i18next/serverSideTranslations' export async function getStaticProps({ locale }) { return { props: { ...(await serverSideTranslations(locale, [ 'common', 'posts', ])), // Will be passed to the page component as props }, } }

(7) 即可在頁面引入 useTranslation hook 使用,注意需從 next-i18next 引用:

import { useTranslation } from 'next-i18next' export const Header = () => { const { t } = useTranslation('common') return ( <div> <h1>{t('home-title')}</h1> </div> ) }

SPA:使用 react-i18next

非 SSR、SSG 環境,只需直接引用 react-i18next 套件即可。

(1) 首先是安裝套件:

npm install react-i18next i18next --save

(2) 建立 app/i18n 目錄,放置管理 i18n 的相關檔案(初始 i18n、翻譯文檔),範例如下:

  • i18n/index.ts:初始 i18n 相關設定
// i18n/index.ts import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import resources from './lang-resource'; i18n .use(initReactI18next) // 初始化設定 .init({ resources, // 引入定義語系與對應文字的 json 檔 lng: 'en', // 預設語系為 en fallbackLng: 'en', // 若找不到對應語系則回傳 en defaultNS: 'common', preload: ['en', 'ja', 'zh'], ns: 'common', interpolation: { escapeValue: false, }, parseMissingKeyHandler: () => { return ''; } , react: { useSuspense: false }, }); export default i18n;
  • i18n/lang-resource:定義語系與對應文字的 json 檔
// i18n/lang-resource const resources = { en: { common: { 'login': 'Login', 'logout': 'Log out', }, }, ja: { common: { 'login': 'ログイン', 'logout': 'ログアウト', }, }, zh: { common: { 'login': '登入', 'logout': '登出', }, }, }; export default resources;

(3) 設定完成後,即可在 app/pages/layout 引入 i18n/index.ts 使用:

// app/pages/layout import './i18n' export default function RootLayout() { // ... }

app/pages/login/page.tsx 為例:

// app/pages/login/page.tsx import { useTranslation } from 'react-i18next'; export default function Login() { const { t } = useTranslation(); return ( <div> <p>{t('login')}</p> <p>{t('logout')}</p> <div/> ) }

(4) 若要切換語系,可使用 i18n.changeLanguage 方法:

// app/component/header.tsx import { useTranslation } from 'react-i18next'; export function Header() { const { t, i18n } = useTranslation(); const handleChangeLanguage = () => { i18n.changeLanguage('en') } }

結語

其實一開始研究實作 i18n 功能時,看到官方文件整個頭昏眼花,Next.js 內建的路由實作上又稍嫌複雜,React 生態不像 Angular 框架能直接引入內建功能實作,反而有很多種套件能夠選擇使用。

但問題來了,套件引入使用下來卻噴一堆 Error,才發現到由於 Next.js 路由系統架構,需搭配支援 App Router 或 Page Router 的套件使用,其實想成是在實作 React 多國語系功能就單純許多;雖然文中提及的 Page Router 還沒有機會實作,概念上還有點模糊,但還是先寫下筆記留作紀錄。

參考資料