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 相关文章推荐
跨域表单提交状态的变相判断代码
Nov 12 Javascript
Array, Array Constructor, for in loop, typeof, instanceOf
Sep 13 Javascript
jquery删除数据记录时的弹出提示效果
May 06 Javascript
jQuery实现响应浏览器缩放大小并改变背景颜色
Oct 31 Javascript
javascript学习总结之js使用技巧
Sep 02 Javascript
Struts2+jquery.form.js实现图片与文件上传的方法
May 05 Javascript
Bootstrap学习笔记 轮播(Carousel)插件
Mar 21 Javascript
node.js使用express框架进行文件上传详解
Mar 03 Javascript
js实现小球在页面规定的区域运动
Jun 16 Javascript
vue移动端的左右滑动事件详解
Jun 17 Javascript
vue中keep-alive、activated的探讨和使用详解
Jul 26 Javascript
JavaScript实现网页下拉菜单效果
Nov 20 Javascript
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
一个简单计数器的源代码
2006/10/09 PHP
PHP函数篇详解十进制、二进制、八进制和十六进制转换函数说明
2011/12/05 PHP
PHP面向对象学习笔记之二 生成对象的设计模式
2012/10/06 PHP
thinkphp判断访客为手机端或PC端的方法
2014/11/24 PHP
PHP将身份证正反面两张照片合成一张图片的代码
2017/04/08 PHP
PHP 中TP5 Request 请求对象的实例详解
2017/07/31 PHP
Javascript 判断Flash是否加载完成的代码
2010/04/12 Javascript
node.js中的fs.mkdir方法使用说明
2014/12/17 Javascript
jQuery手机浏览器中拖拽动作的艰难性分析
2015/02/04 Javascript
jQuery子属性过滤选择器用法分析
2015/02/10 Javascript
JQ技术实现注册页面带有校验密码强度
2015/07/27 Javascript
AngularJS+Node.js实现在线聊天室
2015/08/28 Javascript
深入理解angularjs过滤器
2016/05/25 Javascript
jQuery EasyUI常用数据验证汇总
2016/09/18 Javascript
详解ElementUI之表单验证、数据绑定、路由跳转
2017/06/21 Javascript
使用layer弹窗和layui表单实现新增功能
2018/08/09 Javascript
JS实现关闭小广告特效
2021/01/29 Javascript
单线程JavaScript实现异步过程详解
2020/05/19 Javascript
js实现翻牌小游戏
2020/07/31 Javascript
vue+canvas实现拼图小游戏
2020/09/18 Javascript
用Python实现web端用户登录和注册功能的教程
2015/04/30 Python
Python中time模块和datetime模块的用法示例
2016/02/28 Python
Python基于Floyd算法求解最短路径距离问题实例详解
2018/05/16 Python
Django继承自带user表并重写的例子
2019/11/18 Python
opencv3/C++图像像素操作详解
2019/12/10 Python
python绘制雪景图
2019/12/16 Python
python实现文字版扫雷
2020/04/24 Python
Python中random模块常用方法的使用教程
2020/10/04 Python
OpenCV实现机器人对物体进行移动跟随的方法实例
2020/11/09 Python
python 写一个文件分发小程序
2020/12/05 Python
斯洛伐克时尚服装网上商店:Cellbes
2016/10/20 全球购物
英国安全产品购物网站:The Safe Shop
2017/03/20 全球购物
Guess美国官网:美国知名服装品牌
2019/04/08 全球购物
家长会演讲稿
2014/04/26 职场文书
复兴之路纪录片观后感
2015/06/02 职场文书
一级电子管军用接收机测评
2022/04/05 无线电