在项目集成国际化能力之前,需要了解国际化相关概念。理解核心概念可以帮助你快速建立稳定的翻译体系,帮助我们更好地解决使用过程中的各种问题。
i18n 是 Internationalization 的缩写,指让应用在不同语言、地区和文化中都能良好运行,需要在设计阶段就考虑多语言资源、数字/日期/货币以及文化差异等因素。
i18next 是一个通用的国际化框架,提供语言检测、资源管理、插值、复数等能力。@modern-js/plugin-i18n 默认基于 i18next,请参考其官方文档获取完整的配置说明。
react-i18next 是 i18next 的 React 绑定库,提供 useTranslation、Trans 等 Hook/组件,实现与 React 生命周期良好结合:
import { useTranslation } from 'react-i18next';
function App() {
const { t } = useTranslation();
return <h1>{t('welcome')}</h1>;
}i18next 默认导出一个实例,也支持通过 createInstance 生成多实例:
import i18next, { createInstance } from 'i18next';
i18next.init({
/* ... */
});
const custom = createInstance();
await custom.init({
/* 独立配置 */
});实例负责翻译资源、当前语言、切换语言等功能,也可以在 Modern.js 的 runtime 中传入自定义实例。
i18next 通过 init 完成初始化,常用核心选项:
lng:初始语言ns / defaultNS:命名空间列表与默认命名空间supportedLngs:允许的语言集合fallbackLng:缺失资源时的回退语言(可为数组或映射)interpolation:插值设置,React 环境通常配置 escapeValue: falsei18next.init({
lng: 'zh',
ns: ['translation', 'common'],
defaultNS: 'translation',
supportedLngs: ['zh', 'en'],
fallbackLng: ['en'],
interpolation: { escapeValue: false },
});t 函数t 是获取翻译的核心 API,可直接从实例使用,也可以通过 react-i18next Hook 获得:
i18next.t('welcome');const { t } = useTranslation();
t('welcome', { name: 'Modern.js', count: 3 });t 支持插值、复数、上下文等高级特性,后文会展开说明。
语言代码用于标识当前界面语言,遵循 ISO 639-1 标准(en、zh 等),也可以携带地区信息(en-US、zh-CN)。
en-US → en → zh 这类链决定缺失翻译时的查找顺序。// modern.config.ts
import { defineConfig } from '@modern-js/app-tools';
import { i18nPlugin } from '@modern-js/plugin-i18n';
export default defineConfig({
plugins: [
i18nPlugin({
localeDetection: {
languages: ['zh', 'en', 'ja'],
fallbackLanguage: ['zh', 'en'], // 支持回退链
},
}),
],
});💡 建议将 supportedLanguages 与 fallbackLanguage 同步维护,避免出现用户切换到未配置语言的情况。
命名空间(Namespace)用于按照业务模块拆分翻译文件,便于代码分割与按需加载。未指定时使用默认命名空间 translation。
// src/modern.runtime.ts
import { defineRuntimeConfig } from '@modern-js/runtime';
export default defineRuntimeConfig({
i18n: {
initOptions: {
ns: ['translation', 'common', 'dashboard'],
defaultNS: 'translation',
},
},
});在组件中使用不同命名空间:
import { useTranslation } from 'react-i18next';
export function DashboardHeader() {
const { t } = useTranslation(['dashboard', 'common']);
return (
<header>
<h1>{t('dashboard:title')}</h1>
<button>{t('common:button.refresh')}</button>
</header>
);
}命名空间还可以和动态加载结合,按需请求大体量文案。
推荐的资源文件目录:
locales/
├── en/
│ ├── translation.json
│ ├── common.json
│ └── dashboard.json
└── zh/
├── translation.json
├── common.json
└── dashboard.jsonlocales/<language>/<namespace>.json{
"header": {
"title": "欢迎",
"actions": {
"save": "保存",
"cancel": "取消"
}
}
}也可以通过 resources 选项在初始化时直接注入资源,或在运行时调用 addResourceBundle:
i18next.init({
resources: {
en: {
common: {
welcome: 'Welcome',
},
},
zh: {
common: {
welcome: '欢迎',
},
},
},
});
i18next.addResourceBundle('en', 'home', { title: 'Home' });翻译键(Translation Key)是访问翻译的路径,通常使用点号表示层级:common.button.submit。
命名规范建议:
dashboard.table.*): 指定命名空间(common:button.submit)const { t } = useTranslation();
button.textContent = t('common.button.submit', {
defaultValue: 'Submit',
});插值(Interpolation)允许在翻译文本中动态注入变量。
资源文件:
{
"welcome": "欢迎,{{name}}!",
"invite": "{{name}} 邀请你加入 {{project}}",
"formattedValue": "当前价格:{{value, currency}}"
}用法:
const { t } = useTranslation();
return (
<>
<p>{t('welcome', { name: 'John' })}</p>
<p>{t('invite', { name: 'Alice', project: 'Modern.js' })}</p>
</>
);可以直接传递对象或多级变量:
{
"greeting": "你好,{{user.name}},你有 {{user.notifications}} 条新消息"
}t('greeting', {
user: { name: 'Jay', notifications: 3 },
});通过 interpolation.format 函数格式化数字、日期等:
export default defineRuntimeConfig({
i18n: {
initOptions: {
interpolation: {
format(value, format, lng) {
if (format === 'currency') {
return new Intl.NumberFormat(lng, {
style: 'currency',
currency: lng === 'zh' ? 'CNY' : 'USD',
}).format(Number(value));
}
if (value instanceof Date) {
return new Intl.DateTimeFormat(lng, { dateStyle: 'medium' }).format(
value,
);
}
return value;
},
},
},
},
});t('formattedValue', { value: 99.5, format: 'currency' });react-i18next 默认会对插值值进行转义以防止 XSS。如需渲染安全的 HTML,需显式开启 interpolation.escapeValue = false 并确保数据可信。
复数处理根据语言自动选择合适的词形,依赖 count 参数。
{
"item": "1 个条目",
"item_plural": "{{count}} 个条目",
"item_0": "没有条目"
}t('item', { count: 0 }); // 没有条目
t('item', { count: 1 }); // 1 个条目
t('item', { count: 5 }); // 5 个条目不同语言具有不同的复数规则,例如:
_0 键覆盖特殊文案💡 如果需要自定义复数规则,可通过 i18next.services.pluralResolver 扩展,详见高级用法。
嵌套结构可以直观反映 UI 层级。
{
"common": {
"button": {
"submit": "提交",
"cancel": "取消"
}
}
}在代码中使用点号访问:
const { t } = useTranslation();
t('common.button.submit');嵌套结构的优势:
keyPrefix 精简调用:useTranslation('common', { keyPrefix: 'button' })当当前语言缺少某个键时,会按回退语言链继续查找。
export default defineRuntimeConfig({
i18n: {
initOptions: {
lng: 'zh-CN',
fallbackLng: {
'zh-CN': ['zh', 'en'],
default: ['en'],
},
},
},
});
可以将地区语言(如 zh-CN)回退到通用语言(zh),最后再回退到默认语言(en),确保所有键都有可用文本。
i18next 通过语言检测插件自动识别用户语言,Modern.js 插件内置浏览器与服务端支持。
import LanguageDetector from 'i18next-browser-languagedetector';
import i18next from 'i18next';
i18next.use(LanguageDetector).init({
supportedLngs: ['zh', 'en', 'ja'],
detection: {
order: ['path', 'cookie', 'localStorage', 'navigator'],
lookupCookie: 'i18next',
lookupLocalStorage: 'i18nextLng',
},
});Modern.js 中可以直接在插件配置里开启内置检测:
i18nPlugin({
localeDetection: {
i18nextDetector: true,
languages: ['zh', 'en'],
detection: {
order: ['path', 'cookie', 'header'],
},
},
});
启用检测后,无需在 init 中显式设置 lng。如果手动调用 changeLanguage() 未传入语言,也会根据检测配置自动推断。