
前言应用要出海第一道坎就是多语言。HarmonyOS 内置 i18n 国际化框架——把所有文案抽到string.json按语言放不同目录代码用$r(app.string.xxx)引用系统自动按设备语言加载。本篇以「猫猫大作战」中英文切换为锚点把string.json结构、zh_CN/en_US目录分流、$r引用与运行时切换三大要点讲透。提示本系列不讲 ArkTS 基础语法与环境搭建假设你已跟完第 1–28 篇。本篇是布局进阶的第九篇。一、场景拆解中英文切换回顾「猫猫大作战」主菜单文案第 1 篇// 来源entry/src/main/ets/pages/Index.ets MainMenuView() Text(猫猫大作战).fontSize(36).fontWeight(FontWeight.Bold).fontColor(#2C3E50) Text(合并进化 · 策略消除).fontSize(16).fontColor(#95A5A6) Button(开始游戏).width(70%).height(56)痛点所有文案硬编码中文海外用户看不懂。要支持英文得把每个Text(中文)改成Text($r(app.string.xxx))并准备两份string.json。i18n 方案resources/ ├─ base/element/string.json # 默认中文 │ { string: [{ name: app_title, value: 猫猫大作战 }] } ├─ zh_CN/element/string.json # 中文覆盖与 base 相同 │ { string: [{ name: app_title, value: 猫猫大作战 }] } └─ en_US/element/string.json # 英文覆盖 { string: [{ name: app_title, value: Cat Battle }] }// 代码用 $r系统按设备语言自动加载 Text($r(app.string.app_title)) // 设备中文 → 猫猫大作战 // 设备英文 → Cat Battle关键经验i18n 文案抽到 string.json 目录按语言分流 代码用 $r 引用。二、string.json 结构2.1 基本结构resources/base/element/string.json默认中文{ string: [ { name: app_title, value: 猫猫大作战 }, { name: app_subtitle, value: 合并进化 · 策略消除 }, { name: start_game, value: 开始游戏 }, { name: pause, value: 暂停 }, { name: restart, value: 重新开始 }, { name: game_rules, value: 游戏规则 }, { name: rule_1, value: 点击列投放猫咪 }, { name: rule_2, value: 相邻同级猫咪自动合并升级 }, { name: high_score, value: 最高分 }, { name: game_over, value: 游戏结束 }, { name: final_score, value: 最终得分 }, { name: new_record, value: 新纪录 }, { name: play_again, value: 再来一局 }, { name: back_to_menu, value: 返回主菜单 } ] }字段说明字段含义name资源名蛇形与代码引用一致value默认文案2.2 en_US 覆盖resources/en_US/element/string.json{ string: [ { name: app_title, value: Cat Battle }, { name: app_subtitle, value: Merge Evolve · Strategic Elimination }, { name: start_game, value: Start Game }, { name: pause, value: Pause }, { name: restart, value: Restart }, { name: game_rules, value: Game Rules }, { name: rule_1, value: Tap a column to drop a cat }, { name: rule_2, value: Adjacent same-level cats auto-merge }, { name: high_score, value: High Score }, { name: game_over, value: Game Over }, { name: final_score, value: Final Score }, { name: new_record, value: New Record }, { name: play_again, value: Play Again }, { name: back_to_menu, value: Back to Menu } ] }关键经验en_US 的 string.json 只放需要翻译的 key——名字必须与 base 一致否则编译报错。三、语言目录分流3.1 目录命名规则HarmonyOS 按语言-地区命名目录目录语言设备语言匹配base/默认兜底所有设备zh_CN/简体中文设备语言中文地区中国zh_TW/繁体中文设备语言中文地区台湾en_US/美式英文设备语言英文地区美国en_GB/英式英文设备语言英文地区英国ja_JP/日文设备语言日文ko_KR/韩文设备语言韩文3.2 加载优先级1. 精确匹配zh_CN/ 设备中文中国 2. 语言匹配zh_HK/ → zh_CN/ → zh/fallback 3. 默认兜底base/实战经验只准备base/默认中文en_US/英文两套覆盖 95% 场景。其他语言按需追加。3.3 目录结构示例resources/ ├─ base/ │ ├─ element/ │ │ ├─ string.json # 默认中文文案 │ │ └─ color.json │ ├─ media/ │ └─ profile/ ├─ en_US/ │ └─ element/ │ └─ string.json # 英文文案 ├─ zh_CN/ # 可选与 base 相同则省略 │ └─ element/ │ └─ string.json └─ dark/ # 暗色模式第 28 篇 └─ element/ └─ color.json四、改造主菜单支持 i18n4.1 主菜单文案全部用 $rBuilder MainMenuView() { Column() { Spacer().height(15%) // 标题区i18n 改造 Text().fontSize(72).margin({ bottom: 8 }) Text($r(app.string.app_title)) .fontSize(36) .fontWeight(FontWeight.Bold) .fontColor($r(app.color.title_color)) .margin({ bottom: 8 }) Text($r(app.string.app_subtitle)) .fontSize(16) .fontColor($r(app.color.subtitle_color)) .margin({ bottom: 48 }) // 最高分 if (this.highScore 0) { Row() { Text( ).fontSize(16).fontColor(#F1C40F) Text($r(app.string.high_score) : ) .fontSize(16).fontColor(#F1C40F) Text(this.highScore.toString()) .fontSize(20).fontWeight(FontWeight.Bold).fontColor(#F1C40F) }.margin({ bottom: 32 }) } // 开始游戏按钮 Button($r(app.string.start_game)) .width(70%).height(56) .fontSize(20).fontWeight(FontWeight.Bold) .fontColor(#FFFFFF) .backgroundColor($r(app.color.primary_button)) .borderRadius(28) .shadow({ radius: 8, color: rgba(46, 204, 113, 0.4), offsetY: 4 }) .onClick(() { this.startGame(); }) Spacer().height(24) // 游戏规则面板i18n 改造 Scroll() { Column() { Text($r(app.string.game_rules)) .fontSize(14).fontWeight(FontWeight.Bold) .fontColor($r(app.color.text_primary)) Text(• $r(app.string.rule_1).toString()) .fontSize(13).fontColor($r(app.color.text_secondary)) Text(• $r(app.string.rule_2).toString()) .fontSize(13).fontColor($r(app.color.text_secondary)) Text(• $r(app.string.rule_3).toString()) .fontSize(13).fontColor($r(app.color.text_secondary)) Text(• $r(app.string.rule_4).toString()) .fontSize(13).fontColor($r(app.color.text_secondary)) } .alignItems(HorizontalAlign.Start) } .scrollable(ScrollDirection.Vertical) .scrollBar(BarState.Auto) .width(80%).height(180).padding(16) .backgroundColor($r(app.color.card_bg)) .borderRadius(12) Spacer() } .width(100%).height(100%) .linearGradient({ direction: GradientDirection.Bottom, colors: [ [$r(app.color.bg_gradient_start), 0.0], [$r(app.color.bg_gradient_mid), 0.5], [$r(app.color.bg_gradient_end), 1.0] ] }) .alignItems(HorizontalAlign.Center) }4.2 改造要点原写法改造后效果Text(猫猫大作战)Text($r(app.string.app_title))中英自动切Button(开始游戏)Button($r(app.string.start_game))中英自动切最高分: $r(app.string.high_score) : 拼接字符串提示Button也接受$r资源——Button($r(app.string.start_game))直接渲染多语言文案。五、$r 的格式化参数5.1 带占位符的字符串string.json{ name: score_format, value: 得分%d }代码引用Text($r(app.string.score_format, this.score)) // 中文 → 得分99 // 英文en_US string.json: { name: score_format, value: Score: %d }→ Score: 995.2 多参数{ name: battle_result, value: 玩家%s击败了%s获得%d分 }Text($r(app.string.battle_result, playerName, enemyName, score)) // 玩家Alice击败了Boss获得1500分关键经验$r第二个参数起是格式化实参——%s/%d/%f按 C 语言 printf 规则替换。5.3 复数plural某些语言有复数形式如英文 1 cat / 2 catsstring.json{ plural: [ { name: cat_count, value: one cat, value_plural: %d cats } ] }代码引用Text($r(app.plural.cat_count, this.catCount, this.catCount)) // catCount1 → one cat // catCount5 → 5 cats六、运行时语言切换6.1 跟随系统默认不做任何设置应用自动按设备语言加载对应string.json。6.2 应用内强制切换语言HarmonyOS 不直接提供「应用内切语言」API需通过AppStorage 条件渲染模拟StorageProp(appLanguage) appLanguage: string zh; // 根据语言选择 string.json getLocalizedString(name: string): Resource { if (this.appLanguage en) { return $r(app.string. name _en); } return $r(app.string. name _zh); }更优方案使用i18n模块的setAppLocaleAPI 12import { i18n } from kit.LocalizationKit; // 强制应用语言为英文 i18n.System.setAppLocale(en-US); // 恢复跟随系统 i18n.System.setAppLocale();实战经验游戏类应用建议提供「跟随系统 / 简体中文 / English」三选项——海外玩家可能想强制英文。6.3 监听系统语言变化export default class EntryAbility extends UIAbility { onCreate(): void { AppStorage.setOrCreate(systemLanguage, this.context.config.locale.language); } onConfigurationUpdate(newConfig: Configuration): void { AppStorage.setOrCreate(systemLanguage, newConfig.locale.language); } }七、踩坑提示7.1 string.json key 拼错// base string.json { name: app_title, value: 猫猫大作战 } // en_US string.json拼错 { name: app_Title, value: Cat Battle }后果编译报错或 en_US 覆盖失败。en_US 的 key 必须与 base 完全一致。7.2 硬编码文案未替换// ❌ 错误部分文案还是硬编码 Text(猫猫大作战) // 硬编码 Button($r(app.string.start_game)) // 已适配 // ✅ 正确所有文案统一用 $r Text($r(app.string.app_title)) Button($r(app.string.start_game))7.3 格式化参数类型不匹配// string.json: { name: score, value: 得分%d } // ❌ 错误传字符串给 %d Text($r(app.string.score, abc)) // ✅ 正确传数字 Text($r(app.string.score, 99))7.4 复数使用错误// ❌ 错误plural 用 string 引用 Text($r(app.string.cat_count, count)) // ✅ 正确plural 用 plural 引用 Text($r(app.plural.cat_count, count, count))八、调试技巧DevEco 预览器切语言预览器右上角可切换设备语言实时看多语言效果。真机系统设置设置 → 系统和更新 → 语言和输入法 → 添加语言验证应用适配。console.info打 localeonConfigurationUpdate里 lognewConfig.locale.language追系统语言切换。未翻译排查全局搜索Text(和Button(看是否有硬编码文案。九、性能与最佳实践i18n 文案抽 string.json 目录分流 $r 引用——业务代码零改动。准备 base默认 en_US英文两套覆盖 95% 场景。所有文案统一用 $r——硬编码文案不跟随语言。格式化用 %s/%d 占位符——避免字符串拼接。复数用 plural 资源——避免 if/else 判断单复数。提供三选项——跟随系统 / 简体中文 / English。总结本篇我们从多语言 i18n 切入掌握了string.json 结构与 zh_CN/en_US 目录分流、$r 引用与格式化参数、运行时语言切换与监听、复数 plural 资源四大要点并给出了主菜单 i18n 改造完整代码。核心要点i18n 零代码改动全靠 string.json $r准备 base en_US 两套格式化用占位符复数用 plural。下一篇我们将拆解 AttributeModifier——按钮样式复用的利器。如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力相关资源「猫猫大作战」项目源码本仓库entry/src/main/ets/pages/Index.etsHarmonyOS i18n 国际化官方指南string.json 资源结构官方文档LocalizationKit i18n API 参考i18n 国际化最佳实践开源鸿蒙跨平台社区HarmonyOS 开发者官方文档首页系列索引本仓库articles/INDEX.md