1.自定义插件的初衷
最近在开发angularjs项目中使用到了国际化,例如html里有段是这样的
<h1 class="page-header" [translate]="'company.labelCompanyProfile'">Company Profile</h1>
translate会去en.json里找company.labelCompanyProfile的翻译,如果没有就直接显示Company Profile。
开发过程需要在en.json的配置文件里先定义好如下
{
"company": {
"labelCompanyProfile": "Company Profile"
}
}
每次碰到翻译的地方都要重复这个步骤,很烦人。我的想法是只需要关心html的编写,因为编写过程翻译和key都有了,是否有个命令批量将key和对应翻译给加入到en.json里,找了下没有想关的插件,于是就想自己定义一个。本篇记录开发过程,以及遇到的问题和解决方案。
2. i18n-autoinsert的开发过程记录
2.1 npm 插件开发很简单,可以参看自定义一个npm插件
2.2 安装commander插件
npm install commander
2.3 主要逻辑
index.js
#!/usr/bin/env node
var program = require('commander');
var fs = require("fs");
var path = require('path');
program
.version('1.0.6')
.option('-t, --translationFile [value]', 'tranFile')
.option('-h, --htmlFile [value]', 'htmlFile')
.option('-p, --htmlPath [value]', 'htmlPath')
.option('-s, --showlog')
.parse(process.argv);
var projectPath=path.join(__dirname,'../../');
if (!program.translationFile) {
console.log('need specify -t for translationFile');
process.exit(1);
}
if (!program.htmlFile && !program.htmlPath) {
console.log('need specify -h for htmlFile or -p for htmlPath');
process.exit(1);
}
//找到翻译文件
var enFile=path.join(projectPath,program.translationFile);
var htmlFiles=[];
//找出所有的html文件放到数组htmlFiles里
if(program.htmlFile) {
var htmlFile = path.join(projectPath, program.htmlFile);
htmlFiles.push(htmlFile);
}
if(program.htmlPath){
var htmlPath=path.join(projectPath, program.htmlPath);
addTargetFile(htmlPath,htmlFiles);
}
//循环htmlFiles加入翻译
htmlFiles.forEach(function(f){
autoInsertTranslation(enFile,f);
});
console.info("done!")
function addTargetFile(htmlPath,htmlFiles){
var files =fs.readdirSync(htmlPath);
files.forEach( function (file){
var f = path.join(htmlPath, file);
if(file.indexOf(".html")!=-1||file.indexOf(".HTML")!=-1){
htmlFiles.push(f);
}else{
var stats=fs.statSync(f);
if(stats.isDirectory()){
addTargetFile(f,htmlFiles);
}
}
});
}
function autoInsertTranslation(enFile,htmlFile) {
if(program.showlog) {
console.log("translation file : "+ enFile);
console.log("html file : "+ htmlFile);
}
var data = fs.readFileSync(enFile);
var tranJson = JSON.parse('{}');
if(data.toString().trim().length!=0){
tranJson = JSON.parse(data);
}
var data = fs.readFileSync(htmlFile);
//正则表达式的匹配,例如
//<input class="xxx" [translate]="'abc'">helloworld<input>
var matches = data.toString().match(/\[translate\]=\"\'.*\'\"[\s\S]*?</gmi);
for (var i in matches) {
var str = matches[i];
if((/\'(.*)\'\"[\s\S]*>([\s\S]*)</).test(str)) {
if(RegExp.$1)
addToJson(tranJson, RegExp.$1.trim(), RegExp.$2.trim());
}
}
fs.writeFileSync(enFile, JSON.stringify(tranJson, null, ' '));
}
function addToJson(json,key,value){
var keys=key.split("\.");
var tmpJson=json;
for( var j=0;j<keys.length-1;j++){
if(!tmpJson[keys[j]]) {
tmpJson[keys[j]] = {};
}
tmpJson=tmpJson[keys[j]];
}
//tmpJson must be a json,not a string
if(typeof tmpJson==="object"){
if(!tmpJson[keys[keys.length-1]])
tmpJson[keys[keys.length-1]]=value;
}else{
console.error("ERROR:can't auto insert ["+key+"], because ["+key.substr(0,key.lastIndexOf("\."))+"] is not a json object in translation file," +
"please check!!!")
}
}
2.4 使用
插件发布好后
npm install -save-dev i18n-autoinsert
- 在 package.json 里 script 加入
"scripts": {
...
"i18n-autoinsert": "i18n-autoinsert -t /src/assets/i18n/en.json -p /src/app/page/company/approval-groups/ --showlog"
...
}
-t 指定翻译文件,必须指定
-p 指定html的路径,程序会递归所有的子文件夹并找出html文件,-p和-h必须指定一个
-h 指定单个html路径,-p和-h必须指定一个
–showlog 可有可无,打印日志
- 运行
npm run i18n-autoinsert
不用头疼在en.json里一个个去写翻译
3.问题记录
npm install i i8n-autoinsert
生成出来的npm_module/.bin/i18n-autoinsert.cmd
@"%~dp0\..\i18n-autoinsert\index.js" %*
而不是
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\..\i18n-autoinsert\index.js" %*
) ELSE (
@SETLOCAL
@SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\..\i18n-autoinsert\index.js" %*
)
解决方式,需要在index.js加入
#!/usr/bin/env node
- 正则表达式匹配问题
碰到html是
<ng-template dbsTabTitle><span [translate]="'company.labelApprovalGroups'">Approval Groups</span></ng-template>
使用
var matches=data.toString().match(/\[translate\]=\"\'.*\'\">.*</gmi)
匹配到的是
[translate]="'company.labelApprovalGroups'">Approval Groups</span><
而不是想要的结果
[translate]="'company.labelApprovalGroups'">Approval Groups<
解决方式 : 使用非贪婪匹配模式,如下
/\[translate\]=\"\'.*\'\">.*?</gmi
而不是
/\[translate\]=\"\'.*\'\">.*<?/gmi
- 相对路径的获取
在代码里要获取到index.js所在的路径可以使用
__dirname
4.使用过程中发现下面的匹配不到
<h2 class="form-section-header" style="margin-top: 0px" [translate]="'company-group.create.step1.group.detail'">
Step 1: Approval group details</h2>
原来的是
var matches = data.toString().match(/\[translate\]=\"\'.*\'\">.*?</gmi);
解决方式
var matches = data.toString().match(/\[translate\]=\"\'.*\'\">[\s\S]*?</gmi);
.不包含换行,[\s\S]则包含所有的字符
5.使用过程中发现下面的匹配不到,
>前多了个空格
<button (click)="createNewGroup()" [translate]="'labelCreateAnotherGroup'" >Create another Group</button>
或
<button [translate]="'labelCreateAnotherGroup'" (click)="createNewGroup()" >Create another Group</button>
解决方式
var matches = data.toString().match(/\[translate\]=\"\'.*\'\"[\s\S]*?</gmi);
for (var i in matches) {
var str = matches[i];
if((/\'(.*)\'\"[\s\S]*>([\s\S]*)</).test(str)) {
if(RegExp.$1)
addToJson(tranJson, RegExp.$1.trim(), RegExp.$2.trim());
}
}