Angular 项目 国际化

发布时间:2019-06-28 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了Angular 项目 国际化脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

正如Angular官网所说,项目国际化是一件具有挑战性,需要多方面的努力、持久的奉献和决心的任务
本文将介绍angular项目的国际化方案,涉及静态文件(htML)和ts文件文案的国际化。

背景

  • Angular: 5.0
  • Angular Cli: 1.6.1(1.5.x也可以)
  • NG-ZORRO: 0.6.8
  • @H_512_13@

    Angular i18n

    i18n模板翻译流程有四个阶段
    1. 在组件模板中标记需要翻译的静态文本信息(即打上i18n标签)。
    2. Angular的i18n工具将标记的信息提取到一个行业标准的翻译文件(如.xlf文件,使用ng xi18n)。
    3. 翻译人员编辑该文件,翻译提取出来的文本信息到目标语言,并将该文件还给你(需要翻译人员接入,本文采用将xlf文件转为json格式文件输出,最终将json文件转换回xlf格式文件)。
    4. Angular编译器导入完成翻译的文件,使用翻译的文本替换原始信息,并生成新的目标语言版本的应用程序。
    你可以为每种支持的语言构建和部署单独的项目版本,仅需替换翻译后的xlf文件即可。
    
    何在模板文件中使用?

    i18n提供了几种使用方式,还专门为单复数提供了翻译方式(个人没有使用,感觉不太方便)。接下来以一个单独的html文件来介绍几种使用方法。

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Angular i18n</title>
    </head>
    <body>
        <h1 i18n="Site Header|An introduction header for i18n PRoject@@stTitle">Angular 国际化项目</h1>
        <p>
          <span i18n="@@agDescription">国际化是一项很具有挑战性,需要多方面的努力、持久的奉献和决心的任务。</span>
          <span class="delete" i18n-title="@@agDelete" title="删除"></span>
        </p>
        <p><ng-container i18n=@@agLetGo>让我们现在开始吧!</ng-container>朋友!</p>
    </body>
    </html>
    

    上述代码展示了几种i18n的使用方式:

    1、使用i18n属性标记(可添加上说明性文案,格式如:title|description@@id,title和description可帮助翻译人员更好地理解文案含义,是否添加取决于自身项目情况)
    可以在静态标签上直接打上i18n的tag,如
    <span i18n="@@agDescription"></span>
    生成的xlf(xml)字段格式为
    <trans-unit id="agDescription" datatype="html">
      <source>国际化是一项很具有挑战性,需要多方面的努力、持久的奉献和决心的任务。</source>
      <context-group purpose="location">
        <context context-type="sourcefile">xxx.ts</context>
        <context context-type="linenumber">linenum</context>
      </context-group>
    </trans-unit>
    
    2、为title添加i18n属性
    对于html标签属性,同样可以添加i18n,如
    <span @H_978_304@class="delete" i18n-title="@@agDelete" title="删除"></span>
    生成的xlf(xml)格式同上
    
    3、翻译文本,而不必创建元素
    我们有时候会出现一句话多个断句情况,如果每次都添加span、label这些元素包裹的话,可能严重影响页面布局,这时候我们可以使用ng-container来包裹需要翻译的文案。
    <p>
        <ng-container i18n=@@agLetGo>让我们现在开始吧!</ng-container>朋友!
    </p>
    在页面显示为
    <p>
        <!---->
        LET'S GO朋友!
    </p>
    * ng-container变为了注释块,这样做不会影响页面布局(尤其是应用了style样式的情况)
    

    打上标签后,我们只要执行ng xi18n即可自动创建出xlf文件,通常为message.xlf,如需自定义,可自行前往 Angular CLI 官网查看。

    XLF与JSON转换

    xlf转json方法
    我个人是采用xml2js库进行操作,简单代码如下:
    const fs = require('fs');
    xml2js = require('xml2js');
    var parser = new xml2js.Parser();
    fs.reaDFile(fileName, 'utf8', (@H_846_406@err, data) => {
      parser.parseString(data, function (err, result) {
        // 读取新文件全部需要翻译的数据,并对比已翻译的进行取舍,具体转换成的格式结构可自行查看
        result['xliff']['file'][0]['body'][0]['trans-unit'].forEach((item) => {
          var itemFormat = {
            "key"  : item['$']['id'],
            "value": item['source'][0]
          };
          // 执行相关操作,key-value形式是为了统一翻译文件结构,可按需定义
        })
      });
    });
    
    json转xlf方法
    function backToXLF(translatedParams) {
      // 文件格式可自行参考angular.cn官网的例子
      var xlfFormat = {
        "xliff": {
          "$"   : {
            "version": "1.2",
            ">"urn:oasis:names:tc:xliff:document:1.2"
          },
          "file": [
            {
              "$"   : {
                "source-language": "en",
                "datatype"       : "plaintext",
                "original"       : "ng2.template"
              },
              "body": [
                {
                  "trans-unit": []
                }
              ]
            }
          ]
        }
      };
      if (translatedParams instanceof Array) {
        // 获取原始名称
        translatedParams.forEach((data) => {
          var tmp = {
            "$"     : {
              "id"      : data.key,
              "datatype": "html"
            },
            "source": [i18nItemsOrigin[data.key]], // 这里的i18nItemsOrigin是json格式,属性名为key值,表示原始文案
            "target": [data.value]
          };
          // 数组,json项
          xlfFormat['xliff']['file'][0]['body'][0]['trans-unit'].push(tmp);
        });
      }
      var builder = new xml2js.Builder();
      var xml = builder.buildObject(xlfFormat);
      return xml;
    }
    
    这样提取文案信息和转换翻译后的文件就完成了,接下来我们需要把翻译好的文案应用到项目中去。
    

    部署翻译文件

    JIT模式
    src目录下新建locale文件夹,将翻译转换后的demo.en-US.xlf文件存在改目录下
    app文件夹下新建i18n-providers.ts

    import {
      LOCALE_ID,
      MissingTranslationStrategy,
      StaticProvider,
      TRANSLATIONS,
      TRANSLATIONS_FORMAT
    } from '@angular/core';
    import { CompilerConfig } from '@angular/compiler';
    import { Observable } from 'rxjs/Observable';
    import { LOCALE_LANGUAGE } from './app.config'; // 自行定义配置位置
    
    export function getTranslationProviders(): Promise<StaticProvider[]> {
    
      // get the locale string from the document
      const locale = LOCALE_LANGUAGE.toString();
    
      // return no providers
      const noProviders: StaticProvider[] = [];
    
      // no locale or zh-CN: no translation providers
      if (!locale || locale === 'zh-CN') {
        return Promise.resolve(noProviders);
      }
    
      // Ex: 'locale/demo.zh-MO.xlf`
      const translationFile = `./locale/demo.${locale}.xlf`;
    
      return getTranslationsWithSystemJs(translationFile)
        .then((translations: string) => [
          { provide: TRANSLATIONS, useValue: translations },
          { provide: TRANSLATIONS_FORMAT, useValue: 'xlf' },
          { provide: LOCALE_ID, useValue: locale },
          {
            provide: CompilerConfig,
            useValue: new CompilerConfig({ missingTranslation: MissingTranslationStrategy.Error })
          }
        ]).catch(() => noProviders); // ignore if file not found
    }
    
    declare var System: any;
    // 获取locale文件
    function getTranslationsWithSystemJs(file: string) {
      let text = '';
      const fileRequest = new XMLHttpRequest();
      fileRequest.open('GET', file, false);
      fileRequest.onerror = function (err) {
        console.log(err);
      };
      fileRequest.onreadystatechange = function () {
        if (fileRequest.readyState === 4) {
          if (fileRequest.status === 200 || fileRequest.status === 0) {
            text = fileRequest.responseText;
          }
        }
      };
      fileRequest.send();
      const observable = Observable.of(text);
      const prom = observable.toPromise();
      return prom;
    }
    

    main.ts文件修改为

    import { enableProdMode } from '@angular/core';
    import { platformbrowserDynamic } from '@angular/platform-browser-dynamic';
    
    import { AppModule } from './app/app.module';
    import { environment } from './environments/environment';
    import { getTranslationProviders } from './app/i18n-providers';
    
    if (environment.production) {
      enableProdMode();
    }
    
    getTranslationProviders().then(providers => {
      const options = { providers };
      platformBrowserDynamic().bootstrapModule(AppModule, options)
        .catch(err => console.log(err));
    });

    别忘了将locale目录添加到.angular-cli.json里,来单独打包。

    AOT模式(推荐)

    对于AOT模式打包的持续来说,不需要上述复杂的配置,只需要在原有build基础上,加上相应的i18n文件即可,如:

    ng build --prod --build-optimizer -bh / --i18n-format=xlf --locale=en --i18n-file=./src/locale/demo.en-US.xlf
    

    这样打出的包会自动将翻译文件应用到项目中。

    这样我们对静态文案的翻译工作基本已经完成了,但是有些动态文案如ts文件里的文案或者第三方框架属性该如何翻译呢?下面会介绍针对 ts 文件和 NG-ZORRO 框架实现动态文案翻译的方案。

    ts文件文案和NG-ZORRO框架文案翻译

    具体思路
    @H_777_823@通过Pipe调用Service方法,根据对应的唯一id值匹配json对象里的翻译结果,进而返回渲染到前端,参考于NG-ZORRO框架的国际化实现方案。

    首先我们定义一下json翻译对象的格式,全部为三层结构,动态变量需要按%%包裹,这样做的原因是和项目结构相关联,也便于后期和i18n方式格式统一。

    {
        "app": {
            "base": {
                "hello": "文件文案",
                "userCount": "一共%num%人"
            }
        }
    }
    

    格式已定,我们继续定义Service处理方式
    这里复用 NG-ZORRO的国际化方案 ,可以简化我们的开发,有兴趣的可以参看一下其源码。

    *** TranslateService ***
    import { Injectable } from '@angular/core';
    // 引入语言配置和国际化文件文案对象
    import { LOCALE_LANGUAGE } from '../app.config';
    import { enUS } from '../locales/demo.en-US';
    import { zhCN } from '../locales/stream.zh-CN';
    
    @Injectable()
    export class TranslateService {
    
      private _locale = LOCALE_LANGUAGE.toString() === 'zh-CN' ? zhCN : enUS;
    
      constructor() {
      }
      // path为app.base.hello格式的字符串,这里按json层级取匹配改变
      translate(path: string, data?: any): string {
        let content = this._getObjectPath(this._locale, path) as string;
        if (typeof content === 'string') {
          if (data) {
            Object.keys(data).forEach((key) => content = content.replace(new RegExp(`%${key}%`, 'g'), data[key]));
          }
          return content;
        }
        return path;
      }
    
      private _getObjectPath(obj: object, path: string): string | object {
        let res = obj;
        const paths = path.split('.');
        const depth = paths.length;
        let index = 0;
        while (res &amp;& index < depth) {
          res = res[paths[index++]];
        }
        return index === depth ? res : null;
      }
    }
    

    这样,只需要在Pipe中调用Service的translate方法即可

    *** NzTranslateLocalePipe ***
    import { Pipe, PipeTransform } from '@angular/core';
    import { TranslateService } from '../services/translate.service';
    
    @Pipe({
      name: 'nzTranslateLocale'
    })
    export class NzTranslateLocalePipe implements PipeTransform {
      constructor(private _locale: TranslateService) {
      }
    
      transform(path: string, keyValue?: object): string {
        return this._locale.translate(path, keyValue);
      }
    }
    

    好了,现在我们处理逻辑已经完全结束了,下面介绍一下如何使用

    *** NG-ZORRO 控件 ***
    <nz-input [nzPlaceHolder]="'app.base.hello'|nzTranslateLocale"></nz-input> // 无动态参数
    <nz-popconfirm [nzTitle]="'app.base.userCount'|nzTranslateLocale: {num:users.length}" ...>
    ... // 有动态参数
    </nz-popconfirm>
    
    *** ts文件 ***
    export class AppComponent implements OnInit {
      demoTitle='';
      users = ['Jack', 'Johnson', 'Lucy'];
      constructor(privete translateService: TranslateService) {
      }
      ngOnInit() {
        this.demoTitle = this.translateService.translate('app.base.hello');
      }
    }
    

    以上流程基本上能满足大部分angular项目的国际化需求,如果需要更加复杂的国际化情况,欢迎讨论。

    总结

    Angular到5.0的国际化已经相对来说简便了很多,我们只需要在合适的地方打上i18n的tag即可方便快速地提取需要翻译文案,具体如何处理翻译后的文件因人而异,多种方法可帮助我们转换(如本文通过nodejs)。

    复杂一点的是无法通过打i18n标签来翻译的文本,NG-ZORRO的国际化方案弥补了这方面的不足,结合起来可以很方便地完成项目的国际化。 国际化如果没有专门的团队支持,翻译难度很大,需要考虑的东西很多,比如繁体还有澳门繁体、台湾繁体等,语法也不尽相同。

    参考目录

    脚本宝典总结

    以上是脚本宝典为你收集整理的Angular 项目 国际化全部内容,希望文章能够帮你解决Angular 项目 国际化所遇到的问题。

    如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

    本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
    如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。