Angular使用管道和指令进行多语言切换,无第三方库引用

        工作中经常遇到需要进行多种语言切换的项目。本文记录了一种在Angular页面中通过使用管道和自定义指令实现的语言切换方案。

1、实现效果

        页面显示文字根据选择的语言自动进行翻译切换,如下图所示:

        此时,页面模板的字符串全部按照管道格式书写: 

<h1>{
   
   { "app.title" | translate}}</h1>
<div>
  <span>{
   
   { "app.demo.text" | translate}}</span>
  <input type="text" placeholder="{
   
   { 'app.demo.placeholder' | translate}}">
  <button (click)="showMessage()">{
   
   {"app.demo.btn" | translate}}</button>
</div>
<button *ngFor="let lan of languages" (click)="setLanguage(lan.code)">
  {
   
   {lan.icon + " " + lan.text}}
</button>

        其中 "app.title","app.demo.text",'app.demo.placeholder',"app.demo.btn" 等为各字符串的key,translate为管道选择器。translate管道通过key查找对应翻译进行翻译。用于查找翻译的字典通过加载文件方式加载到页面内存中。翻译文件格式如下:

        管道通过调用语言服务类(LanService)中的翻译方法查找对应翻译。

2、语言服务类

        语言服务类(LanService)中主要包含加载翻译文件、同步翻译、异步翻译、设置语言方法。

2.1、加载翻译文件方法

        通过HttpClient.get()加载翻译文件:

  /**
   * 加载翻译文件
   * @returns 
   */
  loadTranslation(languageCode: string = this.curLanguage) {
    const jsonFile = `assets/language/${languageCode}.json`;
    this.isLoading = true;
    return new Promise<void>((resolve, reject) => {
      this.http.get<any>(jsonFile).subscribe(data => {
        this.translation = data;
        this.isLoading = false;
        this.lanChange.next(languageCode);
        resolve();
      }, err => {
        reject(`load translation json error: ${err}`);
      })
    });
  }

        文件加载完成后会发出一个语言切换的通知(this.lanChange.next(languageCode);),订阅了该通知的方法将执行对应订阅逻辑。

2.2、同步翻译方法

        通过key获取当前字典中对应的翻译,未查找到对应key的翻译直接返回key,如果服务正在加载翻译文件也直接返回key:

  /**
   * 获取单个文本的翻译 同步方法
   * @param key 文本资源key
   * @returns 对应翻译结果字符串,未查找到返回key
   */
  translate(key: string) {
    if (this.isLoading) return key;
    else return this.translation[key] || key;
  }

        对于一些在确定语言包json已加载完成的场景中可以使用同步翻译方法获取翻译。比如页面中点击按钮提示的文字就可以采用同步方法获取翻译:

  /**
   * 弹出提示文字
   */
  showMessage() {
    alert(this.lanService.translate("app.welcome"));
  }

2.3、异步翻译方法

        通过key获取当前字典中对应的翻译,返回数据以Observable格式封装。如果服务正在加载翻译文件,则订阅语言切换的通知,待文件加载完成发出通知后,再查找对应翻译;相反则直接查找翻译:

  /**
   *  获取单个文本的翻译 异步方法
   * @param key 文本资源key
   * @returns 对应翻译结果字符串,未查找到返回key
   */
  get(key: string) {
    if (this.isLoading) {
      return from(new Promise<string>(resolve => {
        this.lanChange.subscribe(lan => {
          resolve(this.translation[key] || key);
        })
      }));
    } else {
      return of(this.translation[key] || key);
    }
  }

2.4、设置语言方法

        方法调用时,根据传入的语言重新加载翻译文件,加载后更新当前语言:

  /**
   * 设置页面语言
   * @param languageCode 语言代码 zh-CN | en-US
   */
  setLanguage(languageCode: string) {
    if (this.curLanguage != languageCode) {
      this.loadTranslation(languageCode).then(() => {
        this.curLanguage = languageCode;
      });
    }
  }

