UNPKG

i18n-text-tools

Version:

一个用于自动提取、管理和同步项目多语言文本的命令行工具,支持 Vue3、JS/TS 文件的国际化文本收集、唯一key生成、重复检测、与YiCAT平台集成等功能。

2 lines (1 loc) 17.8 kB
var ce=Object.create;var k=Object.defineProperty;var ue=Object.getOwnPropertyDescriptor;var ge=Object.getOwnPropertyNames;var de=Object.getPrototypeOf,fe=Object.prototype.hasOwnProperty;var me=(s,e)=>{for(var t in e)k(s,t,{get:e[t],enumerable:!0})},W=(s,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of ge(e))!fe.call(s,i)&&i!==t&&k(s,i,{get:()=>e[i],enumerable:!(o=ue(e,i))||o.enumerable});return s};var x=(s,e,t)=>(t=s!=null?ce(de(s)):{},W(e||!s||!s.__esModule?k(t,"default",{value:s,enumerable:!0}):t,s)),pe=s=>W(k({},"__esModule",{value:!0}),s);var Ce={};me(Ce,{CustomCommand:()=>V});module.exports=pe(Ce);var ae=require("commander");var y=x(require("colors"),1),I=x(require("path"),1),J=x(require("fs"),1);var U=x(require("fs"),1),ee=x(require("path"),1),he=x(require("colors"),1);var F=x(require("fs"),1),Y=x(require("path"),1),A=x(require("colors"),1),H=x(require("crypto"),1),B=x(require("figlet"),1),X=x(require("writejson"),1),T=(process.env.NODE_ENV||"").trim()==="dev_debug",L=s=>{let e=Y.default.dirname(s);F.default.existsSync(e)||(L(e),F.default.mkdirSync(e))},b=s=>{L(s),F.default.existsSync(s)||F.default.writeFileSync(s,"")},E=(s,e)=>{if(!(0,F.existsSync)(s))return e;let t=(0,F.readFileSync)(s).toString("utf-8");return t?JSON.parse(t):e},P=(s,e)=>{X.default.sync(s,e,{spaces:2})},G=s=>{console.log(A.default.yellow(s))};var j=s=>{let e=H.default.createHash("md5");return e.update(s),e.digest("hex")},K=s=>{let e=Object.keys(s).sort(),t={};return e.forEach(o=>{t[o]=s[o]}),t},Z=async()=>{let s=await(0,B.default)("I 1 8 N - T E X T - T O O L S");console.log(A.default.green(s))};var R=class{srcCodeDir="src";outputDir="i18n-messages";translateFunctionName=["t","$t"];defaultLanuage="zh";targetLanuages=["en","ja","ko","vi","th","id","ms","pt","ru","es"];i18nLocaleDir="i18n/locales";targetFormat="json";microsoftInfo={endpoint:"https://api.cognitive.microsofttranslator.com",key:"",location:"eastasia"};xiaoniuTrans;constructor(){let e=ee.default.join(process.cwd(),"package.json");if(!U.default.existsSync(e))console.warn("Cannot find package.json file. It's recommended to run this command in your project root.");else try{let o=E(e,{}).i18nPickConfig;if(!o)throw new Error("Cannot find i18nPickConfig field in package.json.");o.srcCodeDir&&(this.srcCodeDir=o.srcCodeDir),o.outputDir&&(this.outputDir=o.outputDir),o.translateFunctionName&&(this.translateFunctionName=o.translateFunctionName),o.defaultLanuage&&(this.defaultLanuage=o.defaultLanuage),o.targetLanuages&&o.targetLanuages.length&&(this.targetLanuages=o.targetLanuages),o.i18nLocaleDir&&(this.i18nLocaleDir=o.i18nLocaleDir),o.targetFormat&&(this.targetFormat=o.targetFormat),o.microsoftInfo&&(this.microsoftInfo=o.microsoftInfo),o.xiaoniuTrans&&(this.xiaoniuTrans=o.xiaoniuTrans)}catch(t){console.warn(t.message)}}};var M=require("glob");var oe=x(require("vue/compiler-sfc"),1),z=x(require("@babel/parser"),1),re=x(require("@babel/traverse"),1),Q=x(require("@babel/generator"),1),ie=x(require("dayjs"),1);var te=async(s,e,t="zh-Hans",o="en")=>{if(!e||!e.endpoint||!e.key||!e.location)return"";T&&console.log("try translate:",s,t,o);let r=await(await fetch(`${e.endpoint}/translate?api-version=3.0&from=${t}&to=${o}`,{method:"POST",headers:{"Ocp-Apim-Subscription-Key":e.key,"Ocp-Apim-Subscription-Region":e.location,"Content-type":"application/json","X-ClientTraceId":Date.now().toString()},body:JSON.stringify([{text:s}])})).json();try{let n=r[0].translations.find(a=>a.to.includes(o)).text;return console.log("\u5FAE\u8F6F\u7FFB\u8BD1\u7ED3\u679C:",n),n}catch(n){throw console.log("\u5FAE\u8F6F\u7FFB\u8BD1\u9519\u8BEF"),console.log(JSON.stringify(r)),n}},ne=async(s,e,t="zh",o="en")=>{if(!e||!e.appId||!e.apiKey)return"";T&&console.log("try translate:",s,t,o);let i=Math.floor(Date.now()/1e3),r=[`apikey=${e.apiKey}`,`appId=${e.appId}`,`from=${t}`,`to=${o}`,`srcText=${s}`,`timestamp=${i}`].sort((c,l)=>c.localeCompare(l)).join("&"),n=j(r),d=(await(await fetch("https://api.niutrans.com/v2/text/translate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({from:t,to:o,appId:e.appId,srcText:s,timestamp:i,authStr:n})})).json()).tgtText;return console.log("\u5C0F\u725B\u7FFB\u8BD1\u7ED3\u679C:",d),d};var se=s=>s&&s.type==="StringLiteral",xe=s=>s&&s.type==="TemplateLiteral",ye=s=>s&&s.type==="ObjectExpression",Te=/^\d{14}_[0-9a-zA-Z]{6}/,w=()=>`${(0,ie.default)().format("YYYYMMDDHHmmss")}_${Math.random().toString(16).slice(4,10)}`,$=s=>{let e="",t=s,o=s.match(Te);return o&&(e=o[0],t=s.slice(e.length+1)),{msgId:e,originalTranlationText:t}},S=s=>/[\u4e00-\u9fa5]/.test(s),D=(s,e,t)=>{t(s,e),s.content&&s.content.type&&t(s.content,s),s.exp&&s.exp.type&&t(s.exp,s),s.value&&s.value.type&&t(s.value,s),s.props&&s.props.length&&s.props.forEach(o=>{D(o,s,t)}),s.children&&s.children.length&&s.children.forEach(o=>{D(o,s,t)}),s.properties&&s.properties.length&&s.properties.forEach(o=>{D(o,s,t)})},O=class{config;fileItems=[];changedFiles=[];constructor(){this.config=new R}async start(e){await Z(),T&&G("-------------------------\u5F00\u53D1\u73AF\u5883\u8FD0\u884C\u4E2D------------------------"),console.log(y.default.green("start:"),"start extract files in dir: "+this.config.srcCodeDir+"..."),this.fileItems=this.scanFiles(),this.changedFiles=this.fileItems.filter(t=>t.isFileNeedExtract),console.log(),console.log(`-------------------------- ${y.default.green(`${this.changedFiles.length}`)} files changed------------------`),console.log(),this.processFiles(),await this.saveTranslationData(),this.changedFiles.forEach(t=>{t.newFileContent!==t.fileContent&&!T&&(t.md5=j(t.newFileContent),J.default.writeFileSync(t.fileName,t.newFileContent))}),this.saveFileCacheInfo(),this.generateResultFile()}scanFiles(){let e=this.getFileCacheInfo(),t=M.glob.sync(this.config.srcCodeDir+"/**/*.vue"),o=M.glob.sync(this.config.srcCodeDir+"/**/*.ts"),i=M.glob.sync(this.config.srcCodeDir+"/**/*.js"),r=t.concat(o).concat(i);return r.sort(),r.map(a=>{let g=J.default.readFileSync(a,"utf-8"),d=j(g),c=e[a]!==d||T;return{fileId:"",fileName:a,fileComment:"",fileContent:g,newFileContent:c?g:"",md5:d,translations:[],isFileNeedExtract:c}})}async processFiles(){for(let e=0;e<this.changedFiles.length;e++){let t=this.changedFiles[e];if(console.log(y.default.green("process: ------------------"),y.default.white(`${t.fileName}`),y.default.green(" ------------------")),!!t.fileContent&&(t.fileName.endsWith(".ts")||t.fileName.endsWith(".js")?this.processTsFile(t):t.fileName.endsWith(".vue")?this.processVueFile(t):t.fileName.endsWith(".html"),t.newFileContent!==t.fileContent&&console.log(y.default.yellow("file content transformed: "),t.fileName),(t.newFileContent!==t.fileContent||T)&&(t.md5=j(t.fileContent),T))){let o=I.default.join(this.config.outputDir,t.fileName);L(o),J.default.writeFileSync(o,t.newFileContent)}}}getCodeAst(e){try{return z.default.parse(e,{attachComment:!1,sourceType:"module",plugins:["typescript"]})}catch(t){throw t}}getAstCommentByKey(e,t){if(!e||!e.comments||!e.comments.length)return"";let o=e.comments.find(i=>i.value.includes(t));return o?o.value.replace(t,"").trim():""}dealJSON(e){e.ast=z.default.parseExpression(e.sourceCode,{attachComment:!1,sourceType:"module",plugins:["typescript"]});let t={fileId:"",fileComment:"",newContent:e.sourceCode,translations:[]},o=0;return D(e.ast,null,(i,r)=>{if(i.type==="ObjectProperty"&&i.value&&i.value.type==="StringLiteral"){let n=i.value,a=n.value;if(S(a)){let{msgId:g,originalTranlationText:d}=$(a);if(!g){g=w();let c=n.extra.raw[0]==="'",l=g+"_"+d,f=c?`$t('${l}')`:`$t("${l}")`;t.newContent=t.newContent.substring(0,n.start+o)+f+t.newContent.substring(n.end+o),o+=f.length-(n.end-n.start),t.translations.push({msgId:g,sourceText:d})}}}if(i.type==="CallExpression"&&this.config.translateFunctionName.includes(i.callee.name)){let n=i.arguments[0],a=n.value;if(S(a)){let{msgId:g,originalTranlationText:d}=$(a);if(!g){g=w();let c=n.extra.raw[0]==="'",l=g+"_"+d,f=c?`'${l}'`:`"${l}"`;t.newContent=t.newContent.substring(0,n.start+o)+f+t.newContent.substring(n.end+o),o+=f.length-(n.end-n.start)}t.translations.push({msgId:g,sourceText:d})}}}),t}dealJsCode(e){if(e.replaceTranslateFunctionName=e.replaceTranslateFunctionName||"t",!e.sourceCode)throw new Error("sourceCode is required");if(!e.fileName)throw new Error("fileName is required");if(!e.ast)try{e.ast=this.getCodeAst(e.sourceCode)}catch{return this.dealJSON(e)}let t={fileId:"",fileComment:"",newContent:e.sourceCode,translations:[]};e.needFileId&&(t.fileId=this.getAstCommentByKey(e.ast,"@i18n-file-id:")),e.needFileComment&&(t.fileComment=this.getAstCommentByKey(e.ast,"@i18n-file-comment:"));let{sourceCode:o}=e,i=0;return T&&console.log("\u5F00\u59CB\u89E3\u6790js\u4EE3\u7801*******************************"),re.default.default(e.ast,{enter(r){},TemplateLiteral(r){if(r.parent.type==="VariableDeclarator"){let n=r.node.expressions.concat(r.node.quasis);n.sort((d,c)=>d.start-c.start);let a=[],g=n.map(d=>{if(d.type==="TemplateElement")return d.value.raw;if(d.type==="Identifier")return a.push(d.name),"{"+(a.length-1)+"}"}).join("");if(S(g)){let d=w(),c=g,l=`${e.replaceTranslateFunctionName}('${g}', [${a.join(", ")}])`;t.newContent=t.newContent.substring(0,r.node.start+i)+l+t.newContent.substring(r.node.end+i),i+=l.length-(r.node.end-r.node.start),t.translations.push({msgId:d,sourceText:c})}}},StringLiteral(r){let n=r.node.value;if(S(n)){if(r.container.type==="ObjectProperty"&&r.node.value){let{msgId:a,originalTranlationText:g}=$(n);if(!a){a=w();let d=r.node.extra.raw[0]==="'",c=a+"_"+g,l=d?`${e.replaceTranslateFunctionName}('${c}')`:`${e.replaceTranslateFunctionName}("${c}")`;t.newContent=t.newContent.substring(0,r.node.start+i)+l+t.newContent.substring(r.node.end+i),i+=l.length-r.node.extra.raw.length}t.translations.push({msgId:a,sourceText:g})}if(r.container.type==="VariableDeclarator"){let a=w(),g=n,d=r.node.extra.raw[0]==="'",c=a+"_"+g,l=d?`${e.replaceTranslateFunctionName}('${c}')`:`${e.replaceTranslateFunctionName}("${c}")`;t.newContent=t.newContent.substring(0,r.node.start+i)+l+t.newContent.substring(r.node.end+i),i+=l.length-r.node.extra.raw.length,t.translations.push({msgId:a,sourceText:g})}if(r.container.type==="ExpressionStatement"){let a=w(),g=n,d=a+"_"+g,c=`${e.replaceTranslateFunctionName}('${d}')`;t.newContent=t.newContent.substring(0,r.node.start+i)+c+t.newContent.substring(r.node.end+i),i+=c.length-r.node.extra.raw.length,t.translations.push({msgId:a,sourceText:g})}}},CallExpression:r=>{var d,c,l,f;let n=r.node,a=this.config.translateFunctionName.includes(n.callee.name),g=o.substring(n.start,n.end);if(a){let m=n.arguments[0];if(se(m)){let u=m.value,{msgId:p,originalTranlationText:h}=$(u);if(!p){p=w();let C=m.extra.raw[0]==="'";m.value=p+"_"+h,m.extra.raw=C?`'${m.value}'`:`"${m.value}"`,m.extra.rawValue=m.value;let v=Q.default.default(n,{}).code;t.newContent=t.newContent.substring(0,n.start+i)+v+t.newContent.substring(n.end+i),i+=v.length-g.length}t.translations.push({msgId:p,sourceText:h})}else if(xe(n.arguments[0])){let u=o.substring(n.arguments[0].start,n.arguments[0].end);console.log(y.default.bgRed("\u4E0D\u652F\u6301\u6A21\u677F\u5B57\u7B26\u4E32: "),y.default.blue(e.fileName),": ",y.default.yellow(u),', please use placeholder string instead like: "hello {0}"')}else if(ye(n.arguments[0])){let u=n.arguments[0],p=(c=(d=u.properties.find(C=>C.key.name==="t"))==null?void 0:d.value)==null?void 0:c.value,h=((f=(l=u.properties.find(C=>C.key.name==="id"))==null?void 0:l.value)==null?void 0:f.value)||p;t.translations.push({msgId:h,sourceText:p})}else console.log(y.default.red("no handler for this translation function call: ")),console.log(g)}else{let m=!1;n.arguments.forEach((u,p)=>{var h;if(se(u)){let C=u.value;if(S(C)){if(((h=n.callee.object)==null?void 0:h.name)==="console")return;let{msgId:N,originalTranlationText:_}=$(C);if(!N){N=w();let le=u.extra.raw[0]==="'";u.value=N+"_"+_,u.extra.raw=le?`'${u.value}'`:`"${u.value}"`,u.extra.rawValue=u.value;let q=`t(${Q.default.default(u,{}).code})`;t.newContent=t.newContent.substring(0,u.start+i)+q+t.newContent.substring(u.end+i),i+=q.length-(u.end-u.start)}m=!0,t.translations.push({msgId:N,sourceText:_})}}})}}}),t}dealVueTemplate(e,t,o,i){let r={newContent:t,translations:[]};return D(e,null,(n,a)=>{var d,c;let g=["MemberExpression"];if((d=n.ast)!=null&&d.type&&g.includes(n.ast.type)){T&&console.log("\u5FFD\u7565AST",n.ast.type);return}if(n.type!==1){if(n.type===2){if(a&&a.type!==1)return;let l=[],f=n.loc.start.offset,m=/((\n|\r|\r\n)\s*){1,}/,u=n.loc.source,p=u.match(m);for(;p;){let h=u.slice(0,p.index);h&&(l.push({text:h,start:f,end:f+h.length}),f+=h.length,u=u.slice(h.length)),p[0]&&(f+=p[0].length,u=u.slice(p[0].length)),p=u.match(m)}u&&l.push({text:u,start:f,end:f+u.length}),l.forEach(h=>{if(S(h.text)){let C=w(),v=h.text,_=`{{ $t('${C+"_"+v}') }}`;i(h.start,h.end,_),r.translations.push({msgId:C,sourceText:v})}})}else if(n.type===4){if(!n.ast)return;if(n.ast.type==="StringLiteral"&&S(n.ast.value)){let l=n.ast.value,{msgId:f,originalTranlationText:m}=$(l);if(!f){f=w();let u=n.content[0]==="'",p=f+"_"+m,h=u?`$t('${p}')`:`$t("${p}")`;i(n.loc.start.offset,n.loc.end.offset,h)}r.translations.push({msgId:f,sourceText:m})}else{let l=this.dealJsCode({ast:null,sourceCode:n.content,fileName:o,needFileId:!1,needFileComment:!1,replaceTranslateFunctionName:"$t"});if(!l)return;(l==null?void 0:l.newContent)!==n.content&&i(n.loc.start.offset,n.loc.end.offset,l.newContent),r.translations.push(...l.translations)}}else if(n.type===6&&S((c=n.value)==null?void 0:c.content)){let l=w(),f=n.value.content,m=l+"_"+f,p=n.value.loc.source[0]==="'"?`:${n.name}='$t("${m}")'`:`:${n.name}="$t('${m}')"`;i(n.loc.start.offset,n.loc.end.offset,p),r.translations.push({msgId:l,sourceText:f})}}}),r}processTsFile(e){let t=e.fileContent,o=this.dealJsCode({ast:null,sourceCode:t,fileName:e.fileName,needFileId:!0,needFileComment:!0,replaceTranslateFunctionName:"t"});o&&(o.fileId||(console.warn(y.default.yellow("No i18n file id\uFF1A "),"The file "+y.default.red(`"${e.fileName}"`)+' has no i18n-file-id in line comment, please add it as first line like: "// @i18n-file-id: your-file-id"'),o.fileId=I.default.basename(e.fileName)),e.fileId=o.fileId,e.translations=o.translations,e.newFileContent=o.newContent)}processVueFile(e){let t=e.fileContent,o=oe.parse(t,{filename:e.fileName}),i="",r="",n=0,a=o.descriptor.source,g=[];[o.descriptor.script,o.descriptor.scriptSetup,o.descriptor.template].filter(c=>c).sort((c,l)=>c.loc.start.offset-l.loc.start.offset).forEach(c=>{if(c.type==="script"){let l=this.dealJsCode({ast:null,sourceCode:c.content,fileName:e.fileName,needFileId:!0,needFileComment:!0,replaceTranslateFunctionName:"t"});if(!l)return;a=a.substring(0,c.loc.start.offset+n)+l.newContent+a.substring(c.loc.end.offset+n),n+=l.newContent.length-c.content.length,!i&&l.fileId&&(i=l.fileId),!r&&l.fileComment&&(r=l.fileComment),g.push(...l.translations)}else if(c.type==="template"){let l=c.ast.source;console.log("\u5F00\u59CB\u5904\u7406template\u6587\u672C\u63D0\u53D6");let f=this.dealVueTemplate(c.ast,l,e.fileName,(m,u,p)=>{a=a.substring(0,m+n)+p+a.substring(u+n),n+=p.length-(u-m)});g.push(...f.translations)}}),i||(console.warn(y.default.yellow("No File Id\uFF1A "),"The file "+y.default.red(`"${e.fileName}"`)+' has no i18n file id in line comment, please add it as first line like: "// @i18n-file-id: your-file-id"'),i=I.default.basename(e.fileName)),e.fileId=i,e.fileComment=r,e.translations=g,e.newFileContent=a}getFileCacheInfo(){let e=this.config.outputDir,t=I.default.join(e,".file_md5.json");return b(t),E(t,JSON.stringify({}))}saveFileCacheInfo(){let e=this.fileItems.reduce((i,r)=>(i[r.fileName]=r.md5,i),{}),t=this.config.outputDir,o=I.default.join(t,".file_md5.json");b(o),P(o,K(e))}getOldTranslationTextItems(){let e=this.config.outputDir,t=I.default.join(e,".translation_items.json");return b(t),E(t,[])}async saveAllTranslationTextItems(e){let t=this.config.outputDir,o=I.default.join(t,".translation_items.json"),i=new Map;if(e.forEach(r=>{i.set(r.sourceText,r.targetText)}),!T)for(let r=0;r<e.length;r++){let n=e[r];try{if(!n.targetText){let a=i.get(n.sourceText);a&&(n.targetText=a)}n.targetText||(n.targetText=await ne(n.sourceText,this.config.xiaoniuTrans)),n.targetText||(n.targetText=await te(n.sourceText,this.config.microsoftInfo))}catch{}}b(o),P(o,e)}generateResultFile(){let e=this.getOldTranslationTextItems(),t={};e.forEach(n=>{n.isExistInFile&&(t[n.msgId+"_"+n.sourceText]=n.sourceText)});let o=this.config.outputDir,i=I.default.join(o,".translation_map.json");b(i);let r=K(t);P(i,r)}async saveTranslationData(){let e=this.getOldTranslationTextItems(),t=e.reduce((r,n)=>(r[n.msgId]=n,r),{}),o=[],i={};this.changedFiles.forEach(r=>{r.translations.forEach(n=>{if(i[n.msgId]=i[n.msgId]||0,i[n.msgId]++,i[n.msgId]>1&&console.log(y.default.bgRed("Repeat Text"),r.fileName,n,"\u8BF7\u4FEE\u6539msgId"),t[n.msgId]){let a=t[n.msgId];a.sourceText!==n.sourceText&&(a.targetText=""),a.fileComment=r.fileComment,a.sourceText=n.sourceText,a.msgId=n.msgId,a.isExistInFile=!0}else o.push({fileId:r.fileId,filePath:"",fileComment:r.fileComment,sourceText:n.sourceText,msgId:n.msgId,isExistInFile:!0,targetText:""})})}),await this.saveAllTranslationTextItems(e.concat(o))}};var V=class{run(){let e=new ae.Command;e.name("i18n-text-tools").version("1.0.0"),e.command("extract").option("-a, --all","extract all files").description("scan the src dir and extract translations").action(()=>{new O().start()}),e.command("upload").description("upload extracted files to yicat").action(()=>{}),e.command("apply").description("apply latest messages to i18n locales dir").action(()=>{}),e.command("fetch").description("fetch translations from yicat").action(()=>{}),e.parse()}};0&&(module.exports={CustomCommand});