前言

移动应用的全球化部署离不开国际化(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-HKzh-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 就是用这个语言环境来做实际格式化工作的。它提供三个核心类:NumberFormatDateTimeFormatRelativeTimeFormat

导入方式

两个模块都从 @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 快捷预设

如果不想逐项配置每个字段,可以使用预设的 dateStyletimeStyle

// 短格式日期
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:

  • 系统 localei18n.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 国际化开发的两个核心模块:

  1. @ohos.i18n.System — 四个静态方法获取系统语言环境信息
  2. @ohos.intl — 三个格式化类(NumberFormat、DateTimeFormat、RelativeTimeFormat)覆盖数字、日期和相对时间的本地化格式化

通过 10 种 locale 的实时对比 Demo,你可以直观感受国际化格式化的必要性——同一组数据在不同文化背景下的视觉呈现完全不同。HarmonyOS 提供的国际化 API 与 Web 标准高度一致,学习曲线平缓,是构建全球化应用的基石。

国际化不是"做完翻译就完事"的附加功能,而是从架构设计阶段就应当融入应用的基础能力。使用 intl 格式化器而不是手动拼接字符串,可以让你的应用天然具备多语言、多地区的扩展能力。


Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