iT邦幫忙

2023 iThome 鐵人賽

DAY 21
0

來設定 Next 專案的 i18n 功能。

Next App Router 架構納入了 React Server component ,所以要想辦法設定能夠在 Client 跟 Server 兩邊通用的 i18n 架構。

主要參考這篇: i18n with Next.js 13 and app directory / App Router

首先建立動態語系的頁面。

pnpm exec nx generate @nx/next:page --name="[lang]"

安裝套件。

pnpm add -D react-i18next i18next-resources-to-backend

首先來設定 Server Component 的部分,在 src 目錄下新增以下結構。

src
└── i18n
    ├── locales
    │   ├── en
    │   │   └── common.json
    │   └── zh-TW
    │       └── common.json
    ├── index.ts
    └── settings.ts

locales 目錄下根據語系開立目錄,並新增不同 namespace 的語系檔案。

settings 做整體的設定,包含使用語系,預設 namespace 等。

export const fallbackLng = 'en';
export const languages = [fallbackLng, 'zh-TW'];
export const defaultNS = 'common';

export function getOptions(
  lng = fallbackLng,
  ns = defaultNS as string | string[],
) {
  return {
    // debug: true,
    supportedLngs: languages,
    fallbackLng,
    lng,
    fallbackNS: defaultNS,
    defaultNS,
    ns,
  };
}

index 中載入翻譯檔,設定並生成 i18n 實體,然後製作翻譯函式 useTranslation,這邊雖然用 hook 的命名方式但並不是 hook。

// index.ts
import { createInstance } from 'i18next';
import resourcesToBackend from 'i18next-resources-to-backend';
import { initReactI18next } from 'react-i18next/initReactI18next';
import { getOptions } from './settings';

const initI18next = async (lng: string, ns?: string | string[]) => {
  const i18nInstance = createInstance();
  await i18nInstance
    .use(initReactI18next)
    .use(
      resourcesToBackend(
        (language: string, namespace: string) =>
          import(`./locales/${language}/${namespace}.json`),
      ),
    )
    .init(getOptions(lng, ns));
  return i18nInstance;
};

export async function useTranslation(
  lng: string,
  ns?: string | string[],
  options = {} as { keyPrefix?: string },
) {
  const i18nextInstance = await initI18next(lng, ns);
  return {
    t: i18nextInstance.getFixedT(
      lng,
      Array.isArray(ns) ? ns[0] : ns,
      options.keyPrefix,
    ),
    i18n: i18nextInstance,
  };
}

然後就能用在 page 上了。

import { useTranslation } from '../../src/i18n';

export interface PageProps {
  params: {
    lang: string;
  };
}

export default async function Page({ params: { lang } }: PageProps) {
  const { t } = await useTranslation(lang);
  return <div>{t('select')}</div>;
}

Client Component 的部分就用原本 hook 的方式設定。

追加一個 client.ts。

src
└── i18n
    ├── locales
    │   ├── en
    │   │   └── common.json
    │   └── zh-TW
    │       └── common.json
    ├── index.ts
    ├── client.ts
    └── settings.ts

hook 設定要限定 client side。

'use client';

import { useEffect } from 'react';
import i18next from 'i18next';
import {
  initReactI18next,
  useTranslation as useTranslationOrg,
} from 'react-i18next';
import resourcesToBackend from 'i18next-resources-to-backend';
import { getOptions, languages } from './settings';

const runsOnServerSide = typeof window === 'undefined';

i18next
  .use(initReactI18next)
  .use(
    resourcesToBackend(
      (language: string, namespace: string) =>
        import(`./locales/${language}/${namespace}.json`),
    ),
  )
  .init({
    ...getOptions(),
    lng: undefined,
    detection: {
      order: ['path', 'htmlTag', 'cookie', 'navigator'],
    },
    preload: runsOnServerSide ? languages : [],
  });

export function useTranslation(
  lng: string,
  ns?: string | string[],
  options = {} as { keyPrefix?: string },
) {
  const ret = useTranslationOrg(ns, options);

  useEffect(() => {
    ret.i18n.changeLanguage(lng);
  }, [lng]);

  return ret;
}

這樣如果 page 設定為 Client Component,就用 hook 的方式。

'use client';
import { useTranslation } from '../../src/i18n/client';

export interface PageProps {
  params: {
    lang: string;
  };
}

export default function Page({ params: { lang } }: PageProps) {
  const { t } = useTranslation(lang);
  return <div>{t('select')}</div>;
}

上一篇
NX 自定義 plugin 建立客製化的 generator
下一篇
Next.js Server Component 偵測與記憶語系
系列文
組裝前端開發工具箱,從 NX 入手30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言