国际化(i18n,因为internationalization首尾之间夹了18个字母而得名)听起来是个大工程,但拆开来看,它本质上只解决两件事:一是把界面文案从业务代码中剥离出来,二是根据用户的语言偏好返回对应的内容。Node.js作为服务端运行时,天然适合承担这份工作,无论是渲染页面还是输出JSON接口,都可以在出口处统一完成语言切换。这篇文章会从语言包的组织、主流库的接入、本地化格式处理这几个层面,把Node.js项目的多语言方案完整讲一遍。

一、先理解国际化和本地化的边界
很多人把i18n和l10n(localization)混为一谈,其实两者侧重点不同。国际化指的是架构层面的准备:代码中不硬编码任何语言相关的字符串,日期、数字的格式化不写死规则,为后续接入任意语言留好接口。本地化则是针对某一具体语言区域的适配工作,比如把文案翻译成日语、把日期格式调整成年月日顺序、把货币单位换成日元。
落到Node.js项目里,国际化的第一原则是文案与逻辑分离。看一段反面教材:
const message = '登录失败,请检查用户名和密码';
res.json({ code: 401, message });
// 文案写死在代码里,后续要支持英文就得改业务代码,翻译人员也没法协作正确的做法是只保留一个key,渲染时根据语言取值:
const message = req.t('login.failed');
res.json({ code: 401, message });
// 'login.failed' 在 zh.json 里对应中文,在 en.json 里对应英文这样做的好处不只是能翻译。文案集中管理后,产品经理改一句话不需要动代码,翻译人员只面对资源文件,代码评审时也不会被大段文案干扰。这就是国际化的架构收益。
二、语言资源文件的组织方式
语言包通常按语言代码分文件存放,常见目录结构如下:
project/
├── locales/
│ ├── zh-CN.json
│ ├── en-US.json
│ └── ja-JP.json
└── src/
└── app.js资源文件本身是嵌套的JSON结构,按功能模块分组能让维护成本大幅降低:
// locales/zh-CN.json
{
"common": {
"confirm": "确定",
"cancel": "取消"
},
"login": {
"failed": "登录失败,请检查用户名和密码",
"welcome": "欢迎回来,{{name}}"
},
"errors": {
"network": "网络异常,请稍后重试"
}
}命名空间(namespace)进一步细分场景也是常见做法。比如把后台管理和用户端拆成两个语言文件,按需加载,避免一次性把几百KB的文案全部读进内存。对于文案量特别大的项目,还可以配合数据库存储,把翻译交给专门的翻译平台管理,构建时再同步成本地JSON文件。
有一点需要特别注意:{{name}}这类插值占位符,千万不要让翻译人员改动它。不同语言的语序差异很大,英文可能是Welcome back, {{name}},日文可能是{{name}}さん、おかえりなさい,占位符位置完全不同,这正是插值机制存在的意义。
三、用i18next快速接入多语言支持
i18next是Node.js生态中最成熟的国际化框架,功能覆盖插值、复数、嵌套、延迟加载等几乎全部场景。先安装依赖:
npm install i18next i18next-fs-backend
接着在项目中初始化:
const i18next = require('i18next');
const Backend = require('i18next-fs-backend');
const path = require('path');
await i18next.use(Backend).init({
lng: 'zh-CN',
fallbackLng: 'zh-CN',
preload: ['zh-CN', 'en-US', 'ja-JP'],
ns: ['common', 'login'],
defaultNS: 'common',
backend: {
loadPath: path.join(__dirname, '../locales/{{lng}}/{{ns}}.json')
}
});几个关键参数值得说明。fallbackLng指定当某个key在目标语言中缺失时的回退语言,避免界面直接显示出原始key;preload预加载语言列表,也可以不配置改用懒加载;ns是命名空间列表,配合目录locales/{{lng}}/{{ns}}.json实现按模块拆分文件。
在Express中使用时,一般会封装一个中间件,从请求中解析用户的语言偏好,再调用i18next.changeLanguage切换语言:
const express = require('express');
const i18next = require('i18next');
const { middleware, handle } = require('i18next-http-middleware');
const app = express();
app.use(middleware.handle(i18next, {
loadLanguagesFromHeaders: true
}));
app.get('/api/profile', (req, res) => {
// req.language 是探测到的语言,req.t 是绑定了该语言的翻译函数
const msg = req.t('login.welcome', { name: '张三' });
res.json({ message: msg });
});这个中间件会依次检查URL参数?lng=en-US、Cookie和请求头Accept-Language,取到的语言还会写入Cookie以便后续请求复用,省去了自己解析的麻烦。
四、复数与插值的处理技巧
复数是国际化里容易被忽视的坑。中文没有单复数之分,但英文、俄文、阿拉伯文的情况完全不同。比如英文中1 message和2 messages,直接拼接字符串会出现1 messages这种错误。i18next用复数后缀解决这个问题:
// en-US.json
{
"cart": {
"item_one": "{{count}} item in your cart",
"item_other": "{{count}} items in your cart"
}
}
// 中文资源文件可以只写一条,因为中文没有复数形式
// zh-CN.json
{
"cart": {
"item_other": "购物车中有 {{count}} 件商品"
}
}调用时传入count参数,i18next会自动根据语言规则选择对应后缀:
req.t('cart.item', { count: 1 }); // 1 item in your cart
req.t('cart.item', { count: 5 }); // 5 items in your cart插值方面,除了基本的{{name}},还支持格式化函数。金额、日期这类值往往不能直接拼进字符串,因为不同区域的格式差异极大,下一节专门讨论这个问题。
五、日期、数字与货币的本地化
国际化不只是翻译文案。同样是2024年3月5日,美式英文显示为03/05/2024,德文显示为05.03.2024,中文显示为2024年3月5日。数字和货币同理,一千二百三十四点五在英文里是1,234.5,德文里却是1.234,5。这些格式问题要用Intl对象处理,它是Node.js内置的国际化API,无需额外依赖:
const date = new Date('2024-03-05T10:00:00Z');
new Intl.DateTimeFormat('zh-CN').format(date); // 2024/3/5
new Intl.DateTimeFormat('en-US').format(date); // 3/5/2024
new Intl.DateTimeFormat('de-DE').format(date); // 5.3.2024
new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(1234.5);
// ¥1,234.50
new Intl.NumberFormat('ja-JP', { style: 'currency', currency: 'JPY' }).format(1234.5);
// ¥1,235(日元没有小数,自动取整)有一点必须提醒:Intl的完整功能依赖ICU数据。如果使用Alpine Linux等精简镜像构建Docker镜像,可能会遇到full-icu未安装导致格式化结果退化的情况,需要在Dockerfile中安装icu-data-full包,或者直接换用带完整ICU的官方镜像,这是生产环境高频踩坑点。
六、按需加载与性能优化建议
语言文件虽然不大,但在高并发场景下仍然值得优化。几个实践建议:第一,服务启动时只preload核心语言和核心命名空间,小语种延迟加载;第二,语言文件读取后i18next默认会缓存在内存中,重复请求不会触发磁盘IO,不要在请求处理函数里每次都新建i18next实例,那会带来严重的性能开销;第三,对于前后端分离项目,接口只返回key和参数,具体文案由前端渲染,这样语言文件只需要打包到前端,服务端彻底解耦。
另外建议在CI流程中加一步校验,对比各语言资源文件的key集合,及时发现某个语言漏翻译导致的回退问题。i18next-scanner这类工具可以自动扫描代码中的t函数调用,提取key生成语言文件骨架,翻译人员填充即可,能显著降低多语言项目的协作成本。
总结
Node.js的国际化方案核心在于三点:文案抽离到资源文件、用i18next配合中间件按请求切换语言、用Intl处理日期数字货币的本地化格式。落地时注意复数规则的差异、fallback语言的兜底、Docker镜像的ICU数据完整性,再配合按需加载和CI校验,一套可维护的多语言体系就搭建完成了。国际化做得越早,后期改造的成本越低,如果项目有出海计划,建议在架构设计阶段就把i18n纳入考虑。
Node.js国际化i18n本地化配置修改时间:2026-09-15 19:47:13