当小程序开始面向海外用户或港澳台市场时,「把文案翻译一遍」远不足以支撑一个合格的多语言版本。真正的国际化要处理三大类问题:语言(文案翻译与动态占位)、格式(日期、时间、货币、数字的本地习惯)、地区(法律法规、支付渠道、内容政策差异)。小程序的发布结构还让「多语言版本」有了独特的技术选型。本文给出小程序 i18n 的完整落地方案。
一、国际化的挑战与方案选型
1.1 三类挑战
| 类别 | 例子 | 处理位置 |
|---|---|---|
| 语言 | 中文「领取优惠券」→ 英文 | 前端文案 + 服务端文案 |
| 格式 | 日期 2026/09/30 vs 30/09/2026 | 前端格式化 + 服务端字段 |
| 地区 | 地区法律、支付、内容审核差异 | 服务端 + 运营策略 |
1.2 方案选型对比
| 方案 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 端内 i18n | 文案包打进小程序,运行时切换 | 响应快、离线可用 | 包体积增大、需发版更新 |
| 云端文案 | 服务端下发文案 JSON | 免发版更新、支持实时修正 | 依赖网络、首屏需等待 |
| 混合 | 端内为主 + 云端热更关键文案 | 平衡体验与灵活 | 两套管理成本 |
一句话:推荐混合方案——常用文案内置保证首屏,运营敏感文案云端下发保证灵活。
1.3 语言识别
获取用户语言可以结合 wx.getSystemInfo 与 wx.getAppBaseInfo:
// 获取系统语言与地区
function detectLocale() {
const baseInfo = wx.getAppBaseInfo();
const systemInfo = wx.getSystemInfoSync();
const lang = baseInfo.language || 'zh_CN';
const region = systemInfo.country || 'CN';
// 语言优先,地区补充
return normalizeLocale(lang, region);
}
function normalizeLocale(lang, region) {
const map = {
'en': 'en-US',
'zh_CN': 'zh-CN',
'zh_TW': 'zh-TW',
'zh_HK': 'zh-HK',
'ja': 'ja-JP'
};
return map[lang] || map[region] || 'en-US';
}
二、文案管理与 i18n 框架
2.1 文案文件结构
按语言组织文案文件,结构化存放 key-value:
{
"common": {
"confirm": "确认",
"cancel": "取消",
"loading": "加载中..."
},
"order": {
"status_paid": "已支付",
"status_shipped": "已发货",
"total": "共 {count} 件商品"
}
}
{
"common": {
"confirm": "Confirm",
"cancel": "Cancel",
"loading": "Loading..."
},
"order": {
"status_paid": "Paid",
"status_shipped": "Shipped",
"total": "{count} items in total"
}
}
2.2 前端 i18n 工具函数
// utils/i18n.js:轻量 i18n 实现
const locales = {
'zh-CN': require('./locales/zh-CN.json'),
'en-US': require('./locales/en-US.json')
};
let currentLocale = 'zh-CN';
function setLocale(locale) {
currentLocale = locales[locale] ? locale : 'en-US';
wx.setStorageSync('locale', currentLocale);
}
function t(key, params) {
let text = currentLocale in locales
? lookup(locales[currentLocale], key)
: key;
// 占位符替换:{count} → 实际值
if (params) {
Object.keys(params).forEach((k) => {
text = text.replace('{' + k + '}', String(params[k]));
});
}
return text;
}
function lookup(obj, key) {
return key.split('.').reduce((o, k) => (o && o[k]) || key, obj);
}
module.exports = { t, setLocale, getLocale: () => currentLocale };
2.3 文案管理与翻译流程
| 环节 | 做法 | 工具/规范 |
|---|---|---|
| 文案提取 | 开发统一走 t(key),禁止硬编码 | 代码规范 |
| 文案评审 | 产品与运营审核关键文案 | 文案 review 会 |
| 翻译管理 | 使用翻译管理平台(如 Crowdin) | 外部协作 |
| 版本同步 | 新 key 自动生成待翻译清单 | CI 检查 |
| 上线兜底 | 缺翻译时回退默认语言 | 运行时 fallback |
一句话:国际化的第一步不是翻译,而是「消灭硬编码文案」,所有展示文案都走统一的取词入口。
三、时区、日期与货币适配
3.1 时间与日期的本地习惯
| 地区 | 日期示例 | 时间示例 |
|---|---|---|
| 中国大陆 | 2026/09/30 | 15:30 |
| 美国 | 09/30/2026 | 3:30 PM |
| 欧洲 | 30/09/2026 | 15:30 |
| 日本 | 2026年9月30日 | 15:30 |
服务端应统一存储 UTC 时间戳,前端按用户本地时区格式化:
// 按用户时区格式化日期
function formatDate(ts, locale) {
const d = new Date(ts);
const opts = { year: 'numeric', month: '2-digit', day: '2-digit' };
return d.toLocaleDateString(locale, opts);
}
// 统一接口:服务端返回 UTC 毫秒时间戳
{
"created_at": 1780200000000,
"server_timezone": "UTC"
}
3.2 货币与金额
金额字段必须包含币种,不能只存数字。服务端推荐用「金额 + 币种」结构化返回,展示层按当地格式输出:
// 金额格式化:币种 + 千分位 + 小数位
function formatMoney(amount, currency, locale) {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency
}).format(amount / 100); // 金额以「分」为单位存储
}
// 示例:formatMoney(123456, 'CNY', 'zh-CN') → ¥1,234.56
// formatMoney(19900, 'USD', 'en-US') → $199.00
| 币种 | 符号 | 小数位 |
|---|---|---|
| CNY | ¥ | 2 |
| USD | $ | 2 |
| JPY | ¥ | 0 |
| EUR | € | 2 |
一句话:时间一律存 UTC 毫秒戳,金额一律带币种,格式化交给端上的 Intl,才能避免「日期错位、金额对不上」的国际事故。
3.3 数字与度量差异
- 数字格式:英文用千分位逗号
1,234,部分地区用空格或句点。 - 百分比:
50%在不同语言前后位置不同。 - 单位:重量(磅/千克)、长度(英尺/米)、温度(华氏/摄氏)需按地区换算。
- 地址排序:国家/省份/城市/街道的顺序各地区不同。
四、地区化差异适配
4.1 本地化资产
除了文字,图片、图标、颜色、排版都可能需要本地化:
| 资产类型 | 本地化要点 |
|---|---|
| 图片 | 含文字的 banner 需按语言出图 |
| 图标 | 含义在不同文化有歧义时更换 |
| 颜色 | 注意宗教/文化对颜色的禁忌 |
| 布局 | 长文案导致的折行与高度变化 |
| RTL | 阿拉伯语/希伯来语需镜像布局 |
4.2 RTL 与布局适配
部分语言从右向左排版,小程序需要在样式层支持:
/* 针对 RTL 语言的样式适配 */
.page-rtl {
direction: rtl;
text-align: right;
}
// 根据语言设置 RTL 标记
function applyDirection(locale) {
const rtlLangs = ['ar', 'he', 'fa'];
const isRtl = rtlLangs.includes(locale.split('-')[0]);
wx.setStorageSync('is_rtl', isRtl);
if (isRtl) {
wx.setNavigationBarTitle({ title: t('app.name') });
}
}
4.3 支付与渠道的本地化
不同市场支付渠道差异巨大,多语言版本往往意味着多支付渠道:
| 市场 | 常见支付方式 |
|---|---|
| 中国大陆 | 微信支付、支付宝 |
| 港澳台 | 支付宝HK、信用卡 |
| 东南亚 | 本地钱包、信用卡 |
| 欧美 | 信用卡、PayPal |
支付渠道的适配建议参考小程序微信支付的经验,扩展为按地区路由的支付网关层。
五、多语言版本的发布与运营
5.1 多语言版本策略
微信小程序可以按不同主体/版本面向不同市场,常见三种策略:
| 策略 | 做法 | 适用 |
|---|---|---|
| 单一版本多语言 | 一个代码包内置多语言,运行时切换 | 全球化产品 |
| 按地区分版本 | 不同主体分别发布不同语言版本 | 有合规隔离需求 |
| 主版本 + 海外版 | 国内稳定版 + 海外独立版本 | 业务差异大 |
5.2 文案更新的发布节奏
云端文案方案支持免发版更新,适合运营高频修正:
运营修改文案
↓ 翻译平台完成多语言翻译
↓ 发布到云端文案服务(带版本号)
客户端启动拉取文案版本
↓ 有新版则增量更新本地文案
下次启动生效(或提示刷新)
5.3 运营与合规差异
- 隐私政策:不同地区要求不同(GDPR、个保法),需按地区展示对应版本。
- 年龄限制:部分市场对未成年保护要求更严格。
- 内容审核:输出内容需适配当地法规与敏感词表。
- 客服与售后:多语言客服体系、时区化服务时间。
一句话:多语言运营不是「翻译完就结束」,而是语言、支付、合规、客服四个维度都要按地区对齐。
六、工具链与测试
6.1 国际化质量门禁
在 CI 中加入国际化检查,防止漏译与占位符错配:
// 检查工具:占位符一致性校验
function validatePlaceholders(keys, refLocale, targetLocales) {
const refTexts = keys.map((k) => lookup(refLocale, k));
targetLocales.forEach((locale) => {
keys.forEach((k, i) => {
const target = lookup(locale, k);
// 占位符集合必须一致
const refVars = refTexts[i].match(/\{[^}]+\}/g) || [];
const tgtVars = (target.match(/\{[^}]+\}/g) || []);
if (refVars.sort().join() !== tgtVars.sort().join()) {
console.error('占位符不一致:', k, locale);
}
});
});
}
6.2 测试要点
| 测试类型 | 覆盖内容 |
|---|---|
| 语言切换 | 全页面取词、运行时热切 |
| 格式边界 | 长文案折行、超长用户名、货币精度 |
| 时区边界 | 跨日、跨月、夏令时 |
| RTL 布局 | 镜像排版、图标翻转 |
| 文案缺省 | 缺失 key 的 fallback 表现 |
| 服务端字段 | 时区戳、币种、语言字段正确性 |
6.3 模拟器与真机
开发者工具的「模拟器 - 语言设置」可切换系统语言验证;真机测试务必覆盖不同语言系统下的实际表现,因为部分接口返回的系统语言在模拟器中与真机有差异。
七、总结
小程序国际化是一个「语言 + 格式 + 地区」三维立体的工程:前端消灭硬编码文案、统一走 i18n 取词,服务端统一存储 UTC 时间与带币种金额,运营侧按地区对齐支付、合规与客服。方案上推荐「端内文案 + 云端热更」混合,策略上按业务差异选择单一版本多语言或分版本发布。最关键的是把国际化当成从第一天就考虑的基础架构,而不是上线前的翻译补丁——这样后续每新增一个市场,成本都只是「加语言 + 加地区适配」,而不是推倒重来。结合小程序灰度发布分市场逐步放量,配合测试与 CI守住文案与占位符质量,再借助数据分析观察各语言市场漏斗,国际化才能真正转化为海外增长。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。