前端多语言资源按需加载与运行时切换实践
1. 阅读导引
当一个前端应用包含多个业务模块或子应用时,如果在启动阶段一次性加载全部语言资源,会增加首屏资源体积和初始化耗时。更合适的做法是将资源按使用范围拆分:公共资源随应用初始化加载,业务模块资源在真正进入模块时,按照当前语言按需加载。
本文结合 tighten、pdm、pressFit 等 App 的资源组织方式,介绍资源分层、命名约定、加载时机、缓存、合并和运行时切换等问题。除了为全新 App 建立独立页面和语言资源,方案还需要支持 pressFit 这类复用已有 App 页面、但使用自身业务术语的场景。
本次优化主要实现以下目标:
- 公共资源常驻,业务 App 资源按当前语言加载,控制首屏资源体积。
- 全新 App 可以建立独立页面、namespace 和语言资源。
- 复用 App 可以沿用既有页面和翻译 key,同时通过独立语言文件呈现自身术语。
- App 切换和语言切换时正确应用目标资源,避免共享 namespace 带来的文案残留。
示例用于说明方案如何落地,具体实现仍可以由项目使用的国际化库和构建工具负责承载。
2. 资源分层与命名约定
2.1 资源分层
多语言资源可以按使用范围分为两类:
| 类型 | 资源范围 | 加载时机 | 适用场景 |
|---|---|---|---|
| 公共资源 | 导航、登录、基础组件、平台级功能 | 应用初始化时 | 多个模块都会使用的通用文案 |
| 业务资源 | tighten、pdm、irun、energy 等 App 的专属文案 |
进入对应 App 时 | 仅部分用户或部分页面会使用的文案 |
公共资源应保持稳定、精简,避免把大量业务文案不断加入公共资源。业务资源则以模块为边界拆分,使资源加载范围与用户当前访问范围保持一致。
2.2 命名约定
每个业务模块需要有一个稳定的资源前缀,资源文件名由模块前缀、标准化语言标识和扩展名组成:
1 | {module}_{locale}.json |
例如,tighten App 的资源前缀为 tighten,中文和英文资源可以命名为:
1 | tighten_zh-CN.json |
App 配置只需要保存资源前缀,不应同时保存语言标识和文件扩展名。例如,数据库字段 lang_file 保存 tighten,后端返回给前端的 App 配置可表示为 langFile: "tighten"。语言标识由当前用户语言统一计算,避免配置与实际语言状态不一致。
如果某个模块没有额外语言资源,可以将资源前缀配置为空,加载器应直接跳过请求。
资源配置的职责是选择已经随前端版本发布的资源,而不是让前端任意从服务端下载 JSON。新增或修改资源后,应与前端版本一起构建和发布。
3. 目录组织
推荐将公共资源和业务资源分开存放:
1 | locale/ |
目录名称可以根据项目习惯调整,但应保证公共资源和业务资源有明确边界。构建工具的按需加载范围只应覆盖业务资源目录,避免公共资源被重复打包或重复请求。
4. 应用初始化与公共资源
应用初始化阶段只注册公共资源和基础组件所需的语言资源。例如,common 可以承载登录、导航等通用文案,core 可以承载平台管理等核心功能文案。初始化完成后,这些页面可以直接使用公共翻译。
初始化阶段不应根据菜单或权限列表批量加载所有业务模块资源。菜单和权限信息可以用于生成可访问路由,但不应因此触发业务语言文件的加载。
这种划分可以将“应用能够启动所必需的资源”和“用户访问特定功能后才需要的资源”分开,降低首屏加载压力。
5. 进入业务模块时的加载流程
资源加载应发生在页面首次渲染之前。以用户首次进入 tighten App、当前语言为 zh-CN 为例,完整流程如下:
- 路由或页面上下文确定当前所属 App 为
tighten。 - 从 App 配置中取得资源前缀
tighten,并读取当前生效的语言zh-CN。 - 将不同格式的语言标识统一为系统支持的标准格式,例如将多种中文写法统一为
zh-CN,将英文区域语言统一为en。 - 根据资源前缀和标准语言标识生成资源请求键
./tighten_zh-CN.json。 - 通过构建工具提供的静态资源映射或按需加载机制请求该资源。
- 资源加载完成后,将
tighten的 messages 合并到zh-CN语言的资源集合中。 - 确认资源准备完成后,再继续路由跳转或页面渲染。
资源加载属于页面进入流程的一部分,而不是组件渲染后的补救动作。这样可以避免页面短暂显示翻译 key,也能让资源加载失败进入统一的错误处理流程。
6. 公共资源判断
资源加载器应维护公共资源前缀集合,例如:
1 | {common, core} |
当 langFile 为 common、core 或空值时,加载器直接复用初始化阶段已经注册的 messages,不再发起业务资源请求。公共资源集合应集中维护,并在新增公共模块时同步更新。
7. 按需加载与缓存策略
7.1 资源映射
动态资源加载需要依赖构建阶段生成的静态资源映射。运行时请求的资源键必须与构建工具收集到的资源键保持一致,例如使用映射中的 ./tighten_zh-CN.json,而不是自行拼出一段无法与构建结果对应的完整目录路径。
这种约束可以让构建工具提前发现资源引用,并将业务语言文件拆分为独立资源。目录、文件名或语言标识不一致时,应在构建或发布阶段尽早暴露问题。
7.2 缓存维度
缓存键至少应包含业务模块和语言两个维度:
1 | {langFile}_{locale} |
推荐遵循以下规则:
- 加载中的请求缓存为 Promise,避免并发进入同一模块时重复请求。
- 加载成功后缓存解析后的 messages,后续进入同一模块时直接复用。
- 每次进入模块时重新应用该模块的 messages,以支持多个模块复用同一个 namespace。
- 加载失败时清理对应缓存,允许后续重新尝试。
- 不同语言分别缓存,例如
tighten_zh-CN与tighten_en互不影响。
缓存应区分“正在加载的请求”和“已经加载完成的资源”,这样既能合并并发请求,也能避免把失败状态永久留在缓存中。
8. 运行时切换语言
运行时切换语言时,不能只更新全局语言标识,还需要确保当前页面所需的目标语言资源已经准备完成。推荐的处理顺序如下:
- 规范化目标语言。
- 确定当前页面所属的业务模块。
- 如果当前页面属于业务模块,加载并合并该模块的目标语言资源;如果只依赖公共资源,则直接复用已有资源。
- 资源准备完成后,更新全局语言状态。
- 触发页面重新渲染,使公共文案和业务文案同时切换。
例如,用户在 tighten 页面从中文切换到英文时,只需加载并合并 tighten_en.json,不需要同时加载 pdm_en.json、irun_en.json 等其他 App 资源。用户之后进入其他 App 时,再按相同规则加载对应资源。
9. messages 合并规则
业务资源通常以 namespace 组织,并合并到当前语言已有的 messages 中。建议明确以下规则:
- 对象节点采用递归合并,保留未被覆盖的其他资源。
- 普通值和数组由新资源覆盖,避免产生不明确的部分合并结果。
- 合并前复制资源对象,避免修改缓存中的原始内容。
- 业务模块的默认 namespace 与资源前缀保持一致,便于定位和维护。
- 如果多个模块需要复用同一套页面文案,可以显式共享 namespace,但应记录复用关系。
例如,模块资源可以使用以下结构:
1 | { |
通常,App 的顶层 namespace 与 langFile 一致。需要复用既有页面时,也可以共享 namespace。例如,pressFit_zh-CN.json 的顶层仍使用 tighten,从而复用页面中的 tighten.example 等完整翻译 key,而不必复制和改写整套页面文案。
资源加载器需要分别缓存 tighten 和 pressFit 的 messages,并在进入 App 时重新应用当前资源。这样即使两个 App 共享 namespace,也不会遗留上一个 App 对同名术语的覆盖结果。
10. 新增 App 的两种接入方式
新增 App 时,首先需要判断它是拥有独立业务页面,还是复用已有 App 的页面。两种场景都需要独立的 App 配置,但页面、namespace 和语言资源的组织方式不同。
10.1 全新 App:新增一套页面
全新 App 拥有独立的业务页面和术语体系,适合使用独立的资源前缀与 namespace。以新增质量管理 App quality 为例:
- 新增
quality对应的页面和路由,并让路由能够关联到该 App。 - 在 App 配置中设置
lang_file = quality,前端取得langFile: "quality"。 - 创建
quality_zh-CN.json、quality_en.json等语言资源。 - 语言文件使用
quality作为顶层 namespace。 - 新页面统一使用
quality.xxx形式的翻译 key。 - 将页面和语言资源一起构建发布,再启用对应的 App 配置。
新增独立路由
quality_en.json
翻译 key 使用 quality.xxx
启用 App 配置后加载资源
这种方式的关键是让 App、页面、资源前缀和 namespace 保持一致。读者看到 quality,就能直接定位到对应页面和语言资源,后续维护成本较低。
10.2 复用 App:沿用已有页面
复用 App 不新增一套完整页面,而是沿用已有 App 的页面结构和翻译 key,同时通过独立语言文件替换业务术语。以 pressFit 复用 tighten 页面为例:
- 创建
pressFit的 App 配置和路由入口,但路由仍指向tighten的已有页面。 - 为
pressFit配置独立资源前缀,即langFile: "pressFit"。 - 创建
pressFit_zh-CN.json、pressFit_en.json等语言资源。 - 语言文件的顶层 namespace 仍使用
tighten,并保持与原页面一致的 key 结构。 - 进入
pressFit时加载其语言文件,再把 messages 合并到tightennamespace。 - 复用页面继续使用
tighten.xxx,但最终展示的是pressFit对应的业务术语。
复用 tighten 页面
pressFit_en.json
继续使用 tighten.xxx
重新应用当前 App messages
这里需要区分资源前缀和 namespace:langFile 决定加载哪个 App 的资源,namespace 决定这些资源供哪些页面 key 使用。因此,pressFit 可以加载自己的资源文件,同时继续服务于使用 tighten.xxx 的既有页面。
由于 tighten 和 pressFit 会写入同一个 namespace,缓存不能只判断资源是否曾经加载过。每次进入 App 时,都需要重新应用当前 App 已缓存的 messages,避免页面遗留上一个 App 的术语。
10.3 两种接入方式的区别
| 对比项 | 全新 App:quality |
复用 App:pressFit |
|---|---|---|
| 页面 | 新增独立页面 | 复用 tighten 页面 |
| 路由 | 指向新页面 | 指向已有页面 |
langFile |
quality |
pressFit |
| 语言文件 | quality_*.json |
pressFit_*.json |
| 顶层 namespace | quality |
tighten |
| 页面翻译 key | quality.xxx |
tighten.xxx |
| 适用场景 | 页面结构和术语均独立 | 页面结构相同,但业务术语不同 |
无论采用哪种方式,都应先发布包含页面和语言资源的前端版本,再启用新的 App 配置,避免配置已经生效但旧版本前端尚未包含对应资源。
11. 总结
前端多语言资源按需加载的核心,是将资源加载范围与页面访问范围对齐:公共资源负责支撑应用启动和通用页面,业务资源则在模块进入时按当前语言加载。
在此基础上,通过统一语言标识、构建期资源映射、模块与语言维度的缓存、明确的 messages 合并规则,以及“资源准备完成后再渲染”的页面生命周期控制,可以在降低首屏资源体积的同时,保证多语言切换的一致性和可维护性。
