鸿蒙新特性:@ohos.i18n 与 @ohos.intl 国际化实战
前言
移动应用的全球化部署离不开国际化(i18n)支持。不同地区的用户对数字格式、日期显示、货币符号、相对时间表达有截然不同的习惯——中文用户看到"1,234,567.89"和"2026年7月11日",德国用户则期望"1.234.567,89"和"11. Juli 2026"。这些差异并非简单的文本翻译,而是深层的文化约定。
HarmonyOS 通过两个核心模块提供完整的国际化能力:@ohos.i18n 负责系统语言环境检测,@ohos.intl 提供数字、日期、相对时间的本地化格式化。两者都从 @kit.LocalizationKit 导入,协同工作覆盖了应用国际化最常见的需求场景。
本文带你深入探索这两个模块的全部核心 API,通过一个可交互的国际化实验室 Demo,直观感受 10 种语言环境下同一数据的格式化差异。
@ohos.i18n —— 系统语言环境
@ohos.i18n 模块通过 System 对象提供四个静态方法,让应用获知当前设备的区域设置。
System.getSystemLocale()
获取系统语言环境字符串,返回格式如 "zh-Hans-CN",包含语言、文字和地区信息。这个字符串可以直接传给 intl 格式化器作为 locale 参数。
import { i18n } from '@kit.LocalizationKit';
let locale: string = i18n.System.getSystemLocale();
// 返回示例: "zh-Hans-CN"
Locale 字符串由三部分组成:语言代码(ISO 639)、文字代码(ISO 15924,可选)、地区代码(ISO 3166-1)。例如 zh-Hans-CN 表示简体中文-中国,en-US 表示英语-美国。不同组合可能对应不同的格式化规则——zh-HK 和 zh-CN 虽然同属中文,但数字格式有细微差别。
System.getSystemLanguage()
仅返回语言代码部分,不包含文字和地区信息。
let language: string = i18n.System.getSystemLanguage();
// 返回示例: "zh"
这个方法适合只需要检测语种的场景,比如根据系统语言决定默认内容语言,而不关心具体地区变体。
System.getSystemRegion()
返回地区代码,代表用户所在国家或地区。
let region: string = i18n.System.getSystemRegion();
// 返回示例: "CN"
地区信息本身不足以决定语言,但在组合使用时非常关键。例如数字格式化中,同样是英语(en),美国(US)使用逗号千分位和句点小数点,而英国(GB)同样使用这些符号,但德国的英语用户可能希望看到欧式格式。
System.is24HourClock()
返回布尔值,表示当前系统是否使用 24 小时制。
let is24h: boolean = i18n.System.is24HourClock();
// 返回: true 或 false
中国、法国、德国等多数国家习惯 24 小时制,而美国、加拿大、澳大利亚等英语国家更多使用 12 小时制(带 AM/PM)。日期格式化器默认会遵循系统设置,但你也可以根据此值手动调整时间显示。
错误处理
所有四个方法在极端情况下(如系统语言设置损坏)可能抛出异常。实际开发中应当加上 try-catch:
try {
let locale = i18n.System.getSystemLocale();
let language = i18n.System.getSystemLanguage();
let region = i18n.System.getSystemRegion();
let is24h = i18n.System.is24HourClock();
} catch (e) {
console.error('获取系统语言环境失败');
}
但这种情况在生产设备上极为罕见——系统语言环境是 HarmonyOS 核心功能之一,始终有合法默认值。
@ohos.intl —— 本地化格式化
如果说 @ohos.i18n 告诉你用户在哪个语言环境,@ohos.intl 就是用这个语言环境来做实际格式化工作的。它提供三个核心类:NumberFormat、DateTimeFormat 和 RelativeTimeFormat。
导入方式
两个模块都从 @kit.LocalizationKit 导入:
import { i18n, intl } from '@kit.LocalizationKit';
intl.NumberFormat —— 数字格式化
数字格式化是国际化中最常见的需求。不同地区的千分位分隔符、小数点符号、数字分组规则各不相同。
基本用法
// 创建指定 locale 的格式化器
let formatter: intl.NumberFormat = new intl.NumberFormat('zh-CN');
// 格式化数字
let result: string = formatter.format(1234567.89);
// zh-CN: "1,234,567.89"
构造函数的第一个参数是 locale 字符串,也支持 locale 数组(按优先级尝试)。第二个可选参数是 NumberOptions,可以精确控制格式化行为。
不同 locale 的数字格式对比
同样一个数字 1234567.89,不同 locale 下的格式化结果差异显著:
| Locale | 格式化结果 | 特点 |
|---|---|---|
| zh-CN | 1,234,567.89 | 逗号千分位,句点小数点 |
| en-US | 1,234,567.89 | 同中文格式 |
| de-DE | 1.234.567,89 | 句点千分位,逗号小数点 |
| fr-FR | 1 234 567,89 | 空格千分位,逗号小数点 |
| ar-SA | ١٬٢٣٤٬٥٦٧٫٨٩ | 阿拉伯数字字符 |
| ja-JP | 1,234,567.89 | 同美式,但日语环境下可能使用万进位 |
| ko-KR | 1,234,567.89 | 同美式格式 |
| zh-HK | 1,234,567.89 | 同简体中文格式 |
德国(de-DE)和法国(fr-FR)的格式尤其需要关注——如果你不经过本地化直接展示美式数字格式,欧洲用户会完全看不懂。小数点和千分位符号互换对于金融类应用是致命的格式化错误。

