鸿蒙国际化示例——多语言新闻阅读器 — 使用环境与场景篇


一、它解决什么场景问题
一款面向全球发行的新闻 App,语言不是一个设置项,而是产品形态本身。本文不讨论"怎么翻译",而是讨论:当用户把语言从中文切成英文,产品里到底哪些东西该变、哪些不该变、怎么变才自然。
本应用以"多语言新闻阅读器"为业务载体,完整演示了 8 种语言切换时,一个真实内容产品的全链路响应。它回答的是三个生产级问题:
- 语言选择体验:用户怎么选语言、选完是否记住、切换是否即时生效;
- 内容本地化:新闻标题、摘要、正文、分类、时间、数字、阅读量,哪些要跟着语言变;
- 降级兜底:当某条文案没翻译、某语言没覆盖时,用户看到什么而不是崩溃。
二、典型用户故事
故事 A:留学生小林(zh_CN → en_US)
小林刚到美国留学,系统是英文的,但看新闻还是习惯中文。他把 App 语言切到简体中文后,发现连"18 分钟前""12.8 万阅读"这种细节都跟着变成中文习惯——而不是简单地把界面文字翻译一遍。这正是 i18n 与"翻译"的区别。
故事 B:日本商务人士田中(en_US → ja_JP)
田中常驻东京,但英文新闻源更全。他在英文界面看财经新闻,需要切回日语确认关键术语时,发现新闻详情页也在同一个语言体系里即时刷新——他刚才读到一半的文章,标题、正文、评论数全部变成日语,而不是回到列表重新找。这正是"单页分支 + 全局语言状态"设计带来的体验优势。
故事 C:阿拉伯语用户 Ahmed(默认 → ar_AE 未覆盖)
Ahmed 的系统语言是阿拉伯语,但本应用只内置 8 种语言,没有阿拉伯语。此时发生两件事:界面文案降级到默认中文;新闻正文降级到英文(国际语种)。他依然能完整阅读,只是语言不是母语——这就是降级策略的价值:宁可显示第二优先语言,绝不显示空白或报错。
故事 D:德国用户 Klaus(de_DE)
Klaus 用德语界面阅读科技新闻。他发现德语标题特别长(“HarmonyOS 6.1 veröffentlicht: KI bis in die Systemebene”),但卡片没有撑破布局——因为摘要被 maxLines(2) 截断、标题自适应换行。同时数字格式也变了:23145 显示为 “23.145”(德语用点做千分位),时间显示 “vor 8 Min.”。他意识到这个 App 不是"翻译了界面",而是"整个产品说德语"。
三、适用使用环境
| 环境类型 | 适配说明 |
|---|---|
| 多语言国家/地区发行 | 用户可自选语言,不依赖系统设置 |
| 跨境电商工具 | 业务人员看中英文切换的界面术语 |
| 教育演示 | 课堂上直观展示不同语言/日期/数字格式差异 |
| 旅游出行类 App | 游客随时切到母语 |
| 开发者自测 | 快速验证文案与本地化覆盖是否完整 |
四、目标用户画像
- 海外用户:母语非中文,需要英文/法/德/西等语言;
- 跨境从业者:中英切换查看术语;
- 演示/教学者:需要多语言并行展示;
- 测试人员:回归验证 i18n 覆盖。
五、关键设计决策
1. 持久化
用户切换一次后,下次启动仍是同样的语言。若每次重启都回到默认,体验会非常差。用 @StorageLink + PersistentStorage 实现:
@StorageLink(STORAGE_LOCALE) currentLocale: string = DEFAULT_LOCALE;
aboutToAppear(): void {
if (!AppStorage.get<string>(STORAGE_LOCALE)) {
AppStorage.setOrCreate(STORAGE_LOCALE, DEFAULT_LOCALE);
}
PersistentStorage.persistProp(STORAGE_LOCALE, DEFAULT_LOCALE);
this.timer = setInterval(() => { this.now = Date.now(); }, 60000);
}
@StorageLink 让语言成为全局响应式状态——任何页面、任何组件读取它都会在变化时自动重渲染;PersistentStorage.persistProp 把它落盘,重启后从磁盘恢复。
2. 即时生效
不要求退出重进,切换那一秒界面就变,反馈强、零门槛。因为 currentLocale 是响应式状态,赋值瞬间所有依赖它的 t()、NumberFormat、DateTimeFormat 全部重新执行:
private T(key: string): string {
return t(this.currentLocale, key); // 语言一变,这里返回的新文案驱动重渲染
}
3. 降级兜底
即使某条文案漏翻,也显示默认语言而非空白,保证"永远可读":
/** 三级降级:目标语言 → 默认语言 → key 本身 */
function t(code: string, key: string): string {
const v = STRINGS.get(code)?.get(key);
if (v !== undefined) {
return v;
}
return STRINGS.get(DEFAULT_LOCALE)?.get(key) ?? key;
}
4. 内容层与 UI 层分开降级
UI 文案缺翻译 → 回退默认语言(面向用户);
新闻正文缺翻译 → 回退英文(面向全球,英文是事实标准)。
两个回退目标刻意不同,这是真实产品最常见的配置:
function newsBody(code: string, n: NewsItem): string {
return n.bodies.get(code) ?? n.bodies.get(DEFAULT_LOCALE) ?? '';
}
六、相对时间与数字的本地化(场景核心)
新闻列表的"8 分钟前""12.8 万阅读"是 i18n 最容易漏掉的地方。本应用用两个函数统一处理:
/** 相对时间:按分钟数分档,每档文案来自文案表 */
function relativeTime(code: string, min: number): string {
if (min < 1) {
return t(code, K.justNow); // 刚刚 / just now / gerade eben
}
if (min < 60) {
return tf(code, K.minAgo, min); // {n} 分钟前 / {n} min ago / vor {n} Min.
}
const hours = Math.floor(min / 60);
if (hours < 24) {
return tf(code, K.hourAgo, hours); // {n} 小时前 / {n} hr ago
}
return tf(code, K.dayAgo, Math.floor(hours / 24)); // {n} 天前
}
注意阿拉伯语的词序:قبل {n} دقيقة(“在 n 分钟之前”)与中文的"n 分钟前"结构完全不同——所以不能把中文模板硬编码成"n 分钟前",必须让每种语言用自己的占位符模板。
数字格式化交给 intl.NumberFormat:
private num(n: number): string {
try {
return new intl.NumberFormat(this.currentLocale).format(n);
} catch (err) {
return String(n); // 兜底:格式化失败时直接转字符串
}
}
23145 在不同语言下的呈现:23,145(英文)、23.145(德语)、٢٣٬١٤٥(阿拉伯语)。数字不是"翻译"出来的,是"格式化"出来的——这是 i18n 的核心认知。
七、边界与降级
- 不支持的语言:App 只内置 8 种,用户系统语言不在列表时默认简体中文——可用系统语言检测后再选最接近项;
- 文案缺失:走三级降级(目标→默认→key);
- 日期极老/未来:
Intl.DateTimeFormat对合法 Date 均能格式化,越界请在传入前校验; - RTL 语言:本应用文案本身方向变化由系统排布接管,复杂排版见应用 07。
八、竞品对比(同类方案取舍)
| 方案 | 优点 | 缺点 | 本应用选择 |
|---|---|---|---|
| 系统资源限定符(应用 12) | 官方标准、零代码 | 动态切换需重启或特殊处理 | 应用内切语言场景用文案表 |
| 运行时文案表(本应用) | 即时生效、可热更新 | 需自行维护 key 对应关系 | ✅ |
| 云端热更新 | 无需发版补翻 | 依赖网络、需配置 CDN | 扩展方向 |
九、扩展方向
- 语言记忆到云端:登录后跨设备同步语言偏好;
- 自动跟随系统语言:见应用 14;
- 文案热更新:把字符串放到远端 JSON,无需发版即可补翻;
- 引用语义化多语言:与资源限定符方案结合(应用 12)。
九、一次语言切换的完整数据流(场景复盘)
把"用户点击阿拉伯语"到"界面全变"的每一步拆开,能看清 i18n 的响应式本质:
用户点击「العربية」标签
│ 1. 触发 onClick
▼
this.currentLocale = 'ar_AE' // @StorageLink 赋值
│ 2. AppStorage 广播变更
▼
PersistentStorage 落盘 // 重启后恢复
│ 3. 依赖 currentLocale 的 UI 订阅
▼
t() / tf() 重新执行 // 文案:导航、分类、按钮
intl.NumberFormat 重新执行 // 数字:阅读量 ٢٣٬١٤٥
intl.DateTimeFormat 重新执行 // 时间:قبل 8 دقيقة
newsTitle/Summary/Body 重新执行 // 内容:标题/摘要/正文
│ 4. ArkUI 增量渲染
▼
界面整体刷新,无空白、无闪烁
关键点:语言是一个"状态",不是"一次操作"。开发者只需要保证所有 UI 文本都从 currentLocale 派生,切换时框架自动完成重渲染——不需要手动遍历每个控件去改文本。这正是声明式 UI(ArkUI)相对命令式 UI 的优势:状态驱动视图,而非代码驱动视图。
十、生产级注意事项
- 文案表与代码解耦:
STRINGS表集中管理所有 key,翻译人员只改表不动代码;若接入翻译平台,key 就是唯一契约; - 占位符规范:含数字的文案统一用
{n}占位(如{n} 分钟前),不要写死在模板里——不同语言数字位置不同; - 语言代码规范:用 BCP-47(
zh_CN、en_US),不要自造zh-cn之类的变体,否则intl无法识别; - 缺省语言必配:
DEFAULT_LOCALE必须保证所有 key 都有值,它是降级的最终兜底; - 测试矩阵:每个语言都要过一遍"列表→详情→切语言→返回"全流程,重点验证长文案溢出、数字格式、时间格式三处。
十一、结语
多语言切换器不是"入门第一课",而是 i18n 的骨架课——它把语言选、存、用、显四个环节全部打通,并且用真实新闻产品演示了"一次切换、五层联动"(导航、列表、详情、时间、数字)。
更多推荐




所有评论(0)