Appearance
10_Engineering/08 - 国际化(i18n)
1. 国际化概述
国际化(Internationalization, i18n)是指应用支持多语言、多文化的能力。HarmonyOS 提供了完整的国际化方案,通过资源限定符自动切换语言。
1.1 国际化 vs 本地化
| 概念 | 说明 |
|---|---|
| 国际化 (i18n) | 让应用能够适配不同地区/语言 |
| 本地化 (l10n) | 将应用适配到具体地区的语言和文化 |
| 全球化 (g11n) | 国际化的上层概念,涵盖所有地区 |
2. 资源限定符体系
2.1 语言/地区限定符
resources/
├── base/ # 默认语言(回退)
│ └── element/
│ ├── string.json # 字符串
│ └── color.json # 颜色
├── zh_CN/ # 简体中文
│ └── element/
│ └── string.json
├── en_US/ # 美式英语
│ └── element/
│ └── string.json
├── en_GB/ # 英式英语
│ └── element/
│ └── string.json
├── ja_JP/ # 日语
│ └── element/
│ └── string.json
├── ko_KR/ # 韩语
│ └── element/
│ └── string.json
├── fr_FR/ # 法语
│ └── element/
│ └── string.json
└── de_DE/ # 德语
└── element/
└── string.json2.2 语言代码规范
zh_CN = Chinese (China)
zh_TW = Chinese (Taiwan)
en_US = English (United States)
en_GB = English (United Kingdom)
ja_JP = Japanese (Japan)
ko_KR = Korean (Korea)
fr_FR = French (France)
de_DE = German (Germany)
es_ES = Spanish (Spain)
ar_SA = Arabic (Saudi Arabia)3. 多语言资源配置
3.1 string.json 配置
json5
// base/element/string.json (默认语言 - 中文)
{
"string": [
{ "name": "app_name", "value": "我的应用" },
{ "name": "welcome", "value": "欢迎使用" },
{ "name": "login", "value": "登录" },
{ "name": "logout", "value": "退出登录" },
{ "name": "hello_format", "value": "你好,{0}!" },
{ "name": "price_format", "value": "¥{0}" },
{ "name": "confirm", "value": "确认" },
{ "name": "cancel", "value": "取消" },
{ "name": "language", "value": "语言" }
]
}
// en_US/element/string.json (美式英语)
{
"string": [
{ "name": "app_name", "value": "My App" },
{ "name": "welcome", "value": "Welcome" },
{ "name": "login", "value": "Login" },
{ "name": "logout", "value": "Logout" },
{ "name": "hello_format", "value": "Hello, {0}!" },
{ "name": "price_format", "value": "${0}" },
{ "name": "confirm", "value": "Confirm" },
{ "name": "cancel", "value": "Cancel" },
{ "name": "language", "value": "Language" }
]
}
// ja_JP/element/string.json (日语)
{
"string": [
{ "name": "app_name", "value": "マイアプリ" },
{ "name": "welcome", "value": "ようこそ" },
{ "name": "login", "value": "ログイン" },
{ "name": "logout", "value": "ログアウト" },
{ "name": "hello_format", "value": "こんにちは、{0}さん!" },
{ "name": "price_format", "value": "¥{0}" },
{ "name": "confirm", "value": "確認" },
{ "name": "cancel", "value": "キャンセル" },
{ "name": "language", "value": "言語" }
]
}3.2 颜色国际化
json5
// base/element/color.json
{
"color": [
{ "name": "primary_color", "value": "#FF0080" },
{ "name": "bg_color", "value": "#F1F3F5" },
{ "name": "text_color", "value": "#18181C" }
]
}
// en_US/element/color.json
{
"color": [
{ "name": "primary_color", "value": "#0052CC" }, // 不同品牌色
{ "name": "bg_color", "value": "#FFFFFF" },
{ "name": "text_color", "value": "#1A1A1A" }
]
}3.3 图片国际化
resources/
├── base/media/
│ ├── logo.png # 默认 logo
│ └── icon_home.png
├── en_US/media/
│ └── logo.png # 英文版 logo(可能需要不同文字)
└── ja_JP/media/
└── logo.png # 日文版 logo4. 资源访问方式
4.1 UI 模板中访问
typescript
@Entry
@Component
struct I18nPage {
build() {
Column() {
// 通过 $r 装饰器访问
Text($r('app.string.welcome'))
.fontSize(24)
.fontWeight(FontWeight.Bold)
Text($r('app.string.app_name'))
.fontSize(18)
// 带参数的字符串
Text($r('app.string.hello_format', 'World'))
// 按钮文本
Button($r('app.string.login'))
.width('100%')
.height(48)
.onClick(() => { this.navigateToLogin(); })
}
.width('100%')
.height('100%')
}
navigateToLogin(): void {
router.pushUrl({ url: 'pages/LoginPage' });
}
}4.2 代码中访问
typescript
import { resourceManager } from '@kit.AbilityKit';
class I18nService {
private rm: ResourceManager;
constructor() {
this.rm = getContext(this).resourceManager;
}
/** 获取字符串 */
getString(name: string): string {
return this.rm.getStringByName(name);
}
/** 获取带参数的字符串 */
getStringFormat(name: string, params: string[]): string {
return this.rm.getStringByName(name, params);
}
/** 获取颜色 */
getColor(name: string): string {
return this.rm.getColorByName(name);
}
/** 获取当前语言 */
getCurrentLanguage(): string {
const locale = this.rm.getLocale();
return locale.language + '_' + locale.country;
}
/** 设置语言 */
setLanguage(lang: string): void {
const locale = new Locale();
if (lang.startsWith('zh')) {
locale.language = 'zh';
locale.country = 'CN';
} else if (lang.startsWith('en')) {
locale.language = 'en';
locale.country = 'US';
}
// 注意:设置语言需要重新应用
}
}5. 运行时语言切换
5.1 语言选择器
typescript
@Entry
@Component
struct LanguageSelector {
@State currentLang: string = 'zh_CN';
private languages: string[] = ['zh_CN', 'en_US', 'ja_JP', 'ko_KR'];
private langNames: Record<string, string> = {
'zh_CN': '简体中文',
'en_US': 'English',
'ja_JP': '日本語',
'ko_KR': '한국어',
};
build() {
Column() {
Text($r('app.string.language'))
.fontSize(20)
.margin({ bottom: 16 })
ForEach(this.languages, (lang: string) => {
Row() {
Text(this.langNames[lang])
.fontSize(16)
.fontColor(lang === this.currentLang ? '#0080FF' : '#333')
if (lang === this.currentLang) {
Text('✓')
.fontColor('#0080FF')
.fontWeight(FontWeight.Bold)
}
}
.width('100%')
.height(48)
.onClick(() => {
this.switchLanguage(lang);
})
.borderRadius(8)
.backgroundColor(lang === this.currentLang ? '#E8F4FF' : 'transparent')
})
}
.width('100%')
.height('100%')
.padding(16)
}
switchLanguage(lang: string): void {
this.currentLang = lang;
// 通过配置资源管理器切换语言
const locale = new Locale();
const parts = lang.split('_');
locale.language = parts[0];
locale.country = parts[1];
// 重新配置 ResourceManager 以应用新语言
this.applyLanguage(locale);
}
applyLanguage(locale: Locale): void {
// 触发页面重新渲染
// 实际项目中可通过 Context 或全局状态管理实现
}
}6. 国际化最佳实践
6.1 规范
| 规范 | 说明 |
|---|---|
| 字符串参数化 | 使用 {0}, {1} 占位符,而非字符串拼接 |
| 默认语言 | base 目录始终包含所有字符串 |
| 遗漏检测 | 确保每种语言都有对应的字符串 |
| 复数处理 | 注意不同语言的复数规则(en: 1 item / 2 items) |
| RTL 语言 | Arabic/Hebrew 需要 RTL 布局支持 |
| 日期/时间 | 使用 Intl.DateTimeFormat 格式化 |
| 数字格式 | 不同地区千分位/小数点不同 |
6.2 遗漏字符串处理
当 en_US 中缺少某个字符串时,会自动回退到 base 目录的字符串。
查找顺序:设备语言 → 对应语言目录 → base 目录6.3 RTL(从右到左)语言
typescript
// 阿拉伯语需要 RTL 布局
@Entry
@Component
struct RTLPage {
build() {
Row() {
// RTL 下自动从右到左排列
Text($r('app.string.app_name'))
.textDirection(TextDirection.RTL)
}
.width('100%')
.height('100%')
.direction(DirDirection.RTL) // 整个布局 RTL
}
}7. 面试高频考点
Q1: 如何管理多语言字符串?
回答要点:
- 使用
resources/<lang>/element/string.json按语言目录管理base目录放默认语言(回退用)- 通过
$r('app.string.name')访问- 支持带参数
{0},{1}的字符串- 字符串参数化,不硬编码中文
Q2: 运行时如何切换语言?
回答要点:
- 通过
ResourceManager设置 Locale- 重新渲染当前页面
- 注意 RTL 语言(阿拉伯语/希伯来语)的布局翻转
- 部分系统语言由设备决定,应用可覆盖
Q3: 资源回退机制是什么?
回答要点:
- 查找顺序:设备语言 → 对应语言目录 → base 目录
- 如果
en_US缺少某个字符串,自动回退到base- 确保
base包含所有字符串作为兜底- 这是 HarmonyOS 的默认行为,不需要额外配置
8. Android 对比
| 概念 | Android | HarmonyOS |
|---|---|---|
| 资源目录 | res/values-en/ | resources/en_US/ |
| 字符串文件 | strings.xml | string.json |
| 资源引用 | @string/name | $r('app.string.name') |
| 语言代码 | ISO 639-1 + 国家代码 | 相同规范 |
| RTL 支持 | layoutDirection | direction + textDirection |
| 格式化 | String.format | $r with params |
| 资源定位器 | Resources.getString() | resourceManager.getStringByName() |