ArkUI 国际化基础:资源读取与页面内 Locale 监听

举报
蓝瘦的蜕变 发表于 2026/07/27 10:07:18 2026/07/27
【摘要】 国际化页面不维护硬编码多语言数组。静态文案进入 string.json,页面通过 $r('app.string.xxx') 读取;数字和日期格式交给 intl;系统语言、地区和区域变化在 Demo 页面内通过公共事件 COMMONEVENTLOCALECHANGED 感知。 参考能力: @kit.Localizatio

ArkUI 国际化基础:资源读取与页面内 Locale 监听

国际化页面不维护硬编码多语言数组。静态文案进入 string.json,页面通过 $r('app.string.xxx') 读取;数字和日期格式交给 intl;系统语言、地区和区域变化在 Demo 页面内通过公共事件 COMMON_EVENT_LOCALE_CHANGED 感知。

参考能力:

  • @kit.LocalizationKit
  • i18n.System.getSystemLanguage
  • i18n.System.getSystemRegion
  • i18n.System.getAppPreferredLanguage
  • intl.NumberFormat
  • intl.DateTimeFormat
  • $r('app.string.xxx')
  • commonEventManager.Support.COMMON_EVENT_LOCALE_CHANGED

本篇边界

这篇只做资源化文案读取、系统语言/地区读取、页面内 Locale 变化监听和格式化演示,不修改系统语言,不接在线翻译,也不做多语言后台。i18n.System.setAppPreferredLanguage() 会影响应用资源加载策略,通常需要冷启动才能完整体现,不放进这个 Demo 的点击操作里。

监听只放在 ArkUILocalizationDemo 页面内:aboutToAppear() 创建订阅者并订阅 COMMON_EVENT_LOCALE_CHANGEDaboutToDisappear() 退订。事件回调直接更新页面 @State,不再通过 EntryAbilityAppStorage 中转。

交互预期

  • 进入页面时会读取一次系统语言、系统地区、组合 Locale 和应用首选语言。
  • 修改系统语言、系统地区或系统区域后,如果页面仍在前台,COMMON_EVENT_LOCALE_CHANGED 回调会直接刷新页面状态。
  • 从系统设置返回页面时,onPageShow() 会再读取一次系统语言和地区,作为后台切换场景的兜底。
  • 点击“读取系统区域信息”会手动刷新一次页面状态。
  • “资源化文案”卡片里的业务文案来自 app.string,不是 ArkTS 硬编码多语言数组。
  • 点击不同 locale 标签只切换数字和日期格式化预览,不修改页面文案语言。

资源配置

文件位置:

  • entry/src/main/resources/base/element/string.json
  • entry/src/main/resources/en_US/element/string.json
  • entry/src/main/resources/fr_FR/element/string.json

三套资源使用同名 key,例如:

{
  "string": [
    {
      "name": "localization_page_title",
      "value": "ArkUI 国际化基础"
    },
    {
      "name": "localization_order_title",
      "value": "订单中心"
    },
    {
      "name": "localization_order_empty",
      "value": "暂无订单"
    },
    {
      "name": "localization_order_action",
      "value": "查看订单详情"
    }
  ]
}

完整页面代码

文件位置:entry/src/main/ets/pages/arkUI/theme/ArkUILocalizationDemo.ets

import { BusinessError, commonEventManager } from '@kit.BasicServicesKit'
import { i18n, intl } from '@kit.LocalizationKit'

class LocaleOption {
  locale: string

  constructor(locale: string) {
    this.locale = locale
  }
}

class LocaleFormatItem {
  label: ResourceStr
  value: string

  constructor(label: ResourceStr, value: string) {
    this.label = label
    this.value = value
  }
}

@Entry
@Component
struct ArkUILocalizationDemo {
  @State localeIndex: number = 0
  @State systemLanguage: string = ''
  @State systemRegion: string = ''
  @State systemLocale: string = ''
  @State appPreferredLanguage: string = ''
  @State hasReadSystemInfo: boolean = false
  private localeSubscriber: commonEventManager.CommonEventSubscriber | undefined = undefined

  private localeOptions: LocaleOption[] = [
    new LocaleOption('zh-Hans-CN'),
    new LocaleOption('en-US'),
    new LocaleOption('fr-FR')
  ]

  aboutToAppear(): void {
    this.readSystemLocale()
    this.subscribeLocaleChanged()
  }

  aboutToDisappear(): void {
    this.unsubscribeLocaleChanged()
  }

  onPageShow(): void {
    this.readSystemLocale()
  }

  private currentLocale(): string {
    return this.localeOptions[this.localeIndex].locale
  }

