For AI agents: the complete documentation index is available at /next/zh/llms.txt, the full documentation bundle is available at /next/zh/llms-full.txt, and this page is available as Markdown at /next/zh/guide/inclusion/internationalization.md.
Lynx
  • 简体中文
  • 国际化

    国际化(Internationalization,简写为 I18n),是指在设计和开发产品、应用程序时,对其进行本地化,以适应不同的文化、地区或语言的目标用户。你可以使用 i18next 等 i18n 库实现国际化,为用户提供易于访问的体验。

    Intl API

    Intl 对象是 ECMAScript 国际化 API 的一个命名空间,提供了一组处理国际化与本地化的方法。通过 Intl API,能够处理数字、日期和时间等相关问题,比如数字格式化、日期和时间格式化。

    目前在 Lynx 中 Intl API 尚未实现,并将在后续版本中支持。如果你需要使用在 Lynx 中使用 Intl API,你可以安装对应的 Polyfill,例如 @formatjs/intl-numberformat @formatjs/intl-datetimeformatintl-pluralrules 等。

    使用 i18next

    i18next 是一个 JavaScript 国际化框架,在 ReactLynx 中使用它有以下优点:

    1. 简洁i18next 提供了简单易用的 API,让在 ReactLynx 中实现国际化变得更加简单
    2. 按需加载:支持按需加载语言资源,减少首屏加载时间
    3. 广泛支持:兼容多种格式和后端,允许与不同的翻译存储解决方案(如 JSON 文件、远程 API 等)轻松集成。
    4. 支持缓存:内置缓存机制加快语言资源的加载速度,提升用户体验。
    5. 丰富的社区支持:拥有庞大的社区和丰富的插件,满足多样化的国际化需求。
    6. 可靠性:在众多项目中得到验证,提供稳定性和可靠性。
    7. 热重载:语言资源的更改可以立即生效,无需重新发布应用。

    安装

    你需要安装 i18next 作为依赖:

    npm
    yarn
    pnpm
    bun
    deno
    npm install i18next@^23.16.8
    Tip

    i18next@24.0.0+ 版本要求运行环境必须能够使用 Intl.pluralRules API,但是目前 Lynx 当中尚未实现该 API,因此需要:

    1. 使用 v23 并且启用 compatibilityJSON: 'v3' 选项
    2. 使用 v24 并且对 Intl.PluralRules API 进行 polyfill

    创建第一个文案翻译

    假设我们有如下的文案资源:

    src/locales/en.json
    {
      "world": "World"
    }

    创建翻译函数只需要以下三个步骤:

    1. 引入文案资源 ./locales/en.json
    2. 使用 createInstance() 函数创建 i18next 实例
    3. 用引入的文案资源初始化 i18n 实例
    src/i18n.ts
    import i18next from 'i18next';
    import type { i18n } from 'i18next';
    
    import enTranslation from './locales/en.json';
    
    const localI18nInstance: i18n = i18next.createInstance();
    
    localI18nInstance.init({
      lng: 'en',
      // The default JSON format needs `Intl.PluralRules` API, which is currently unavailable in Lynx.
      compatibilityJSON: 'v3',
      resources: {
        en: {
          translation: enTranslation, // `translation` is the default namespace
        },
      },
    });
    
    export { localI18nInstance as i18n };
    Tip

    如果你在 TypeScript 文件中引入 *.json, 你需要在 tsconfig.json 文件中设置 compilerOptions.resolveJsonModule 选项为 true

    tsconfig.json
    {
      "compilerOptions": {
        "resolveJsonModule": true
      }
    }

    接下来,可以直接使用 i18n.t 函数来进行文案翻译:

    src/App.tsx
    import { useEffect } from '@lynx-js/react';
    
    import { i18n } from './i18n.js'; 
    
    export function App() {
      useEffect(() => {
        console.log(`Hello, ReactLynx x i18next!`);
      }, []);
    
      return (
        <view>
          <text>Hello, {i18n.t('world')}</text>
        </view>
      );
    }

    同步加载文案资源

    在真实的项目中,通常有多个不同语言的文案资源。

    你可以使用 import.meta.webpackContext API 来一次性将他们全部引入:

    import one-by-one
    // Static-imported locales that can be shown at first screen
    import enTranslation from './locales/en.json';
    import zhTranslation from './locales/zh.json';
    import itTranslation from './locales/it.json';
    import jpTranslation from './locales/jp.json';
    import deTranslation from './locales/de.json';
    import esTranslation from './locales/es.json';
    import frTranslation from './locales/fr.json';
    import idTranslation from './locales/id.json';
    import ptTranslation from './locales/pt.json';
    import.meta.webpackContext
    const localesContext = import.meta.webpackContext('./locales', {
      recursive: false,
      regExp: /\.json$/,
    });
    const enTranslation = localesContext('en.json');

    这些资源也可以被添加到 i18next.init() 中来让首屏渲染中的文案得到翻译:

    src/i18n.ts
    import i18next from 'i18next';
    import type { i18n } from 'i18next';
    
    // Localizations imported statically, available at the initial screen
    const localesContext = import.meta.webpackContext('./locales', {
      recursive: false,
      regExp: /\.json$/,
    });
    
    const localI18nInstance: i18n = i18next.createInstance();
    
    localI18nInstance.init({
      lng: 'en',
      // The default JSON format needs Intl.PluralRules API, which is currently unavailable in Lynx.
      compatibilityJSON: 'v3',
      // Add all statically imported localizations to i18next resources.
      resources: Object.fromEntries(
        localesContext.keys().map((key) => [
          key.match(/\/([^/]+)\.json$/)?.[1] || key,
          {
            translation: localesContext(key) as Record<string, string>,
          },
        ]),
      ),
    });
    
    export { localI18nInstance as i18n };
    Tip

    你可以需要 Rspeedy Type Declaration 来获得 import.meta.webpackContext 的 TypeScript 类型定义

    异步按需加载文案资源

    同步加载资源会使得全部的文案资源都打包在产物中,导致首屏加载性能较差。 我们也可以通过 import() 来异步、按需引入文案资源。

    首先需要安装 i18next-resources-to-backend 作为依赖:

    npm
    yarn
    pnpm
    bun
    deno
    npm install i18next-resources-to-backend

    接下来在 src/i18n.ts 中添加下面的代码:

    src/i18n.ts
    import i18next from 'i18next';
    import type { i18n } from 'i18next';
    import resourcesToBackend from 'i18next-resources-to-backend';
    
    // Localizations imported statically, available at the initial screen
    const localesContext = import.meta.webpackContext('./locales', {
      recursive: false,
      regExp: /(en|zh)\.json$/,
    });
    
    const localI18nInstance: i18n = i18next.createInstance();
    
    // We can only loading resources on a background thread
    if (__JS__) {
      localI18nInstance.use(
        // See: https://www.i18next.com/how-to/add-or-load-translations#lazy-load-in-memory-translations
        resourcesToBackend(
          (language: string) =>
            // Dynamic-imported locales can be used with `i18n.loadLanguages`
            import(`./locales/${language}.json`),
        ),
      );
    }
    
    localI18nInstance.init({
      lng: 'en',
      // The default JSON format needs Intl.PluralRules API, which is currently unavailable in Lynx.
      compatibilityJSON: 'v3',
      // Add all statically imported localizations to i18next resources.
      resources: Object.fromEntries(
        localesContext.keys().map((key) => [
          key.match(/\/([^/]+)\.json$/)?.[1] || key,
          {
            translation: localesContext(key) as Record<string, string>,
          },
        ]),
      ),
      partialBundledLanguages: true,
    });
    
    export { localI18nInstance as i18n };

    在上面的例子中

    1. 一个 i18next 的中间件 i18next-resources-to-backend 在后台线程中被添加到 localI18nInstance.use 当中
    2. 文案资源可以被异步按需加载(其中的部分,如 zh, en 依然同步加载)

    在产物中,可以看到生成了多个 JavaScript 文件,其中包含了文案资源:

    src_locales_it-IT_json.js
    'use strict';
    exports.ids = ['src_locales_it-IT_json'];
    exports.modules = {
      './src/locales/it-IT.json': function (module) {
        module.exports = JSON.parse('{"world": "Mondo"}');
      },
    };
    src_locales_ja-JP_json.js
    'use strict';
    exports.ids = ['src_locales_ja-JP_json'];
    exports.modules = {
      './src/locales/ja-JP.json': function (module) {
        module.exports = JSON.parse('{"world": "世界"}');
      },
    };

    你可能还会注意到这两个没有被加载,这就是为什么它被称为按需加载,对资源的请求仅在需要时才会发送。

    💡 为什么没有为 src/locales/en.json 生成单独的 JavaScript 文件?

    这是因为该模块已经包含在主产物中,Webpack/Rspack 会自动将其移除。

    请参见 optimization.removeAvailableModulesoptimization.removeEmptyChunks

    切换语言

    调用 i18next.changeLanguage API 可以在不同语言间进行切换:

    src/App.tsx
    import { useEffect, useState } from '@lynx-js/react';
    
    import { i18n } from './i18n.js';
    
    export function App() {
      const [locale, setLocale] = useState('en');
    
      useEffect(() => {
        console.log('Hello, ReactLynx3 x i18next!');
      }, []);
    
      const getNextLocale = (locale: string) => {
        // mock locales
        const locales = ["en", "zh-CN"];
        const index = locales.indexOf(locale);
        return locales[(index + 1) % locales.length];
      };
    
      return (
        <view>
          <text style={{ color: 'red' }}>Current locale: {locale}</text>
          <text
            bindtap={async () => {
              const nextLocale = getNextLocale(locale);
              await i18n.changeLanguage(nextLocale);
              setLocale(nextLocale);
            }}
          >
            Tap to change locale
          </text>
          <text>Hello, {i18n.t('world')}</text>
        </view>
      );
    }

    提取翻译

    如果你下载了很多翻译资源,但项目中只使用了其中一部分,就可以通过提取来减小产物体积。

    提取源码中实际用到的翻译资源有两种方式。

    插件方式(推荐)

    你可以使用 rsbuild-plugin-i18next-extractor 来提取源码中用到的翻译资源。这个插件基于 i18next-cli,并利用 Rspack 的模块图提取当前构建中实际导入模块里的 i18n 消息。这样可以按需提取翻译,避免打包未使用的消息,并降低依赖中使用了翻译但遗漏提取的风险。

    Tip

    这个插件要求 Node.js 22 或更高版本。

    首先安装插件以及它的 i18next-cli peer dependency:

    npm
    yarn
    pnpm
    bun
    deno
    npm install rsbuild-plugin-i18next-extractor i18next-cli -D

    lynx.config.ts 中启用该插件,并指定 localesDir 选项,也就是原始翻译资源所在的目录:

    下面的示例统一假设原始翻译资源放在 src/locales/ 中。

    lynx.config.ts
    import { defineConfig } from '@lynx-js/rspeedy';
    import { pluginI18nextExtractor } from 'rsbuild-plugin-i18next-extractor';
    
    export default defineConfig({
      plugins: [
        pluginI18nextExtractor({
          localesDir: './src/locales',
        }),
      ],
    });

    然后从上一步指定的 localesDir 中导入翻译资源:

    src/i18n.ts
    import i18next from 'i18next';
    import type { i18n } from 'i18next';
    
    import enTranslation from './locales/en.json';
    
    const localI18nInstance: i18n = i18next.createInstance();
    
    localI18nInstance.init({
      lng: 'en',
      resources: {
        en: {
          translation: enTranslation,
        },
      },
    });
    Tip

    在执行 DEBUG=rsbuild:i18next rspeedy devDEBUG=rsbuild:i18next rspeedy build 之后,你可以在 node_modules/.rsbuild-plugin-i18next-extractor 目录中查看提取结果。

    配置项

    该插件默认会扫描 node_modules。你可以使用 i18nextToolkitConfig.extract.ignore 选项忽略文件:

    lynx.config.ts
    import { defineConfig } from '@lynx-js/rspeedy';
    import { pluginI18nextExtractor } from 'rsbuild-plugin-i18next-extractor';
    
    export default defineConfig({
      plugins: [
        pluginI18nextExtractor({
          localesDir: './src/locales',
          i18nextToolkitConfig: {
            extract: {
              ignore: ['node_modules/**'],
            },
          },
        }),
      ],
    });

    你还可以通过回调处理缺失的翻译键:

    lynx.config.ts
    import { defineConfig } from '@lynx-js/rspeedy';
    import { pluginI18nextExtractor } from 'rsbuild-plugin-i18next-extractor';
    
    export default defineConfig({
      plugins: [
        pluginI18nextExtractor({
          localesDir: './src/locales',
          onKeyNotFound: (key, locale, localeFilePath, entryName) => {
            console.warn(`Missing translation key: ${key} for locale: ${locale}`);
          },
        }),
      ],
    });

    进阶:去重 Lynx 产物中的翻译

    对于 Lynx 应用,建议将 @lynx-js/i18next-translation-dedupersbuild-plugin-i18next-extractor 一起使用,以避免重复打包相同的翻译。

    @lynx-js/i18next-translation-dedupe 会读取 rsbuild-plugin-i18next-extractor 提取出的翻译,跳过提取器默认生成的资源文件,并将翻译写入 Lynx 产物的 customSections 中,供运行时加载。

    首先安装该包:

    npm
    yarn
    pnpm
    bun
    deno
    npm install @lynx-js/i18next-translation-dedupe -D

    然后添加 pluginLynxI18nextTranslationDedupe()

    lynx.config.ts
    import { defineConfig } from '@lynx-js/rspeedy';
    import { pluginLynxI18nextTranslationDedupe } from '@lynx-js/i18next-translation-dedupe';
    import { pluginI18nextExtractor } from 'rsbuild-plugin-i18next-extractor';
    
    export default defineConfig({
      plugins: [
        pluginI18nextExtractor({
          localesDir: './src/locales',
        }),
        pluginLynxI18nextTranslationDedupe(),
      ],
    });

    运行时,从 Lynx 产物的 customSections 中加载提取出来的翻译:

    src/i18n.ts
    import i18next from 'i18next';
    import type { i18n } from 'i18next';
    import { loadI18nextTranslations } from '@lynx-js/i18next-translation-dedupe';
    
    const localI18nInstance: i18n = i18next.createInstance();
    
    localI18nInstance.init({
      lng: 'en',
      resources: loadI18nextTranslations(),
    });

    命令行方式

    你也可以使用 i18next-cli 来提取翻译。

    Tip

    i18next-cli 要求 Node.js 22 或更高版本。

    首先安装 CLI:

    npm
    yarn
    pnpm
    bun
    deno
    npm install i18next-cli -D

    在项目根目录下新增一个 i18next.config.ts 文件,例如:

    i18next.config.ts
    import { defineConfig } from 'i18next-cli';
    
    export default defineConfig({
      locales: ['en', 'zh'],
      extract: {
        input: ['src/**/*.{js,jsx,ts,tsx}'],
        output: 'src/locales/{{language}}.json',
      },
    });

    你可以根据项目结构调整 localesextract.inputextract.output。如果你使用多个 namespace,可以把 {{namespace}} 加回到输出路径中。

    然后运行下面的命令来提取翻译:

    npm
    yarn
    pnpm
    bun
    deno
    npm i18next-cli extract
    除非另有说明,本项目采用知识共享署名 4.0 国际许可协议进行许可,代码示例采用 Apache License 2.0 许可协议进行许可。