3、页面启动加载翻译文件

        页面启动时需要加载翻译文件,可以在AppModule中注册页面启动的加载方法:

export function StartUpServiceFactory(startUpService: StartUpService) {
  return () => startUpService.load();
}
const APP_INIT_PROVIDERS = [
  StartUpService,
  {
    provide: APP_INITIALIZER,
    useFactory: StartUpServiceFactory,
    deps: [StartUpService],
    multi: true
  }
];
@NgModule({
    //...
    providers: [
        ...APP_INIT_PROVIDERS
    ],
    // ...
})
export class AppModule { }

        在启动服务类(StartUpService)中调用语言服务类(LanService)的加载翻译文件方法:

  /**
   * 启动加载项
   */
  load() {
    this.lan.loadTranslation();
  }

4、管道方法

        管道方法中采用异步获取翻译的方式进行文本转换,会将传入的key及其翻译进行缓存,每次查询会优先查询缓存值。没有缓存才会进行异步获取翻译。管道还会订阅语言服务类(LanService)的语言切换通知,收到通知后重新获取翻译,更新翻译结果:

  transform(key: string) {
    if (this.cacheKey == key) return this.translation;
    this.cacheKey = key;
    this.updateTranslation(key);
    this.unsubscribe();
    if (!this.lanChange$) {
      this.lanChange$ = this.lanService.lanChange.subscribe(lan => {
        if (this.cacheKey) {
          this.cacheKey = "";
          this.updateTranslation(key);
        }
      });
    }
    return this.translation;
  }

        其中updateTranslation()方法完成了异步获取翻译的操作:

  /**
   * 异步更新翻译
   * @param key 字符串key
   */
  updateTranslation(key: string) {
    let callback = (tran: string) => {
      this.translation = tran || key;
      this.cacheKey = key;
      this._ref.markForCheck();
    }
    this.lanService.get(key).subscribe(callback);
  }

        为了使Angular状态检查能够检测到管道值的变化,管道装饰器的pure字段必须设置为false,否则页面不会渲染更新修改语言而导致的文本变化:

@Pipe({
  name: 'translate',
  pure: false
})
export class TranslatePipe implements PipeTransform{}

5、自定义指令实现

        使用上述管道就可以完全满足语言切换的需求,但是使用管道的时候,模板HTML页面代码中只能看到各字符串对应的key,如果是团队合作的代码或者长时间之前写的代码,就不能马上理解到key对应的翻译文本。这时候如果采用指令的形式实现,将key通过参数传入指令,通过指令修改元素innnerText。此时模板HTML页面中就可以保留任意语言对应的翻译,当然汉语最熟悉:

<h1 [lan]="'app.title'">多语言切换演示</h1>
<span [lan]="'app.demo.text'">文本内容演示</span>
<button [lan]="'app.demo.btn'" (click)="showMessage()">点击提示文字</button>

        这样就既能满足切换语言需求,又能一眼看到中文信息,提高代码可读性。指令代码:

  /**
   * 传入的字符串key
   */
  @Input('lan')
  set lan(value: string) {
    this.key = value;
    this.setText(value);
  }
  
    /**
   * 异步获取翻译设置元素内容文本
   * @param key 
   */
  setText(key: string) {
    key = key || '';
    if (key.length > 0) {
      const el = this.el.nativeElement;
      this.lanService.get(key).subscribe(tran => {
        el.innerText = tran;
        this.changeDetectorRef.markForCheck();
      });
    }
  }
  
  //订阅服务类通知更新翻译
  this.lanChange$ = this.lanService.lanChange.subscribe((lan) => {
      if (this.key.length > 0) {
        this.setText(this.key);
      }
   });

        但是像placeholder这种还是只能使用管道。

        完整项目代码下载:Angular使用管道和指令实现多语言切换示例,无第三方库引用-Typescript文档类资源-CSDN下载

猜你喜欢

转载自blog.csdn.net/evanyanglibo/article/details/128084926