  private readSystemLocale(): void {
    const systemLanguage = i18n.System.getSystemLanguage()
    const systemRegion = i18n.System.getSystemRegion()
    this.systemLanguage = systemLanguage
    this.systemRegion = systemRegion
    this.systemLocale = `${systemLanguage}-${systemRegion}`
    this.appPreferredLanguage = i18n.System.getAppPreferredLanguage()
    this.hasReadSystemInfo = true
  }

  private subscribeLocaleChanged(): void {
    if (this.localeSubscriber !== undefined) {
      return
    }

    const subscribeInfo: commonEventManager.CommonEventSubscribeInfo = {
      events: [commonEventManager.Support.COMMON_EVENT_LOCALE_CHANGED]
    }

    commonEventManager.createSubscriber(subscribeInfo)
      .then((subscriber: commonEventManager.CommonEventSubscriber) => {
        this.localeSubscriber = subscriber
        commonEventManager.subscribe(subscriber, (err: BusinessError, data: commonEventManager.CommonEventData) => {
          if (err) {
            return
          }
          if (data.event === commonEventManager.Support.COMMON_EVENT_LOCALE_CHANGED) {
            this.readSystemLocale()
          }
        })
      })
      .catch(() => {
      })
  }

  private unsubscribeLocaleChanged(): void {
    const subscriber = this.localeSubscriber
    if (subscriber === undefined) {
      return
    }

    commonEventManager.unsubscribe(subscriber, (err: BusinessError) => {
      if (err) {
        return
      }
    })
    this.localeSubscriber = undefined
  }

  private formatItems(): LocaleFormatItem[] {
    const locale = this.currentLocale()
    const priceFormatter = new intl.NumberFormat(locale)
    const dateFormatter = new intl.DateTimeFormat(locale)
    return [
      new LocaleFormatItem($r('app.string.localization_number_format_label'), priceFormatter.format(12888.6)),
      new LocaleFormatItem($r('app.string.localization_date_format_label'), dateFormatter.format(new Date(2026, 5, 24))),
      new LocaleFormatItem($r('app.string.localization_locale_label'), locale)
    ]
  }

  @Builder
  systemSectionTitle() {
    Column({ space: 6 }) {
      Text($r('app.string.localization_system_title'))
        .fontSize(19)
        .fontWeight(FontWeight.Bold)
        .fontColor('#111827')
      Text($r('app.string.localization_system_desc'))
        .fontSize(13)
        .fontColor('#6B7280')
        .lineHeight(20)
    }
    .width('100%')
    .alignItems(HorizontalAlign.Start)
  }

  @Builder
  resourceSectionTitle() {
    Column({ space: 6 }) {
      Text($r('app.string.localization_resource_title'))
        .fontSize(19)
        .fontWeight(FontWeight.Bold)
        .fontColor('#111827')
      Text($r('app.string.localization_resource_desc'))
        .fontSize(13)
        .fontColor('#6B7280')
        .lineHeight(20)
    }
    .width('100%')
    .alignItems(HorizontalAlign.Start)
  }

  @Builder
  formatSectionTitle() {
    Column({ space: 6 }) {
      Text($r('app.string.localization_format_title'))
        .fontSize(19)
        .fontWeight(FontWeight.Bold)
        .fontColor('#111827')
      Text($r('app.string.localization_format_desc'))
        .fontSize(13)
        .fontColor('#6B7280')
        .lineHeight(20)
    }
    .width('100%')
    .alignItems(HorizontalAlign.Start)
  }

  @Builder
  validationSectionTitle() {
    Column({ space: 6 }) {
      Text($r('app.string.localization_validation_title'))
        .fontSize(19)
        .fontWeight(FontWeight.Bold)
        .fontColor('#111827')
      Text($r('app.string.localization_validation_desc'))
        .fontSize(13)
        .fontColor('#6B7280')
        .lineHeight(20)
    }
    .width('100%')
    .alignItems(HorizontalAlign.Start)
  }

  @Builder
  systemLanguageRow() {
    Row() {
      Text($r('app.string.localization_system_language_label'))
        .fontSize(14)
        .fontColor('#475569')
        .layoutWeight(1)
      Text(this.systemLanguage)
        .fontSize(15)
        .fontWeight(FontWeight.Bold)
        .fontColor('#111827')
    }
    .width('100%')
  }

  @Builder
  systemRegionRow() {
    Row() {
      Text($r('app.string.localization_system_region_label'))
        .fontSize(14)
        .fontColor('#475569')
        .layoutWeight(1)
      Text(this.systemRegion)
        .fontSize(15)
        .fontWeight(FontWeight.Bold)
        .fontColor('#111827')
    }
    .width('100%')
  }

