i18n

我们推荐使用 Lingui 来创建国际化(i18n)应用程序。

NOTE

使用 create-pareto 创建项目时,可以选择带有 i18n 选项的模板。

完整的例子请参考

安装

pnpm i @lingui/cli @lingui/macro @types/accepts -D
pnpm i babel-plugin-macros @lingui/core @lingui/react accepts

设置

提取和编译消息

在项目的根目录创建 lingui.config.js

/** @type {import('@lingui/conf').LinguiConfig} */
module.exports = {
  locales: ['en', 'zh'],
  sourceLocale: 'en',
  catalogs: [
    {
      path: '<rootDir>/app/{name}/locales/{locale}/messages',
      include: ['<rootDir>/app/{name}/'],
    },
  ],
  format: 'po',
}

在项目的根目录创建 babel.config.js

module.exports = {
  plugins: ['macros'],
}

更新你的 package.json 文件。

{
  "scripts": {
    "extract": "lingui extract",
    "compile": "lingui compile"
  }
}

运行 pnpm extract 提取消息。 运行 pnpm compile 编译消息。

在项目中使用

使用 @lingui/macroTrans 来翻译你的文本。

每次请求动态加载路由消息

在项目的根目录创建或更新 global.d.ts

/// <reference types="react/canary" />
/// <reference types="@paretojs/core/env" />

declare global {
  interface Window {
    __INITIAL_DATA__: any
    __LOCALE__: string
    __LOCALE_MESSAGE__: any
  }
}

export {}

在项目的根目录创建 i18n.ts

import { i18n, Messages } from '@lingui/core'

export function loadCatalog(page: string, locale: string) {
  /* 在生产环境中,当需要加载消息时,
   ** 可以在 pnpm build 后使用脚本将 locales 文件夹的内容移动到 .pareto 文件夹,并在此处修改导入路径。
   */
  const data = require(`./app/${page}/locales/${locale}/messages`)
  return data.messages
}

export function initLinguiServer(messages: Messages, locale: string) {
  if (locale !== i18n.locale) {
    i18n.loadAndActivate({ locale, messages })
  }
}

export function initLinguiClient() {
  const locale = window.__LOCALE__
  const messages = window.__LOCALE_MESSAGE__

  i18n.load(locale, messages)
  i18n.activate(locale)
}

在项目的根目录修改 server-entry.tsx 文件。

import accepts from 'accepts'
import { initLinguiServer, loadCatalog } from './i18n'
import { I18nProvider } from '@lingui/react'
import { i18n } from '@lingui/core'

const app = express()
const ABORT_DELAY = 5_000

app.get('*', async (req, res, next) => {
  const path = req.path.slice(1)
  const accept = accepts(req)
  const locale = accept.language(['en', 'zh']) || 'en'
  const messages = loadCatalog(path, locale)
  initLinguiServer(messages, locale)

  const handler = paretoRequestHandler({
    delay: ABORT_DELAY,
    pageWrapper: Page => {
      return props => (
        <I18nProvider i18n={i18n}>
          <Page {...props} />
          <script
            dangerouslySetInnerHTML={{
              __html: `
            window.__LOCALE__ = "${locale}";
            window.__LOCALE_MESSAGE__ = JSON.parse('${JSON.stringify(
              messages,
            )}');
          `,
            }}
          />
        </I18nProvider>
      )
    },
  })

  await handler(req, res)
})

在项目的根目录修改 client-entry.tsx 文件。

import { I18nProvider } from '@lingui/react'
import { i18n } from '@lingui/core'
import { initLinguiClient } from './i18n'

const startApp = async (Page: ParetoPage) => {
  const root = document.getElementById('main') as HTMLElement
  const __INITIAL_DATA__ = window.__INITIAL_DATA__ as Record<string, any>
  initLinguiClient()

  await Page.setUpClient?.()

  hydrateRoot(
    root,
    <StrictMode>
      <PageContext>
        <I18nProvider i18n={i18n}>
          <Page initialData={__INITIAL_DATA__} />
        </I18nProvider>
      </PageContext>
    </StrictMode>,
  )
}