如何实现在程序运行时动态添加并使用多语言资源
======================

#### 问题现象

有的程序中使用的部分多语言国际化文本是动态从云端获取的，此时该如何保存到程序中并使用，同时可以保证程序中的文本跟随系统语言的变化而变化。

#### 背景知识

*   通过applicationContext[订阅环境变量变化](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/subscribe-system-environment-variable-changes)，可以在程序中监测系统语言的变化。
*   AppStorage提供了[应用全局的UI状态存储](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-appstorage)，可以在整个应用范围内更新相关UI的变化。

#### 解决方案

1.  由于resource文件夹中的多语言资源属于静态资源，在打包后无法在其中增加或减少相关多语言资源，所以这里自行实现一个I18n模块以管理动态增加的多语言资源。
2.  解决思路：实现一个I18n单例类功能模块，用来管理程序中动态增加的语言包，并把当前语言使用的语言包放到AppStorage中供其他页面使用。通过applicationContext订阅环境变量变化，监听系统语言的变化，发生变化时通过I18n模块将使用的语言包替换为新的语言包，即可达到显示文本跟随系统语言变化的效果。

*   以下是一个简单实现，其中语言包的存储方式和单例实现逻辑可根据需要修改，代码示例如下：
    
    _// i18n功能模块_
    export type LanguagePack = Record<string, string>;
    
    
    export class MyI18n {
      private static instance: MyI18n;
      private currentLanguage: string;
      private languagePacks: Record<string, LanguagePack> = {};
    
    
      private set appLanguagePacks(pack: Record<string, string>) {
        AppStorage.setOrCreate('LanguagePack', pack);
      }
    
    
      get appLanguagePacks() {
        return AppStorage.get('LanguagePack') as Record<string, string>;
      }
    
    
     _ // 私有构造函数，防止外部直接实例化_
      private constructor(defaultLanguage: string) {
        this.currentLanguage = defaultLanguage;
        this.languagePacks\[defaultLanguage\] = {};
        this.appLanguagePacks = this.languagePacks\[defaultLanguage\];
      }
    
    
     _ // 静态方法，用于获取单例实例_
      public static getInstance(defaultLanguage: string = 'en'): MyI18n {
        if (!MyI18n.instance) {
          MyI18n.instance = new MyI18n(defaultLanguage);
        }
        return MyI18n.instance;
      }
    
    
     _ // 加载语言包_
      loadLanguagePack(language: string, pack: LanguagePack) {
        this.languagePacks\[language\] = pack;
        if (this.currentLanguage === language) {
          this.appLanguagePacks = this.languagePacks\[language\];
        }
      }
    
    
     _ // 设置当前语言_
      setLanguage(language: string) {
        if (this.languagePacks\[language\]) {
          this.currentLanguage = language;
          this.appLanguagePacks = this.languagePacks\[language\];
        } else {
          console.warn(\`Language pack for ${language} is not loaded.\`);
        }
      }
    
    
     _ // 获取当前语言_
      getCurrentLanguage() {
        return this.currentLanguage;
      }
    
    
      updateLanguagePack(language: string, newLanguagePack: LanguagePack) {
        const tempPack = this.languagePacks\[language\] ?? {};
        for (let key of Object.keys(newLanguagePack)) {
          tempPack\[key\] = newLanguagePack\[key\];
        }
        this.languagePacks\[language\] = tempPack;
        if (this.currentLanguage === language) {
          this.appLanguagePacks = this.languagePacks\[language\];
        }
      }
    }
    
*   设置系统语言变化监听：
    
    import { ConfigurationConstant, EnvironmentCallback, UIAbility } from '@kit.AbilityKit';
    import { hilog } from '@kit.PerformanceAnalysisKit';
    import { window } from '@kit.ArkUI';
    import { MyI18n } from '../commons/I18n';
    
    
    const DOMAIN = 0x0000;
    
    
    export default class EntryAbility extends UIAbility {
      onCreate(): void {
        try {
          this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR\_MODE\_LIGHT);
        } catch (err) {
          hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err));
        }
        hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
      }
    
    
      onWindowStageCreate(windowStage: window.WindowStage): void {
        hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
    
    
        windowStage.loadContent('pages/Index', (err) => {
          if (err.code) {
            hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
            return;
          }
          hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
        });
    
    
        this.subscribeConfigurationUpdate();
      }
    
    
      subscribeConfigurationUpdate() {
        let systemLanguage: string | undefined = this.context.config.language; _// 获取系统当前语言_
    
      _  // 1.获取ApplicationContext_
        let applicationContext = this.context.getApplicationContext();
        MyI18n.getInstance(systemLanguage);
       _ // 2.通过applicationContext订阅环境变量变化_
        let environmentCallback: EnvironmentCallback = {
          onConfigurationUpdated(newConfig) {
            const appLanguage = MyI18n.getInstance().getCurrentLanguage();
            if (appLanguage !== newConfig.language && newConfig.language) {
              MyI18n.getInstance().setLanguage(newConfig.language);
            }
          },
          onMemoryLevel(level) {
            console.info(\`onMemoryLevel level: ${level}\`);
          }
        };
    
    
        applicationContext.on('environment', environmentCallback);
      }
    };
    
*   页面中模拟动态获取多语言资源以及使用语言包显示:
    
    import { LanguagePack, MyI18n } from '../commons/I18n';
    
    
    interface LanguageMap {
      zh\_CN: string;
      en: string;
    }
    
    
    function mockApi(): Promise<Record<string, LanguageMap>> {
      return new Promise((resolve) => {
        resolve({
          'name': {
            zh\_CN: '中文',
            en: '英文'
          }
        });
      });
    }
    
    
    @Entry
    @Component
    struct Index {
      @StorageProp('LanguagePack') languagePack: Record<string, string> = {};
    
    
      aboutToAppear(): void {
        mockApi().then((languageResource: Record<string, LanguageMap>) => {
          const newZhPack: LanguagePack = {};
          const newEnPack: LanguagePack = {};
          Object.keys(languageResource).forEach((key) => {
            newZhPack\[key\] = languageResource\[key\].zh\_CN;
            newEnPack\[key\] = languageResource\[key\].en;
          });
          MyI18n.getInstance().updateLanguagePack('zh-Hans-CN', newZhPack);
          MyI18n.getInstance().updateLanguagePack('en-Latn-CN', newEnPack);
        });
      }
    
    
      build() {
        RelativeContainer() {
          Text(this.languagePack\['name'\])
            .fontSize($r('app.float.page\_text\_font\_size'))
            .fontWeight(FontWeight.Bold)
            .alignRules({
              center: { anchor: '\_\_container\_\_', align: VerticalAlign.Center },
              middle: { anchor: '\_\_container\_\_', align: HorizontalAlign.Center }
            });
        }
        .height('100%')
        .width('100%');
      }
    }
    

运行示例：

当系统语言为中文时，显示的是中文的字段：

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/43/v3/DXOTpCDnSFGvydyRDmmyOA/zh-cn_image_0000002659022315.png?HW-CC-KV=V1&HW-CC-Date=20260709T134136Z&HW-CC-Expire=86400&HW-CC-Sign=48574D138E036FC928E365D094682FAE69729944E8C077DCA9F3BDB827A9C36E "点击放大") ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/3d/v3/tCUTiewPQFectiaRmAMPwA/zh-cn_image_0000002659062369.png?HW-CC-KV=V1&HW-CC-Date=20260709T134136Z&HW-CC-Expire=86400&HW-CC-Sign=5521C3E01355F72976CB83D2307E72397A02FEE2C1B537BA767EA22C8311DE80 "点击放大")

当系统语言为英文时，显示的是英文对应的字段：

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/38/v3/0Fw0k_q-R4m0SaN_koFTTw/zh-cn_image_0000002628822998.png?HW-CC-KV=V1&HW-CC-Date=20260709T134136Z&HW-CC-Expire=86400&HW-CC-Sign=A06A1D6EFE89DC85CF3BD168A69D724C07EB36DB6B1F185C1BC84F5570BC3C49 "点击放大") ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/72/v3/KYCLzKLPRNC4VNdeGrvVlA/zh-cn_image_0000002628663110.png?HW-CC-KV=V1&HW-CC-Date=20260709T134136Z&HW-CC-Expire=86400&HW-CC-Sign=168E20E94F0C24D36BC0B0DEEC1B53DD3281CA72016BBF86D00ED020C8480E9A "点击放大")

动图效果如下：

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/61/v3/X_JnN9YhRRqcKYqqBy1Yjg/zh-cn_image_0000002659022317.png?HW-CC-KV=V1&HW-CC-Date=20260709T134136Z&HW-CC-Expire=86400&HW-CC-Sign=2462FBCC655BA79A741EB69AF59B8B52CE8264BA8CEF845434F4F199B487B407 "点击放大")