@lobehub/i18n-cli
Version:
Lobe i18n is a CLI tool that automate translate your i18n localization with AI
20 lines (16 loc) • 26.9 kB
JavaScript
var At=Object.defineProperty;var a=(n,t)=>At(n,"name",{value:t,configurable:!0});var Et=(n,t)=>()=>(t||n((t={exports:{}}).exports,t),t.exports);import{jsx as k,jsxs as B}from"react/jsx-runtime";import{alert as b,TextInput as nt,ConfigPanel as $t,useTheme as Pt,SplitView as Jt,Spinner as Rt,ProgressBar as Bt,StatusMessage as Ut,render as U}from"@lobehub/cli-ui";import{Command as Dt,Option as D}from"commander";import _t from"update-notifier";import zt from"conf";import{cosmiconfigSync as Kt}from"cosmiconfig";import{memo as ot,useState as rt,useMemo as qt,useRef as st,useEffect as Gt}from"react";import f from"chalk";import Vt from"dotenv";import{merge as N,sumBy as Qt,isPlainObject as it,cloneDeep as Wt,unset as Yt,set as Xt,reduce as Ht,isString as Zt}from"lodash-es";import{consola as h}from"consola";import{resolve as C,dirname as te,join as at,relative as _}from"node:path";import{Text as I,Box as ee}from"ink";import ct from"p-map";import{encode as lt}from"gpt-tokenizer";import ut from"remark-frontmatter";import ft from"remark-gfm";import ne from"remark-parse";import oe from"remark-stringify";import{unified as pt}from"unified";import{visit as gt}from"unist-util-visit";import{diff as re}from"just-diff";import se from"json-stable-stringify";import{writeFileSync as ht,readFileSync as mt,existsSync as A,mkdirSync as dt}from"node:fs";import ie from"dirty-json";import ae from"openai";import{globSync as yt}from"glob";import*as ce from"node:process";import kt from"gray-matter";var ln=Et((Z,tt)=>{var le="@lobehub/i18n-cli",ue="1.22.1",fe="Lobe i18n is a CLI tool that automate translate your i18n localization with AI",pe=["ai","i18n","openai","gpt"],ge="https://github.comlobehub/lobe-cli-toolbox/tree/master/packages/lobe-i18n",he={url:"https://github.com/lobehub/lobe-cli-toolbox/issues/new"},me={type:"git",url:"https://github.com/lobehub/lobe-cli-toolbox.git"},de="MIT",ye="LobeHub <i@lobehub.com>",ke=!1,we="module",Se={"@":"./src"},Z={require:{types:"./dist/index.d.cts",default:"./dist/index.cjs"},import:{types:"./dist/index.d.mts",default:"./dist/index.mjs"}},xe="./dist/index.cjs",tt="./dist/index.mjs",be="./dist/index.d.cts",ve={"lobe-i18n":"dist/cli.js"},Te=["dist"],Me={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"},Ce={"@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"},Oe={"@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"},Fe="pnpm@10.10.0",Le={node:">=18"},Ne={access:"public",registry:"https://registry.npmjs.org/"},z={name:le,version:ue,description:fe,keywords:pe,homepage:ge,bugs:he,repository:me,license:de,author:ye,sideEffects:ke,type:we,imports:Se,exports:Z,main:xe,module:tt,types:be,bin:ve,files:Te,scripts:Me,dependencies:Ce,devDependencies:Oe,packageManager:Fe,engines:Le,publishConfig:Ne},E=(n=>(n.MDAST="mdast",n.STRING="string",n))(E||{});const K=a(n=>`.${n}.md`,"getDefaultExtension"),je={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},wt="o4-mini",Ie={concurrency:5,markdown:{entry:[],mode:E.STRING,outputExtensions:K},modelName:wt,temperature:0},O=a((n,t)=>{n[t]||b.error(`Can't find ${f.bold.yellow("outputLocales")} in config`)},"checkOptionKeys"),St={apiBaseUrl:{default:"",type:"string"},openaiToken:{default:"",type:"string"}},q=new zt({projectName:"lobe-i18n",schema:St});class Ae{static{a(this,"ExplorerConfig")}explorer;customConfig;constructor(){this.explorer=Kt("i18n")}loadCustomConfig(t){this.customConfig=t}getConfigFile(){return this.customConfig?this.explorer.load(this.customConfig)?.config:this.explorer.search()?.config}}const G=new Ae;Vt.config();const V=a(n=>q.get(n),"getConfig"),Ee=a(n=>St[n].default,"getDefulatConfig"),$e=a((n,t)=>q.set(n,t),"setConfig"),Pe=a(()=>process.env.OPENAI_API_KEY||V("openaiToken"),"getOpenAIApiKey"),Je=a(()=>process.env.OPENAI_PROXY_URL||V("apiBaseUrl"),"getOpenAIProxyUrl"),Q=a(()=>{const n=G.getConfigFile();return n?N(Ie,n):b.error(`Can't find ${f.bold.yellow("config")}`,!0)},"getConfigFile"),Re=a(()=>{const n=Q();return O(n,"entry"),O(n,"entryLocale"),O(n,"output"),O(n,"outputLocales"),n},"getLocaleConfig"),Be=a(()=>{const n=Q();if(!n.markdown)return b.error(`Can't find ${f.bold.yellow("config.markdown")}`,!0);const t=N(n?.markdown||{},{entryLocale:n?.markdown?.entryLocale||n.entryLocale,outputLocales:n?.markdown?.outputLocales||n.outputLocales});return O(t,"entry"),O(t,"entryLocale"),O(t,"outputLocales"),t},"getMarkdownConfigFile");var v={getConfig:V,getConfigFile:Q,getDefulatConfig:Ee,getLocaleConfig:Re,getMarkdownConfigFile:Be,getOpenAIApiKey:Pe,getOpenAIProxyUrl:Je,setConfig:$e};const Ue=a(()=>{const n=q.store;return{get:v.getConfig,getDefault:v.getDefulatConfig,set:v.setConfig,store:n}},"useConfStore"),De=ot(()=>{const[n,t]=rt(),{store:e,set:o,getDefault:r}=Ue(),i=a((c,u)=>{o(c,u),t("")},"setConfig"),s=qt(()=>[{children:k(nt,{defaultValue:e.openaiToken,onSubmit:a(c=>i("openaiToken",c),"onSubmit"),placeholder:"Input OpenAI token..."}),defaultValue:r("openaiToken"),key:"openaiToken",label:"OpenAI token",showValue:!1,value:e.openaiToken},{children:k(nt,{defaultValue:e.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:e.apiBaseUrl}],[e]);return k($t,{active:n,items:s,logo:"\u{1F92F}",setActive:t,title:"Lobe I18N Config"})}),P=ot(({hide:n,filename:t,to:e,from:o,progress:r,maxStep:i,step:s,isLoading:c,needToken:u})=>{const l=Pt(),[p,w]=rt(0),S=st(0),m=st(null);return Gt(()=>(S.current=r,m.current&&clearInterval(m.current),m.current=setInterval(()=>{w(L=>{const x=S.current,g=x-L;if(Math.abs(g)<1)return m.current&&(clearInterval(m.current),m.current=null),x;const y=Math.sign(g)*Math.max(.2,Math.min(1,Math.abs(g)*.05));return L+y})},300),()=>{m.current&&(clearInterval(m.current),m.current=null)}),[r]),n?null:B(Jt,{flexDirection:"column",children:[k(I,{backgroundColor:l.colorBgLayout,color:l.colorText,children:` \u{1F4DD} ${t} `}),B(I,{color:l.colorTextDescription,children:["- from ",k(I,{bold:!0,color:l.colorInfo,children:o})," to ",k(I,{bold:!0,color:l.colorInfo,children:e}),k(I,{color:l.colorTextDescription,children:` [Tokens: ${u}]`})]}),c?B(ee,{children:[k(Rt,{label:` ${Math.round(p)}% [${s}/${i} chunks] `}),k(Bt,{value:Math.round(p)})]}):k(Ut,{variant:"success",children:"Success"})]})}),_e=3,J=2,xt=2,M=a(n=>lt(n).length,"calcToken"),$=a(n=>lt(String(n)).length,"calcEncodedKeyToken"),bt=a(n=>$(n)+_e,"calcPrimitiveValueToken"),vt=a((n,t=0)=>Qt(Object.entries(n),([e,o])=>$(e)+J+(it(o)?vt(o,t+1):bt(o)))+xt+t,"calcJsonToken"),ze=new Set([`
`,`\r
`,"[!NOTE]","[!IMPORTANT]","[!WARNING]","[!CAUTION]","\\[!NOTE]","\\[!IMPORTANT]","\\[!WARNING]","\\[!CAUTION]","\\[!NOTE]\\","\\[!IMPORTANT]\\","\\[!WARNING]\\","\\[!CAUTION]\\"]),Ke=a(n=>ze.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"),Tt=a(async n=>pt().use(ne).use(ft).use(ut).parse(n.trim()),"convertMarkdownToMdast"),qe=a((n,t)=>{const e={};let o=0;return gt(n,t||"text",r=>{e[o]=r.value,o++}),e},"convertMdastToMdastObj"),Ge=a(n=>{const t={};for(const[e,o]of Object.entries(n))Ke(o)||(t[Number(e)]=o);return t},"pickMdastObj"),Ve=a(({mdast:n,entry:t,target:e},o)=>{const r={...t,...e};let i=0;return gt(n,o||"text",s=>{s.value=r[i],i++}),n},"mergeMdastObj"),W=a(async n=>pt().use(oe,{bullet:"-",emphasis:"*",fences:!0,rule:"-",strong:"*",tightDefinitions:!0}).use(ut).use(ft).stringify(n).toString(),"convertMdastToMarkdown"),Y=a((n,t)=>{const e=re(t,n),o=e.filter(c=>c.op==="add"),r=e.filter(c=>c.op==="remove"),i=Wt(t),s={};for(const c of r)Yt(i,c.path);for(const c of o)Xt(s,c.path,c.value);return{add:o,entry:s,remove:r,target:i}},"diff"),Qe=a((n,t)=>Ht(Object.entries(n),(e,[o,r])=>{let[i,s]=e.pop()||[{},xt];const c=it(r)?vt(r,1):bt(r);return s+$(o)+J+c<=t?(i[o]=r,s+=$(o)+J+c,e.push([i,s])):e.push([i,s],[{[o]:r},$(o)+J+c]),e},[]).map(([e])=>e),"splitJSONtoSmallChunks"),Mt=a((n,t)=>{let e=(je[n.modelName||wt]-M(t))/3;return n.splitToken&&n.splitToken<e&&(e=n.splitToken),e=Math.floor(e),e},"getSplitToken"),We=a((n,t,e,o)=>{const r=Y(t,e).entry,i=Mt(n,o);return Qe(r,i)},"splitJsonToChunks");class et{static{a(this,"RecursiveCharacterTextSplitter")}chunkSize;chunkOverlap;lengthFunction;separators;constructor(t){this.chunkSize=t.chunkSize,this.chunkOverlap=t.chunkOverlap,this.lengthFunction=t.lengthFunction||(e=>e.length),this.separators=[`
`,`
`," ",""]}static fromLanguage(t,e){const o=new et(e);return t==="markdown"&&(o.separators=[`
## `,`
### `,`
#### `,`
##### `,`
###### `,`
`,`
`," ",""]),o}async splitText(t){return this.splitTextRecursively(t,this.separators)}splitTextRecursively(t,e){const o=[];let r=e.at(-1)||"",i=[];for(let l=0;l<e.length;l++){const p=e[l];if(p){if(p===""){r=p;break}if(t.includes(p)){r=p,i=e.slice(l+1);break}}}const s=this.splitTextWithSeparator(t,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(t,e){let o;return e?o=t.split(e):o=t.split(""),o.filter(r=>r!=="")}mergeSplits(t,e){const o=[],r=[];let i=0;for(const c of t){const u=this.lengthFunction(c);if(i+u+(r.length>0?this.lengthFunction(e):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,e);for(l!==null&&o.push(l);i>this.chunkOverlap||i+u+(r.length>0?this.lengthFunction(e):0)>this.chunkSize&&i>0;){const p=r[0];if(p)i-=this.lengthFunction(p)+(r.length>1?this.lengthFunction(e):0),r.shift();else break}}r.push(c),i+=u+(r.length>1?this.lengthFunction(e):0)}const s=this.joinDocs(r,e);return s!==null&&o.push(s),o}joinDocs(t,e){const o=t.join(e).trim();return o===""?null:o}}let Ye=class{static{a(this,"TranslateMarkdown")}mdast;entry={};config;check;definition;constructor(t){this.config=t,this.check=["text","yaml",t?.markdown?.translateCode&&"code"].filter(Boolean)}async genTarget(t){return this.mdast=await Tt(t),this.entry=qe(this.mdast,this.check),Ge(this.entry)}async genMarkdownByMdast(t){if(!t)return;const e=Ve({entry:this.entry,mdast:this.mdast,target:t},this.check);return W(e)}async clearMarkdownString(t){const e=[],o=await Tt(t);return o.children=o.children.map(r=>r.type==="definition"?(e.push(r),!1):r).filter(Boolean),{content:await W(o),definition:await W({children:e,type:"root"})}}async genSplitMarkdown(t,e){this.definition="";const{content:o,definition:r}=await this.clearMarkdownString(t);return this.definition=r,await et.fromLanguage("markdown",{chunkOverlap:0,chunkSize:Mt(this.config,e),lengthFunction:a(s=>M(s),"lengthFunction")}).splitText(o)}async genMarkdownByString(t){return[...t,this.definition].join(`
`)}};const X=a(n=>{const t=mt(n,"utf8");return JSON.parse(t)},"readJSON"),F=a((n,t)=>{const e=se(t,{space:" "});ht(n,e+`\r
`,"utf8")},"writeJSON"),Xe=a(n=>mt(n,"utf8"),"readMarkdown"),Ct=a((n,t)=>{ht(n,t,"utf8")},"writeMarkdown"),He=a(n=>{let t={};for(const e of n)t=N(t,e);return t},"mergeJsonFromChunks");class R{static{a(this,"ChatPromptTemplate")}messages;constructor(t){this.messages=t}static fromMessages(t){const e=t.map(([o,r])=>({content:r,role:o}));return new R(e)}async formatMessages(t){return this.messages.map(e=>({content:this.formatString(e.content,t),role:e.role}))}formatString(t,e){let o=t;for(const[r,i]of Object.entries(e))if(i!==void 0){const s=new RegExp(`\\{${r}\\}`,"g");o=o.replace(s,i)}return o}}const Ot="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.",Ze=a((n=Ot)=>R.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"),tn=a((n=Ot)=>R.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 en=class{static{a(this,"TranslateLocale")}client;config;isJsonMode;promptJson;promptString;constructor(t,e,o){this.config=t,this.client=new ae({apiKey:e,baseURL:o,maxRetries:4}),this.promptJson=Ze(t.reference),this.promptString=tn(t.reference),this.isJsonMode=!!this.config?.experimental?.jsonMode}async runByString({from:t,to:e,text:o}){try{const r=await this.promptString.formatMessages({from:t||this.config.entryLocale,text:o,to:e}),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:t,to:e,json:o}){try{const r=await this.promptJson.formatMessages({from:t||this.config.entryLocale,json:JSON.stringify(o),to:e}),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{b.warn("parse fail, try to use dirty json");try{return ie.parse(s)}catch{b.error("i18n dirty json fail"),b.error(s,!0)}}}catch(r){this.handleError(r)}}handleError(t){b.error(`Translate failed, ${t||"please check your network or try again..."}`,!0)}};class Ft{static{a(this,"I18n")}config;maxStep=1;translateLocaleService;translateMarkdownService;constructor({openAIApiKey:t,openAIProxyUrl:e,config:o}){this.config=o,this.translateLocaleService=new en(o,t,e),this.translateMarkdownService=new Ye(o)}async translateMarkdown(t){return t.mode===E.STRING?this.translateMarkdownByString(t):this.translateMarkdownByMdast(t)}async translateMarkdownByString({md:t,to:e,onProgress:o,from:r,filename:i,onChunkComplete:s}){const c=await this.translateLocaleService.promptString.formatMessages({from:r||this.config.entryLocale,text:"",to:e}),u=await this.translateMarkdownService.genSplitMarkdown(t,JSON.stringify(c));if(this.maxStep=u.length,u.length===0)return;const l=u.length*M(JSON.stringify(c))+M(JSON.stringify(u)),p=Array.from({length:this.maxStep},()=>0);let w="";const S=a(()=>{const x=p.filter(d=>d===2).length,g=p.filter(d=>d===1).length;let y=0;if(x===this.maxStep)y=100;else if(g>0){const d=x/this.maxStep*100,T=(x+1)/this.maxStep*100;y=Math.floor(Math.max(d,T-1))}else y=Math.floor(x/this.maxStep*100);o?.({isLoading:x<this.maxStep,maxStep:this.maxStep,needToken:l,progress:y,step:x})},"updateProgress");o?.({isLoading:!0,maxStep:this.maxStep,needToken:l,progress:0,step:0});const m=await ct(u,async(x,g)=>{p[g]=1,S(),setTimeout(()=>{S()},800);const y=await this.translateLocaleService.runByString({from:r||this.config.entryLocale,text:x,to:e});if(p[g]=2,this.config.saveImmediately&&i){const d=[...m];d[g]=y,(g===0||p.slice(0,g).every(T=>T===2))&&(w=await this.translateMarkdownService.genMarkdownByString(d.filter(Boolean)),Ct(i,w),s?.(y,w))}return S(),y},{concurrency:this.config?.concurrency});return o?.({isLoading:!1,maxStep:this.maxStep,needToken:l,progress:100,step:this.maxStep}),{result:i?w:await this.translateMarkdownService.genMarkdownByString(m),tokenUsage:l+M(JSON.stringify(m))}}async translateMarkdownByMdast({md:t,...e}){const o=await this.translateMarkdownService.genTarget(t),{matter:r,mode:i,onChunkComplete:s,...c}=e,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:t,target:e,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=We(this.config,t,e,JSON.stringify(u));if(this.maxStep=l.length,l.length===0)return;const p=l.length*M(JSON.stringify(u))+M(JSON.stringify(l)),w=Array.from({length:this.maxStep},()=>0);let S=N({},e);const m=a(()=>{const g=w.filter(T=>T===2).length,y=w.filter(T=>T===1).length;let d=0;if(g===this.maxStep)d=100;else if(y>0){const T=g/this.maxStep*100,It=(g+1)/this.maxStep*100;d=Math.floor(Math.max(T,It-1))}else d=Math.floor(g/this.maxStep*100);r?.({isLoading:g<this.maxStep,maxStep:this.maxStep,needToken:p,progress:d,step:g})},"updateProgress");r?.({isLoading:!0,maxStep:this.maxStep,needToken:p,progress:0,step:0});const L=await ct(l,async(g,y)=>{w[y]=1,m(),setTimeout(()=>{m()},800);const d=await this.translateLocaleService.runByJson({from:i||this.config.entryLocale,json:g,to:o});return w[y]=2,this.config.saveImmediately&&s&&(S=N(S,d),F(s,S),c?.(d,S)),m(),d},{concurrency:this.config?.concurrency});return r?.({isLoading:!1,maxStep:this.maxStep,needToken:p,progress:100,step:this.maxStep}),{result:s?S:await N(e,He(L)),tokenUsage:p+M(JSON.stringify(L))}}}const nn=a(n=>{for(const t of n.outputLocales){const e=C(n.output,`${t}.json`);A(e)||F(e,{})}},"checkLocales"),on=a((n,t)=>{for(const e of n.outputLocales){const o=C(n.output,e);A(o)||dt(o)}for(const e of n.outputLocales)for(const o of t){const r=C(n.output,e,o);try{const i=te(r);dt(i,{recursive:!0})}catch{}A(r)||F(r,{})}},"checkLocaleFolders"),rn=a(n=>{try{const t=C("./",n.entry);return A(t)||b.error(`Can't find ${f.bold.yellow(n.entry)} in dir`,!0),X(t)}catch{ce.exit(1)}},"getEntryFile"),sn=a(n=>{const t=n.entry.replaceAll("*","").replaceAll("*.json",""),e=yt(at(t,"**/*.json").replaceAll("\\","/"),{nodir:!0}),o={};for(const r of e)o[_(t,r)]=X(r);if(Object.keys(o).length===0){b.error(`Can't find .json files in ${f.bold.yellow(t)}`,!0);return}return o},"getEntryFolderFiles"),Lt=a(n=>{const t=X(n);return t||(F(n,{}),{})},"getLocaleObj"),H=a((n,t)=>{try{if(n===t)return!0;if(typeof n!=typeof t)return!1;if(typeof n=="object"&&typeof t=="object"){const e=Object.keys(n),o=Object.keys(t);if(e.length!==o.length)return!1;for(const r of e)if(!o.includes(r)||!H(n[r],t[r]))return!1}return typeof n==typeof t}catch{return!1}},"isEqualJsonKeys");class an{static{a(this,"TranslateLocale")}config;query=[];i18n;constructor(){this.config=v.getLocaleConfig(),this.i18n=new Ft({config:this.config,openAIApiKey:v.getOpenAIApiKey(),openAIProxyUrl:v.getOpenAIProxyUrl()})}async start(){h.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():h.success("No content requiring translation was found."),h.success("All i18n tasks have been completed\uFF01")}async runQuery(){h.info(`Current model setting: ${f.cyan(this.config.modelName)} (temperature: ${f.cyan(this.config.temperature)}) ${this.config.experimental?.jsonMode?f.red(" [JSON Mode]"):""}}`);let t=0;for(const e of this.query){const o={filename:e.filename,from:e.from||this.config.entryLocale,to:e.to},{rerender:r,clear:i}=U(k(P,{hide:!0,isLoading:!0,maxStep:1,progress:0,step:0,...o})),s=await this.i18n.translate({...e,filename:e.filename,onChunkComplete:a(()=>{},"onChunkComplete"),onProgress:a(u=>{u.maxStep>0?r(k(P,{...u,...o})):i()},"onProgress")});i();const c=_(".",e.filename);s?.result&&Object.keys(s.result).length>0?(this.config.saveImmediately||F(e.filename,s.result),t+=s.tokenUsage,h.success(f.yellow(c),f.gray(`[Token usage: ${s.tokenUsage}]`))):h.warn("No translation result was found:",f.yellow(c))}t>0&&h.info("Total token usage:",f.cyan(t))}genFolderQuery(){const t=this.config,e=sn(t),o=Object.keys(e);h.info(`Running in ${f.bold.cyan("\u{1F4C2} Folder Mode")} and has found ${f.bold.cyan(o.length)} files.`),on(t,o);for(const r of t.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=C(t.output,r,s),u=e[s],l=Y(u,Lt(c)).target;F(c,l),!H(u,l)&&this.query.push({entry:u,filename:c,from:t.entryLocale,target:l,to:r})}process.stdout.isTTY&&(process.stdout.clearLine(0),process.stdout.cursorTo(0))}genFlatQuery(){const t=this.config,e=rn(t);h.start(`Running in ${f.bold.cyan("\u{1F4C4} Flat Mode")}, and translating ${f.bold.cyan(t.outputLocales.join("/"))} locales..`),nn(t);for(const o of t.outputLocales){const r=C(t.output,o)+".json",i=e,s=Y(i,Lt(r)).target;F(r,s),!H(i,s)&&this.query.push({entry:i,filename:r,from:t.entryLocale,target:s,to:o})}}}const Nt=a((n,t)=>n.map(e=>e.includes("*")||e.includes(t)?e:at(e,`**/*${t}`).replaceAll("\\","/")),"matchInputPattern");class jt{static{a(this,"TranslateMarkdown")}config;markdownConfig;query=[];i18n;constructor(){this.markdownConfig=v.getMarkdownConfigFile();const t=v.getConfigFile();this.config={...t,entryLocale:t.entryLocale||this.markdownConfig.entryLocale,markdown:this.markdownConfig,outputLocales:t.outputLocales||this.markdownConfig.outputLocales},this.i18n=new Ft({config:this.config,openAIApiKey:v.getOpenAIApiKey(),openAIProxyUrl:v.getOpenAIProxyUrl()})}async start(){h.start("Lobe I18N is analyzing your markdown... \u{1F92F}\u{1F30F}\u{1F50D}");const t=this.markdownConfig.entry;(!t||t.length===0)&&b.error("No markdown entry was found.",!0);let e=yt(Nt(t,".md"),{ignore:this.markdownConfig.exclude?Nt(this.markdownConfig.exclude||[],".md"):void 0,nodir:!0});this.markdownConfig.entryExtension&&(e=e.filter(o=>o.includes(this.markdownConfig.entryExtension||".md"))),(!e||e.length===0)&&b.error("No markdown entry was found.",!0),this.genFilesQuery(e),this.query.length>0?await this.runQuery():h.success("No content requiring translation was found."),h.success("All i18n tasks have been completed\uFF01")}async runQuery(){h.info(`Current model setting: ${f.cyan(this.config.modelName)} (temperature: ${f.cyan(this.config.temperature)}) ${this.config.experimental?.jsonMode?f.red(" [JSON Mode]"):""}}`);let t=0;for(const e of this.query){const o={filename:e.filename,from:e.from||this.markdownConfig.entryLocale||this.config.entryLocale,to:e.to},{rerender:r,clear:i}=U(k(P,{hide:!0,isLoading:!0,maxStep:1,progress:0,step:0,...o})),s=await this.i18n.translateMarkdown({...e,onProgress:a(u=>{u.maxStep>0?r(k(P,{...u,...o})):i()},"onProgress")});i();const c=_(".",e.filename);if(s?.result&&Object.keys(s.result).length>0){let u=s.result;this.markdownConfig.includeMatter||(u=kt.stringify(s.result,e.matter)),this.config.saveImmediately||Ct(e.filename,u),t+=s.tokenUsage,h.success(f.yellow(c),f.gray(`[Token usage: ${s.tokenUsage}]`))}else h.warn("No translation result was found:",f.yellow(c))}t>0&&h.info("Total token usage:",f.cyan(t))}genFilesQuery(t,e){const o=this.markdownConfig;e||h.start(`Running in ${f.bold.cyan(`\u{1F4C4} ${t.length} Markdown`)}, and translating to ${f.bold.cyan(o?.outputLocales?.join("/"))} locales..`);for(const r of t)try{const i=Xe(r);for(const s of o.outputLocales||[]){const c=this.getTargetExtension(s,r,i),u=this.getTargetFilename(r,c);if(A(u))continue;const l=this.getMode(r,i),{data:p,content:w}=kt(i);h.info(`\u{1F4C4} To ${s}: ${f.yellow(u)}`),this.query.push({filename:u,from:o.entryLocale,matter:p,md:this.markdownConfig.includeMatter?i:w,mode:l,to:s})}}catch{b.error(`${r} not found`,!0)}}getTargetExtension(t,e,o){return this.markdownConfig.outputExtensions?.(t,{fileContent:o,filePath:e,getDefaultExtension:K})||K(t)}getTargetFilename(t,e){if(this.markdownConfig.entryExtension)return C(".",t.replace(this.markdownConfig.entryExtension||".md",e));if(this.markdownConfig.entryLocale&&t.includes(`.${this.markdownConfig.entryLocale}.`))return[t.split(`.${this.markdownConfig.entryLocale}.`)[0],e].join("");{const o=t.split(".");return o.pop(),[o.join("."),e].join("")}}getMode(t,e){const o=this.markdownConfig.mode;return o?Zt(o)?o:o({fileContent:e,filePath:t})||E.STRING:E.STRING}}const cn=_t({pkg:z,shouldNotifyInNpmScript:!0});cn.notify({isGlobal:!0});const j=new Dt;j.name("lobe-i18n").description(z.description).version(z.version).addOption(new D("-o, --option","Setup lobe-i18n preferences")).addOption(new D("-c, --config <string>","Specify the configuration file")).addOption(new D("-m, --with-md","Run i18n translation and markdown translation simultaneously")),j.command("locale",{isDefault:!0}).action(async()=>{const n=j.opts();n.option?U(k(De,{})):(n.config&&G.loadCustomConfig(n.config),await new an().start(),n.withMd&&await new jt().start())}),j.command("md").action(async()=>{const n=j.opts();n.config&&G.loadCustomConfig(n.config),await new jt().start()}),j.parse()});export default ln();