HarmonyOS 应用如何实现中英日三语切换:资源、本地数据与重启刷新
多语言真正难在哪里
为 HarmonyOS App 增加英语和日语资源并不难,真正容易出问题的是这些场景:
- 用户在系统语言不变的情况下,只切换应用语言;
- 切换后部分
$r文案更新,部分运行时字符串仍是旧语言; - 默认习惯以中文写进磁盘,切到日文后依然显示中文;
- 新增一个资源 Key,只补了两种语言;
- 桌面应用名称和应用内语言不一致;
- 杀进程重启后,语言选择丢失。
因此我把多语言拆成三层:
- 资源层:静态文案和应用名称;
- 设置层:用户选择的语言偏好;
- 数据层:哪些值应该稳定保存,哪些值应该按当前语言展示。
一、资源目录如何组织
当前项目使用三套资源:
entry/src/main/resources/
├─ base/element/string.json
├─ zh_CN/element/string.json
└─ ja_JP/element/string.json
其中:
base保存英文,作为基础资源;zh_CN保存简体中文;ja_JP保存日文。
应用级名称则位于 AppScope/resources 下,对应英文 MoodMemoir、中文“心晴手记”和日文“こころ日記”。
每个语言文件必须拥有相同的资源 Key。例如:
{
"string": [
{ "name": "today", "value": "今日" },
{ "name": "history", "value": "历史" },
{ "name": "insights", "value": "统计" }
]
}
页面可以通过资源引用或运行时名称获取:
Text($r('app.string.today'))
private named(name: string): string {
try {
return this.context.resourceManager.getStringByNameSync(name);
} catch (_) {
return name;
}
}
第二种方式适合资源名称来自模型定义的情况,例如不同心情类型各自保存 resourceName。
二、保存的是语言偏好,不是当前翻译结果
设置模型定义四种选择:
export enum LanguagePreference {
SYSTEM = 'SYSTEM',
CHINESE = 'CHINESE',
ENGLISH = 'ENGLISH',
JAPANESE = 'JAPANESE'
}
默认值是跟随系统:
languagePreference: LanguagePreference.SYSTEM
随后把设置转换为语言标签:
export function languageTag(
preference: LanguagePreference
): string {
switch (preference) {
case LanguagePreference.CHINESE:
return 'zh-Hans-CN';
case LanguagePreference.ENGLISH:
return 'en';
case LanguagePreference.JAPANESE:
return 'ja';
default:
return '';
}
}
这里持久化的是“用户想用哪种语言”,而不是某一刻系统解析出来的语言。这样用户选择 English 后,即使系统仍是中文,应用重启也能恢复 English。
三、启动时先应用语言,再展示依赖资源的内容
项目加载顺序如下:
async aboutToAppear(): Promise<void> {
await appRepository.initialize(this.context);
const state: PersistedState = await appRepository.load();
this.applyLanguagePreference(
state.settings.languagePreference
);
this.settings = state.settings;
this.moodEntries = state.moodEntries;
this.habits = state.habits;
this.completions = state.habitCompletions;
this.loadTodayDraft();
this.loaded = true;
}
应用语言偏好的函数为:
private applyLanguagePreference(
preference: LanguagePreference
): void {
const tag: string = languageTag(preference);
i18n.System.setAppPreferredLanguage(
tag.length > 0
? tag
: i18n.System.getFirstPreferredLanguage()
);
}
先设置首选语言,再让页面进入完整显示状态,可以减少启动时短暂出现错误语言的概率。
四、为什么切换后选择 restartApp
语言选择改变时,项目先保存设置,然后应用首选语言:
const next: AppSettings = copySettings(this.settings);
next.languagePreference = preference;
this.settings = next;
await this.persist();
this.applyLanguagePreference(preference);
理论上可以尝试让当前页面所有组件原地刷新,但复杂页面可能同时包含:
$r()资源引用;ResourceManager动态获取的字符串;- 已经生成的日期格式;
- Builder 中缓存的显示内容;
- 依赖语言的默认习惯名称。
为了避免出现半中半英,项目调用 restartApp():
const want: Want = {
bundleName: 'com.zhouyajie.moodbloomharmony',
abilityName: 'EntryAbility'
};
this.context.getApplicationContext().restartApp(want);
因为偏好已经先持久化,重启后的冷启动会直接进入新语言。
这是稳定性优先的选择。对于页面结构较小、状态管理完全可控的应用,也可以研究不重启的动态刷新方案,但需要逐项验证所有资源来源。
五、默认数据不要保存翻译后的名字
多语言最容易被忽略的是“初始化到本地的数据”。
如果第一次启动时把“早睡”直接保存到 Habit.name,那么用户切换到英语后,磁盘里仍是“早睡”。
项目为默认习惯定义稳定名称:
export const STARTER_HABITS: StarterHabit[] = [
{
resourceName: 'starter_sleep',
storedName: 'sleep-early',
legacyNames: ['早睡', 'Sleep early', '早寝'],
icon: 'moon_z',
color: '#5E7CE2'
}
];
写入时保存 storedName:
name: definition.storedName
展示时再查找本地化资源:
private localizedHabitName(habit: Habit): string {
const starter = this.starterDefinition(habit.name);
return starter
? this.named(starter.resourceName)
: habit.name;
}
这样可以同时满足:
- 默认习惯跟随应用语言;
- 用户自定义名称保持原样;
legacyNames兼容早期版本曾经保存过的翻译文本。
这个模式也适用于分类名、内置标签、模板名称等任何“系统预置但会进入持久化”的内容。
六、日期和星期也要使用当前 Locale
不要把星期写死成“日一二三四五六”。项目使用当前应用语言生成:
private localeTag(): string {
return i18n.System
.getAppPreferredLanguage()
.replace('_', '-');
}
new Date(timestampMillis).toLocaleDateString(
this.localeTag(),
{
year: 'numeric',
month: 'short',
day: 'numeric',
weekday: 'short'
}
);
星期标题同样通过日期格式化生成,而不是手写三份数组。这样可以自然获得不同语言的文字和排列习惯。
七、用脚本阻止漏翻译进入构建
人工维护三个资源文件,很容易新增 Key 时漏掉一种语言。
项目中的验证脚本会收集三套资源 Key,排序后比较:
for (const locale of ['base', 'zh_CN', 'ja_JP']) {
const strings = readJson(
`entry/src/main/resources/${locale}/element/string.json`
).string;
const names = strings.map((item) => item.name);
const sortedNames = names.slice().sort();
// 与 base 的 Key 集合比较
}
它还会检查重复 Key 和 AppScope 中的应用名称。这样“资源文件不完整”会在发布前变成可见错误,而不是交给某个语言用户发现。
八、建议覆盖的测试路径
多语言至少应该测试:
- 中文系统首次启动,默认跟随系统;
- 中文切 English,自动重启后全部变成英文;
- 杀进程重开,仍保持 English;
- English 切日本語;
- 默认习惯随语言变化;
- 自定义习惯名称保持原样;
- 日期、星期、统计文案使用同一种语言;
- 隐私政策和支持页面跳转到对应语言版本;
- 字体变长后按钮没有截断;
- 三套资源 Key 完全一致。
总结
HarmonyOS 多语言的关键并不是翻译文件本身,而是处理好三种边界:
- 资源边界:所有静态文案拥有一致 Key;
- 设置边界:保存用户语言偏好,并在启动时恢复;
- 数据边界:稳定内部值与本地化显示值分离。
在复杂页面中,保存偏好后重启应用虽然不够“炫”,却能用较低复杂度换来一致结果。再配合自动资源校验和多语言回归路径,三语支持才算真正可维护。
本文案例来自“心晴手记(MoodMemoir / こころ日記)”HarmonyOS 版。
更多推荐


所有评论(0)