Angular 项目实现国际化的方法


Posted in Javascript onJanuary 08, 2018

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

背景

  1. Angular: 5.0
  2. Angular Cli: 1.6.1(1.5.x也可以)
  3. NG-ZORRO: 0.6.8

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 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', (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",
    "xmlns" : "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;
}

 

这样提取文案信息和转换翻译后的文件就完成了,接下来我们需要把翻译好的文案应用到项目中去。

部署翻译文件

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里,来单独打包。

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

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

具体思路

通过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 && 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的国际化(i18n)在线例子
NG-ZORRO Locale 国际化

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jQuery 前的按键判断代码
Mar 19 Javascript
JavaScript 学习笔记二 字符串拼接
Mar 28 Javascript
快速排序 php与javascript的不同之处
Feb 22 Javascript
javascript事件模型实例分析
Jan 30 Javascript
javascript编程异常处理实例小结
Nov 30 Javascript
jQuery.Ajax()的data参数类型详解
Jul 23 jQuery
Node.js静态服务器的实现方法
Feb 28 Javascript
spirngmvc js传递复杂json参数到controller的实例
Mar 29 Javascript
Angular angular-file-upload文件上传的示例代码
Aug 23 Javascript
对Vue.js之事件的绑定(v-on: 或者 @ )详解
Sep 15 Javascript
详解Puppeteer前端自动化测试实践
Feb 21 Javascript
Vue router配置与使用分析讲解
Dec 24 Vue.js
jQuery+CSS实现的table表格行列转置功能示例
Jan 08 #jQuery
简易Vue评论框架的实现(父组件的实现)
Jan 08 #Javascript
关于react-router/react-router-dom v4 history不能访问问题的解决
Jan 08 #Javascript
OkHttp踩坑随笔为何 response.body().string() 只能调用一次
Jan 08 #Javascript
Vue 组件(component)教程之实现精美的日历方法示例
Jan 08 #Javascript
一步步教你利用webpack如何搭一个vue脚手架(超详细讲解和注释)
Jan 08 #Javascript
深入理解 webpack 文件打包机制(小结)
Jan 08 #Javascript
You might like
php实现设计模式中的单例模式详解
2014/10/11 PHP
php的api数据接口书写实例(推荐)
2016/09/22 PHP
php array_key_exists() 与 isset() 的区别
2016/10/24 PHP
php数据序列化测试实例详解
2017/08/12 PHP
input按钮的事件处理大全
2010/12/10 Javascript
autoIMG 基于jquery的图片自适应插件代码
2011/03/12 Javascript
AngularJS入门教程(一):静态模板
2014/12/06 Javascript
JS实现向表格中动态添加行的方法
2015/03/30 Javascript
JS简单生成两个数字之间随机数的方法
2016/08/03 Javascript
JavaScript函数表达式详解及实例
2017/05/05 Javascript
原生JS 购物车及购物页面的cookie使用方法
2017/08/21 Javascript
React SSR样式及SEO的实践
2018/10/22 Javascript
详解webpack引用jquery(第三方模块)的三种办法
2019/08/21 jQuery
vue中可编辑树状表格的实现代码
2020/10/31 Javascript
[01:00]选手抵达华西村 整装待发备战2016国际邀请赛中国区预选赛
2016/06/25 DOTA
Python线性方程组求解运算示例
2018/01/17 Python
python使用Tkinter实现在线音乐播放器
2018/01/30 Python
Python读写及备份oracle数据库操作示例
2018/05/17 Python
Python实现重建二叉树的三种方法详解
2018/06/23 Python
python3+opencv3识别图片中的物体并截取的方法
2018/12/05 Python
python批量解压zip文件的方法
2019/08/20 Python
在python中做正态性检验示例
2019/12/09 Python
python爬虫开发之Request模块从安装到详细使用方法与实例全解
2020/03/09 Python
Keras之fit_generator与train_on_batch用法
2020/06/17 Python
一款纯css3实现的非常实用的鼠标悬停特效演示
2014/11/05 HTML / CSS
css3实例教程 一款纯css3实现的发光屏幕旋转特效
2014/12/07 HTML / CSS
HTML中fieldset标签概述及使用方法
2013/02/01 HTML / CSS
Brydge英国:适用于Apple iPad和Microsoft Surface Pro的蓝牙键盘
2019/05/16 全球购物
Claire’s法国:时尚配饰、美容、珠宝、头发
2021/01/16 全球购物
大学生自我评价怎样写好
2013/10/23 职场文书
励志演讲稿600字
2014/08/21 职场文书
商场父亲节活动方案
2014/08/27 职场文书
感恩教师节主题班会
2015/08/12 职场文书
2016教师节感恩话语
2015/12/09 职场文书
学习型家庭事迹材料(2016精选版)
2016/02/29 职场文书
CSS3鼠标悬浮过渡缩放效果
2021/04/17 HTML / CSS