适配场景:NestJS+Next.js SSR架构、20+语言支持、翻译团队统一维护文案、客户端纯服务端水合翻译JSON、本地/测试/生产环境差异化加载
核心优化:路由+语言按需加载、LRU+Redis双层缓存、环境差异化加载策略、工程化脚本提效、全链路体积压缩,兼顾性能/可用性/可维护性,完全匹配公司实际业务流程
统一本地Baseline目录结构,与API/翻译团队维护的路由+common拆分规则完全对齐,语言采用ISO 639-1标准码,支持20+语言无缝扩展。
# 项目本地仅存Baseline(开发补全/API故障兜底,体积极小)
locales/
├─ baseline/ # 本地兜底:仅存核心通用文案,所有语言<200KB
│ ├─ common/ # 公共通用:提交/取消/确认/网络错误等(所有页面共享)
│ │ ├─ en.json
│ │ ├─ zh-CN.json
│ │ └─ ...(20+语言)
│ ├─ home/ # 路由级:各页面核心兜底文案
│ │ ├─ en.json
│ │ └─ ...
│ └─ [route]/ # 其他业务路由:与项目实际路由完全一致(trade/order/user等)
API/翻译团队侧:按「locale/route.json」「locale/common.json」维护,与本地baseline目录结构完全对称,保证服务端拉取/合并逻辑统一。
common.submit/home.title),避免翻译团队/开发团队命名混乱。核心逻辑:缓存优先 → API拉取(测试/生产唯一来源)→ 本地Baseline故障兜底 → 拼接翻译JSON → 水合到客户端,全程保证按需加载、首屏快速、故障可用
1. 接收请求,解析用户语言(优先级:URL参数 > Cookie > 请求头Accept-Language)+ 当前路由;
2. 优先从「LRU内存缓存」读取「当前语言+当前路由+common」的全量翻译,命中则直接使用(耗时<1ms);
3. LRU未命中,读取「Redis二级缓存」,命中则返回结果并更新LRU缓存;
4. 双层缓存均未命中,**异步拉取API的翻译数据**(测试/生产拉取翻译团队维护的「当前语言+当前路由」+「当前语言+common」);
- 本地开发:API返回数据覆盖本地Baseline,API未返回的字段用本地Baseline补全;
- 测试/生产:仅使用API返回数据,本地Baseline不参与正常流程;
5. API拉取成功:拼接「route+common」翻译数据,更新到「LRU+Redis缓存」(配置1h过期),生成翻译JSON供客户端水合;
6. API拉取失败(网络/服务故障):**立即降级到本地Baseline**,使用「当前语言+当前路由+common」的兜底文案,保证核心功能可用,同时上报监控告警;
7. 服务端将最终拼接的翻译JSON注入页面,客户端纯水合使用,不做任何二次请求。
缓存核心目标:命中率>90%、更新及时、集群适配、缓存粒度精准
i18n:{locale}:{route}(如i18n:en:home)+ i18n:{locale}:common(如i18n:en:common),按需缓存,避免全量失效;i18n:{locale}:{route}/i18n:{locale}:common),方便统一管理;聚焦开发提效和线上问题拦截,移除无用脚本,保留贴合公司流程的核心能力,开发/翻译团队各司其职。
baseline/common/baseline/[route]生成所有语言的空JSON文件,避免手动新建20+语言文件;locale.config.js,新增语言/路由仅修改该文件,一劳永逸;npm run generate:locales(新增路由/语言时执行)。git commit前自动执行,校验失败直接阻止提交;npm run check:locales(可手动执行,也可集成到CI/CD)。从构建时、传输层、服务端三个维度做极致优化,最小化翻译数据的体积和加载耗时。
jsonminify对本地Baseline文件做压缩(移除空格/换行/注释),单文件体积减少30-40%,优于简单的JSON.stringify;next.config.js中配置webpack,将本地Baseline翻译文件单独打包为独立小chunk,避免混入主包,且仅在API故障时加载,不影响主包构建/加载速度;核心要求:翻译团队更新文案后,线上快速生效,无需重启NestJS/Next.js服务,且保证缓存一致性,适配公司翻译团队统一维护的流程。
1. 翻译团队在专属平台更新「指定语言+指定路由」的文案,发布后同步到API服务;
2. 执行**缓存失效脚本**,主动删除Redis中对应「i18n:{locale}:{route}」和「i18n:{locale}:common」的key,同时清除所有NestJS实例的LRU缓存;
3. 后续用户请求会触发服务端重新从API拉取最新翻译数据,更新到缓存,完成线上文案更新;
4. 全程无需重启服务,线上无感知,且仅影响更新的语言+路由,不影响其他文案。
客户端不做任何翻译相关的请求/处理,完全复用服务端水合的翻译JSON,保证逻辑统一,避免二次开销。
window.__NEXT_I18N__全局变量,客户端直接读取;useTranslation Hook,内部读取全局翻译JSON,提供t(key)方法(如t('common.submit')),开发直接调用,无需关心底层逻辑;t(key)方法做类型校验,避免开发使用不存在的key,提升开发体验;针对生产环境可能出现的API故障、缓存失效、文件错误等异常,设计多层降级策略,保证国际化功能核心可用。
try/catch捕获错误,使用默认语言(如en)+ 原始key作为兜底,避免页面崩溃,同时上报监控(附带具体语言/路由/文件路径);| 公司核心诉求 | 解决方案 | 核心落地效果 |
|---|---|---|
| 客户端纯服务端水合翻译JSON | 服务端拼接翻译JSON注入页面,客户端无二次请求 | 避免客户端重复请求,提升渲染速度 |
| 测试/生产统一走API拉取文案 | 环境差异化加载策略,测试/生产屏蔽本地Baseline | 保证文案准确性,匹配翻译团队维护流程 |
| 本地开发本地文案补全 | 本地开发「API覆盖+本地兜底」 | 提升开发效率,无需等待API同步 |
| 20+语言按需加载 | 仅拉取「当前语言+当前路由」数据 | 最小化传输体积,避免全量加载开销 |
| 翻译更新无需重启服务 | 缓存主动失效+API实时拉取 | 线上无感知更新,适配翻译团队迭代节奏 |
| 生产环境高可用 | LRU+Redis双层缓存+API故障兜底 | 缓存命中率>90%,API故障核心功能可用 |
| 开发提效,规避线上问题 | 自动化脚本+git hooks校验+类型安全 | 减少80%手动操作,从源头拦截线上问题 |
回答:因为公司有专业的翻译团队统一维护文案,核心诉求是测试/生产文案的准确性,所以测试/生产统一走API拉取翻译,屏蔽本地文件;而本地开发时,为了提升效率,避免等待API同步文案,设计了「API覆盖+本地Baseline补全」的策略,既保证开发效率,又不影响线上文案准确性,这是基于公司实际业务的架构取舍。
回答:主要有两个核心原因:一是性能,客户端单独请求会增加二次网络开销,首屏渲染速度变慢,而服务端水合能让客户端直接读取全局变量,无需等待;二是逻辑统一,避免客户端和服务端出现翻译数据不一致的问题,同时减少客户端的代码复杂度,所有翻译的加载/拼接/缓存逻辑都在服务端统一管理,便于维护和故障排查。
回答:双层缓存是为了平衡性能和集群可用性——LRU内存缓存耗时<1ms,保证单实例的高性能;Redis分布式缓存,保证集群部署时的缓存一致性。而缓存粒度精准到「语言+路由」,是为了避免全量缓存失效,比如更新中文首页的文案,仅清除i18n:zh-CN:home的缓存,不影响其他语言/其他路由的缓存命中率,同时最小化缓存的内存占用,提升缓存利用率。
回答:因为本地Baseline有三个核心优势:一是无IO开销,本地文件同步加载,耗时极短,能保证首屏快速渲染;二是体积小,仅存核心兜底文案,不影响服务端打包/加载;三是适配性强,目录结构与API完全对齐,服务端拼接逻辑无需修改,能快速降级。同时本地Baseline仅做故障兜底,测试/生产正常流程不依赖,不会影响文案准确性,是兼顾可用性和准确性的最优选择。
回答:不会,我们通过三层核心设计完全规避了路由切换的性能损耗,核心逻辑是「按需加载+缓存复用+客户端路由优化」,具体如下:
next/link/useRouter),我们做了两层优化:
总结来说,我们的设计核心是“将翻译请求与SSR页面请求融合,通过缓存最大化复用数据,通过按需加载最小化传输成本”,既保证SSR层面的翻译一致性,又完全避免路由切换的性能损耗。