ArkUI 国际化基础:资源读取与页面内 Locale 监听
【摘要】 国际化页面不维护硬编码多语言数组。静态文案进入 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.LocalizationKiti18n.System.getSystemLanguagei18n.System.getSystemRegioni18n.System.getAppPreferredLanguageintl.NumberFormatintl.DateTimeFormat$r('app.string.xxx')commonEventManager.Support.COMMON_EVENT_LOCALE_CHANGED
本篇边界
这篇只做资源化文案读取、系统语言/地区读取、页面内 Locale 变化监听和格式化演示,不修改系统语言,不接在线翻译,也不做多语言后台。i18n.System.setAppPreferredLanguage() 会影响应用资源加载策略,通常需要冷启动才能完整体现,不放进这个 Demo 的点击操作里。
监听只放在 ArkUILocalizationDemo 页面内:aboutToAppear() 创建订阅者并订阅 COMMON_EVENT_LOCALE_CHANGED,aboutToDisappear() 退订。事件回调直接更新页面 @State,不再通过 EntryAbility 或 AppStorage 中转。
交互预期
- 进入页面时会读取一次系统语言、系统地区、组合 Locale 和应用首选语言。
- 修改系统语言、系统地区或系统区域后,如果页面仍在前台,
COMMON_EVENT_LOCALE_CHANGED回调会直接刷新页面状态。 - 从系统设置返回页面时,
onPageShow()会再读取一次系统语言和地区,作为后台切换场景的兜底。 - 点击“读取系统区域信息”会手动刷新一次页面状态。
- “资源化文案”卡片里的业务文案来自
app.string,不是 ArkTS 硬编码多语言数组。 - 点击不同 locale 标签只切换数字和日期格式化预览,不修改页面文案语言。
资源配置
文件位置:
entry/src/main/resources/base/element/string.jsonentry/src/main/resources/en_US/element/string.jsonentry/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)