@lobehub/i18n-cli
Version:
Lobe i18n is a CLI tool that automate translate your i18n localization with AI
20 lines (16 loc) • 27.4 kB
JavaScript
;var Se=Object.defineProperty;var a=(n,e)=>Se(n,"name",{value:e,configurable:!0});var h=require("react/jsx-runtime"),g=require("@lobehub/cli-ui"),q=require("commander"),ve=require("update-notifier"),xe=require("conf"),be=require("cosmiconfig"),j=require("react"),f=require("chalk"),Te=require("dotenv"),S=require("lodash-es"),m=require("consola"),x=require("node:path"),A=require("ink"),H=require("p-map"),Z=require("gpt-tokenizer"),ee=require("remark-frontmatter"),te=require("remark-gfm"),Me=require("remark-parse"),Oe=require("remark-stringify"),ne=require("unified"),oe=require("unist-util-visit"),Ce=require("just-diff"),je=require("json-stable-stringify"),M=require("node:fs"),Fe=require("dirty-json"),Le=require("openai"),re=require("glob"),Ne=require("node:process"),se=require("gray-matter");function Ie(n){var e=Object.create(null);return n&&Object.keys(n).forEach(function(t){if(t!=="default"){var o=Object.getOwnPropertyDescriptor(n,t);Object.defineProperty(e,t,o.get?o:{enumerable:!0,get:a(function(){return n[t]},"get")})}}),e.default=n,Object.freeze(e)}a(Ie,"_interopNamespaceDefault");var Ae=Ie(Ne),Ee="@lobehub/i18n-cli",$e="1.22.1",Pe="Lobe i18n is a CLI tool that automate translate your i18n localization with AI",qe=["ai","i18n","openai","gpt"],Je="https://github.comlobehub/lobe-cli-toolbox/tree/master/packages/lobe-i18n",Re={url:"https://github.com/lobehub/lobe-cli-toolbox/issues/new"},Be={type:"git",url:"https://github.com/lobehub/lobe-cli-toolbox.git"},De="MIT",Ue="LobeHub <i@lobehub.com>",_e=!1,ze="module",Ke={"@":"./src"},Ve={require:{types:"./dist/index.d.cts",default:"./dist/index.cjs"},import:{types:"./dist/index.d.mts",default:"./dist/index.mjs"}},Ge="./dist/index.cjs",Qe="./dist/index.mjs",We="./dist/index.d.cts",Ye={"lobe-i18n":"dist/cli.js"},Xe=["dist"],He={build:"npm run type-check && pkgroll --minify -p tsconfig.prod.json --env.NODE_ENV=production && npm run shebang",dev:"pkgroll -p tsconfig.prod.json --env.NODE_ENV=development --watch",link:"npm run build && npm link -f",prepack:"clean-package",postpack:"clean-package restore",shebang:"lobe-shebang -t ./dist/cli.js",start:"node ./dist/cli.js",test:"vitest --passWithNoTests","test:coverage":"vitest run --coverage --passWithNoTests","type-check":"tsc --noEmit"},Ze={"@lobehub/cli-ui":"1.12.0",chalk:"^5.4.1",commander:"^13.0.0",conf:"^13.1.0",consola:"^3.3.3",cosmiconfig:"^9.0.0","dirty-json":"^0.9.2",dotenv:"^16.4.7","fast-deep-equal":"^3.1.3",glob:"^10.4.5","gpt-tokenizer":"^2.8.1","gray-matter":"^4.0.3",ink:"^6.0.0","json-stable-stringify":"^1.2.1","just-diff":"^6.0.2","lodash-es":"^4.17.21",openai:"^4.103.0","p-map":"^7.0.3",pangu:"^4.0.7",react:"^19.0.0","remark-frontmatter":"^5.0.0","remark-gfm":"^4.0.0","remark-parse":"^11.0.0","remark-stringify":"^11.0.0",swr:"^2.3.0",unified:"^11.0.5","unist-util-visit":"^5.0.0","update-notifier":"^7.3.1",zustand:"^5.0.3"},et={"@types/json-stable-stringify":"^1.1.0","@types/lodash-es":"^4.17.12","@types/node":"^22.10.5","@types/unist":"^3.0.3","clean-package":"^2.2.0"},tt="pnpm@10.10.0",nt={node:">=18"},ot={access:"public",registry:"https://registry.npmjs.org/"},D={name:Ee,version:$e,description:Pe,keywords:qe,homepage:Je,bugs:Re,repository:Be,license:De,author:Ue,sideEffects:_e,type:ze,imports:Ke,exports:Ve,main:Ge,module:Qe,types:We,bin:Ye,files:Xe,scripts:He,dependencies:Ze,devDependencies:et,packageManager:tt,engines:nt,publishConfig:ot},$=(n=>(n.MDAST="mdast",n.STRING="string",n))($||{});const U=a(n=>`.${n}.md`,"getDefaultExtension"),rt={o3:2e5,"o4-mini":2e5,"gpt-4.1":1047576,"gpt-4.1-mini":1047576,"gpt-4.1-nano":1047576,"o3-mini":2e5,"o1-mini":128e3,o1:2e5,"o1-preview":128e3,"gpt-4.5-preview":128e3,"gpt-4o-mini":128e3,"gpt-4o-2024-11-20":128e3,"gpt-4o":128e3,"gpt-4o-2024-05-13":128e3,"chatgpt-4o-latest":128e3,"gpt-4-turbo":128e3,"gpt-4-turbo-2024-04-09":128e3,"gpt-4-turbo-preview":128e3,"gpt-4-0125-preview":128e3,"gpt-4-1106-preview":128e3,"gpt-4":8192,"gpt-4-0613":8192,"gpt-4-32k":32768,"gpt-3.5-turbo":16384,"gpt-3.5-turbo-0125":16384,"gpt-3.5-turbo-1106":16384},ie="o4-mini",st={concurrency:5,markdown:{entry:[],mode:$.STRING,outputExtensions:U},modelName:ie,temperature:0},L=a((n,e)=>{n[e]||g.alert.error(`Can't find ${f.bold.yellow("outputLocales")} in config`)},"checkOptionKeys"),ae={apiBaseUrl:{default:"",type:"string"},openaiToken:{default:"",type:"string"}},_=new xe({projectName:"lobe-i18n",schema:ae});class it{static{a(this,"ExplorerConfig")}explorer;customConfig;constructor(){this.explorer=be.cosmiconfigSync("i18n")}loadCustomConfig(e){this.customConfig=e}getConfigFile(){return this.customConfig?this.explorer.load(this.customConfig)?.config:this.explorer.search()?.config}}const z=new it;Te.config();const K=a(n=>_.get(n),"getConfig"),at=a(n=>ae[n].default,"getDefulatConfig"),ct=a((n,e)=>_.set(n,e),"setConfig"),lt=a(()=>process.env.OPENAI_API_KEY||K("openaiToken"),"getOpenAIApiKey"),ut=a(()=>process.env.OPENAI_PROXY_URL||K("apiBaseUrl"),"getOpenAIProxyUrl"),V=a(()=>{const n=z.getConfigFile();return n?S.merge(st,n):g.alert.error(`Can't find ${f.bold.yellow("config")}`,!0)},"getConfigFile"),ft=a(()=>{const n=V();return L(n,"entry"),L(n,"entryLocale"),L(n,"output"),L(n,"outputLocales"),n},"getLocaleConfig"),pt=a(()=>{const n=V();if(!n.markdown)return g.alert.error(`Can't find ${f.bold.yellow("config.markdown")}`,!0);const e=S.merge(n?.markdown||{},{entryLocale:n?.markdown?.entryLocale||n.entryLocale,outputLocales:n?.markdown?.outputLocales||n.outputLocales});return L(e,"entry"),L(e,"entryLocale"),L(e,"outputLocales"),e},"getMarkdownConfigFile");var O={getConfig:K,getConfigFile:V,getDefulatConfig:at,getLocaleConfig:ft,getMarkdownConfigFile:pt,getOpenAIApiKey:lt,getOpenAIProxyUrl:ut,setConfig:ct};const gt=a(()=>{const n=_.store;return{get:O.getConfig,getDefault:O.getDefulatConfig,set:O.setConfig,store:n}},"useConfStore"),dt=j.memo(()=>{const[n,e]=j.useState(),{store:t,set:o,getDefault:r}=gt(),i=a((c,u)=>{o(c,u),e("")},"setConfig"),s=j.useMemo(()=>[{children:h.jsx(g.TextInput,{defaultValue:t.openaiToken,onSubmit:a(c=>i("openaiToken",c),"onSubmit"),placeholder:"Input OpenAI token..."}),defaultValue:r("openaiToken"),key:"openaiToken",label:"OpenAI token",showValue:!1,value:t.openaiToken},{children:h.jsx(g.TextInput,{defaultValue:t.apiBaseUrl,onSubmit:a(c=>i("apiBaseUrl",c),"onSubmit"),placeholder:"Set openAI API proxy, default value: https://api.openai.com/v1/..."}),defaultValue:r("apiBaseUrl"),desc:"OpenAI API proxy, default value: https://api.openai.com/v1/",key:"apiBaseUrl",label:"OpenAI API proxy",showValue:!1,value:t.apiBaseUrl}],[t]);return h.jsx(g.ConfigPanel,{active:n,items:s,logo:"\u{1F92F}",setActive:e,title:"Lobe I18N Config"})}),J=j.memo(({hide:n,filename:e,to:t,from:o,progress:r,maxStep:i,step:s,isLoading:c,needToken:u})=>{const l=g.useTheme(),[p,v]=j.useState(0),b=j.useRef(0),y=j.useRef(null);return j.useEffect(()=>(b.current=r,y.current&&clearInterval(y.current),y.current=setInterval(()=>{v(I=>{const T=b.current,d=T-I;if(Math.abs(d)<1)return y.current&&(clearInterval(y.current),y.current=null),T;const w=Math.sign(d)*Math.max(.2,Math.min(1,Math.abs(d)*.05));return I+w})},300),()=>{y.current&&(clearInterval(y.current),y.current=null)}),[r]),n?null:h.jsxs(g.SplitView,{flexDirection:"column",children:[h.jsx(A.Text,{backgroundColor:l.colorBgLayout,color:l.colorText,children:` \u{1F4DD} ${e} `}),h.jsxs(A.Text,{color:l.colorTextDescription,children:["- from ",h.jsx(A.Text,{bold:!0,color:l.colorInfo,children:o})," to ",h.jsx(A.Text,{bold:!0,color:l.colorInfo,children:t}),h.jsx(A.Text,{color:l.colorTextDescription,children:` [Tokens: ${u}]`})]}),c?h.jsxs(A.Box,{children:[h.jsx(g.Spinner,{label:` ${Math.round(p)}% [${s}/${i} chunks] `}),h.jsx(g.ProgressBar,{value:Math.round(p)})]}):h.jsx(g.StatusMessage,{variant:"success",children:"Success"})]})}),ht=3,R=2,ce=2,F=a(n=>Z.encode(n).length,"calcToken"),P=a(n=>Z.encode(String(n)).length,"calcEncodedKeyToken"),le=a(n=>P(n)+ht,"calcPrimitiveValueToken"),ue=a((n,e=0)=>S.sumBy(Object.entries(n),([t,o])=>P(t)+R+(S.isPlainObject(o)?ue(o,e+1):le(o)))+ce+e,"calcJsonToken"),mt=new Set([`
`,`\r
`,"[!NOTE]","[!IMPORTANT]","[!WARNING]","[!CAUTION]","\\[!NOTE]","\\[!IMPORTANT]","\\[!WARNING]","\\[!CAUTION]","\\[!NOTE]\\","\\[!IMPORTANT]\\","\\[!WARNING]\\","\\[!CAUTION]\\"]),yt=a(n=>mt.has(n)?!0:/^[\s\p{P}\p{S}\u{1F300}-\u{1F5FF}\u{1F600}-\u{1F64F}\u{1F680}-\u{1F6FF}\u{1F700}-\u{1F77F}]*$/u.test(n.replaceAll(" ","")),"checkMdString"),fe=a(async n=>ne.unified().use(Me).use(te).use(ee).parse(n.trim()),"convertMarkdownToMdast"),kt=a((n,e)=>{const t={};let o=0;return oe.visit(n,e||"text",r=>{t[o]=r.value,o++}),t},"convertMdastToMdastObj"),wt=a(n=>{const e={};for(const[t,o]of Object.entries(n))yt(o)||(e[Number(t)]=o);return e},"pickMdastObj"),St=a(({mdast:n,entry:e,target:t},o)=>{const r={...e,...t};let i=0;return oe.visit(n,o||"text",s=>{s.value=r[i],i++}),n},"mergeMdastObj"),G=a(async n=>ne.unified().use(Oe,{bullet:"-",emphasis:"*",fences:!0,rule:"-",strong:"*",tightDefinitions:!0}).use(ee).use(te).stringify(n).toString(),"convertMdastToMarkdown"),Q=a((n,e)=>{const t=Ce.diff(e,n),o=t.filter(c=>c.op==="add"),r=t.filter(c=>c.op==="remove"),i=S.cloneDeep(e),s={};for(const c of r)S.unset(i,c.path);for(const c of o)S.set(s,c.path,c.value);return{add:o,entry:s,remove:r,target:i}},"diff"),vt=a((n,e)=>S.reduce(Object.entries(n),(t,[o,r])=>{let[i,s]=t.pop()||[{},ce];const c=S.isPlainObject(r)?ue(r,1):le(r);return s+P(o)+R+c<=e?(i[o]=r,s+=P(o)+R+c,t.push([i,s])):t.push([i,s],[{[o]:r},P(o)+R+c]),t},[]).map(([t])=>t),"splitJSONtoSmallChunks"),pe=a((n,e)=>{let t=(rt[n.modelName||ie]-F(e))/3;return n.splitToken&&n.splitToken<t&&(t=n.splitToken),t=Math.floor(t),t},"getSplitToken"),xt=a((n,e,t,o)=>{const r=Q(e,t).entry,i=pe(n,o);return vt(r,i)},"splitJsonToChunks");class X{static{a(this,"RecursiveCharacterTextSplitter")}chunkSize;chunkOverlap;lengthFunction;separators;constructor(e){this.chunkSize=e.chunkSize,this.chunkOverlap=e.chunkOverlap,this.lengthFunction=e.lengthFunction||(t=>t.length),this.separators=[`
`,`
`," ",""]}static fromLanguage(e,t){const o=new X(t);return e==="markdown"&&(o.separators=[`
## `,`
### `,`
#### `,`
##### `,`
###### `,`
`,`
`," ",""]),o}async splitText(e){return this.splitTextRecursively(e,this.separators)}splitTextRecursively(e,t){const o=[];let r=t.at(-1)||"",i=[];for(let l=0;l<t.length;l++){const p=t[l];if(p){if(p===""){r=p;break}if(e.includes(p)){r=p,i=t.slice(l+1);break}}}const s=this.splitTextWithSeparator(e,r);let c=[];const u=r===""?"":r;for(const l of s)if(this.lengthFunction(l)<this.chunkSize)c.push(l);else{if(c.length>0){const p=this.mergeSplits(c,u);o.push(...p),c=[]}if(i.length===0)o.push(l);else{const p=this.splitTextRecursively(l,i);o.push(...p)}}if(c.length>0){const l=this.mergeSplits(c,u);o.push(...l)}return o}splitTextWithSeparator(e,t){let o;return t?o=e.split(t):o=e.split(""),o.filter(r=>r!=="")}mergeSplits(e,t){const o=[],r=[];let i=0;for(const c of e){const u=this.lengthFunction(c);if(i+u+(r.length>0?this.lengthFunction(t):0)>this.chunkSize&&(i>this.chunkSize&&console.warn(`Created a chunk of size ${i}, which is longer than the specified ${this.chunkSize}`),r.length>0)){const l=this.joinDocs(r,t);for(l!==null&&o.push(l);i>this.chunkOverlap||i+u+(r.length>0?this.lengthFunction(t):0)>this.chunkSize&&i>0;){const p=r[0];if(p)i-=this.lengthFunction(p)+(r.length>1?this.lengthFunction(t):0),r.shift();else break}}r.push(c),i+=u+(r.length>1?this.lengthFunction(t):0)}const s=this.joinDocs(r,t);return s!==null&&o.push(s),o}joinDocs(e,t){const o=e.join(t).trim();return o===""?null:o}}let bt=class{static{a(this,"TranslateMarkdown")}mdast;entry={};config;check;definition;constructor(e){this.config=e,this.check=["text","yaml",e?.markdown?.translateCode&&"code"].filter(Boolean)}async genTarget(e){return this.mdast=await fe(e),this.entry=kt(this.mdast,this.check),wt(this.entry)}async genMarkdownByMdast(e){if(!e)return;const t=St({entry:this.entry,mdast:this.mdast,target:e},this.check);return G(t)}async clearMarkdownString(e){const t=[],o=await fe(e);return o.children=o.children.map(r=>r.type==="definition"?(t.push(r),!1):r).filter(Boolean),{content:await G(o),definition:await G({children:t,type:"root"})}}async genSplitMarkdown(e,t){this.definition="";const{content:o,definition:r}=await this.clearMarkdownString(e);return this.definition=r,await X.fromLanguage("markdown",{chunkOverlap:0,chunkSize:pe(this.config,t),lengthFunction:a(s=>F(s),"lengthFunction")}).splitText(o)}async genMarkdownByString(e){return[...e,this.definition].join(`
`)}};const W=a(n=>{const e=M.readFileSync(n,"utf8");return JSON.parse(e)},"readJSON"),N=a((n,e)=>{const t=je(e,{space:" "});M.writeFileSync(n,t+`\r
`,"utf8")},"writeJSON"),Tt=a(n=>M.readFileSync(n,"utf8"),"readMarkdown"),ge=a((n,e)=>{M.writeFileSync(n,e,"utf8")},"writeMarkdown"),Mt=a(n=>{let e={};for(const t of n)e=S.merge(e,t);return e},"mergeJsonFromChunks");class B{static{a(this,"ChatPromptTemplate")}messages;constructor(e){this.messages=e}static fromMessages(e){const t=e.map(([o,r])=>({content:r,role:o}));return new B(t)}async formatMessages(e){return this.messages.map(t=>({content:this.formatString(t.content,e),role:t.role}))}formatString(e,t){let o=e;for(const[r,i]of Object.entries(t))if(i!==void 0){const s=new RegExp(`\\{${r}\\}`,"g");o=o.replace(s,i)}return o}}const de="You can adjust the tone and style, taking into account the cultural connotations and regional differences of certain words. As a translator, you need to translate the original text into a translation that meets the standards of accuracy and elegance.",Ot=a((n=de)=>B.fromMessages([["system",["Translate the i18n JSON file from {from} to {to} according to the BCP 47 standard",`Here are some reference to help with better translation. ---${n}---`,"Keep the keys the same as the original file and make sure the output remains a valid i18n JSON file.","Do not include any additional text or explanations outside the JSON object.Start directly with a left brace and end with a right brace."].filter(Boolean).join(`
`)],["user","{json}"]]),"promptJsonTranslate"),Ct=a((n=de)=>B.fromMessages([["system",["Translate the markdown file from {from} to {to} according to the BCP 47 standard",`Here are some reference to help with better translation. ---${n}---`,"Make sure the output remains a valid markdown file."].filter(Boolean).join(`
`)],["user","{text}"]]),"promptStringTranslate");let jt=class{static{a(this,"TranslateLocale")}client;config;isJsonMode;promptJson;promptString;constructor(e,t,o){this.config=e,this.client=new Le({apiKey:t,baseURL:o,maxRetries:4}),this.promptJson=Ot(e.reference),this.promptString=Ct(e.reference),this.isJsonMode=!!this.config?.experimental?.jsonMode}async runByString({from:e,to:t,text:o}){try{const r=await this.promptString.formatMessages({from:e||this.config.entryLocale,text:o,to:t}),s=(await this.client.chat.completions.create({messages:r,model:this.config.modelName||"gpt-3.5-turbo",temperature:this.config.temperature,top_p:this.config.topP})).choices[0]?.message?.content;return s||this.handleError(),s}catch(r){this.handleError(r)}}async runByJson({from:e,to:t,json:o}){try{const r=await this.promptJson.formatMessages({from:e||this.config.entryLocale,json:JSON.stringify(o),to:t}),s=(await this.client.chat.completions.create({messages:r,model:this.config.modelName||"gpt-3.5-turbo",temperature:this.config.temperature,top_p:this.config.topP,...this.isJsonMode&&{response_format:{type:"json_object"}}})).choices[0]?.message?.content;s||this.handleError();try{return JSON.parse(s)}catch{g.alert.warn("parse fail, try to use dirty json");try{return Fe.parse(s)}catch{g.alert.error("i18n dirty json fail"),g.alert.error(s,!0)}}}catch(r){this.handleError(r)}}handleError(e){g.alert.error(`Translate failed, ${e||"please check your network or try again..."}`,!0)}};class he{static{a(this,"I18n")}config;maxStep=1;translateLocaleService;translateMarkdownService;constructor({openAIApiKey:e,openAIProxyUrl:t,config:o}){this.config=o,this.translateLocaleService=new jt(o,e,t),this.translateMarkdownService=new bt(o)}async translateMarkdown(e){return e.mode===$.STRING?this.translateMarkdownByString(e):this.translateMarkdownByMdast(e)}async translateMarkdownByString({md:e,to:t,onProgress:o,from:r,filename:i,onChunkComplete:s}){const c=await this.translateLocaleService.promptString.formatMessages({from:r||this.config.entryLocale,text:"",to:t}),u=await this.translateMarkdownService.genSplitMarkdown(e,JSON.stringify(c));if(this.maxStep=u.length,u.length===0)return;const l=u.length*F(JSON.stringify(c))+F(JSON.stringify(u)),p=Array.from({length:this.maxStep},()=>0);let v="";const b=a(()=>{const T=p.filter(k=>k===2).length,d=p.filter(k=>k===1).length;let w=0;if(T===this.maxStep)w=100;else if(d>0){const k=T/this.maxStep*100,C=(T+1)/this.maxStep*100;w=Math.floor(Math.max(k,C-1))}else w=Math.floor(T/this.maxStep*100);o?.({isLoading:T<this.maxStep,maxStep:this.maxStep,needToken:l,progress:w,step:T})},"updateProgress");o?.({isLoading:!0,maxStep:this.maxStep,needToken:l,progress:0,step:0});const y=await H(u,async(T,d)=>{p[d]=1,b(),setTimeout(()=>{b()},800);const w=await this.translateLocaleService.runByString({from:r||this.config.entryLocale,text:T,to:t});if(p[d]=2,this.config.saveImmediately&&i){const k=[...y];k[d]=w,(d===0||p.slice(0,d).every(C=>C===2))&&(v=await this.translateMarkdownService.genMarkdownByString(k.filter(Boolean)),ge(i,v),s?.(w,v))}return b(),w},{concurrency:this.config?.concurrency});return o?.({isLoading:!1,maxStep:this.maxStep,needToken:l,progress:100,step:this.maxStep}),{result:i?v:await this.translateMarkdownService.genMarkdownByString(y),tokenUsage:l+F(JSON.stringify(y))}}async translateMarkdownByMdast({md:e,...t}){const o=await this.translateMarkdownService.genTarget(e),{matter:r,mode:i,onChunkComplete:s,...c}=t,u=await this.translate({...c,entry:o,target:{}});if(!u?.result)return;const l=await this.translateMarkdownService.genMarkdownByMdast(u);if(l)return{result:l,tokenUsage:u.tokenUsage}}async translate({entry:e,target:t,to:o,onProgress:r,from:i,filename:s,onChunkComplete:c}){const u=await this.translateLocaleService.promptJson.formatMessages({from:i||this.config.entryLocale,json:JSON.stringify({}),to:o}),l=xt(this.config,e,t,JSON.stringify(u));if(this.maxStep=l.length,l.length===0)return;const p=l.length*F(JSON.stringify(u))+F(JSON.stringify(l)),v=Array.from({length:this.maxStep},()=>0);let b=S.merge({},t);const y=a(()=>{const d=v.filter(C=>C===2).length,w=v.filter(C=>C===1).length;let k=0;if(d===this.maxStep)k=100;else if(w>0){const C=d/this.maxStep*100,we=(d+1)/this.maxStep*100;k=Math.floor(Math.max(C,we-1))}else k=Math.floor(d/this.maxStep*100);r?.({isLoading:d<this.maxStep,maxStep:this.maxStep,needToken:p,progress:k,step:d})},"updateProgress");r?.({isLoading:!0,maxStep:this.maxStep,needToken:p,progress:0,step:0});const I=await H(l,async(d,w)=>{v[w]=1,y(),setTimeout(()=>{y()},800);const k=await this.translateLocaleService.runByJson({from:i||this.config.entryLocale,json:d,to:o});return v[w]=2,this.config.saveImmediately&&s&&(b=S.merge(b,k),N(s,b),c?.(k,b)),y(),k},{concurrency:this.config?.concurrency});return r?.({isLoading:!1,maxStep:this.maxStep,needToken:p,progress:100,step:this.maxStep}),{result:s?b:await S.merge(t,Mt(I)),tokenUsage:p+F(JSON.stringify(I))}}}const Ft=a(n=>{for(const e of n.outputLocales){const t=x.resolve(n.output,`${e}.json`);M.existsSync(t)||N(t,{})}},"checkLocales"),Lt=a((n,e)=>{for(const t of n.outputLocales){const o=x.resolve(n.output,t);M.existsSync(o)||M.mkdirSync(o)}for(const t of n.outputLocales)for(const o of e){const r=x.resolve(n.output,t,o);try{const i=x.dirname(r);M.mkdirSync(i,{recursive:!0})}catch{}M.existsSync(r)||N(r,{})}},"checkLocaleFolders"),Nt=a(n=>{try{const e=x.resolve("./",n.entry);return M.existsSync(e)||g.alert.error(`Can't find ${f.bold.yellow(n.entry)} in dir`,!0),W(e)}catch{Ae.exit(1)}},"getEntryFile"),It=a(n=>{const e=n.entry.replaceAll("*","").replaceAll("*.json",""),t=re.globSync(x.join(e,"**/*.json").replaceAll("\\","/"),{nodir:!0}),o={};for(const r of t)o[x.relative(e,r)]=W(r);if(Object.keys(o).length===0){g.alert.error(`Can't find .json files in ${f.bold.yellow(e)}`,!0);return}return o},"getEntryFolderFiles"),me=a(n=>{const e=W(n);return e||(N(n,{}),{})},"getLocaleObj"),Y=a((n,e)=>{try{if(n===e)return!0;if(typeof n!=typeof e)return!1;if(typeof n=="object"&&typeof e=="object"){const t=Object.keys(n),o=Object.keys(e);if(t.length!==o.length)return!1;for(const r of t)if(!o.includes(r)||!Y(n[r],e[r]))return!1}return typeof n==typeof e}catch{return!1}},"isEqualJsonKeys");class At{static{a(this,"TranslateLocale")}config;query=[];i18n;constructor(){this.config=O.getLocaleConfig(),this.i18n=new he({config:this.config,openAIApiKey:O.getOpenAIApiKey(),openAIProxyUrl:O.getOpenAIProxyUrl()})}async start(){m.consola.start("Lobe I18N is analyzing your project... \u{1F92F}\u{1F30F}\u{1F50D}"),!this.config.entry.includes(".json")||this.config.entry.includes("*")?this.genFolderQuery():this.genFlatQuery(),this.query.length>0?await this.runQuery():m.consola.success("No content requiring translation was found."),m.consola.success("All i18n tasks have been completed\uFF01")}async runQuery(){m.consola.info(`Current model setting: ${f.cyan(this.config.modelName)} (temperature: ${f.cyan(this.config.temperature)}) ${this.config.experimental?.jsonMode?f.red(" [JSON Mode]"):""}}`);let e=0;for(const t of this.query){const o={filename:t.filename,from:t.from||this.config.entryLocale,to:t.to},{rerender:r,clear:i}=g.render(h.jsx(J,{hide:!0,isLoading:!0,maxStep:1,progress:0,step:0,...o})),s=await this.i18n.translate({...t,filename:t.filename,onChunkComplete:a(()=>{},"onChunkComplete"),onProgress:a(u=>{u.maxStep>0?r(h.jsx(J,{...u,...o})):i()},"onProgress")});i();const c=x.relative(".",t.filename);s?.result&&Object.keys(s.result).length>0?(this.config.saveImmediately||N(t.filename,s.result),e+=s.tokenUsage,m.consola.success(f.yellow(c),f.gray(`[Token usage: ${s.tokenUsage}]`))):m.consola.warn("No translation result was found:",f.yellow(c))}e>0&&m.consola.info("Total token usage:",f.cyan(e))}genFolderQuery(){const e=this.config,t=It(e),o=Object.keys(t);m.consola.info(`Running in ${f.bold.cyan("\u{1F4C2} Folder Mode")} and has found ${f.bold.cyan(o.length)} files.`),Lt(e,o);for(const r of e.outputLocales)for(const[i,s]of o.entries()){process.stdout.isTTY&&(process.stdout.clearLine(0),process.stdout.cursorTo(0)),process.stdout.write(`${f.cyan(r)}${f.gray(`[${i+1}/${o.length}] - `)}${f.yellow(s)}`);const c=x.resolve(e.output,r,s),u=t[s],l=Q(u,me(c)).target;N(c,l),!Y(u,l)&&this.query.push({entry:u,filename:c,from:e.entryLocale,target:l,to:r})}process.stdout.isTTY&&(process.stdout.clearLine(0),process.stdout.cursorTo(0))}genFlatQuery(){const e=this.config,t=Nt(e);m.consola.start(`Running in ${f.bold.cyan("\u{1F4C4} Flat Mode")}, and translating ${f.bold.cyan(e.outputLocales.join("/"))} locales..`),Ft(e);for(const o of e.outputLocales){const r=x.resolve(e.output,o)+".json",i=t,s=Q(i,me(r)).target;N(r,s),!Y(i,s)&&this.query.push({entry:i,filename:r,from:e.entryLocale,target:s,to:o})}}}const ye=a((n,e)=>n.map(t=>t.includes("*")||t.includes(e)?t:x.join(t,`**/*${e}`).replaceAll("\\","/")),"matchInputPattern");class ke{static{a(this,"TranslateMarkdown")}config;markdownConfig;query=[];i18n;constructor(){this.markdownConfig=O.getMarkdownConfigFile();const e=O.getConfigFile();this.config={...e,entryLocale:e.entryLocale||this.markdownConfig.entryLocale,markdown:this.markdownConfig,outputLocales:e.outputLocales||this.markdownConfig.outputLocales},this.i18n=new he({config:this.config,openAIApiKey:O.getOpenAIApiKey(),openAIProxyUrl:O.getOpenAIProxyUrl()})}async start(){m.consola.start("Lobe I18N is analyzing your markdown... \u{1F92F}\u{1F30F}\u{1F50D}");const e=this.markdownConfig.entry;(!e||e.length===0)&&g.alert.error("No markdown entry was found.",!0);let t=re.globSync(ye(e,".md"),{ignore:this.markdownConfig.exclude?ye(this.markdownConfig.exclude||[],".md"):void 0,nodir:!0});this.markdownConfig.entryExtension&&(t=t.filter(o=>o.includes(this.markdownConfig.entryExtension||".md"))),(!t||t.length===0)&&g.alert.error("No markdown entry was found.",!0),this.genFilesQuery(t),this.query.length>0?await this.runQuery():m.consola.success("No content requiring translation was found."),m.consola.success("All i18n tasks have been completed\uFF01")}async runQuery(){m.consola.info(`Current model setting: ${f.cyan(this.config.modelName)} (temperature: ${f.cyan(this.config.temperature)}) ${this.config.experimental?.jsonMode?f.red(" [JSON Mode]"):""}}`);let e=0;for(const t of this.query){const o={filename:t.filename,from:t.from||this.markdownConfig.entryLocale||this.config.entryLocale,to:t.to},{rerender:r,clear:i}=g.render(h.jsx(J,{hide:!0,isLoading:!0,maxStep:1,progress:0,step:0,...o})),s=await this.i18n.translateMarkdown({...t,onProgress:a(u=>{u.maxStep>0?r(h.jsx(J,{...u,...o})):i()},"onProgress")});i();const c=x.relative(".",t.filename);if(s?.result&&Object.keys(s.result).length>0){let u=s.result;this.markdownConfig.includeMatter||(u=se.stringify(s.result,t.matter)),this.config.saveImmediately||ge(t.filename,u),e+=s.tokenUsage,m.consola.success(f.yellow(c),f.gray(`[Token usage: ${s.tokenUsage}]`))}else m.consola.warn("No translation result was found:",f.yellow(c))}e>0&&m.consola.info("Total token usage:",f.cyan(e))}genFilesQuery(e,t){const o=this.markdownConfig;t||m.consola.start(`Running in ${f.bold.cyan(`\u{1F4C4} ${e.length} Markdown`)}, and translating to ${f.bold.cyan(o?.outputLocales?.join("/"))} locales..`);for(const r of e)try{const i=Tt(r);for(const s of o.outputLocales||[]){const c=this.getTargetExtension(s,r,i),u=this.getTargetFilename(r,c);if(M.existsSync(u))continue;const l=this.getMode(r,i),{data:p,content:v}=se(i);m.consola.info(`\u{1F4C4} To ${s}: ${f.yellow(u)}`),this.query.push({filename:u,from:o.entryLocale,matter:p,md:this.markdownConfig.includeMatter?i:v,mode:l,to:s})}}catch{g.alert.error(`${r} not found`,!0)}}getTargetExtension(e,t,o){return this.markdownConfig.outputExtensions?.(e,{fileContent:o,filePath:t,getDefaultExtension:U})||U(e)}getTargetFilename(e,t){if(this.markdownConfig.entryExtension)return x.resolve(".",e.replace(this.markdownConfig.entryExtension||".md",t));if(this.markdownConfig.entryLocale&&e.includes(`.${this.markdownConfig.entryLocale}.`))return[e.split(`.${this.markdownConfig.entryLocale}.`)[0],t].join("");{const o=e.split(".");return o.pop(),[o.join("."),t].join("")}}getMode(e,t){const o=this.markdownConfig.mode;return o?S.isString(o)?o:o({fileContent:t,filePath:e})||$.STRING:$.STRING}}const Et=ve({pkg:D,shouldNotifyInNpmScript:!0});Et.notify({isGlobal:!0});const E=new q.Command;E.name("lobe-i18n").description(D.description).version(D.version).addOption(new q.Option("-o, --option","Setup lobe-i18n preferences")).addOption(new q.Option("-c, --config <string>","Specify the configuration file")).addOption(new q.Option("-m, --with-md","Run i18n translation and markdown translation simultaneously")),E.command("locale",{isDefault:!0}).action(async()=>{const n=E.opts();n.option?g.render(h.jsx(dt,{})):(n.config&&z.loadCustomConfig(n.config),await new At().start(),n.withMd&&await new ke().start())}),E.command("md").action(async()=>{const n=E.opts();n.config&&z.loadCustomConfig(n.config),await new ke().start()}),E.parse();