整数格式化
整数同样需要格式化——1234567 在中国显示为 "1,234,567",在德国显示为 "1.234.567"。
let f = new intl.NumberFormat('de-DE');
f.format(1234567); // "1.234.567"
NumberOptions 详解
NumberOptions 提供了丰富的格式化选项:
interface NumberOptions {
locale?: string; // 覆盖 locale
currency?: string; // 货币代码,如 'CNY', 'USD', 'EUR'
currencySign?: string; // 货币符号显示方式
currencyDisplay?: string; // 'symbol' | 'code' | 'name'
unit?: string; // 单位,如 'kilometer', 'celsius'
unitDisplay?: string; // 单位显示方式
signDisplay?: string; // 正负号显示:'auto' | 'always' | 'never'
compactDisplay?: string; // 紧凑显示:'short' | 'long'
notation?: string; // 'standard' | 'scientific' | 'engineering' | 'compact'
minimumIntegerDigits?: number; // 最小整数位数
minimumFractionDigits?: number; // 最小小数位数
maximumFractionDigits?: number; // 最大小数位数
minimumSignificantDigits?: number; // 最小有效位数
maximumSignificantDigits?: number; // 最大有效位数
useGrouping?: boolean; // 是否使用千分位分隔
numberingSystem?: string; // 数字系统,如 'arab', 'fullwide'
style?: string; // 'decimal' | 'currency' | 'percent' | 'unit'
}
常用示例:
// 货币格式化
let currencyFmt = new intl.NumberFormat('zh-CN', {
style: 'currency',
currency: 'CNY'
});
currencyFmt.format(1234.56); // "¥1,234.56"
// 百分比格式化
let percentFmt = new intl.NumberFormat('en-US', {
style: 'percent',
minimumFractionDigits: 1
});
percentFmt.format(0.856); // "85.6%"
// 指定小数位数
let preciseFmt = new intl.NumberFormat('de-DE', {
minimumFractionDigits: 2,
maximumFractionDigits: 4
});
preciseFmt.format(1234.5); // "1.234,50"
intl.DateTimeFormat —— 日期时间格式化
日期格式的跨地区差异比数字更复杂。不仅年月日的排列顺序不同(年-月-日 vs 月-日-年 vs 日-月-年),分隔符、月份名称、星期表达也各不相同。
基本用法
let dtFmt: intl.DateTimeFormat = new intl.DateTimeFormat('zh-CN');
let now: Date = new Date();
let formatted: string = dtFmt.format(now);
// zh-CN: "2026/7/11" 或类似格式
构造函数的第二个参数是 DateTimeOptions,可以精细控制每个日期/时间组件的显示方式。
DateTimeOptions 详解
interface DateTimeOptions {
locale?: string; // 覆盖 locale
dateStyle?: string; // 'full' | 'long' | 'medium' | 'short'
timeStyle?: string; // 'full' | 'long' | 'medium' | 'short'
hourCycle?: string; // 'h11' | 'h12' | 'h23' | 'h24'
timeZone?: string; // 时区,如 'Asia/Shanghai', 'America/New_York'
numberingSystem?: string;
calendar?: string; // 历法
weekday?: string; // 星期显示:'narrow' | 'short' | 'long'
era?: string; // 纪元显示
year?: string; // 'numeric' | '2-digit'
month?: string; // 'numeric' | '2-digit' | 'narrow' | 'short' | 'long'
day?: string;
hour?: string;
minute?: string;
second?: string;
timeZoneName?: string; // 'short' | 'long'
dayPeriod?: string; // 'narrow' | 'short' | 'long'
hour12?: boolean; // 是否12小时制
}
不同 locale 的日期格式对比
同一日期 2026年7月11日,不同语言环境下的显示天差地别:
| Locale | 日期显示 | 特点 |
|---|---|---|
| zh-CN | 2026年7月11日 | 年-月-日,中文数字 |
| en-US | July 11, 2026 | 月-日-年,英文月份名 |
| ja-JP | 2026年7月11日 | 同中文格式 |
| de-DE | 11. Juli 2026 | 日-月-年,句点分隔 |
| fr-FR | 11 juillet 2026 | 日-月-年,小写法语月份 |
| ko-KR | 2026년 7월 11일 | 年月日韩文后缀 |
| ar-SA | ١١ يوليو ٢٠٢٦ | 阿拉伯数字 + 阿拉伯语月份 |
| en-GB | 11 July 2026 | 日-月-年,英式英语 |
注意同是英语的 en-US 和 en-GB 也有差异——美国把月份放前面(July 11, 2026),英国把日子放前面(11 July 2026)。如果你用美国格式展示给英国用户,虽然不至于看不懂,但体验会打折扣。
使用 dateStyle/timeStyle 快捷预设
如果不想逐项配置每个字段,可以使用预设的 dateStyle 和 timeStyle:
// 短格式日期
let shortFmt = new intl.DateTimeFormat('zh-CN', { dateStyle: 'short' });
shortFmt.format(new Date()); // "2026/7/11"
// 长格式日期
let longFmt = new intl.DateTimeFormat('zh-CN', { dateStyle: 'long' });
longFmt.format(new Date()); // "2026年7月11日"
// 完整格式
let fullFmt = new intl.DateTimeFormat('en-US', {
dateStyle: 'full',
timeStyle: 'short'
});
fullFmt.format(new Date()); // "Saturday, July 11, 2026 at 3:30 PM"
intl.RelativeTimeFormat —— 相对时间格式化
相对时间是现代应用中最人性化的时间表达方式。与其显示冰冷的绝对日期 “2026-07-11”,用户更愿意看到 “3天前”、“2小时后” 这样自然语言化的表达。
基本用法
let relFmt: intl.RelativeTimeFormat = new intl.RelativeTimeFormat('zh-CN');
relFmt.format(-3, 'day'); // "3天前"
relFmt.format(2, 'hour'); // "2小时后"
relFmt.format(-1, 'week'); // "1周前"
relFmt.format(5, 'minute'); // "5分钟后"
format 方法接收两个参数:
- value:数值,负值表示过去,正值表示未来
- unit:时间单位,可选
'year'、'quarter'、'month'、'week'、'day'、'hour'、'minute'、'second'
不同 locale 的相对时间对比
| Locale | -3 day | +2 hour | -1 month |
|---|---|---|---|
| zh-CN | 3天前 | 2小时后 | 1个月前 |
| en-US | 3 days ago | in 2 hours | 1 month ago |
| ja-JP | 3日前 | 2時間後 | 1か月前 |
| de-DE | vor 3 Tagen | in 2 Stunden | vor 1 Monat |
| fr-FR | il y a 3 jours | dans 2 heures | il y a 1 mois |
| ko-KR | 3일 전 | 2시간 후 | 1개월 전 |
| ar-SA | قبل ٣ أيام | بعد ٢ ساعة | قبل شهر واحد |
实际应用场景
相对时间格式化最常见的场景是社交媒体动态列表、消息列表、文件管理器中的"最后修改时间":
function formatRelativeTime(timestamp: number, locale: string): string {
let relFmt = new intl.RelativeTimeFormat(locale);
let diff = Date.now() - timestamp;
let seconds = Math.floor(diff / 1000);
let minutes = Math.floor(seconds / 60);
let hours = Math.floor(minutes / 60);
let days = Math.floor(hours / 24);
if (days > 0) return relFmt.format(-days, 'day');
if (hours > 0) return relFmt.format(-hours, 'hour');
if (minutes > 0) return relFmt.format(-minutes, 'minute');
return '刚刚';
}
Demo:国际化实验室
我们在 Demo 中构建了一个完整的国际化实验室,包含以下功能模块:
1. 系统语言环境检测
页面顶部展示当前设备的 Locale、语言、地区和时钟制。这些信息通过 i18n.System 的四个 API 获取,使用不同颜色的卡片区分展示——Locale 蓝、语言粉、地区绿、时钟制橙。
2. 10 种 Locale 切换
提供了 10 个常用 locale 按钮,分成两行排列:
- 中文系:zh-CN、zh-HK、zh-TW
- 英语系:en-US、en-GB
- 日韩系:ja-JP、ko-KR
- 欧洲系:fr-FR、de-DE
- 中东系:ar-SA(阿拉伯语,从右向左书写)
点击按钮即刻切换当前语言环境,下方的格式化结果实时更新,让你对比同一数据在不同语言环境下的呈现差异。
3. 数值输入与实时格式化
你可以输入任意数值(默认 1234567.89),点击"格式化"按钮查看以下结果:
- 数字(小数):完整数字格式化,展示千分位和本地小数点
- 数字(整数):去掉小数部分后格式化,对比整数和浮点数差异
- 日期时间:当前时间的本地化显示
- 相对时间(-3天):表示"3天前"
- 相对时间(+2时):表示"2小时后"
4. Locale 差异对比表
Demo 底部固定展示 7 种典型 locale 的数字、货币、日期格式化结果对比表,让你一眼看懂全球格式化差异。这个表格是静态参考数据,展示了实际开发中会遇到的典型格式。
5. API 速查区
列出全部 8 个核心 API 及其用途说明,可作为日常开发的快速参考卡。
Demo 代码解析
完整页面结构
页面使用 @Entry + @Component 标准结构,核心状态变量:
@State systemLocale: string = ''; // 系统 locale
@State systemLanguage: string = ''; // 系统语言
@State systemRegion: string = ''; // 系统地区
@State is24Hour: boolean = false; // 是否24小时制
@State numberInput: string = '1234567.89'; // 测试数值
@State formatResults: FormatResult[] = []; // 格式化结果
@State selectedLocale: string = 'zh-CN'; // 当前选中locale
aboutToAppear 生命周期中加载系统语言环境并执行首次格式化:
aboutToAppear(): void {
this.loadSystemLocale();
this.runFormats();
}
系统语言环境加载
loadSystemLocale(): void {
try {
let sysLocale: string = i18n.System.getSystemLocale();
this.systemLocale = sysLocale;
this.systemLanguage = i18n.System.getSystemLanguage();
this.systemRegion = i18n.System.getSystemRegion();
this.is24Hour = i18n.System.is24HourClock();
this.statusMsg = '系统语言环境: ' + sysLocale;
} catch (e) {
this.statusMsg = '获取系统语言环境失败';
}
}
格式化执行
runFormats(): void {
let results: FormatResult[] = [];
// 数字格式化(小数)
try {
let numFmt: intl.NumberFormat = new intl.NumberFormat(this.selectedLocale);
if (numFmt) {
results.push(new FormatResult('数字(小数)', this.numberInput,
numFmt.format(parseFloat(this.numberInput))));
}
} catch (e) {
results.push(new FormatResult('数字(小数)', this.numberInput, '格式不支持'));
}
// 数字格式化(整数)
try {
let intFmt: intl.NumberFormat = new intl.NumberFormat(this.selectedLocale);
if (intFmt) {
results.push(new FormatResult('数字(整数)',
Math.floor(parseFloat(this.numberInput)).toString(),
intFmt.format(Math.floor(parseFloat(this.numberInput)))));
}
} catch (e) {
results.push(new FormatResult('数字(整数)', this.numberInput, '格式不支持'));
}
// 日期时间格式化
try {
let now: Date = new Date();
let dtFmt: intl.DateTimeFormat = new intl.DateTimeFormat(this.selectedLocale);
if (dtFmt) {
results.push(new FormatResult('日期时间', now.toISOString(),
dtFmt.format(now)));
}
} catch (e) {
results.push(new FormatResult('日期时间', 'now', '格式不支持'));
}
// 相对时间格式化
try {
let relFmt: intl.RelativeTimeFormat =
new intl.RelativeTimeFormat(this.selectedLocale);
if (relFmt) {
results.push(new FormatResult('相对时间(-3天)', '-3 day',
relFmt.format(-3, 'day')));
results.push(new FormatResult('相对时间(+2时)', '+2 hour',
relFmt.format(2, 'hour')));
}
} catch (e) {
results.push(new FormatResult('相对时间', '-3 day', '格式不支持'));
}
this.formatResults = results;
}
注意每个格式化操作都包裹在独立的 try-catch 中,确保单个格式化失败不影响其他结果的展示。某些极端 locale 字符串(如非常小众的地区变体)可能不被格式化器支持,独立异常处理可以优雅降级。
Locale 切换
changeLocale(locale: string): void {
this.selectedLocale = locale;
this.statusMsg = '切换语言至: ' + locale;
this.runFormats();
}
切换 locale 时立即重新执行所有格式化,@State 驱动 UI 自动刷新。
Locale 按钮实现
使用 ForEach 渲染 locale 按钮,当前选中的 locale 高亮为蓝色:
Row() {
ForEach(this.locales.slice(0, 5), (locale: string) => {
Button(locale)
.fontSize(10)
.fontColor(this.selectedLocale === locale ? '#FFFFFF' : '#444455')
.backgroundColor(this.selectedLocale === locale ? '#1677FF' : '#F5F5F5')
.borderRadius(4)
.height(30)
.layoutWeight(1)
.margin({ right: 4 })
.onClick(() => { this.changeLocale(locale); })
}, (locale: string) => locale)
}
.width('100%')
10 个 locale 分成两行,每行 5 个,使用 slice(0, 5) 和 slice(5, 10) 分割。
实战要点
1. 始终对所有用户可见数字使用 NumberFormat
即使你只面向中文用户,也应该使用格式化器而非手动拼接字符串。手动写 num.toFixed(2) 虽然简单,但遇到扩展需求(多语言版本、海外市场)时就需要全部重写。使用 NumberFormat 从一开始就建立了正确的架构。
// 不推荐:硬编码
let price = '¥' + amount.toFixed(2);
// 推荐:使用格式化器
let f = new intl.NumberFormat('zh-CN', {
style: 'currency',
currency: 'CNY'
});
let price = f.format(amount);
2. 日期展示结合相对时间和绝对日期
对于近期事件使用相对时间,超出一定范围回退到绝对日期:
function smartDate(timestamp: number, locale: string): string {
let diff = Date.now() - timestamp;
let days = Math.floor(diff / (24 * 3600 * 1000));
if (Math.abs(days) < 7) {
let relFmt = new intl.RelativeTimeFormat(locale);
if (days !== 0) return relFmt.format(-days, 'day');
let hours = Math.floor(diff / (3600 * 1000));
if (hours !== 0) return relFmt.format(-hours, 'hour');
return '刚刚';
}
let dtFmt = new intl.DateTimeFormat(locale, { dateStyle: 'medium' });
return dtFmt.format(new Date(timestamp));
}
3. locale 参数的获取方式
有三种方式获取 locale:
- 系统 locale:
i18n.System.getSystemLocale(),最常用 - 应用内设置:让用户在应用内选择语言,存储在 Preferences 中
- 固定 locale:对于特定场景(如显示外国货币时)使用固定 locale
// 方式1:跟随系统
let locale = i18n.System.getSystemLocale();
// 方式2:应用内设置
let locale = this.userPreference.get('language', 'zh-CN');
// 方式3:按场景指定
let formatter = new intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
4. 处理 RTL 语言
阿拉伯语(ar-SA)等 RTL(从右向左)语言不仅文字方向不同,数字字符也可能使用阿拉伯-印度数字(١٢٣)而非阿拉伯数字(123)。intl 模块会自动处理这些差异,你在代码中无需做任何特殊处理。
5. 性能考虑
格式化器对象的创建有一定开销,如果同一页面需要多次格式化(如列表中的大量数据),应当复用格式化器实例而不是每次都 new:
// 适用于列表场景
private numberFormatter: intl.NumberFormat | null = null;
aboutToAppear(): void {
this.numberFormatter = new intl.NumberFormat(
i18n.System.getSystemLocale()
);
}
formatItem(price: number): string {
return this.numberFormatter!.format(price);
}
6. catch 中的降级方案
当格式化失败时(极少发生,可能因 locale 字符串无效),应该有合理的降级方案:
function safeFormat(num: number, locale: string): string {
try {
return new intl.NumberFormat(locale).format(num);
} catch (e) {
// 降级为简单 toString
return num.toString();
}
}
与 Web 标准 Intl API 的比较
如果你熟悉 Web 平台的 Intl API,会发现 HarmonyOS 的 intl 模块设计高度一致:
| 功能 | Web Intl API | HarmonyOS intl |
|---|---|---|
| 数字格式化 | Intl.NumberFormat |
intl.NumberFormat |
| 日期格式化 | Intl.DateTimeFormat |
intl.DateTimeFormat |
| 相对时间 | Intl.RelativeTimeFormat |
intl.RelativeTimeFormat |
| 系统语言 | navigator.language |
i18n.System.getSystemLocale() |
这种一致性降低了前端开发者迁移到鸿蒙的学习成本。如果你有国际化前端开发经验,HarmonyOS 的国际化和本地化实践可以直接复用。
总结
本文详细讲解了 HarmonyOS 国际化开发的两个核心模块:
- @ohos.i18n.System — 四个静态方法获取系统语言环境信息
- @ohos.intl — 三个格式化类(NumberFormat、DateTimeFormat、RelativeTimeFormat)覆盖数字、日期和相对时间的本地化格式化
通过 10 种 locale 的实时对比 Demo,你可以直观感受国际化格式化的必要性——同一组数据在不同文化背景下的视觉呈现完全不同。HarmonyOS 提供的国际化 API 与 Web 标准高度一致,学习曲线平缓,是构建全球化应用的基石。
国际化不是"做完翻译就完事"的附加功能,而是从架构设计阶段就应当融入应用的基础能力。使用 intl 格式化器而不是手动拼接字符串,可以让你的应用天然具备多语言、多地区的扩展能力。
更多推荐



所有评论(0)