locale-wizard
Version:
Automatic JSON localization files translator
9 lines (7 loc) • 7.5 kB
JavaScript
var z=Object.defineProperty;var G=(t,e,s)=>e in t?z(t,e,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[e]=s;var m=(t,e,s)=>G(t,typeof e!="symbol"?e+"":e,s);import I from"node:path";import*as L from"node:process";import J from"openai";var p="\x1B[0m",i={log:t=>console.log("\x1B[37m"+t+p),green:t=>console.log("\x1B[32m"+t+p),red:t=>console.log("\x1B[31m"+t+p),blue:t=>console.log("\x1B[34m"+t+p),yellow:t=>console.log("\x1B[33m"+t+p)};import*as d from"node:process";var P={en:"English",ru:"Russian",es:"Spanish",zh:"Chinese",it:"Italian",ar:"Arabic",de:"German",fr:"French",pt:"Portuguese",hi:"Hindi",ja:"Japanese",ko:"Korean",tr:"Turkish",nl:"Dutch",sv:"Swedish",da:"Danish",no:"Norwegian",fi:"Finnish",pl:"Polish",cs:"Czech",sr:"Serbian",bg:"Bulgarian",hr:"Croatian",el:"Greek",he:"Hebrew",hu:"Hungarian",id:"Indonesian",ms:"Malay",ro:"Romanian",sk:"Slovak",sl:"Slovenian",th:"Thai",vi:"Vietnamese",fa:"Persian",ur:"Urdu",bn:"Bengali",ta:"Tamil",te:"Telugu",ml:"Malayalam",kn:"Kannada",mr:"Marathi",gu:"Gujarati",ka:"Georgian",az:"Azerbaijani",be:"Belarusian",hy:"Armenian",et:"Estonian",lv:"Latvian",lt:"Lithuanian",af:"Afrikaans",sq:"Albanian",am:"Amharic",eu:"Basque",my:"Burmese",ca:"Catalan",km:"Khmer",ky:"Kyrgyz",lo:"Lao",mk:"Macedonian",mn:"Mongolian",ne:"Nepali",pa:"Punjabi",si:"Sinhala",tg:"Tajik",tk:"Turkmen",uz:"Uzbek",cy:"Welsh",yi:"Yiddish",zu:"Zulu",sw:"Swahili",so:"Somali",ha:"Hausa",ig:"Igbo",yo:"Yoruba",gl:"Galician",is:"Icelandic",lb:"Luxembourgish",mt:"Maltese",ps:"Pashto"};var x=t=>`You will be given a JSON file in the format {[key]: [value]}
Translate all values to ${P[t]} language and return the completed JSON with translated values
Try to understand the context for more accurate translation.
Make all the efforts to understand correct plural forms of words(keep in mind that some languages have multiple plural forms (like in Russian: "one window", "two windows", "five windows" becomes "\u043E\u0434\u043D\u043E \u043E\u043A\u041D\u041E", "\u0434\u0432\u0430 \u043E\u043A\u041D\u0410", "\u043F\u044F\u0442\u044C \u043E\u043A\u041E\u041D"))
Most important: Return ONLY the JSON in your response. No extra words or explanations
`;var b=(t,e)=>{let s=Object.keys(t).sort(),n=Object.keys(e).sort();return JSON.stringify(s)===JSON.stringify(n)};var $=t=>new Promise(e=>setTimeout(e,t));function M(t,e=50){let s=Object.entries(t);if(s.length<=e)return[t];let n=Math.ceil(s.length/e),r=[];for(let o=0;o<n;o++){let a=o*e,l=s.slice(a,a+e);r.push(Object.fromEntries(l))}return r}async function K(t,e){if(!this.openai)return i.red("openai is not initialized"),d.exit();let s=M(t),n={},r=0;if(Object.keys(t).length===0)return n;i.blue(`Translating "${e}" locale`);for(let o of s){let a=await S.call(this,o,e);n={...n,...a.translated},r+=a.cost||0}return i.green(`Locale "${e}" successfully translated!!! Cost: ${r} tokens
`),n}async function S(t,e,s){this.openai||(i.red("openai is not initialized"),d.exit());let n=this.customPrompt?this.customPrompt(e):x(e);try{let r=await this.openai.chat.completions.create({messages:[{role:"system",content:n},{role:"user",content:JSON.stringify(t)}],model:this.chatGptModel,response_format:{type:"json_object"}}),o=r.choices[0].message.content;if(typeof o!="string")throw new TypeError("Invalid ChatGPT response");let a=r.usage?.total_tokens,l=JSON.parse(o);if(b(t,l))return await $(500),{translated:l,cost:a};if(s)throw i.red(`Failed to retry translation "${e}" locale.`),d.exit(),new Error(`Failed to translate "${e}" locale`);{i.yellow(`Failed to translate "${e}" locale. Retrying...`);let g=await S.call(this,t,e,!0);return i.green(`Successfully retried translation of "${e}" locale!`),g}}catch(r){throw r}}function F(t,e){let s=e.split(".");function n(r,o){if(o>=s.length)return;let a=s[o];o===s.length-1?delete r[a]:r[a]&&n(r[a],o+1),Object.keys(r[a]||{}).length===0&&delete r[a]}return n(t,0),t}import f from"node:fs";import O from"node:path";var y=(t,e,s)=>{try{return f.readdirSync(`${t}/${e}`).filter(n=>!s.includes(n.split(".json")[0]))}catch{return[]}},h=t=>{try{let e=f.readFileSync(t,"utf8");return JSON.parse(e)}catch{return{}}},N=(t,e)=>{try{let s=O.resolve(t),n=O.dirname(s);f.existsSync(n)||f.mkdirSync(n,{recursive:!0});let r=JSON.stringify(e,null,2);return f.writeFileSync(s,r,"utf8"),!0}catch(s){return i.red(`Error writing file ${t}: ${s.message}`),i.red(`Stack: ${s.stack}`),!1}};var R=t=>{let e={};console.log("flaten",t);for(let s in t){let n=s.split(".");if(n.length===1)e[s]=t[s];else{let r=n[0],o=n[1];if(/^\d+$/.test(o)){e[r]||(e[r]=[]);let l=Number.parseInt(o,10);e[r][l]=t[s]}else e[r]||(e[r]={}),o in e[r]||(e[r][o]=t[s])}}return console.log("nested",e),e};function w(t,e=""){let s=[];for(let n in t){let r=e?`${e}.${n}`:n;typeof t[n]=="object"&&t[n]!==null&&!Array.isArray(t[n])?s=[...s,...w(t[n],r)]:s.push(r)}return s.map(n=>n.replace(":.",":"))}var A=(t,e)=>{let s=e.split("."),n=t;for(let r of s){if(n==null)return;n=n[r]}return n};function k(t,e,s){let n=y(t,e,s),r={};for(let o of n){let a=`${t}/${e}/${o}`,l=o.split(".json")[0],c=h(a),g=w(c);for(let u of g){let B=A(c,u);r[l+":"+u]=B||""}}return r}var T=t=>{let e={};for(let[s,n]of Object.entries(t)){let[r,o]=s.split(":");e[r]={...e[r],[o]:n}}return e};function v(t){let e=[],s=[],n=k(this.localesPath,t,this.ignoreNamespaces);for(let r of Object.keys(n))typeof this.allMainLocaleKeysValuePairs[r]=="string"||Array.isArray(this.allMainLocaleKeysValuePairs[r])||s.push(r);for(let r of Object.keys(this.allMainLocaleKeysValuePairs))typeof n[r]=="string"||Array.isArray(n[r])||e.push(r);if(i.blue(`Locale "${t}":`),e.length>0)for(let r of e)i.red(`Missing key: "${r}"`);else i.green("No missing keys \u2705 ");if(s.length>0)for(let r of s)i.red(`Extra key: "${r}"`);else i.green("No extra keys \u2705 ");this.targetLocalesMeta[t]={missingKeys:e,extraKeys:s}}var j=class{constructor(e){this.config=e;m(this,"openai",null);m(this,"chatGptModel","gpt-4o");m(this,"targetLocales",[]);m(this,"localesPath","");m(this,"ignoreNamespaces",[]);m(this,"customPrompt",null);m(this,"allMainLocaleKeysValuePairs",{});m(this,"targetLocalesMeta",{});m(this,"mainLocaleFiles",[]);this.targetLocales=e.targetLocales,this.localesPath=I.resolve(L.cwd(),e.localesPath),this.ignoreNamespaces=e.ignoreNamespaces||[];let s=y(e.localesPath,e.sourceLocale,this.ignoreNamespaces);s.length===0&&(i.red(`No locale files for source locale "${e.sourceLocale}" found at "${this.localesPath}/${e.sourceLocale}"`),L.exit()),this.mainLocaleFiles=s,this.allMainLocaleKeysValuePairs=k(this.localesPath,e.sourceLocale,this.ignoreNamespaces);for(let n of e.targetLocales)v.call(this,n);e.openAiKey&&(this.openai=new J({apiKey:e.openAiKey})),e.chatGptModel&&(this.chatGptModel=e.chatGptModel),e.customPrompt&&(this.customPrompt=e.customPrompt)}async translate(){for(let[e,{missingKeys:s}]of Object.entries(this.targetLocalesMeta)){let n=e,r=s.reduce((l,c)=>({...l,[c]:this.allMainLocaleKeysValuePairs[c]}),{}),o=await K.call(this,r,n),a=T(o);for(let[l,c]of Object.entries(a)){if(this.ignoreNamespaces.includes(l))continue;let g=`${this.localesPath}/${n}/${l}.json`,u=h(g),B=R({...u,...c});N(g,B)}}}async removeExtraKeys(){for(let[e,{extraKeys:s}]of Object.entries(this.targetLocalesMeta)){let n=y(this.localesPath,e,this.ignoreNamespaces);for(let r of n){let o=`${this.localesPath}/${e}/${r}`,a=h(o),l={};if(s.length>0){for(let c of s){let[g,u]=c.split(":");`${g}.json`===r&&(l=F(a,u),i.log(`"${e}/${r}" extra key "${u}" removed`))}N(o,l)}}}}};export{j as LocaleWizard};