Angular 项目实现国际化的方法
正如angular官网所说,项目国际化是一件具有挑战性,需要多方面的努力、持久的奉献和决心的任务。
本文将介绍angular项目的国际化方案,涉及静态文件(html)和ts文件文案的国际化。
背景
- angular: 5.0
- angular cli: 1.6.1(1.5.x也可以)
- ng-zorro: 0.6.8
angular i18n
i18n模板翻译流程有四个阶段:
- 在组件模板中标记需要翻译的静态文本信息(即打上i18n标签)。
- angular的i18n工具将标记的信息提取到一个行业标准的翻译源文件(如.xlf文件,使用ng xi18n)。
- 翻译人员编辑该文件,翻译提取出来的文本信息到目标语言,并将该文件还给你(需要翻译人员接入,本文采用将xlf文件转为json格式文件输出,最终将json文件转换回xlf格式文件)。
- 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 国际化
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。