国际化i18n实现?架构方案全解
▌ 技术引导 我见过太多团队做国际化i18n,要么搞一半就卡死,要么全盘复用框架默认,最后上线后用户根本看不懂。别再走老路了,现在说清楚一点:i18n不是换个语言包就完事。它是个系统工程,从数据层到展示层都得打通。我们用react + next.js + i18next,配合webstorm + prettier + eslint,搭建出来的方案,可以支持多语言切换、动态加载、后端接口适配,还能在CI中自动检测翻译缺失。重点是别瞎折腾,少用第三方库,多用原生方案,否则性能会炸。如果你在用spring boot + thymeleaf,也可以用messageSource + localeResolver,但得自己处理前端资源。我踩过坑,知道怎么做才稳。直接上干货,别问为什么,按我说的来。 ▌ 技术参考 一 在react企业级项目中,i18n架构设计必须从组件树和数据流入手。我们用next.js + i18next,把i18n配置放在next.config.js里,设置i18n: { locales: ['en', 'zh'], defaultLocale: 'zh' },这样next会自动处理多语言路由。但别忘了,在pages目录下每个页面要支持多语言,所以要用next-intl这个库,配合i18next的backend配置,把语言文件放在public/locales下。推荐用json格式,结构是locale: { key: value },这样方便维护。 二 i18next的backend配置需要指定存储路径和查询方式,比如用fileBackend的话,配置是backend: { loadPath: '/locales/{{lng}}/{{ns}}.json' }。别用默认的json文件结构,因为next-intl需要特定的命名规则,比如zh-CN和zh-TW。这样在前端切换语言时,能准确加载对应的资源。我们还用到了i18next-http-middleware,这样可以在服务端渲染时处理语言切换,避免页面加载时出现空字段。记得在server.js里加上中间件,否则会报错。 三 用next-intl时,组件要包裹在里,这能确保所有翻译函数都能正常工作。但别把翻译函数随便写在组件内部,这样会污染代码结构。推荐用自定义hook,比如useTranslation,统一管理翻译逻辑。另外,翻译文件要按命名规范来,比如zh-CN.json、en-US.json,而不是随便放个zh.json。否则next-intl会出现加载错误,特别是多语言环境下的路由问题。 四 前端资源翻译容易出错的地方是动态内容和格式化字符串。比如日期、数字、货币这些,不能直接翻译,得用format函数。我们用的是i18next的format方法,比如t('date', { date: new Date(), format: 'date' })。别用简单的字符串替换,这样会漏掉格式化逻辑。另外,翻译文件要按ns分组,比如common、ui、api,这样能减少冲突,也方便维护。在next.config.js里设置i18next: { ns: ['common', 'ui', 'api'] }。 五 后端接口要统一处理多语言请求,用Spring Boot的话,可以配置MessageSource + LocaleResolver。MessageSource的basename设置为messages,这样不同语言文件会加载成messages_en.properties和messages_zh.properties。但千万别用默认的localeResolver,得自己写一个,根据请求头Accept-Language来判断,比如用HeaderLocaleResolver实现。这样能确保后端返回的数据能和前端翻译系统对齐,避免出现“中文字段对应英文翻译”的混乱。 六 服务端渲染时,翻译文件需要提前加载,否则页面会空白。我们用的是i18next的useServerSideTranslations钩子,配合next-intl在app目录下的布局组件。这样在页面加载时就能获取到翻译数据,避免用户看到未翻译的内容。但要注意,这个钩子只能用在app目录下的页面里,不能用于pages目录。所以如果你用的是next.js的旧版架构,得考虑迁移到app目录,否则会出问题。 七 在CI/CD中自动检测翻译缺失,用的是Prettier配合i18next的检查工具。配置Prettier的ignorePatterns,排除翻译文件,不过在检查时要强制检查,用--check模式。另外,用i18next的checker插件,能扫描所有翻译键,确保没有未翻译内容。配置文件里加上checker: { missingKey: 'warn', emptyString: 'warn' },这样能提前发现问题。别等到上线才发现翻译漏了,这样代价太大。 八 翻译文件的结构要规范,避免嵌套和歧义。比如不要用对象嵌套,直接写成key: value。这样在前端调用时不会出错,也方便后端处理。我们用的是flat结构,每个翻译键独立存在,而不是嵌套结构。另外,翻译文件要按ns分组,比如common、ui、api,这样能减少翻译文件之间的依赖。别让一个翻译文件依赖另一个,这会让维护变得麻烦。 九 前端组件要支持动态语言切换,用next-intl的useTranslations钩子,配合一个语言选择组件。比如用select-language组件封装,里面监听语言变化,然后调用setLocale。但这里有个坑,切换语言后页面会重新加载,导致状态丢失。解决办法是用useRouter钩子,手动更新路由,同时用useEffect监听locale变化。这样能确保切换语言后页面状态不会重置,用户体验更好。 十 在多语言环境下,日期、时间、货币这些格式化内容要特别小心。用i18next的format方法能把数字格式化成不同语言下的标准写法,比如中文是“1,000.00”,英文是“1,000.00”。但别忘了,有些格式化规则是locale相关的,比如数字的千位分隔符。我们用的是Intl.NumberFormat,配合i18next的format方法,这样能确保所有格式化内容都符合语言习惯。别用简单的字符串替换,否则会漏掉格式化逻辑。 十一 如果项目不是用next.js,用react-i18next也可以。但配置起来更麻烦,需要自己处理路由和翻译键。我们用的是react-i18next,配合react-i18next的Provider,然后在每个组件中通过useTranslation调用翻译函数。但别用默认的i18next配置,得自己封装一个Context,这样能避免全局污染。另外,react-i18next的useTranslation不能用在SSR环境中,得用react-i18next的useServerSideTranslations,这样才不会出错。 十二 在开发时,要确保翻译文件和代码文件同步。比如,某个组件调用了t('welcome'),但翻译文件里没有这个键,就会报错。我们用的是TypeScript + Prettier + ESLint,配合i18next的checker插件,这样在编译时就能检测到缺失的翻译键。配置Prettier的ignorePatterns,排除翻译文件,但检查时要用--check模式。这样能确保代码和翻译文件不脱节,避免上线后出现翻译缺失的问题。 十三 翻译文件的版本管理要严格。每次更新语言时,得确保翻译文件和前端代码是同步的。我们用的是Git + LFS,这样能追踪翻译文件的变更。另外,翻译文件要放在git仓库的特定分支,比如translations,这样能避免主分支被污染。在合并到主分支之前,要确保所有翻译键都已经被使用,否则会报错。别让翻译文件成孤岛,必须和代码保持一致。 十四 在实际项目中,翻译文件的维护是个大问题。我们用的是webstorm的翻译插件,自动识别翻译键,然后生成翻译文件。但这个插件会生成很多冗余字段,得手动清理。另外,有些翻译键是动态生成的,比如api返回的数据,这时候得用i18next的pluralization和interpolation功能,确保这些动态内容也能被正确翻译。别用静态文件,动态内容需要配合后端接口一起处理。 十五 如果你用的是vue + vue-i18n,配置会更简单。但要注意,vue-i18n的messages结构要和代码中的翻译键对应。我们用的是vue-i18n 9.x,配置messages: { en: { ... }, zh: { ... } },然后在组件中通过$t调用。但别用默认的locale配置,得在main.js里设置i18n的全局配置,这样切换语言才能生效。别把翻译逻辑分散在各个组件里,统一用一个i18n实例管理,这样才不会出错。