  @Builder
  systemLocaleRow() {
    Row() {
      Text($r('app.string.localization_system_locale_label'))
        .fontSize(14)
        .fontColor('#475569')
        .layoutWeight(1)
      Text(this.systemLocale)
        .fontSize(15)
        .fontWeight(FontWeight.Bold)
        .fontColor('#111827')
    }
    .width('100%')
  }

  @Builder
  appPreferredLanguageRow() {
    Row() {
      Text($r('app.string.localization_app_language_label'))
        .fontSize(14)
        .fontColor('#475569')
        .layoutWeight(1)
      Text(this.appPreferredLanguage)
        .fontSize(15)
        .fontWeight(FontWeight.Bold)
        .fontColor('#111827')
    }
    .width('100%')
  }

  @Builder
  systemInfoPanel() {
    Column({ space: 10 }) {
      if (this.hasReadSystemInfo) {
        this.systemLanguageRow()
        this.systemRegionRow()
        this.systemLocaleRow()
        this.appPreferredLanguageRow()
      } else {
        Text($r('app.string.localization_feedback_initial'))
          .fontSize(14)
          .fontColor('#1F2937')
          .lineHeight(22)
      }
    }
    .width('100%')
    .padding(12)
    .backgroundColor('#F3F4F6')
    .borderRadius(10)
  }

  @Builder
  localeButton(index: number) {
    Button(this.localeOptions[index].locale)
      .height(42)
      .layoutWeight(1)
      .fontSize(13)
      .fontColor(this.localeIndex === index ? '#FFFFFF' : '#334155')
      .backgroundColor(this.localeIndex === index ? '#2563EB' : '#E5E7EB')
      .onClick(() => {
        this.localeIndex = index
      })
  }

  @Builder
  resourcePreview() {
    Column({ space: 12 }) {
      Text($r('app.string.localization_order_title'))
        .fontSize(22)
        .fontWeight(FontWeight.Bold)
        .fontColor('#111827')
      Text($r('app.string.localization_order_empty'))
        .fontSize(14)
        .fontColor('#6B7280')
        .lineHeight(22)
      Text($r('app.string.localization_order_action'))
        .fontSize(14)
        .fontColor('#FFFFFF')
        .padding({ left: 12, right: 12, top: 8, bottom: 8 })
        .backgroundColor('#2563EB')
        .borderRadius(8)
      Text($r('app.string.localization_resource_note'))
        .fontSize(12)
        .fontColor('#64748B')
        .lineHeight(18)
    }
    .width('100%')
    .padding(16)
    .borderRadius(14)
    .backgroundColor('#FFFFFF')
    .border({ width: 1, color: '#E5E7EB' })
    .alignItems(HorizontalAlign.Start)
  }

  @Builder
  formatRow(item: LocaleFormatItem) {
    Row() {
      Text(item.label)
        .fontSize(14)
        .fontColor('#475569')
        .layoutWeight(1)
      Text(item.value)
        .fontSize(15)
        .fontWeight(FontWeight.Bold)
        .fontColor('#111827')
    }
    .width('100%')
    .padding(12)
    .borderRadius(10)
    .backgroundColor('#FFFFFF')
    .border({ width: 1, color: '#E5E7EB' })
  }

  build() {
    Scroll() {
      Column({ space: 18 }) {
        Text($r('app.string.localization_page_title'))
          .fontSize(26)
          .fontWeight(FontWeight.Bold)
          .fontColor('#0F172A')
          .width('100%')
        Text($r('app.string.localization_page_desc'))
          .fontSize(14)
          .fontColor('#64748B')
          .lineHeight(22)
          .width('100%')

        this.systemSectionTitle()
        Button($r('app.string.localization_read_system'))
          .height(46)
          .width('100%')
          .fontSize(15)
          .backgroundColor('#0F766E')
          .onClick(() => {
            this.readSystemLocale()
          })
        this.systemInfoPanel()

        this.resourceSectionTitle()
        this.resourcePreview()

        this.formatSectionTitle()
        Row({ space: 8 }) {
          this.localeButton(0)
          this.localeButton(1)
          this.localeButton(2)
        }
        .width('100%')
        ForEach(this.formatItems(), (item: LocaleFormatItem) => {
          this.formatRow(item)
        }, (item: LocaleFormatItem) => item.value)

        this.validationSectionTitle()
      }
      .width('100%')
      .padding(20)
      .alignItems(HorizontalAlign.Start)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F8FAFC')
  }
}
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。