Umi Max 国际化(i18n)完整指南:从 locale 配置、运行时接口到插件源码实现 Umi Max 国际化i18n完整指南从 locale 配置、运行时接口到插件源码实现【免费下载链接】umiA framework in react community ✨项目地址: https://gitcode.com/GitHub_Trending/um/umiumijs/max内置的国际化插件让 React 应用的多语言支持开箱即用。本篇指南完整覆盖从约定式src/locales目录、.umirc.ts的locale配置到FormattedMessage、useIntl、setLocale等运行时接口的全部用法并结合开源仓库中插件的真实源码locale.ts 与四个生成模板深入解释语言识别优先级、无刷新切换的原理和路由标题国际化的实现机制帮助你在项目中稳定落地国际化并读懂其底层行为。插件激活方式与整体结构国际化插件位于 packages/plugins/src/locale.ts。阅读源码可以发现一个关键前提插件通过api.describe注册时声明了enableBy: api.EnableBy.configlocale.ts也就是说只有在配置文件中出现locale键时插件才会启用。这也是官方文档“Getting Started”要求先在.umirc.ts里写locale配置的原因——它既是配置入口也是开关。启用后插件在onGenerateFiles钩子中locale.ts扫描语言文件用 Mustache 渲染 templates/locale 目录下的模板生成临时文件locale.tsx、localeExports.ts、runtime.tsx、SelectLang.tsx和index.ts并通过api.addRuntimePluginKey(() [locale])locale.ts把locale注册为运行时插件 key使src/app.ts中的locale运行时配置生效。此外还有一个兼容性细节插件通过api.addEntryImportsAhead检查targets配置如果目标浏览器过旧如 IE 10、老版本 iOS/Android会自动在入口前置导入intlpolyfilllocale.ts老浏览器项目无需额外处理。快速开始约定目录与文件命名规范国际化插件采用约定式目录结构多语言文件统一放在src/locales目录中。文件命名需遵循规范langseparatorCOUNTRY.(js|json|ts)其中separator是分隔符默认为-可通过baseSeparator选项配置。从源码看localeUtils.ts 中扫描语言文件的正则是const localeFileMath new RegExp( ^([a-z]{2})${separator}?([A-Z]{2})?\.(js|json|ts)$, );这意味着文件必须满足语言代码为2 位小写字母如zh、en、sk国家/地区代码为可选的 2 位大写字母如CN、US分隔符与baseSeparator配置一致扩展名只能是js、json、ts。不满足正则的文件例如zh_CN在baseSeparator为-时不会被识别为语言文件。扫描范围除src/locales外还包括pages目录下任意层级的locales文件夹**/locales/*.{ts,js,json}同名的多个文件会被lodash.groupBy归并到同一语言下合并处理。以支持简体中文和英语为例创建如下结构src locales zh-CN.ts en-US.ts pages在.umirc.ts中配置国际化插件export default { locale: { // 默认使用 src/locales/zh-CN.ts 作为多语言文件 default: zh-CN, baseSeparator: -, }, };现在添加第一份多语言内容// src/locales/zh-CN.ts export default { welcome: 欢迎光临 Umi 的世界, };// src/locales/en-US.ts export default { welcome: Welcome to Umis world!, };也可以直接.json文件存放多语言内容// src/locales/zh-CN.json { welcome: 欢迎光临 Umi 的世界, }// src/locales/en-US.json { welcome: Welcome to Umis world!, }一切就绪使用FormattedMessage /组件消费多语言内容将welcome作为id参数传入即可import { FormattedMessage } from umi; export default function Page() { return ( div FormattedMessage idwelcome / /div ); };渲染结果为!-- zh-CN -- div欢迎光临 Umi 的世界/div !-- en-US -- divWelcome to Umis world!/div仓库中的示例项目 examples/config-locale 就是一个最小可用工程其 locales/zh-CN.js 与 locales/en-US.js 分别导出{ HELLO: 你好 }与{ HELLO: Hello }页面 pages/index.tsx 演示了useIntl、getLocale、动态addLocale与无刷新setLocale的组合用法可作为实操参照。嵌套 Key 是如何变成user.welcome的多语言文件常写成嵌套对象而id使用点号路径如user.welcome。这依赖 localeExports.tpl 中生成的flattenMessages函数它递归地把嵌套对象拍平为前缀.key形式的扁平字典。因此你只需按自然结构组织语言文件id直接用点号路径引用。在组件参数中使用多语言内容很多场景需要把多语言文案作为参数传给组件例如 antd 的Alert的message此时使用intl对象import { Alert } from antd; import { useIntl } from umi; export default function Page() { const intl useIntl(); const msg intl.formatMessage({ id: welcome, }); return Alert message{msg} typesuccess /; };底层上国际化插件基于react-intl封装。从 localeExports.tpl 可以看到FormattedMessage、useIntl、injectIntl、IntlProvider、FormattedDate、FormattedNumber等全部接口均直接自react-intl包导出因此react-intl提供的完整能力数字、日期、复数格式化等在这里都可以直接使用。上面的示例使用useIntl()初始化intl对象再调用其formatMessage()方法完成字符串格式化。生成的umi入口index.ts见 locale.ts 的模板输出统一导出了addLocale、setLocale、getLocale、getIntl、useIntl、injectIntl、formatMessage、FormattedMessage、getAllLocales及SelectLang这些就是你从umi导入时得到的全部国际化 API。格式化字符串动态插值多语言文案中常需要动态占位符用{name}这类特殊语法书写// src/locales/zh-CN.ts export default { user: { welcome: {name}今天也是美好的一天, }, };// src/locales/en-US.ts export default { user: { welcome: {name}, what a nice day!, }, };运行时通过values赋值import { FormattedMessage } from umi; export default function Page() { return ( p FormattedMessage iduser.welcome values{{ name: 张三 }} / /p ); };若希望通过intl对象实现则把占位符对象作为formatMessage()的第二个参数传入import { useIntl } from umi; export default function Page() { const intl useIntl(); const msg intl.formatMessage( { id: user.welcome, }, { name: 张三, }, ); return p{msg}/p; };渲染结果!-- zh-CN -- p张三今天也是美好的一天/p !-- en-US -- p张三, what a nice day!/p切换语言预置组件SelectLang预设的SelectLang /组件可以快速为项目添加语言切换入口import { SelectLang } from umi; export default function Page() { return SelectLang /; };源码层面SelectLang由 SelectLang.tpl 模板生成。从 locale.ts 的生成条件可以看到ShowSelectLang: localeList.length 1 !!antd——即必须识别到多于一种语言文件且项目开启了 antd 集成组件才会渲染实际的下拉菜单基于 antd 的DropdownMenu内置了 40 余种语言的标签与国旗图标映射未知语言回退为图标与语言 key 本身否则渲染空节点。点击菜单项最终调用setLocale(key, reload)完成切换组件还支持postLocalesData、onItemClick、icon、reload等 props 做二次定制。编程式接口setLocale需要自研语言切换 UI 时setLocale()是直接手段import { setLocale } from umi; // 切换时刷新页面 setLocale(en-US);默认会刷新当前页面将第二个参数设为false即可无刷新切换// 切换时不刷新页面 setLocale(en-US, false);若要切回默认语言直接不带参数调用即可等价于setLocale(zh-CN)取决于你的default配置setLocale();localeExports.tpl 中的setLocale实现揭示了无刷新切换的原理它先把新语言写入localStoragekey 为umi_locale再执行setIntl(lang)重建全局 intl当realReload为true时调用window.location.reload()为false时则通过EventEmitter广播LANG_CHANGE_EVENT并额外派发一个标准的languagechange事件注释说明是因为 Chrome 不原生支持该事件。监听这一事件的是 locale.tpl 中生成的_LocaleContainer它在useLayoutEffect中订阅LANG_CHANGE_EVENT收到后同时更新 moment/dayjs 的 locale、调用getIntl(locale)重建 intl 对象并触发 React 状态更新——这正是useIntl/FormattedMessage能在不刷新页面的情况下重渲染的原因。示例 examples/config-locale/pages/index.tsx 中setLocale(isZh ? en-US : zh-CN, false)就是典型的无刷新切换场景。多语言默认值与缺省回退为保证页面一致性如果 Umi 在当前多语言文件中找不到id对应的内容会直接把id本身渲染到页面上。例如// src/locales/zh-CN.ts export default { table: { submit: 提交表单, }, };// src/locales/en-US.ts export default { // table: { // submit: SUBMIT TABLE, // }, };组件import { Button } from antd; import { FormattedMessage } from umi; export default function Page() { return ( Button typeprimary FormattedMessage idtable.submit / /Button ); };渲染结果!-- zh-CN -- button typeprimary提交表单/button !-- en-US -- button typeprimarytable.submit/button如果国际化尚未适配完成、想提供一个兜底文案使用defaultMessage参数import { Button } from antd; import { FormattedMessage } from umi; export default function Page() { return ( Button typeprimary FormattedMessage idtable.submit defaultMessageSUBMIT TABLE / /Button ); };formatMessage()方法同理import { Button } from antd; import { useIntl } from umi; export default function Page() { const intl useIntl(); const msg intl.formatMessage({ id: table.submit, defaultMessage: SUBMIT TABLE, }); return Button typeprimary{msg}/Button; };不建议长期依赖defaultMessage因为会引入大量重复的国际化内容最佳实践是在国际化适配阶段确保每种语言的每个文件都包含全部所用 key。另外源码里还有一条值得了解的回退链localeExports.tpl 的getIntl在发现当前 locale 不在localeInfo中时会先warning提醒“当前语言不存在请检查 locales 文件夹”然后回退到default配置的语言若该语言也不存在则返回一个空messages的 intl 实例——这解释了为什么拼错语言 key 时页面不会崩溃但文案会“消失”。常用接口详解addLocale动态增加语言支持无需创建独立的多语言文件addLocale()可以在运行时动态追加语言支持接受三个参数参数类型说明nameString语言 keymessageObject多语言内容对象optionsObjectmomentLocale与antd配置例如动态引入繁体中文支持import { addLocale } from umi; import zhTW from antd/es/locale/zh_TW; addLocale( zh-TW, { welcome: 歡迎光臨 Umi 的世界, }, { momentLocale: zh-tw, antd: zhTW, }, );从 localeExports.tpl 的实现可以确认两个行为细节若localeInfo[name]已存在比如该语言已有静态文件新 messages 会与旧内容合并Object.assign而不是整体覆盖第三参数仅追加 messages 时可省略此时momentLocale与antd会沿用已有配置若新增语言恰好是当前语言会立即触发LANG_CHANGE_EVENT强制重新渲染。getAllLocales获取语言列表getAllLocales()返回当前全部语言选项的数组即localeInfo的 keys包含通过addLocale()动态追加的语言。默认扫描src/locales下形如zh-CN.(js|json|ts)的文件并返回语言 keyimport { getAllLocales } from umi; getAllLocales(); // [en-US, zh-CN, ...]getLocale获取当前选中的语言import { getLocale } from umi; getLocale(); // zh-CNlocaleExports.tpl 中的getLocale明确了完整的识别优先级这也是配置项baseNavigator的语义来源运行时自定义getLocale在src/app.ts的locale运行时配置中提供见下文localStorage中的umi_locale值useLocalStorage: true且浏览器支持 cookie/localStorage 时浏览器语言检测navigator.language并按baseSeparator把连字符替换为配置的分隔符baseNavigator: true时生效default配置的默认语言未配置时为zhbaseSeparatorCN即zh-CN或zh_CN。源码注释特别提醒修改baseSeparator配置后需要清空localStorage否则旧的umi_locale值可能与应用语言 key 不匹配。useIntl获取intl对象useIntl()是开发中最常用的接口。通过它拿到intl对象后可调用formatMessage()等方法满足各种需求// src/locales/en-US.json { welcome: Hi, {name}. }import { useIntl } from umi; const intl useIntl(); const msg intl.formatMessage( { id: welcome, }, { name: Jackson, }, ); console.log(msg); // Hi, Jackson.intl对象的更多用法formatNumber、formatDate、FormattableHTML等参考react-intl官方 API 文档即可因为所有接口都是原样透传。setLocale编程式设置语言setLocale()通过编程方式动态设置当前语言两个参数参数类型说明langString要切换的语言realReloadBoolean切换时是否刷新页面默认true刷新import { setLocale } from umi; // 切换时刷新页面 setLocale(en-US); // 切换时不刷新页面 setLocale(en-US, false);插件配置项详解在.umirc.ts中配置国际化插件默认值如下export default { locale: { antd: false, // 若项目依赖中包含 antd则默认为 true baseNavigator: true, baseSeparator: -, default: zh-CN, title: false, useLocalStorage: true, }, };各配置项说明配置项类型默认值说明antdBooleanfalse项目包含antd依赖时为true开启 antd 国际化联动。开启后应用外层会被 antd 的ConfigProvider包裹locale取自各语言对应的 antd locale 对象baseNavigatorBooleantrue开启浏览器语言检测。默认识别优先级为localStorage的umi_locale值 浏览器检测 default设置的默认语言 zh-CNbaseSeparatorString-语言与国家代码之间的分隔符。默认-时语言 key 形如zh-CN、en-US、sk若指定为_则默认语言为zh_CNdefaultStringzh-CN项目的默认语言。未检测到特定语言时使用它titleBooleanfalse开启路由标题国际化见下文useLocalStorageBooleantrue自动使用localStorage保存当前使用的语言对照源码 locale.ts 的defaultConfig与 localeUtils.ts 可以补充几点实现事实antd的默认值不是写死的false而是启动时尝试require.resolve(antd)自动探测hasAntd所以安装了 antd 的项目无需显式开启antd联动还决定了 moment/dayjs locale 的自动注册locale.tpl会按识别出的语言列表导入对应moment/locale/*并在容器挂载时执行moment.locale(...)若配置了moment2dayjs则解析为 dayjs 的 locale 文件开启antd后_LocaleContainer还会根据 localeExports.tpl 的getDirection()判断 RTL 语言he、ar、fa、ku前缀并把direction传给ConfigProvider阿拉伯语等 RTL 场景会自动获得directionrtl。路由标题国际化title: true在路由配置中为路由添加title字段即可启用标题国际化页面标题会被自动转换为对应语言的内容。例如多语言文件// src/locales/zh-CN.ts export default { site.title: Umi - 企业级 React 应用开发框架, about.title: Umi - 关于我, };// src/locales/en-US.ts export default { site.title: Umi - Enterprise-level React Application Framework, about.title: Umi - About me, };路由配置// .umirc.ts export default { title: site.title, routes: [ { path: /, component: Index, }, { path: /about, component: About, title: about.title, }, ], };访问页面时的效果/路由语言为zh-CN时页面标题是Umi - 企业级 React 应用开发框架en-US时是Umi - Enterprise-level React Application Framework。/about路由语言为zh-CN时页面标题是Umi - 关于我en-US时是Umi - About me。机制上runtime.tpl 生成的patchRoutes运行时钩子会递归遍历所有路由凡带title字段的路由都通过intl.formatMessage({ id: route.title })解析成当前语言的文案写回route.title语言切换时 locale.tpl 还会把全局titlekey 的文案同步到document.title。注意title国际化仅在locale.title为true且项目配置了全局title时才会被模板注入见 locale.ts 的Title: title api.config.title。运行时扩展国际化插件支持在运行时进一步扩展与定制入口都是src/app.ts中导出的locale运行时配置对应addRuntimePluginKey(() [locale])注册的插件 key。自定义getLocale可以自定义获取页面语言的逻辑。例如识别 URL 的?localeen-US参数使当前页面使用en-US// src/app.ts import qs from qs; export const locale { getLocale() { const { search } window.location; const { locale zh-CN} qs.parse(search, { ignoreQueryPrefix: true }); return locale; }, };源码印证localeExports.tpl 中getLocale()的第一步就是通过插件系统的modify钩子key 为locale收集运行时配置若runtimeLocale.getLocale是函数则直接优先调用它——自定义逻辑完全接管默认优先级链。自定义react-intl初始化选项Umi 的 i18n 基于react-intl实现。需要配置更多react-intl初始化选项时同样在app.ts中配置// src/app.ts import { RuntimeConfig } from umijs/max; export const locale: RuntimeConfig[locale] { textComponent: span, onError: () { console.log(error handler...); }, // locale: string // formats: CustomFormats // messages: Recordstring, string | Recordstring, MessageFormatElement[] // defaultLocale: string // defaultFormats: CustomFormats // timeZone?: string // textComponent?: React.ComponentType | keyof React.ReactHTML // wrapRichTextChunksInFragment?: boolean // defaultRichTextElements?: Recordstring, FormatXMLElementFnReact.ReactNode // onError(err: string): void };这些字段的类型由插件动态生成locale.ts 会基于react-intl的createIntl参数推导IRuntimeConfig.locale类型OmitParameterstypeof createIntl[0], locale | defaultLocale与getLocale、cache的交集因此 IDE 能获得完整提示。运行时这些配置在_createIntl中被展开后传给createIntllocaleExports.tpl例如自定义onError可以接管富文本消息解析异常formats可以注入全局日期/数字格式。FAQ为什么不用formatMessage语法糖umi入口确实导出了一个全局formatMessage函数但不推荐直接使用它。原因全局语法糖与 React 生命周期脱钩——它基于模块级的g_intl单例语言切换时无法触发依赖它的组件重新渲染。最严重的后果就是切换语言后 DOM 不更新被迫刷新浏览器体验很差。源码中该函数也标注了deprecated且首次调用时通过warning包打印提醒「使用此 API 会造成切换语言时无法自动刷新请使用 useIntl 或 injectIntl」localeExports.tpl。正确姿势是用useIntl()或injectIntl它们通过RawIntlProvider的 Context 提供 intl 实例语言切换事件触发_LocaleContainer更新 Context 值时所有消费方组件自动重渲染功能与全局语法糖完全一致。小结一次语言切换的完整链路把源码证据串起来setLocale(en-US, false)之后发生的事是setLocale写入localStorage的umi_locale并调用setIntl重建全局 intl广播LANG_CHANGE_EVENT并派发languagechange事件_LocaleContainer监听事件同步 moment/dayjs locale 并更新 React 状态RawIntlProvider的 value 变化useIntl/FormattedMessage所在的组件重渲染新语言文案即时生效。配合 examples/config-locale 这类示例工程与 locale.ts、templates/locale 的源码可以完整理解从配置、文件约定到运行时行为的每一环便于在真实项目中排查“语言没切换”“key 找不到”“SelectLang 不显示”语言文件少于两个或未安装 antd等常见问题。【免费下载链接】umiA framework in react community ✨项目地址: https://gitcode.com/GitHub_Trending/um/umi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考