当前位置: 首页 > news >正文

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
http://www.cnnetsun.cn/news/3697662.html

相关文章:

  • 智能家居双摄监控部署指南:从硬件解析到实战设置
  • 基于本地大语言模型的剪贴板翻译工具TransPaste:无感操作与隐私安全的AI翻译实践
  • TPIC7710EVM评估板实战指南:从芯片验证到电子驻车制动系统开发
  • Three.js 大规模 3D 场景的渲染攻坚:实例绘制与视锥剔除优化
  • Agentic RAG技术解析:从原理到企业级实践
  • 基于Arduino与模拟反馈舵机的简易机械臂闭环控制实践
  • STM32F4芯片线刷救砖与Klipper固件烧录实战指南
  • Melo TTS开源语音合成系统安装与使用指南
  • Unity 2020安卓异形屏黑边适配:从原理到实战解决方案
  • HarmonyOS应用开发实战:猫猫大作战-onTouch 三阶段触发、TouchType 类型判定、TouchObject 坐标信息、与 onCli
  • PCA算法在三维点云平面拟合中的原理与实践
  • K210开发实战:从硬件连接到AI模型部署的完整排错指南
  • DDD视角下的Openfeign设计与实践
  • 基于ESP32的四足机器人DIY:从硬件选型到步态算法全解析
  • LiveKit终极指南:5分钟搭建企业级实时音视频服务器
  • 变异粒子群算法在主动配电网故障恢复中的应用与Matlab实现
  • 当我把 Docker 迁移交给 AI 之后……符号链接的致命陷阱
  • 装Office被坑过的,这个10MB小工具能救命!
  • AI聊天应用开发实战:从AnuNeko关闭看技术架构与成本优化
  • Chili3D:基于WebAssembly的浏览器端3D CAD技术深度解析
  • 解决Blur常见问题:消除运动模糊中的拖影 artifacts 实用技巧
  • 终极开源预测引擎:MiroFish群体智能实战指南
  • Python控制乐高EV3机器人:从环境搭建到自动避障项目实战
  • MiroFish完整指南:三步开启未来预测革命,用群体智能引擎看见每一个“如果“
  • Linux中断处理中的Tasklet机制详解
  • C++文件操作类封装:RAII设计、跨平台实现与性能优化实战
  • AI驱动数据仓库模型评审:LLM技术实践与效率提升
  • 10机39节点电力系统仿真建模与Matlab实践
  • 嵌入式Wi-Fi模块AT指令驱动故障排查与修复实战
  • Anime.js实战指南:深度解析现代JavaScript动画引擎的完整应用