news 2026/7/28 3:24:18

HarmonyOS应用开发实战:猫猫大作战-`string.json` 结构、`zh_CN`/`en_US` 目录分流、`$r` 引用与运行时切换

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HarmonyOS应用开发实战:猫猫大作战-`string.json` 结构、`zh_CN`/`en_US` 目录分流、`$r` 引用与运行时切换

前言

应用要出海,第一道坎就是多语言。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

四、改造主菜单支持 i18n

4.1 主菜单文案全部用 $r

@Builder 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: 99

5.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 cats):

string.json

{ "plural": [ { "name": "cat_count", "value": "one cat", "value_plural": "%d cats" } ] }

代码引用

Text($r('app.plural.cat_count', this.catCount, this.catCount)) // catCount=1 → one cat // catCount=5 → 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模块的setAppLocale(API 12+):

import { 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))

八、调试技巧

  1. DevEco 预览器切语言:预览器右上角可切换设备语言,实时看多语言效果。
  2. 真机系统设置:设置 → 系统和更新 → 语言和输入法 → 添加语言,验证应用适配。
  3. console.info打 localeonConfigurationUpdate里 lognewConfig.locale.language,追系统语言切换。
  4. 未翻译排查:全局搜索Text('Button(',看是否有硬编码文案。

九、性能与最佳实践

  1. i18n = 文案抽 string.json + 目录分流 + $r 引用——业务代码零改动。
  2. 准备 base(默认)+ en_US(英文)两套,覆盖 95% 场景。
  3. 所有文案统一用 $r——硬编码文案不跟随语言。
  4. 格式化用 %s/%d 占位符——避免字符串拼接。
  5. 复数用 plural 资源——避免 if/else 判断单复数。
  6. 提供三选项——跟随系统 / 简体中文 / 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.ets
  • HarmonyOS i18n 国际化官方指南
  • string.json 资源结构官方文档
  • LocalizationKit i18n API 参考
  • i18n 国际化最佳实践
  • 开源鸿蒙跨平台社区
  • HarmonyOS 开发者官方文档首页
  • 系列索引:本仓库articles/INDEX.md
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/28 3:24:15

智能家居双摄监控部署指南:从硬件解析到实战设置

1. 从“单眼”到“双眼”:为什么双摄是智能家居监控的必然升级最近在折腾家里的安防设备,把之前用的小方舟单摄版本换成了新出的双摄版。说实话,一开始我也觉得,一个摄像头不就够了吗?但实际用下来,尤其是在…

作者头像 李华
网站建设 2026/7/28 3:22:45

TPIC7710EVM评估板实战指南:从芯片验证到电子驻车制动系统开发

1. 项目概述:从芯片到系统,TPIC7710EVM如何成为电子驻车制动开发的“加速器”在汽车电子,特别是车身控制模块的开发中,电子驻车制动(EPB)系统是一个集安全性、可靠性与精密控制于一体的复杂单元。当工程师拿…

作者头像 李华
网站建设 2026/7/28 3:21:52

Three.js 大规模 3D 场景的渲染攻坚:实例绘制与视锥剔除优化

Three.js 大规模 3D 场景的渲染攻坚:实例绘制与视锥剔除优化 一、当物体数量突破十万:场景为何骤然崩溃 去年一个智慧园区项目,演示当天现场演示机突然卡到风扇狂转。最后定位原因:场景里堆了 12 万棵树苗模型,每帧 12…

作者头像 李华
网站建设 2026/7/28 3:20:59

Agentic RAG技术解析:从原理到企业级实践

1. 从传统RAG到Agentic RAG的技术演进RAG(Retrieval-Augmented Generation)技术自2020年提出以来,已经成为连接大语言模型与外部知识库的标准范式。传统RAG的工作流程可以概括为:用户提问→检索相关文档→将文档作为上下文输入LLM…

作者头像 李华
网站建设 2026/7/28 3:19:27

基于Arduino与模拟反馈舵机的简易机械臂闭环控制实践

1. 项目缘起:为什么选择180模拟反馈舵机做机械臂?如果你玩过Arduino或者树莓派,大概率接触过舵机。最常见的SG90、MG996R这类舵机,我们通常通过PWM信号控制它转到某个角度,但有一个问题:我们只知道“发出了…

作者头像 李华