Skip to content

Internationalization (i18n)

Reference doc — auto-synced from the harmony-app-dev AgentSkill. Source: references/i18n.md

HarmonyOS 国际化(i18n)与本地化(l10n):多语言、多区域、文化差异适配。

Purpose

读这个文件当:

  • 应用要支持多语言(至少中英)
  • 用户可能在不同区域使用(日期、货币、数字格式不同)
  • 需要 RTL(阿拉伯语、希伯来语)布局
  • 时间 / 数字 / 货币的本地化展示

i18n 与 resource-management.md 紧密关联——i18n 是「为什么要做」,resource-management 是「怎么组织文件」。

Capability mapping

  • coverage 域:E4. 国际化 (i18n / l10n)
  • 关联文件:resource-management.md(多语言资源放置),ui-implementation-rules.md(不要硬编码文本)

Official documentation entry points

Concept model

关键概念

  • Locale:语言_地区,如 zh_CNen_USja_JPar_SA
  • i18n:framework + API(intl / i18n 模块)
  • l10n:在不同 locale 下提供对应资源

系统语言 vs 应用语言

系统语言(用户设置中切换)
└── 默认情况下,应用跟随系统
└── 应用也可强制覆盖(应用内语言切换)

资源限定符与 locale 的对应

限定符目录匹配
zh_CN/简体中文(中国大陆)
zh_TW/繁体中文(台湾)
zh/中文(任何地区,作为后备)
en_US/英文(美国)
en/英文(任何地区)
base/全部 fallback

匹配优先级:精确 locale > 仅语言 > base/

Decision tree

应用需要支持几种语言?
├─ 1 种 → 不用做 i18n,硬编码也行
└─ ≥ 2 种 → 走 i18n 流程
需要应用内语言切换?
├─ 不需要(跟随系统) → 只需放好资源文件
└─ 需要 → 配合 i18n.System.setAppPreferredLanguage
需要本地化日期/数字/货币?
├─ 否 → 用 toString 即可
└─ 是 → 用 intl.DateTimeFormat / NumberFormat
需要支持 RTL?
├─ 否(仅 zh/en/ja 等 LTR) → 不需要特别处理
└─ 是(包含 ar/he 等) → 用 Direction.Rtl + 镜像图标

Implementation patterns

Pattern 1: 多语言字符串

resources/zh_CN/element/string.json
{ "string": [
{ "name": "welcome", "value": "欢迎使用" },
{ "name": "cart_items", "value": "购物车有 %d 件商品" }
]}
// resources/en_US/element/string.json
{ "string": [
{ "name": "welcome", "value": "Welcome" },
{ "name": "cart_items", "value": "%d items in cart" }
]}
Text($r('app.string.welcome'))
Text($r('app.string.cart_items', itemCount))

Pattern 2: 应用内强制切换语言

import { i18n } from '@kit.LocalizationKit';
// 切换为英文(重启 UIAbility 后生效)
i18n.System.setAppPreferredLanguage('en-US');
// 获取当前应用语言
const lang = i18n.System.getAppPreferredLanguage();
// 获取系统语言(用户设置)
const sysLang = i18n.System.getSystemLanguage();

⚠️ 切换语言后,当前 UIAbility 需要重新加载页面才能生效。建议:

  • 切换后弹出「重启应用」提示
  • 或主动 router.replaceUrl 把当前页面重新加载

Pattern 3: 日期与时间格式化

import { intl } from '@kit.LocalizationKit';
const date = new Date();
// 中文长格式:2026年5月11日 星期一
const zhFormat = new intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long'
});
console.info(zhFormat.format(date));
// 英文短格式:5/11/26
const enFormat = new intl.DateTimeFormat('en-US', { dateStyle: 'short' });
console.info(enFormat.format(date));
// 时间相对("3 分钟前")
const rtf = new intl.RelativeTimeFormat('zh-CN');
console.info(rtf.format(-3, 'minute')); // "3 分钟前"

Pattern 4: 数字与货币

import { intl } from '@kit.LocalizationKit';
// 千分位
const numFmt = new intl.NumberFormat('en-US');
console.info(numFmt.format(1234567.89)); // "1,234,567.89"
// 中文人民币
const cnyFmt = new intl.NumberFormat('zh-CN', {
style: 'currency',
currency: 'CNY'
});
console.info(cnyFmt.format(99.9)); // "¥99.90"
// 美元
const usdFmt = new intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
// 百分比
const pctFmt = new intl.NumberFormat('zh-CN', {
style: 'percent',
minimumFractionDigits: 1
});
console.info(pctFmt.format(0.135)); // "13.5%"

Pattern 5: RTL 适配

import { i18n } from '@kit.LocalizationKit';
// 检测当前 locale 是否 RTL
const isRtl = i18n.isRTL(i18n.System.getSystemLanguage());
// 在容器上设置方向
Row()
.direction(isRtl ? Direction.Rtl : Direction.Ltr)
.width('100%')
// 图标镜像(前进/后退箭头)
Image($r('app.media.ic_arrow_back'))
.scale({ x: isRtl ? -1 : 1 })

Pattern 6: 复数处理

// 不同语言对复数的处理不同
// 英文:1 item / 2 items
// 中文:N 件商品(无单复数)
// 俄文:5 形态规则
// 推荐:分别定义资源 key(避免复杂的复数逻辑)
$r('app.string.cart_zero') // 购物车空
$r('app.string.cart_singular') // 1 item
$r('app.string.cart_plural', n) // %d items

Common pitfalls

Pitfall 1: 字符串拼接破坏语序

// ❌ 错误:英文里 "go to {city}",中文需要 "去 {city}",但词序可能不同
Text(`Go to ${cityName}`)
// ✅ 正确:用占位符 + 资源
$r('app.string.go_to_city', cityName)
// zh: "前往 %s"
// en: "Go to %s"
// ja: "%s に行く"

Pitfall 2: 时间用 toLocaleString 不可控

// ❌ 不同设备 / 系统语言下输出格式无法预测
new Date().toLocaleString()
// ✅ 用 intl.DateTimeFormat 显式指定 locale 与格式
new intl.DateTimeFormat('zh-CN', { dateStyle: 'medium' }).format(date)

Pitfall 3: 货币不能直接用「¥」

// ❌ ¥99 会被英文用户误认为日元
Text(`¥${price}`)
// ✅ 用 currency 格式化(中国 = CNY,日本 = JPY)
new intl.NumberFormat(locale, { style: 'currency', currency: 'CNY' }).format(price)

Pitfall 4: 假定所有语言长度相近

德语单词常常是英文 1.5 倍长,UI 容器不能用固定宽度装文本。

  • 按钮宽度用 padding 而不是固定 width
  • 标题区域允许换行或省略号
  • 用 LayoutInspector 在长文本下检验

Pitfall 5: 忘记翻译资源回退

base/element/string.json 必须包含所有 key,否则某 locale 缺 key 时会显示空白或资源 ID。

Verification before commit

  • 所有用户可见文本走资源系统
  • base/ 包含所有 string key 的默认值
  • 至少 2 个 locale(zh_CN + en_US)的字符串完整
  • 日期/数字/货币不直接 toString(),统一走 intl
  • 长字符串场景做了 UI 容器宽度测试

When to escalate to official docs

  • 复杂复数规则(俄语、阿拉伯语等)
  • 自定义日历(伊斯兰日历、农历)
  • 双向文本(混合 LTR + RTL)的高级排版
  • 字符集(拉丁扩展、CJK、表情符号)渲染问题

参考 official-search-playbook.md