@fluenti/cli 0.3.4 → 0.4.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/ai-provider.d.ts +4 -3
  2. package/dist/ai-provider.d.ts.map +1 -1
  3. package/dist/cli.cjs +13 -12
  4. package/dist/cli.cjs.map +1 -1
  5. package/dist/cli.js +419 -261
  6. package/dist/cli.js.map +1 -1
  7. package/dist/{compile-CXReVuTG.js → compile-CdA4EZ-p.js} +2 -2
  8. package/dist/compile-CdA4EZ-p.js.map +1 -0
  9. package/dist/{compile-CX1b_JVQ.cjs → compile-kXClO6q4.cjs} +2 -2
  10. package/dist/compile-kXClO6q4.cjs.map +1 -0
  11. package/dist/compile-worker.cjs +1 -1
  12. package/dist/compile-worker.js +1 -1
  13. package/dist/{extract-cache-CGSKwh76.cjs → extract-cache-BioSaoFo.cjs} +2 -2
  14. package/dist/{extract-cache-CGSKwh76.cjs.map → extract-cache-BioSaoFo.cjs.map} +1 -1
  15. package/dist/{extract-cache-BTxWgic2.js → extract-cache-C-MI1_ll.js} +3 -3
  16. package/dist/{extract-cache-BTxWgic2.js.map → extract-cache-C-MI1_ll.js.map} +1 -1
  17. package/dist/glossary.d.ts.map +1 -1
  18. package/dist/index.cjs +1 -1
  19. package/dist/index.js +2 -2
  20. package/dist/migrate.d.ts.map +1 -1
  21. package/dist/translate-parse.d.ts +10 -0
  22. package/dist/translate-parse.d.ts.map +1 -0
  23. package/dist/translate-prompt.d.ts +9 -0
  24. package/dist/translate-prompt.d.ts.map +1 -0
  25. package/dist/translate.d.ts +9 -8
  26. package/dist/translate.d.ts.map +1 -1
  27. package/dist/{tsx-extractor-BOD7JJQK.cjs → tsx-extractor-B0vFXziu.cjs} +2 -2
  28. package/dist/{tsx-extractor-BOD7JJQK.cjs.map → tsx-extractor-B0vFXziu.cjs.map} +1 -1
  29. package/dist/vue-extractor.cjs +1 -1
  30. package/package.json +2 -2
  31. package/dist/compile-CX1b_JVQ.cjs.map +0 -1
  32. package/dist/compile-CXReVuTG.js.map +0 -1
@@ -2,9 +2,10 @@ export type AIProvider = 'claude' | 'codex';
2
2
  export interface AIInvokeOptions {
3
3
  readonly provider: AIProvider;
4
4
  readonly prompt: string;
5
- readonly maxRetries?: number;
6
- readonly initialDelayMs?: number;
7
- readonly maxBuffer?: number;
5
+ readonly maxRetries?: number | undefined;
6
+ readonly initialDelayMs?: number | undefined;
7
+ readonly maxBuffer?: number | undefined;
8
+ readonly timeoutMs?: number | undefined;
8
9
  }
9
10
  export interface AIInvokeResult {
10
11
  readonly stdout: string;
@@ -1 +1 @@
1
- {"version":3,"file":"ai-provider.d.ts","sourceRoot":"","sources":["../src/ai-provider.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAA;AAE3C,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAA;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAiBD,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAgChF"}
1
+ {"version":3,"file":"ai-provider.d.ts","sourceRoot":"","sources":["../src/ai-provider.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAA;AAE3C,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAA;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACxC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACxC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAiBD,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CA0ChF"}
package/dist/cli.cjs CHANGED
@@ -1,23 +1,24 @@
1
1
  #!/usr/bin/env node
2
- const e=require(`./extract-cache-CGSKwh76.cjs`),t=require(`./tsx-extractor-BOD7JJQK.cjs`),n=require(`./compile-CX1b_JVQ.cjs`);let r=require(`@fluenti/core/internal`),i=require(`node:fs`),a=require(`node:path`),o=require(`node:url`),s=require(`fast-glob`);s=e.l(s);let c=require(`node:crypto`),l=require(`citty`),u=require(`consola`);u=e.l(u);let d=require(`node:child_process`),f=require(`node:util`),p=require(`node:timers/promises`),m=require(`@fluenti/core/config`);var h=`█`,g=`░`;function _(e,t=20){let n=Math.round(Math.max(0,Math.min(100,e))/100*t);return h.repeat(n)+g.repeat(t-n)}function v(e){let t=e.toFixed(1)+`%`;return e>=90?`\x1b[32m${t}\x1b[0m`:e>=70?`\x1b[33m${t}\x1b[0m`:`\x1b[31m${t}\x1b[0m`}function y(e,t,n){let r=t>0?n/t*100:0,i=t>0?v(r):`—`,a=t>0?_(r):``;return` ${e.padEnd(8)}│ ${String(t).padStart(5)} │ ${String(n).padStart(10)} │ ${a} ${i}`}function b(e){try{let t=(0,r.parse)(e),n=new Set;return x(t,n),[...n].sort()}catch{let t=new Set,n=0,r=0;for(;r<e.length;){if(e[r]===`{`){if(n++,n===1){let n=e.indexOf(`}`,r+1);if(n!==-1){let i=e.slice(r+1,n).trim(),a=/^(\w+)/.exec(i);a&&t.add(a[1])}}}else e[r]===`}`&&n--;r++}return[...t].sort()}}function x(e,t){for(let n of e)if(n.type===`variable`&&n.name!==`#`)t.add(n.name);else if(n.type===`plural`||n.type===`select`){t.add(n.variable);for(let e of Object.values(n.options))x(e,t)}else n.type===`function`&&t.add(n.variable)}function S(e,t){let n=[],{sourceLocale:r}=t,i=t.locales??Object.keys(e),a=e[r];if(!a)return n.push({rule:`missing-source`,severity:`error`,message:`Source locale catalog "${r}" not found`}),n;let o=Object.entries(a).filter(([,e])=>!e.obsolete).map(([e])=>e);for(let t of i){if(t===r)continue;let i=e[t];if(!i){n.push({rule:`missing-locale`,severity:`error`,message:`Catalog for locale "${t}" not found`,locale:t});continue}for(let e of o){let r=a[e],o=i[e];if(!o||!o.translation||o.translation.length===0){n.push({rule:`missing-translation`,severity:`error`,message:`Missing translation for "${e}" in locale "${t}"`,messageId:e,locale:t});continue}let s=b(r.message??e),c=b(o.translation),l=s.filter(e=>!c.includes(e)),u=c.filter(e=>!s.includes(e));l.length>0&&n.push({rule:`inconsistent-placeholders`,severity:`error`,message:`Translation for "${e}" in "${t}" is missing placeholders: ${l.map(e=>`{${e}}`).join(`, `)}`,messageId:e,locale:t}),u.length>0&&n.push({rule:`inconsistent-placeholders`,severity:`warning`,message:`Translation for "${e}" in "${t}" has extra placeholders: ${u.map(e=>`{${e}}`).join(`, `)}`,messageId:e,locale:t}),o.fuzzy&&n.push({rule:`fuzzy-translation`,severity:`warning`,message:`Translation for "${e}" in "${t}" is marked as fuzzy`,messageId:e,locale:t})}for(let[e,r]of Object.entries(i))r.obsolete||(!a[e]||a[e].obsolete)&&n.push({rule:`orphan-translation`,severity:`warning`,message:`Translation "${e}" in "${t}" has no corresponding source message`,messageId:e,locale:t})}let s=new Map;for(let[e,t]of Object.entries(a)){if(t.obsolete)continue;let n=t.message??e,r=s.get(n);r?r.push(e):s.set(n,[e])}for(let[e,t]of s)t.length>1&&n.push({rule:`duplicate-message`,severity:`info`,message:`Duplicate source message "${e.slice(0,60)}${e.length>60?`...`:``}" used by ${t.length} entries: ${t.join(`, `)}`,locale:r});return n}function ee(e){if(e.length===0)return` ✓ All checks passed`;let t=[],n=te(e,e=>e.rule);for(let[e,r]of Object.entries(n)){t.push(` ${e} (${r.length}):`);for(let e of r){let n=e.severity===`error`?`✗`:e.severity===`warning`?`⚠`:`ℹ`;t.push(` ${n} ${e.message}`)}}let r=e.filter(e=>e.severity===`error`).length,i=e.filter(e=>e.severity===`warning`).length,a=e.filter(e=>e.severity===`info`).length;return t.push(``),t.push(` Summary: ${r} errors, ${i} warnings, ${a} info`),t.join(`
3
- `)}function te(e,t){let n={};for(let r of e){let e=t(r);(n[e]??(n[e]=[])).push(r)}return n}function C(e,t){let{sourceLocale:n,minCoverage:r,locale:i}=t,a=e[n];if(!a)return{results:[],passed:!1,minCoverage:r,actualCoverage:0,diagnostics:[{rule:`missing-source`,severity:`error`,message:`Source locale catalog "${n}" not found`}]};let o=Object.entries(a).filter(([,e])=>!e.obsolete).map(([e])=>e),s=o.length,c=i?[i]:Object.keys(e).filter(e=>e!==n),l=[];for(let t of c){let n=e[t];if(!n){l.push({locale:t,total:s,translated:0,missing:s,fuzzy:0,coverage:0});continue}let r=0,i=0,a=0;for(let e of o){let t=n[e];!t||!t.translation||t.translation.length===0?i++:(r++,t.fuzzy&&a++)}let c=s>0?r/s*100:100;l.push({locale:t,total:s,translated:r,missing:i,fuzzy:a,coverage:c})}let u=l.reduce((e,t)=>e+t.translated,0),d=l.reduce((e,t)=>e+t.total,0),f=d>0?u/d*100:100,p=l.every(e=>e.coverage>=r),m={sourceLocale:n};return i&&(m.locales=[n,i]),{results:l,passed:p,minCoverage:r,actualCoverage:f,diagnostics:S(e,m)}}function w(e){let t=[];for(let n of e.results){let r=n.coverage>=e.minCoverage?`✓`:`✗`,i=n.coverage.toFixed(1),a=n.missing>0?` — ${n.missing} missing`:``,o=n.fuzzy>0?`, ${n.fuzzy} fuzzy`:``;t.push(`${r} ${n.locale}: ${i}% (${n.translated}/${n.total})${a}${o}`)}t.push(``);let n=e.actualCoverage.toFixed(1),r=e.passed?`PASSED`:`FAILED`;return t.push(`Coverage: ${n}% (min: ${e.minCoverage}%) — ${r}`),t.join(`
4
- `)}function T(e,t,n){let r=[],i=n===`json`?`.json`:`.po`;for(let n of e.results)if(n.coverage<e.minCoverage){let a=`${t}/${n.locale}${i}`;r.push(`::error file=${a}::Translation coverage ${n.coverage.toFixed(1)}% below minimum ${e.minCoverage}%`)}let a=e.diagnostics.filter(e=>e.rule===`missing-translation`);for(let e of a)if(e.locale){let n=`${t}/${e.locale}${i}`;r.push(`::warning file=${n}::${e.message}`)}return r.join(`
5
- `)}function E(e){return JSON.stringify({results:e.results,passed:e.passed,minCoverage:e.minCoverage,actualCoverage:Math.round(e.actualCoverage*10)/10},null,2)}var D=`1`,ne=class{data;cachePath;dirty=!1;constructor(e,t){this.cachePath=(0,a.resolve)(t?(0,a.resolve)(e,`.cache`,t):(0,a.resolve)(e,`.cache`),`compile-cache.json`),this.data=this.load()}isUpToDate(e,t){let n=this.data.entries[e];if(!n)return!1;let r=O(t);return n.inputHash===r}set(e,t){this.data.entries[e]={inputHash:O(t)},this.dirty=!0}save(){if(this.dirty)try{(0,i.mkdirSync)((0,a.dirname)(this.cachePath),{recursive:!0}),(0,i.writeFileSync)(this.cachePath,JSON.stringify(this.data),`utf-8`),this.dirty=!1}catch{}}load(){try{if((0,i.existsSync)(this.cachePath)){let e=(0,i.readFileSync)(this.cachePath,`utf-8`),t=JSON.parse(e);if(t.version===D)return t}}catch{}return{version:D,entries:{}}}};function O(e){return(0,c.createHash)(`md5`).update(e).digest(`hex`)}var re=(0,f.promisify)(d.execFile),ie={claude:`npm install -g @anthropic-ai/claude-code`,codex:`npm install -g @openai/codex`};function ae(e,t){return e===`claude`?[`-p`,t]:[`-p`,t,`--full-auto`]}function k(e){return e.code===`ENOENT`}async function A(e){let{provider:t,prompt:n,maxRetries:r=3,initialDelayMs:i=1e3,maxBuffer:a=10*1024*1024}=e,o=ae(t,n),s=t===`claude`?`claude`:`codex`,c;for(let e=0;e<=r;e++)try{let{stdout:t}=await re(s,[...o],{maxBuffer:a});return{stdout:t,attempts:e+1}}catch(n){if(k(n))throw Error(`"${t}" CLI not found. Please install it first:\n ${ie[t]}`);c=n,e<r&&await(0,p.setTimeout)(i*2**e)}throw c}function j(e,t,n,r){let i=JSON.stringify(n,null,2);return[`You are a professional translator. Translate the following messages from "${e}" to "${t}".`,``,...r?[`Project context: ${r}`,``]:[],`Input (JSON):`,i,``,`Rules:`,`- Output ONLY valid JSON with the same keys and translated values.`,`- Keep ICU MessageFormat placeholders like {name}, {count}, {gender} unchanged.`,`- Keep HTML tags unchanged.`,`- Do not add any explanation or markdown formatting, output raw JSON only.`].join(`
6
- `)}function M(e){let t=e.match(/\{[\s\S]*\}/);if(!t)throw Error(`No JSON object found in AI response`);let n;try{n=JSON.parse(t[0])}catch{throw Error(`Failed to parse JSON from AI response: ${t[0].slice(0,200)}`)}if(typeof n!=`object`||!n||Array.isArray(n))throw Error(`AI response is not a valid JSON object`);return n}function N(e){let t={};for(let[n,r]of Object.entries(e))r.obsolete||(!r.translation||r.translation.length===0)&&(t[n]=r.message??n);return t}function P(e,t){let n=Object.keys(e),r=[];for(let i=0;i<n.length;i+=t){let a={};for(let r of n.slice(i,i+t))a[r]=e[r];r.push(a)}return r}async function F(e){let{provider:t,sourceLocale:n,targetLocale:r,catalog:i,batchSize:a,context:o}=e,s=N(i),c=Object.keys(s).length;if(c===0)return{catalog:{...i},translated:0};u.default.info(` ${c} untranslated messages, translating with ${t}...`);let l={...i},d=P(s,a),f=0;for(let e=0;e<d.length;e++){let i=d[e],a=Object.keys(i);d.length>1&&u.default.info(` Batch ${e+1}/${d.length} (${a.length} messages)`);let{stdout:s}=await A({provider:t,prompt:j(n,r,i,o)}),c=M(s);for(let e of a)c[e]&&typeof c[e]==`string`?(l[e]={...l[e],translation:c[e]},f++):u.default.warn(` Missing translation for key: ${e}`)}return{catalog:l,translated:f}}var I=(0,f.promisify)(d.execFile),L={"vue-i18n":{name:`vue-i18n`,framework:`Vue`,configPatterns:[`i18n.ts`,`i18n.js`,`i18n/index.ts`,`i18n/index.js`,`src/i18n.ts`,`src/i18n.js`,`src/i18n/index.ts`,`src/plugins/i18n.ts`],localePatterns:[`locales/*.json`,`src/locales/*.json`,`i18n/*.json`,`src/i18n/*.json`,`lang/*.json`,`src/lang/*.json`,`locales/*.yaml`,`locales/*.yml`],sourcePatterns:[`src/**/*.vue`],migrationGuide:`vue/llms-migration.txt`},"nuxt-i18n":{name:`nuxt-i18n`,framework:`Nuxt`,configPatterns:[`nuxt.config.ts`,`nuxt.config.js`,`i18n.config.ts`,`i18n.config.js`],localePatterns:[`locales/*.json`,`lang/*.json`,`i18n/*.json`,`locales/*.yaml`,`locales/*.yml`],sourcePatterns:[`pages/**/*.vue`,`components/**/*.vue`,`layouts/**/*.vue`],migrationGuide:`nuxt/llms-migration.txt`},"react-i18next":{name:`react-i18next`,framework:`React`,configPatterns:[`i18n.ts`,`i18n.js`,`src/i18n.ts`,`src/i18n.js`,`src/i18n/index.ts`,`src/i18n/config.ts`],localePatterns:[`locales/*.json`,`src/locales/*.json`,`public/locales/**/*.json`,`translations/*.json`,`src/translations/*.json`],sourcePatterns:[`src/**/*.tsx`,`src/**/*.jsx`,`src/**/*.ts`],migrationGuide:`react/llms-migration.txt`},"next-intl":{name:`next-intl`,framework:`Next.js`,configPatterns:[`next.config.ts`,`next.config.js`,`next.config.mjs`,`i18n.ts`,`src/i18n.ts`,`i18n/request.ts`,`src/i18n/request.ts`],localePatterns:[`messages/*.json`,`locales/*.json`,`src/messages/*.json`,`src/locales/*.json`],sourcePatterns:[`app/**/*.tsx`,`src/app/**/*.tsx`,`pages/**/*.tsx`,`components/**/*.tsx`],migrationGuide:`next-plugin/llms-migration.txt`},"next-i18next":{name:`next-i18next`,framework:`Next.js`,configPatterns:[`next-i18next.config.js`,`next-i18next.config.mjs`,`next.config.ts`,`next.config.js`],localePatterns:[`public/locales/**/*.json`],sourcePatterns:[`pages/**/*.tsx`,`src/pages/**/*.tsx`,`components/**/*.tsx`,`src/components/**/*.tsx`],migrationGuide:`next-plugin/llms-migration.txt`},lingui:{name:`lingui`,framework:`React`,configPatterns:[`lingui.config.ts`,`lingui.config.js`,`.linguirc`],localePatterns:[`locales/*.po`,`src/locales/*.po`,`locales/*/messages.po`,`src/locales/*/messages.po`],sourcePatterns:[`src/**/*.tsx`,`src/**/*.jsx`,`src/**/*.ts`],migrationGuide:`react/llms-migration.txt`}},R=Object.keys(L);function z(e){let t=e.toLowerCase().replace(/^@nuxtjs\//,`nuxt-`).replace(/^@/,``);return R.find(e=>e===t)}async function B(e){let t={configFiles:[],localeFiles:[],sampleSources:[],packageJson:void 0},n=(0,a.resolve)(`package.json`);(0,i.existsSync)(n)&&(t.packageJson=(0,i.readFileSync)(n,`utf-8`));for(let n of e.configPatterns){let e=(0,a.resolve)(n);(0,i.existsSync)(e)&&t.configFiles.push({path:n,content:(0,i.readFileSync)(e,`utf-8`)})}let r=await(0,s.default)(e.localePatterns,{absolute:!1});for(let e of r.slice(0,10)){let n=(0,i.readFileSync)((0,a.resolve)(e),`utf-8`);t.localeFiles.push({path:e,content:n.length>5e3?n.slice(0,5e3)+`
2
+ const e=require(`./extract-cache-BioSaoFo.cjs`),t=require(`./tsx-extractor-B0vFXziu.cjs`),n=require(`./compile-kXClO6q4.cjs`);let r=require(`@fluenti/core/internal`),i=require(`node:fs`),a=require(`node:path`),o=require(`node:url`),s=require(`fast-glob`);s=e.l(s);let c=require(`node:crypto`),l=require(`citty`),u=require(`consola`);u=e.l(u);let d=require(`node:child_process`),f=require(`node:util`),p=require(`node:timers/promises`),m=require(`@fluenti/core/config`);var h=`█`,g=`░`;function _(e,t=20){let n=Math.round(Math.max(0,Math.min(100,e))/100*t);return h.repeat(n)+g.repeat(t-n)}function v(e){let t=e.toFixed(1)+`%`;return e>=90?`\x1b[32m${t}\x1b[0m`:e>=70?`\x1b[33m${t}\x1b[0m`:`\x1b[31m${t}\x1b[0m`}function y(e,t,n){let r=t>0?n/t*100:0,i=t>0?v(r):`—`,a=t>0?_(r):``;return` ${e.padEnd(8)}│ ${String(t).padStart(5)} │ ${String(n).padStart(10)} │ ${a} ${i}`}var b=/\{(\w+),\s*(plural|select|selectordinal)\s*,/;function x(e){try{let t=(0,r.parse)(e),n=new Set;return S(t,n),[...n].sort()}catch{let t=new Set,n=0,r=0;for(;r<e.length;){if(e[r]===`{`){if(n++,n===1){let n=e.indexOf(`}`,r+1);if(n!==-1){let i=e.slice(r+1,n).trim(),a=/^(\w+)/.exec(i);a&&t.add(a[1])}}}else e[r]===`}`&&n--;r++}return[...t].sort()}}function S(e,t){for(let n of e)if(n.type===`variable`&&n.name!==`#`)t.add(n.name);else if(n.type===`plural`||n.type===`select`){t.add(n.variable);for(let e of Object.values(n.options))S(e,t)}else n.type===`function`&&t.add(n.variable)}function C(e){let t=new Set,n=/<\/?([a-zA-Z][\w-]*)[^>]*>/g,r;for(;(r=n.exec(e))!==null;)t.add(r[1].toLowerCase());return[...t].sort()}function ee(e,t){let n=x(e),i=x(t),a=C(e),o=C(t),s=n.filter(e=>!i.includes(e)),c=i.filter(e=>!n.includes(e)),l=a.filter(e=>!o.includes(e)),u=o.filter(e=>!a.includes(e)),d=[];if(b.test(t))try{(0,r.parse)(t)}catch(e){d.push(e.message)}return{valid:s.length===0&&c.length===0&&l.length===0&&u.length===0&&d.length===0,missingPlaceholders:s,extraPlaceholders:c,missingHtmlTags:l,extraHtmlTags:u,syntaxErrors:d}}function w(e,t){let n=[],{sourceLocale:r}=t,i=t.locales??Object.keys(e),a=e[r];if(!a)return n.push({rule:`missing-source`,severity:`error`,message:`Source locale catalog "${r}" not found`}),n;let o=Object.entries(a).filter(([,e])=>!e.obsolete).map(([e])=>e);for(let t of i){if(t===r)continue;let i=e[t];if(!i){n.push({rule:`missing-locale`,severity:`error`,message:`Catalog for locale "${t}" not found`,locale:t});continue}for(let e of o){let r=a[e],o=i[e];if(!o||!o.translation||o.translation.length===0){n.push({rule:`missing-translation`,severity:`error`,message:`Missing translation for "${e}" in locale "${t}"`,messageId:e,locale:t});continue}let s=x(r.message??e),c=x(o.translation),l=s.filter(e=>!c.includes(e)),u=c.filter(e=>!s.includes(e));l.length>0&&n.push({rule:`inconsistent-placeholders`,severity:`error`,message:`Translation for "${e}" in "${t}" is missing placeholders: ${l.map(e=>`{${e}}`).join(`, `)}`,messageId:e,locale:t}),u.length>0&&n.push({rule:`inconsistent-placeholders`,severity:`warning`,message:`Translation for "${e}" in "${t}" has extra placeholders: ${u.map(e=>`{${e}}`).join(`, `)}`,messageId:e,locale:t}),o.fuzzy&&n.push({rule:`fuzzy-translation`,severity:`warning`,message:`Translation for "${e}" in "${t}" is marked as fuzzy`,messageId:e,locale:t})}for(let[e,r]of Object.entries(i))r.obsolete||(!a[e]||a[e].obsolete)&&n.push({rule:`orphan-translation`,severity:`warning`,message:`Translation "${e}" in "${t}" has no corresponding source message`,messageId:e,locale:t})}let s=new Map;for(let[e,t]of Object.entries(a)){if(t.obsolete)continue;let n=t.message??e,r=s.get(n);r?r.push(e):s.set(n,[e])}for(let[e,t]of s)t.length>1&&n.push({rule:`duplicate-message`,severity:`info`,message:`Duplicate source message "${e.slice(0,60)}${e.length>60?`...`:``}" used by ${t.length} entries: ${t.join(`, `)}`,locale:r});return n}function T(e){if(e.length===0)return` ✓ All checks passed`;let t=[],n=te(e,e=>e.rule);for(let[e,r]of Object.entries(n)){t.push(` ${e} (${r.length}):`);for(let e of r){let n=e.severity===`error`?`✗`:e.severity===`warning`?`⚠`:`ℹ`;t.push(` ${n} ${e.message}`)}}let r=e.filter(e=>e.severity===`error`).length,i=e.filter(e=>e.severity===`warning`).length,a=e.filter(e=>e.severity===`info`).length;return t.push(``),t.push(` Summary: ${r} errors, ${i} warnings, ${a} info`),t.join(`
3
+ `)}function te(e,t){let n={};for(let r of e){let e=t(r);(n[e]??(n[e]=[])).push(r)}return n}function E(e,t){let{sourceLocale:n,minCoverage:r,locale:i}=t,a=e[n];if(!a)return{results:[],passed:!1,minCoverage:r,actualCoverage:0,diagnostics:[{rule:`missing-source`,severity:`error`,message:`Source locale catalog "${n}" not found`}]};let o=Object.entries(a).filter(([,e])=>!e.obsolete).map(([e])=>e),s=o.length,c=i?[i]:Object.keys(e).filter(e=>e!==n),l=[];for(let t of c){let n=e[t];if(!n){l.push({locale:t,total:s,translated:0,missing:s,fuzzy:0,coverage:0});continue}let r=0,i=0,a=0;for(let e of o){let t=n[e];!t||!t.translation||t.translation.length===0?i++:(r++,t.fuzzy&&a++)}let c=s>0?r/s*100:100;l.push({locale:t,total:s,translated:r,missing:i,fuzzy:a,coverage:c})}let u=l.reduce((e,t)=>e+t.translated,0),d=l.reduce((e,t)=>e+t.total,0),f=d>0?u/d*100:100,p=l.every(e=>e.coverage>=r),m={sourceLocale:n};return i&&(m.locales=[n,i]),{results:l,passed:p,minCoverage:r,actualCoverage:f,diagnostics:w(e,m)}}function ne(e){let t=[];for(let n of e.results){let r=n.coverage>=e.minCoverage?`✓`:`✗`,i=n.coverage.toFixed(1),a=n.missing>0?` — ${n.missing} missing`:``,o=n.fuzzy>0?`, ${n.fuzzy} fuzzy`:``;t.push(`${r} ${n.locale}: ${i}% (${n.translated}/${n.total})${a}${o}`)}t.push(``);let n=e.actualCoverage.toFixed(1),r=e.passed?`PASSED`:`FAILED`;return t.push(`Coverage: ${n}% (min: ${e.minCoverage}%) — ${r}`),t.join(`
4
+ `)}function D(e,t,n){let r=[],i=n===`json`?`.json`:`.po`;for(let n of e.results)if(n.coverage<e.minCoverage){let a=`${t}/${n.locale}${i}`;r.push(`::error file=${a}::Translation coverage ${n.coverage.toFixed(1)}% below minimum ${e.minCoverage}%`)}let a=e.diagnostics.filter(e=>e.rule===`missing-translation`);for(let e of a)if(e.locale){let n=`${t}/${e.locale}${i}`;r.push(`::warning file=${n}::${e.message}`)}return r.join(`
5
+ `)}function O(e){return JSON.stringify({results:e.results,passed:e.passed,minCoverage:e.minCoverage,actualCoverage:Math.round(e.actualCoverage*10)/10},null,2)}var k=`1`,A=class{data;cachePath;dirty=!1;constructor(e,t){this.cachePath=(0,a.resolve)(t?(0,a.resolve)(e,`.cache`,t):(0,a.resolve)(e,`.cache`),`compile-cache.json`),this.data=this.load()}isUpToDate(e,t){let n=this.data.entries[e];if(!n)return!1;let r=j(t);return n.inputHash===r}set(e,t){this.data.entries[e]={inputHash:j(t)},this.dirty=!0}save(){if(this.dirty)try{(0,i.mkdirSync)((0,a.dirname)(this.cachePath),{recursive:!0}),(0,i.writeFileSync)(this.cachePath,JSON.stringify(this.data),`utf-8`),this.dirty=!1}catch{}}load(){try{if((0,i.existsSync)(this.cachePath)){let e=(0,i.readFileSync)(this.cachePath,`utf-8`),t=JSON.parse(e);if(t.version===k)return t}}catch{}return{version:k,entries:{}}}};function j(e){return(0,c.createHash)(`md5`).update(e).digest(`hex`)}var re=(0,f.promisify)(d.execFile),ie={claude:`npm install -g @anthropic-ai/claude-code`,codex:`npm install -g @openai/codex`};function ae(e,t){return e===`claude`?[`-p`,t]:[`-p`,t,`--full-auto`]}function oe(e){return e.code===`ENOENT`}async function se(e){let{provider:t,prompt:n,maxRetries:r=3,initialDelayMs:i=1e3,maxBuffer:a=10*1024*1024,timeoutMs:o=12e4}=e,s=ae(t,n),c=t===`claude`?`claude`:`codex`,l;for(let e=0;e<=r;e++)try{let t=re(c,[...s],{maxBuffer:a}),{stdout:n}=await Promise.race([t,(0,p.setTimeout)(o).then(()=>{throw Error(`AI provider timed out after ${o}ms`)})]);return{stdout:n,attempts:e+1}}catch(n){if(oe(n))throw Error(`"${t}" CLI not found. Please install it first:\n ${ie[t]}`);if(n instanceof Error&&n.message.includes(`timed out`))throw n;l=n,e<r&&await(0,p.setTimeout)(i*2**e)}throw l}var M=1048576;function N(e){if(!(0,i.existsSync)(e))return{};let t=(0,i.statSync)(e).size;if(t>M)throw Error(`Glossary file exceeds maximum size of ${M} bytes (got ${t} bytes)`);let n;try{let t=(0,i.readFileSync)(e,`utf-8`);n=JSON.parse(t)}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Invalid glossary format: failed to parse JSON — ${t}`)}if(typeof n!=`object`||!n||Array.isArray(n))throw Error(`Invalid glossary format: root must be a plain object`);for(let[e,t]of Object.entries(n)){if(typeof t!=`object`||!t||Array.isArray(t))throw Error(`Invalid glossary format: value for "${e}" must be a plain object`);for(let[n,r]of Object.entries(t))if(typeof r!=`string`)throw Error(`Invalid glossary format: "${e}.${n}" must be a string, got ${typeof r}`)}return n}function P(e,t){let n={};for(let[r,i]of Object.entries(e))t in i&&(n[r]=i[t]);return n}function F(e){return e.replace(/\\/g,`\\\\`).replace(/"/g,`\\"`).replace(/[\n\r]/g,` `).replace(/\t/g,` `)}function I(e){let t=Object.entries(e);return t.length===0?``:`=== GLOSSARY (use these exact translations) ===\n${[...t].sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`"${F(e)}" → "${F(t)}"`).join(`
6
+ `)}`}function L(e){let{sourceLocale:t,targetLocale:n,messages:r,glossary:i,context:a}=e,o=JSON.stringify(r,null,2),s=[`You are a professional translator. Translate the following messages from "${t}" to "${n}".`,``,`Rules:`,`1. Output ONLY a valid JSON object with the same keys and translated values.`,`2. Keep ICU MessageFormat placeholders unchanged: {name}, {count, plural, ...}, {val, number}, etc.`,`3. Keep HTML tags unchanged: <b>, <a href="...">, </span>, <br/>, etc.`,`4. Keep numbered rich-text tags unchanged: <0>, </0>, <1/>, etc.`,`5. Do not add any explanation, markdown formatting, or code fences output raw JSON only.`];if(i&&Object.keys(i).length>0){let e=I(i);e&&s.push(``,e)}return a&&s.push(``,`=== PROJECT CONTEXT ===`,a),s.push(``,`Input (JSON):`,o),s.join(`
7
+ `)}function R(e){let t=e.indexOf(`{`);if(t===-1)throw Error(`No JSON object found in AI response`);let n=0,r=!1,i=!1;for(let a=t;a<e.length;a++){let o=e[a];if(i){i=!1;continue}if(o===`\\`&&r){i=!0;continue}if(o===`"`&&!i){r=!r;continue}if(!r&&(o===`{`&&n++,o===`}`&&(n--,n===0)))return e.slice(t,a+1)}throw Error(`Unterminated JSON object in AI response`)}function z(e){let t=e.match(/```(?:json)?\s*\n?([\s\S]*?)```/);return t?t[1]:e}function B(e,t){let n=[],r=R(z(e)),i;try{i=JSON.parse(r)}catch{throw Error(`Failed to parse JSON from AI response: ${r.slice(0,200)}`)}if(typeof i!=`object`||!i||Array.isArray(i))throw Error(`AI response is not a valid JSON object`);let a=i,o={},s=new Set(Object.keys(t));for(let[e,r]of Object.entries(a)){if(!s.has(e)){n.push(`Extra key in AI response (ignored): "${e}"`);continue}if(typeof r!=`string`){n.push(`Non-string value for key "${e}" (ignored)`);continue}o[e]=r;let i=t[e],a=ee(i,r);if(!a.valid){let t=[];a.missingPlaceholders.length>0&&t.push(`missing placeholders: ${a.missingPlaceholders.join(`, `)}`),a.extraPlaceholders.length>0&&t.push(`extra placeholders: ${a.extraPlaceholders.join(`, `)}`),a.missingHtmlTags.length>0&&t.push(`missing HTML tags: ${a.missingHtmlTags.join(`, `)}`),a.extraHtmlTags.length>0&&t.push(`extra HTML tags: ${a.extraHtmlTags.join(`, `)}`),a.syntaxErrors.length>0&&t.push(`ICU syntax errors: ${a.syntaxErrors.join(`; `)}`),n.push(`QA issue for "${e}": ${t.join(`; `)}`)}}for(let e of s)e in o||n.push(`Missing translation for key: "${e}"`);return{translations:o,warnings:n}}function V(e){let t={};for(let[n,r]of Object.entries(e))r.obsolete||(!r.translation||r.translation.length===0)&&(t[n]=r.message??n);return t}function H(e,t){let n=Object.keys(e),r=[];for(let i=0;i<n.length;i+=t){let a={};for(let r of n.slice(i,i+t))a[r]=e[r];r.push(a)}return r}async function U(e){let{provider:t,sourceLocale:n,targetLocale:r,catalog:i,batchSize:a,context:o,glossary:s,timeoutMs:c}=e,l=V(i),d=Object.keys(l).length;if(d===0)return{catalog:{...i},translated:0,warnings:[]};u.default.info(` ${d} untranslated messages, translating with ${t}...`);let f={...i},p=H(l,a),m=0,h=[];for(let e=0;e<p.length;e++){let i=p[e],a=Object.keys(i);p.length>1&&u.default.info(` Batch ${e+1}/${p.length} (${a.length} messages)`);try{let{stdout:e}=await se({provider:t,prompt:L({sourceLocale:n,targetLocale:r,messages:i,glossary:s,context:o}),timeoutMs:c}),{translations:l,warnings:d}=B(e,i);for(let e of d)h.push(`[${r}] ${e}`),u.default.warn(` ${e}`);for(let e of a)e in l&&(f[e]={...f[e],translation:l[e]},m++)}catch(t){let n=t instanceof Error?t.message:String(t);h.push(`[${r}] Batch ${e+1} failed: ${n}`),u.default.error(` Batch ${e+1} failed: ${n}`)}}return{catalog:f,translated:m,warnings:h}}var W=(0,f.promisify)(d.execFile),G={"vue-i18n":{name:`vue-i18n`,framework:`Vue`,configPatterns:[`i18n.ts`,`i18n.js`,`i18n/index.ts`,`i18n/index.js`,`src/i18n.ts`,`src/i18n.js`,`src/i18n/index.ts`,`src/plugins/i18n.ts`],localePatterns:[`locales/*.json`,`src/locales/*.json`,`i18n/*.json`,`src/i18n/*.json`,`lang/*.json`,`src/lang/*.json`,`locales/*.yaml`,`locales/*.yml`],sourcePatterns:[`src/**/*.vue`],migrationGuide:`vue/llms-migration.txt`},"nuxt-i18n":{name:`nuxt-i18n`,framework:`Nuxt`,configPatterns:[`nuxt.config.ts`,`nuxt.config.js`,`i18n.config.ts`,`i18n.config.js`],localePatterns:[`locales/*.json`,`lang/*.json`,`i18n/*.json`,`locales/*.yaml`,`locales/*.yml`],sourcePatterns:[`pages/**/*.vue`,`components/**/*.vue`,`layouts/**/*.vue`],migrationGuide:`nuxt/llms-migration.txt`},"react-i18next":{name:`react-i18next`,framework:`React`,configPatterns:[`i18n.ts`,`i18n.js`,`src/i18n.ts`,`src/i18n.js`,`src/i18n/index.ts`,`src/i18n/config.ts`],localePatterns:[`locales/*.json`,`src/locales/*.json`,`public/locales/**/*.json`,`translations/*.json`,`src/translations/*.json`],sourcePatterns:[`src/**/*.tsx`,`src/**/*.jsx`,`src/**/*.ts`],migrationGuide:`react/llms-migration.txt`},"next-intl":{name:`next-intl`,framework:`Next.js`,configPatterns:[`next.config.ts`,`next.config.js`,`next.config.mjs`,`i18n.ts`,`src/i18n.ts`,`i18n/request.ts`,`src/i18n/request.ts`],localePatterns:[`messages/*.json`,`locales/*.json`,`src/messages/*.json`,`src/locales/*.json`],sourcePatterns:[`app/**/*.tsx`,`src/app/**/*.tsx`,`pages/**/*.tsx`,`components/**/*.tsx`],migrationGuide:`next-plugin/llms-migration.txt`},"next-i18next":{name:`next-i18next`,framework:`Next.js`,configPatterns:[`next-i18next.config.js`,`next-i18next.config.mjs`,`next.config.ts`,`next.config.js`],localePatterns:[`public/locales/**/*.json`],sourcePatterns:[`pages/**/*.tsx`,`src/pages/**/*.tsx`,`components/**/*.tsx`,`src/components/**/*.tsx`],migrationGuide:`next-plugin/llms-migration.txt`},lingui:{name:`lingui`,framework:`React`,configPatterns:[`lingui.config.ts`,`lingui.config.js`,`.linguirc`],localePatterns:[`locales/*.po`,`src/locales/*.po`,`locales/*/messages.po`,`src/locales/*/messages.po`],sourcePatterns:[`src/**/*.tsx`,`src/**/*.jsx`,`src/**/*.ts`],migrationGuide:`react/llms-migration.txt`}},K=Object.keys(G);function ce(e){let t=e.toLowerCase().replace(/^@nuxtjs\//,`nuxt-`).replace(/^@/,``);return K.find(e=>e===t)}async function le(e){let t={configFiles:[],localeFiles:[],sampleSources:[],packageJson:void 0},n=(0,a.resolve)(`package.json`);(0,i.existsSync)(n)&&(t.packageJson=(0,i.readFileSync)(n,`utf-8`));for(let n of e.configPatterns){let e=(0,a.resolve)(n);(0,i.existsSync)(e)&&t.configFiles.push({path:n,content:(0,i.readFileSync)(e,`utf-8`)})}let r=await(0,s.default)(e.localePatterns,{absolute:!1});for(let e of r.slice(0,10)){let n=(0,i.readFileSync)((0,a.resolve)(e),`utf-8`);t.localeFiles.push({path:e,content:n.length>5e3?n.slice(0,5e3)+`
7
8
  ... (truncated)`:n})}let o=await(0,s.default)(e.sourcePatterns,{absolute:!1});for(let e of o.slice(0,5)){let n=(0,i.readFileSync)((0,a.resolve)(e),`utf-8`);t.sampleSources.push({path:e,content:n.length>3e3?n.slice(0,3e3)+`
8
- ... (truncated)`:n})}return t}function V(e){let t=typeof __dirname<`u`?__dirname:(0,a.dirname)((0,o.fileURLToPath)({}.url)),n=[(0,a.resolve)(`node_modules`,`@fluenti`,`cli`,`..`,`..`,e),(0,a.join)(t,`..`,`..`,`..`,e),(0,a.join)(t,`..`,`..`,e)];for(let e of n)if((0,i.existsSync)(e))return(0,i.readFileSync)(e,`utf-8`);return``}function H(e,t,n){let r=[];if(r.push(`You are a migration assistant helping convert a ${e.framework} project from "${e.name}" to Fluenti (@fluenti).`,``,`Your task:`,"1. Generate a `fluenti.config.ts` file based on the existing i18n configuration",`2. Convert each locale/translation file to Fluenti PO format`,`3. List the code changes needed (file by file) to migrate source code from the old API to Fluenti API`,``),n&&r.push(`=== MIGRATION GUIDE ===`,n,``),t.packageJson&&r.push(`=== package.json ===`,t.packageJson,``),t.configFiles.length>0){r.push(`=== EXISTING CONFIG FILES ===`);for(let e of t.configFiles)r.push(`--- ${e.path} ---`,e.content,``)}if(t.localeFiles.length>0){r.push(`=== EXISTING LOCALE FILES ===`);for(let e of t.localeFiles)r.push(`--- ${e.path} ---`,e.content,``)}if(t.sampleSources.length>0){r.push(`=== SAMPLE SOURCE FILES ===`);for(let e of t.sampleSources)r.push(`--- ${e.path} ---`,e.content,``)}return r.push(``,`=== OUTPUT FORMAT ===`,`Respond with the following sections, each starting with the exact header shown:`,``,`### FLUENTI_CONFIG`,"```ts",`// The fluenti.config.ts content`,"```",``,`### LOCALE_FILES`,`For each locale file, output:`,`#### LOCALE: {locale_code}`,"```po",`// The PO file content`,"```",``,`### MIGRATION_STEPS`,`A numbered checklist of specific code changes needed, with before/after examples.`,``,`### INSTALL_COMMANDS`,"```bash",`// The install and uninstall commands`,"```"),r.join(`
9
- `)}async function U(e,t){let n=10*1024*1024;try{if(e===`claude`){let{stdout:e}=await I(`claude`,[`-p`,t],{maxBuffer:n});return e}else{let{stdout:e}=await I(`codex`,[`-p`,t,`--full-auto`],{maxBuffer:n});return e}}catch(t){let n=t;throw n.code===`ENOENT`||n.code===`EPERM`||n.code===`EACCES`?Error(`"${e}" CLI not found or not executable. Please install it first:\n`+(e===`claude`?` npm install -g @anthropic-ai/claude-code`:` npm install -g @openai/codex`)):t}}function W(e){let t={config:void 0,localeFiles:[],steps:void 0,installCommands:void 0},n=e.match(/### FLUENTI_CONFIG[\s\S]*?```(?:ts|typescript)?\n([\s\S]*?)```/);n&&(t.config=n[1].trim());let r=e.match(/### LOCALE_FILES([\s\S]*?)(?=### MIGRATION_STEPS|### INSTALL_COMMANDS|$)/);if(r){let e=/#### LOCALE:\s*(\S+)\s*\n```(?:po)?\n([\s\S]*?)```/g,n;for(;(n=e.exec(r[1]))!==null;)t.localeFiles.push({locale:n[1],content:n[2].trim()})}let i=e.match(/### MIGRATION_STEPS\s*\n([\s\S]*?)(?=### INSTALL_COMMANDS|$)/);i&&(t.steps=i[1].trim());let a=e.match(/### INSTALL_COMMANDS[\s\S]*?```(?:bash|sh)?\n([\s\S]*?)```/);return a&&(t.installCommands=a[1].trim()),t}async function oe(e){let{from:t,provider:n,write:r}=e,i=z(t);if(!i){u.default.error(`Unsupported library "${t}". Supported libraries:`);for(let e of R)u.default.log(` - ${e}`);return}let o=L[i];u.default.info(`Migrating from ${o.name} (${o.framework}) to Fluenti`),u.default.info(`Scanning project for existing i18n files...`);let s=await B(o);if(s.configFiles.length===0&&s.localeFiles.length===0){u.default.warn(`No ${o.name} configuration or locale files found.`),u.default.info(`Make sure you are running this command from the project root directory.`);return}u.default.info(`Found: ${s.configFiles.length} config file(s), ${s.localeFiles.length} locale file(s), ${s.sampleSources.length} source file(s)`);let c=V(o.migrationGuide);u.default.info(`Generating migration plan with ${n}...`);let l=W(await U(n,H(o,s,c)));if(l.installCommands&&(u.default.log(``),u.default.box({title:`Install Commands`,message:l.installCommands})),l.config)if(r){let{writeFileSync:e}=await import(`node:fs`),t=(0,a.resolve)(`fluenti.config.ts`);e(t,l.config,`utf-8`),u.default.success(`Written: ${t}`)}else u.default.log(``),u.default.box({title:`fluenti.config.ts`,message:l.config});if(l.localeFiles.length>0)if(r){let{writeFileSync:e,mkdirSync:t}=await import(`node:fs`),n=`./locales`;t((0,a.resolve)(n),{recursive:!0});for(let t of l.localeFiles){let r=(0,a.resolve)(n,`${t.locale}.po`);e(r,t.content,`utf-8`),u.default.success(`Written: ${r}`)}}else for(let e of l.localeFiles)u.default.log(``),u.default.box({title:`locales/${e.locale}.po`,message:e.content.length>500?e.content.slice(0,500)+`
10
- ... (use --write to save full file)`:e.content});l.steps&&(u.default.log(``),u.default.box({title:`Migration Steps`,message:l.steps})),!r&&(l.config||l.localeFiles.length>0)&&(u.default.log(``),u.default.info(`Run with --write to save generated files to disk:`),u.default.log(` fluenti migrate --from ${t} --write`))}var G=/^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{1,8})*$/;function K(e){if(!G.test(e))throw Error(`Invalid locale format: "${e}"`);return e}var q=[{dep:`next`,name:`nextjs`,pluginPackage:`@fluenti/next`},{dep:`nuxt`,name:`nuxt`,pluginPackage:`@fluenti/vue`},{dep:`@solidjs/start`,name:`solidstart`,pluginPackage:`@fluenti/solid`},{dep:`vue`,name:`vue`,pluginPackage:`@fluenti/vue`},{dep:`solid-js`,name:`solid`,pluginPackage:`@fluenti/solid`},{dep:`react`,name:`react`,pluginPackage:`@fluenti/react`}];function se(e){for(let t of q)if(t.dep in e)return{name:t.name,pluginPackage:t.pluginPackage};return{name:`unknown`,pluginPackage:null}}function ce(e){let t=e.locales.map(e=>`'${e}'`).join(`, `);return`import { defineConfig } from '@fluenti/cli'
9
+ ... (truncated)`:n})}return t}function ue(e){let t=typeof __dirname<`u`?__dirname:(0,a.dirname)((0,o.fileURLToPath)({}.url)),n=[(0,a.resolve)(`node_modules`,`@fluenti`,`cli`,`..`,`..`,e),(0,a.join)(t,`..`,`..`,`..`,e),(0,a.join)(t,`..`,`..`,e)];for(let e of n)if((0,i.existsSync)(e))return(0,i.readFileSync)(e,`utf-8`);return``}function q(e,t,n){let r=[];if(r.push(`You are a migration assistant helping convert a ${e.framework} project from "${e.name}" to Fluenti (@fluenti).`,``,`Your task:`,"1. Generate a `fluenti.config.ts` file based on the existing i18n configuration",`2. Convert each locale/translation file to Fluenti PO format`,`3. List the code changes needed (file by file) to migrate source code from the old API to Fluenti API`,``),n&&r.push(`=== MIGRATION GUIDE ===`,n,``),t.packageJson&&r.push(`=== package.json ===`,t.packageJson,``),t.configFiles.length>0){r.push(`=== EXISTING CONFIG FILES ===`);for(let e of t.configFiles)r.push(`--- ${e.path} ---`,e.content,``)}if(t.localeFiles.length>0){r.push(`=== EXISTING LOCALE FILES ===`);for(let e of t.localeFiles)r.push(`--- ${e.path} ---`,e.content,``)}if(t.sampleSources.length>0){r.push(`=== SAMPLE SOURCE FILES ===`);for(let e of t.sampleSources)r.push(`--- ${e.path} ---`,e.content,``)}return r.push(``,`=== OUTPUT FORMAT ===`,`Respond with the following sections, each starting with the exact header shown:`,``,`### FLUENTI_CONFIG`,"```ts",`// The fluenti.config.ts content`,"```",``,`### LOCALE_FILES`,`For each locale file, output:`,`#### LOCALE: {locale_code}`,"```po",`// The PO file content`,"```",``,`### MIGRATION_STEPS`,`A numbered checklist of specific code changes needed, with before/after examples.`,``,`### INSTALL_COMMANDS`,"```bash",`// The install and uninstall commands`,"```"),r.join(`
10
+ `)}async function de(e,t){let n=10*1024*1024;try{if(e===`claude`){let{stdout:e}=await W(`claude`,[`-p`,t],{maxBuffer:n});return e}else{let{stdout:e}=await W(`codex`,[`-p`,t,`--full-auto`],{maxBuffer:n});return e}}catch(t){let n=t;throw n.code===`ENOENT`||n.code===`EPERM`||n.code===`EACCES`?Error(`"${e}" CLI not found or not executable. Please install it first:\n`+(e===`claude`?` npm install -g @anthropic-ai/claude-code`:` npm install -g @openai/codex`)):t}}function fe(e){let t=5e5,n=e.length>t?e.slice(0,t):e,r={config:void 0,localeFiles:[],steps:void 0,installCommands:void 0},i=n.match(/### FLUENTI_CONFIG[\s\S]*?```(?:ts|typescript)?\n([\s\S]*?)```/);i&&(r.config=i[1].trim());let a=n.match(/### LOCALE_FILES([\s\S]*?)(?=### MIGRATION_STEPS|### INSTALL_COMMANDS|$)/);if(a){let e=/#### LOCALE:\s*(\S+)\s*\n```(?:po)?\n([\s\S]*?)```/g,t;for(;(t=e.exec(a[1]))!==null;)r.localeFiles.push({locale:t[1],content:t[2].trim()})}let o=n.match(/### MIGRATION_STEPS\s*\n([\s\S]*?)(?=### INSTALL_COMMANDS|$)/);o&&(r.steps=o[1].trim());let s=n.match(/### INSTALL_COMMANDS[\s\S]*?```(?:bash|sh)?\n([\s\S]*?)```/);return s&&(r.installCommands=s[1].trim()),r}async function pe(e){let{from:t,provider:n,write:r}=e,i=ce(t);if(!i){u.default.error(`Unsupported library "${t}". Supported libraries:`);for(let e of K)u.default.log(` - ${e}`);return}let o=G[i];u.default.info(`Migrating from ${o.name} (${o.framework}) to Fluenti`),u.default.info(`Scanning project for existing i18n files...`);let s=await le(o);if(s.configFiles.length===0&&s.localeFiles.length===0){u.default.warn(`No ${o.name} configuration or locale files found.`),u.default.info(`Make sure you are running this command from the project root directory.`);return}u.default.info(`Found: ${s.configFiles.length} config file(s), ${s.localeFiles.length} locale file(s), ${s.sampleSources.length} source file(s)`);let c=ue(o.migrationGuide);u.default.info(`Generating migration plan with ${n}...`);let l=fe(await de(n,q(o,s,c)));if(l.installCommands&&(u.default.log(``),u.default.box({title:`Install Commands`,message:l.installCommands})),l.config)if(r){let{writeFileSync:e}=await import(`node:fs`),t=(0,a.resolve)(`fluenti.config.ts`);e(t,l.config,`utf-8`),u.default.success(`Written: ${t}`)}else u.default.log(``),u.default.box({title:`fluenti.config.ts`,message:l.config});if(l.localeFiles.length>0)if(r){let{writeFileSync:e,mkdirSync:t}=await import(`node:fs`),n=`./locales`;t((0,a.resolve)(n),{recursive:!0});for(let t of l.localeFiles){let r=(0,a.resolve)(n,`${t.locale}.po`);e(r,t.content,`utf-8`),u.default.success(`Written: ${r}`)}}else for(let e of l.localeFiles)u.default.log(``),u.default.box({title:`locales/${e.locale}.po`,message:e.content.length>500?e.content.slice(0,500)+`
11
+ ... (use --write to save full file)`:e.content});l.steps&&(u.default.log(``),u.default.box({title:`Migration Steps`,message:l.steps})),!r&&(l.config||l.localeFiles.length>0)&&(u.default.log(``),u.default.info(`Run with --write to save generated files to disk:`),u.default.log(` fluenti migrate --from ${t} --write`))}var me=/^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{1,8})*$/;function J(e){if(!me.test(e))throw Error(`Invalid locale format: "${e}"`);return e}var he=[{dep:`next`,name:`nextjs`,pluginPackage:`@fluenti/next`},{dep:`nuxt`,name:`nuxt`,pluginPackage:`@fluenti/vue`},{dep:`@solidjs/start`,name:`solidstart`,pluginPackage:`@fluenti/solid`},{dep:`vue`,name:`vue`,pluginPackage:`@fluenti/vue`},{dep:`solid-js`,name:`solid`,pluginPackage:`@fluenti/solid`},{dep:`react`,name:`react`,pluginPackage:`@fluenti/react`}];function ge(e){for(let t of he)if(t.dep in e)return{name:t.name,pluginPackage:t.pluginPackage};return{name:`unknown`,pluginPackage:null}}function _e(e){let t=e.locales.map(e=>JSON.stringify(e)).join(`, `);return`import { defineConfig } from '@fluenti/cli'
11
12
 
12
13
  export default defineConfig({
13
- sourceLocale: '${e.sourceLocale}',
14
+ sourceLocale: ${JSON.stringify(e.sourceLocale)},
14
15
  locales: [${t}],
15
16
  catalogDir: './locales',
16
- format: '${e.format}',
17
+ format: ${JSON.stringify(e.format)},
17
18
  include: ['./src/**/*.{vue,tsx,jsx,ts,js}'],
18
19
  compileOutDir: './src/locales/compiled',
19
20
  })
20
- `}async function le(e){let t=(0,a.resolve)(e.cwd,`package.json`);if(!(0,i.existsSync)(t)){u.default.error(`No package.json found in current directory.`);return}let n=JSON.parse((0,i.readFileSync)(t,`utf-8`)),r=se({...n.dependencies,...n.devDependencies});u.default.info(`Detected framework: ${r.name}`),r.pluginPackage&&u.default.info(`Recommended plugin: ${r.pluginPackage}`);let o=(0,a.resolve)(e.cwd,`fluenti.config.ts`);if((0,i.existsSync)(o)){u.default.warn(`fluenti.config.ts already exists. Skipping config generation.`);return}let s=await u.default.prompt(`Source locale?`,{type:`text`,default:`en`,placeholder:`en`});if(typeof s==`symbol`)return;let c=await u.default.prompt(`Target locales (comma-separated)?`,{type:`text`,default:`ja,zh-CN`,placeholder:`ja,zh-CN`});if(typeof c==`symbol`)return;let l=await u.default.prompt(`Catalog format?`,{type:`select`,options:[`po`,`json`],initial:`po`});if(typeof l==`symbol`)return;let d=c.split(`,`).map(e=>e.trim()).filter(Boolean);K(s);for(let e of d)K(e);(0,i.writeFileSync)(o,ce({sourceLocale:s,locales:[s,...d.filter(e=>e!==s)],format:l}),`utf-8`),u.default.success(`Created fluenti.config.ts`);let f=(0,a.resolve)(e.cwd,`.gitignore`),p=`src/locales/compiled/`;(0,i.existsSync)(f)?(0,i.readFileSync)(f,`utf-8`).includes(p)||((0,i.appendFileSync)(f,`\n# Fluenti compiled catalogs\n${p}\n`),u.default.success(`Updated .gitignore`)):((0,i.writeFileSync)(f,`# Fluenti compiled catalogs\n${p}\n`),u.default.success(`Created .gitignore`));let m=n.scripts??{},h={},g=!1;if(m[`i18n:extract`]||(h[`i18n:extract`]=`fluenti extract`,g=!0),m[`i18n:compile`]||(h[`i18n:compile`]=`fluenti compile`,g=!0),g){let e={...n,scripts:{...m,...h}};(0,i.writeFileSync)(t,JSON.stringify(e,null,2)+`
21
+ `}async function ve(e){let t=(0,a.resolve)(e.cwd,`package.json`);if(!(0,i.existsSync)(t)){u.default.error(`No package.json found in current directory.`);return}let n=JSON.parse((0,i.readFileSync)(t,`utf-8`)),r=ge({...n.dependencies,...n.devDependencies});u.default.info(`Detected framework: ${r.name}`),r.pluginPackage&&u.default.info(`Recommended plugin: ${r.pluginPackage}`);let o=(0,a.resolve)(e.cwd,`fluenti.config.ts`);if((0,i.existsSync)(o)){u.default.warn(`fluenti.config.ts already exists. Skipping config generation.`);return}let s=await u.default.prompt(`Source locale?`,{type:`text`,default:`en`,placeholder:`en`});if(typeof s==`symbol`)return;let c=await u.default.prompt(`Target locales (comma-separated)?`,{type:`text`,default:`ja,zh-CN`,placeholder:`ja,zh-CN`});if(typeof c==`symbol`)return;let l=await u.default.prompt(`Catalog format?`,{type:`select`,options:[`po`,`json`],initial:`po`});if(typeof l==`symbol`)return;let d=c.split(`,`).map(e=>e.trim()).filter(Boolean);J(s);for(let e of d)J(e);(0,i.writeFileSync)(o,_e({sourceLocale:s,locales:[s,...d.filter(e=>e!==s)],format:l}),`utf-8`),u.default.success(`Created fluenti.config.ts`);let f=(0,a.resolve)(e.cwd,`.gitignore`),p=`src/locales/compiled/`;(0,i.existsSync)(f)?(0,i.readFileSync)(f,`utf-8`).includes(p)||((0,i.appendFileSync)(f,`\n# Fluenti compiled catalogs\n${p}\n`),u.default.success(`Updated .gitignore`)):((0,i.writeFileSync)(f,`# Fluenti compiled catalogs\n${p}\n`),u.default.success(`Created .gitignore`));let m=n.scripts??{},h={},g=!1;if(m[`i18n:extract`]||(h[`i18n:extract`]=`fluenti extract`,g=!0),m[`i18n:compile`]||(h[`i18n:compile`]=`fluenti compile`,g=!0),g){let e={...n,scripts:{...m,...h}};(0,i.writeFileSync)(t,JSON.stringify(e,null,2)+`
21
22
  `,`utf-8`),u.default.success(`Added i18n:extract and i18n:compile scripts to package.json`)}u.default.log(``),u.default.box({title:`Next steps`,message:[r.pluginPackage?`1. Install: pnpm add -D ${r.pluginPackage} @fluenti/cli`:`1. Install: pnpm add -D @fluenti/cli`,r.name===`nextjs`?`2. Add withFluenti() to your next.config.ts`:r.name===`unknown`?`2. Configure your build tool with the framework Vite plugin or @fluenti/next`:`2. Add the Vite plugin to your vite.config.ts (e.g. fluentiVue() from @fluenti/vue/vite-plugin)`,`3. Run: npx fluenti extract`,`4. Translate your messages`,`5. Run: npx fluenti compile`].join(`
22
- `)})}function J(e){return(0,c.createHash)(`md5`).update(e).digest(`hex`).slice(0,8)}function Y(t,n){if(!(0,i.existsSync)(t))return{};let r=(0,i.readFileSync)(t,`utf-8`);return n===`json`?e.a(r):e.r(r)}function X(t,n,r){(0,i.mkdirSync)((0,a.dirname)(t),{recursive:!0}),(0,i.writeFileSync)(t,r===`json`?e.o(n):e.i(n),`utf-8`)}async function ue(e,n,r){if((0,a.extname)(e)===`.vue`)try{let{extractFromVue:t}=await Promise.resolve().then(()=>require(`./vue-extractor.cjs`));return t(n,e,r)}catch{return u.default.warn(`Skipping ${e}: install @vue/compiler-sfc to extract from .vue files`),[]}return t.t(n,e,r)}var de=(0,l.defineCommand)({meta:{name:`extract`,description:`Extract messages from source files`},args:{config:{type:`string`,description:`Path to config file`},clean:{type:`boolean`,description:`Remove obsolete entries instead of marking them`,default:!1},"no-fuzzy":{type:`boolean`,description:`Strip fuzzy flags from all entries`,default:!1},"no-cache":{type:`boolean`,description:`Disable incremental extraction cache`,default:!1}},async run({args:t}){let n=await(0,m.loadConfig)(t.config),o=(0,r.resolveLocaleCodes)(n.locales);u.default.info(`Extracting messages from ${n.include.join(`, `)}`);let c=await(0,s.default)(n.include,{ignore:n.exclude??[]}),l=[],d=t[`no-cache`]??!1?null:new e.t(n.catalogDir,J(process.cwd())),f=0;for(let e of c){if(d){let t=d.get(e);if(t){l.push(...t),f++;continue}}let t=await ue(e,(0,i.readFileSync)(e,`utf-8`),n.idGenerator);l.push(...t),d&&d.set(e,t)}d&&(d.prune(new Set(c)),d.save()),f>0?u.default.info(`Found ${l.length} messages in ${c.length} files (${f} cached)`):u.default.info(`Found ${l.length} messages in ${c.length} files`);let p=n.format===`json`?`.json`:`.po`,h=t.clean??!1,g=t[`no-fuzzy`]??!1;for(let t of o){let r=(0,a.resolve)(n.catalogDir,`${t}${p}`),{catalog:i,result:o}=e.s(Y(r,n.format),l,{stripFuzzy:g});X(r,h?Object.fromEntries(Object.entries(i).filter(([,e])=>!e.obsolete)):i,n.format);let s=h?`${o.obsolete} removed`:`${o.obsolete} obsolete`;u.default.success(`${t}: ${o.added} added, ${o.unchanged} unchanged, ${s}`)}for(let e of n.plugins??[])await e.onAfterExtract?.({messages:new Map(l.map(e=>[e.id,e])),sourceLocale:n.sourceLocale,targetLocales:o.filter(e=>e!==n.sourceLocale),config:n})}});function fe(e){let t={};for(let[n,r]of Object.entries(e))r.translation&&r.translation.length>0?t[n]=r.translation:r.message&&(t[n]=r.message);return t}async function Z(e,t,n){let r=fe(e);for(let e of n)e.transformMessages&&(r=await e.transformMessages(r,t));let i={};for(let[t,n]of Object.entries(e)){let e=r[t];i[t]=e===void 0?{...n}:{...n,translation:e}}return i}function Q(e,t,n,r){let i={};for(let[e,n]of Object.entries(t))n.translation&&n.translation.length>0?i[e]=n.translation:n.message&&(i[e]=n.message);return{locale:e,messages:i,outDir:n,config:r}}var pe=(0,l.defineCommand)({meta:{name:`compile`,description:`Compile message catalogs to JS modules`},args:{config:{type:`string`,description:`Path to config file`},"skip-fuzzy":{type:`boolean`,description:`Exclude fuzzy entries from compilation`,default:!1},"no-cache":{type:`boolean`,description:`Disable compilation cache`,default:!1},parallel:{type:`boolean`,description:`Enable parallel compilation using worker threads`,default:!1},concurrency:{type:`string`,description:`Max number of worker threads (default: auto)`}},async run({args:t}){let o=await(0,m.loadConfig)(t.config),s=(0,r.resolveLocaleCodes)(o.locales),c=o.format===`json`?`.json`:`.po`;(0,i.mkdirSync)(o.compileOutDir,{recursive:!0});let l={},d={};for(let t of s){let n=(0,a.resolve)(o.catalogDir,`${t}${c}`);if((0,i.existsSync)(n)){let r=(0,i.readFileSync)(n,`utf-8`);d[t]=r,l[t]=o.format===`json`?e.a(r):e.r(r)}else d[t]=``,l[t]={}}let f=n.t(l);u.default.info(`Compiling ${f.length} messages across ${s.length} locales`);let p=t[`skip-fuzzy`]??!1,h=t[`no-cache`]??!1?null:new ne(o.catalogDir,J(process.cwd())),g=t.parallel??!1,_=t.concurrency?parseInt(t.concurrency,10):void 0;if(_!==void 0&&(isNaN(_)||_<1)){u.default.error(`Invalid --concurrency. Must be a positive integer.`),process.exitCode=1;return}let v=0,y=!1,b=[];for(let e of s){if(h&&h.isUpToDate(e,d[e])&&(0,i.existsSync)((0,a.resolve)(o.compileOutDir,`${e}.js`))){v++;continue}b.push(e)}if(b.length>0&&(y=!0),g&&b.length>1){let t=o.plugins??[],n={};for(let e of b){for(let n of t)await n.onBeforeCompile?.(Q(e,l[e],o.compileOutDir,o));n[e]=t.length>0?await Z(l[e],e,t):l[e]}let r=await e.n(b.map(e=>({locale:e,catalog:n[e],allIds:f,sourceLocale:o.sourceLocale,options:{skipFuzzy:p}})),_);for(let e of r){let t=(0,a.resolve)(o.compileOutDir,`${e.locale}.js`);if((0,i.writeFileSync)(t,e.code,`utf-8`),h&&h.set(e.locale,d[e.locale]),e.stats.missing.length>0){u.default.warn(`${e.locale}: ${e.stats.compiled} compiled, ${e.stats.missing.length} missing translations`);for(let t of e.stats.missing)u.default.warn(` ⤷ ${t}`)}else u.default.success(`Compiled ${e.locale}: ${e.stats.compiled} messages → ${t}`)}for(let e of b)for(let r of t)await r.onAfterCompile?.(Q(e,n[e],o.compileOutDir,o))}else{let e=o.plugins??[];for(let t of b){let r=(0,a.resolve)(o.compileOutDir,`${t}.js`);for(let n of e)await n.onBeforeCompile?.(Q(t,l[t],o.compileOutDir,o));let s=e.length>0?await Z(l[t],t,e):l[t],{code:c,stats:m}=n.n(s,t,f,o.sourceLocale,{skipFuzzy:p});if((0,i.writeFileSync)(r,c,`utf-8`),h&&h.set(t,d[t]),m.missing.length>0){u.default.warn(`${t}: ${m.compiled} compiled, ${m.missing.length} missing translations`);for(let e of m.missing)u.default.warn(` ⤷ ${e}`)}else u.default.success(`Compiled ${t}: ${m.compiled} messages → ${r}`);for(let n of e)await n.onAfterCompile?.(Q(t,s,o.compileOutDir,o))}}v>0&&u.default.info(`${v} locale(s) unchanged — skipped`),h&&h.save();let x=(0,a.resolve)(o.compileOutDir,`index.js`),S=(0,a.resolve)(o.compileOutDir,`messages.d.ts`);(y||!(0,i.existsSync)(x))&&((0,i.writeFileSync)(x,n.r(s,o.compileOutDir),`utf-8`),u.default.success(`Generated index → ${x}`)),(y||!(0,i.existsSync)(S))&&((0,i.writeFileSync)(S,n.i(f,l,o.sourceLocale),`utf-8`),u.default.success(`Generated types → ${S}`))}}),$=(0,l.defineCommand)({meta:{name:`stats`,description:`Show translation progress`},args:{config:{type:`string`,description:`Path to config file`}},async run({args:e}){let t=await(0,m.loadConfig)(e.config),n=(0,r.resolveLocaleCodes)(t.locales),i=t.format===`json`?`.json`:`.po`,o=[];for(let e of n){let n=Y((0,a.resolve)(t.catalogDir,`${e}${i}`),t.format),r=Object.values(n).filter(e=>!e.obsolete),s=r.length,c=r.filter(e=>e.translation&&e.translation.length>0).length,l=s>0?(c/s*100).toFixed(1)+`%`:`—`;o.push({locale:e,total:s,translated:c,pct:l})}u.default.log(``),u.default.log(` Locale │ Total │ Translated │ Progress`),u.default.log(` ────────┼───────┼────────────┼─────────────────────────────`);for(let e of o)u.default.log(y(e.locale,e.total,e.translated));u.default.log(``)}}),me=(0,l.defineCommand)({meta:{name:`lint`,description:`Check translation quality (missing, inconsistent placeholders, fuzzy)`},args:{config:{type:`string`,description:`Path to config file`},strict:{type:`boolean`,description:`Treat warnings as errors`,default:!1},locale:{type:`string`,description:`Lint a specific locale only`}},async run({args:e}){let t=await(0,m.loadConfig)(e.config),n=(0,r.resolveLocaleCodes)(t.locales),i=t.format===`json`?`.json`:`.po`,o={};for(let e of n)o[e]=Y((0,a.resolve)(t.catalogDir,`${e}${i}`),t.format);let s=e.locale?[e.locale]:void 0;u.default.info(`Linting ${s?s.join(`, `):`all locales`} (source: ${t.sourceLocale})`);let c={sourceLocale:t.sourceLocale,strict:e.strict??!1};s&&(c.locales=s);let l=S(o,c);u.default.log(``),u.default.log(ee(l)),u.default.log(``);let d=l.filter(e=>e.severity===`error`),f=l.filter(e=>e.severity===`warning`);(d.length>0||e.strict&&f.length>0)&&(process.exitCode=1)}}),he=(0,l.defineCommand)({meta:{name:`check`,description:`Check translation coverage for CI`},args:{config:{type:`string`,description:`Path to config file`},ci:{type:`boolean`,description:`Alias for --format github`,default:!1},"min-coverage":{type:`string`,description:`Minimum coverage percentage (0-100)`,default:`100`},format:{type:`string`,description:`Output format: text | json | github`},locale:{type:`string`,description:`Check a specific locale only`}},async run({args:e}){let t=await(0,m.loadConfig)(e.config),n=(0,r.resolveLocaleCodes)(t.locales),i=t.format===`json`?`.json`:`.po`,o={};for(let e of n)o[e]=Y((0,a.resolve)(t.catalogDir,`${e}${i}`),t.format);let s=parseFloat(e[`min-coverage`]??`100`);if(isNaN(s)||s<0||s>100){u.default.error(`Invalid --min-coverage. Must be a number between 0 and 100.`),process.exitCode=1;return}let c=e.format??(e.ci?`github`:`text`),l={sourceLocale:t.sourceLocale,minCoverage:s,format:c};e.locale&&(l.locale=e.locale);let d=C(o,l);switch(c){case`json`:u.default.log(E(d));break;case`github`:u.default.log(T(d,t.catalogDir,t.format));break;default:u.default.log(``),u.default.log(w(d)),u.default.log(``);break}d.passed||(process.exitCode=1)}}),ge=(0,l.defineCommand)({meta:{name:`translate`,description:`Translate messages using AI (Claude Code or Codex CLI)`},args:{config:{type:`string`,description:`Path to config file`},provider:{type:`string`,description:`AI provider: claude or codex`,default:`claude`},locale:{type:`string`,description:`Translate a specific locale only`},"batch-size":{type:`string`,description:`Messages per batch`,default:`50`},"dry-run":{type:`boolean`,description:`Preview translation results without writing files`,default:!1},context:{type:`string`,description:`Project context description to improve translation quality`}},async run({args:e}){let t=await(0,m.loadConfig)(e.config),n=(0,r.resolveLocaleCodes)(t.locales),i=e.provider;if(i!==`claude`&&i!==`codex`){u.default.error(`Invalid provider "${i}". Use "claude" or "codex".`);return}let o=parseInt(e[`batch-size`]??`50`,10);if(isNaN(o)||o<1){u.default.error(`Invalid batch-size. Must be a positive integer.`);return}let s=e.locale?[e.locale]:n.filter(e=>e!==t.sourceLocale);if(s.length===0){u.default.warn(`No target locales to translate.`);return}u.default.info(`Translating with ${i} (batch size: ${o})`);let c=t.format===`json`?`.json`:`.po`;for(let n of s){u.default.info(`\n[${n}]`);let r=(0,a.resolve)(t.catalogDir,`${n}${c}`),s=Y(r,t.format);if(e[`dry-run`]){let e=Object.entries(s).filter(([,e])=>!e.obsolete&&(!e.translation||e.translation.length===0));if(e.length>0){for(let[t,n]of e)u.default.log(` ${t}: ${n.message??t}`);u.default.success(` ${n}: ${e.length} messages would be translated (dry-run)`)}else u.default.success(` ${n}: already fully translated`);continue}let{catalog:l,translated:d}=await F({provider:i,sourceLocale:t.sourceLocale,targetLocale:n,catalog:s,batchSize:o,...e.context?{context:e.context}:{}});d>0?(X(r,l,t.format),u.default.success(` ${n}: ${d} messages translated`)):u.default.success(` ${n}: already fully translated`)}}}),_e=(0,l.defineCommand)({meta:{name:`migrate`,description:`Migrate from another i18n library using AI`},args:{from:{type:`string`,description:`Source library: vue-i18n, nuxt-i18n, react-i18next, next-intl, next-i18next, lingui`,required:!0},provider:{type:`string`,description:`AI provider: claude or codex`,default:`claude`},write:{type:`boolean`,description:`Write generated files to disk`,default:!1}},async run({args:e}){let t=e.provider;if(t!==`claude`&&t!==`codex`){u.default.error(`Invalid provider "${t}". Use "claude" or "codex".`);return}await oe({from:e.from,provider:t,write:e.write??!1})}});(0,l.runMain)((0,l.defineCommand)({meta:{name:`fluenti`,version:`0.0.1`,description:`Compile-time i18n for modern frameworks`},subCommands:{init:(0,l.defineCommand)({meta:{name:`init`,description:`Initialize Fluenti in your project`},args:{},async run(){await le({cwd:process.cwd()})}}),extract:de,compile:pe,stats:$,lint:me,check:he,translate:ge,migrate:_e}}));
23
+ `)})}function Y(e){return(0,c.createHash)(`md5`).update(e).digest(`hex`).slice(0,8)}function X(t,n){if(!(0,i.existsSync)(t))return{};let r=(0,i.readFileSync)(t,`utf-8`);return n===`json`?e.a(r):e.r(r)}function Z(t,n,r){(0,i.mkdirSync)((0,a.dirname)(t),{recursive:!0}),(0,i.writeFileSync)(t,r===`json`?e.o(n):e.i(n),`utf-8`)}async function ye(e,n,r){if((0,a.extname)(e)===`.vue`)try{let{extractFromVue:t}=await Promise.resolve().then(()=>require(`./vue-extractor.cjs`));return t(n,e,r)}catch{return u.default.warn(`Skipping ${e}: install @vue/compiler-sfc to extract from .vue files`),[]}return t.t(n,e,r)}var be=(0,l.defineCommand)({meta:{name:`extract`,description:`Extract messages from source files`},args:{config:{type:`string`,description:`Path to config file`},clean:{type:`boolean`,description:`Remove obsolete entries instead of marking them`,default:!1},"no-fuzzy":{type:`boolean`,description:`Strip fuzzy flags from all entries`,default:!1},"no-cache":{type:`boolean`,description:`Disable incremental extraction cache`,default:!1}},async run({args:t}){let n=await(0,m.loadConfig)(t.config),o=(0,r.resolveLocaleCodes)(n.locales);u.default.info(`Extracting messages from ${n.include.join(`, `)}`);let c=await(0,s.default)(n.include,{ignore:n.exclude??[]}),l=[],d=t[`no-cache`]??!1?null:new e.t(n.catalogDir,Y(process.cwd())),f=0;for(let e of c){if(d){let t=d.get(e);if(t){l.push(...t),f++;continue}}let t=await ye(e,(0,i.readFileSync)(e,`utf-8`),n.idGenerator);l.push(...t),d&&d.set(e,t)}d&&(d.prune(new Set(c)),d.save()),f>0?u.default.info(`Found ${l.length} messages in ${c.length} files (${f} cached)`):u.default.info(`Found ${l.length} messages in ${c.length} files`);let p=n.format===`json`?`.json`:`.po`,h=t.clean??!1,g=t[`no-fuzzy`]??!1;for(let t of o){let r=(0,a.resolve)(n.catalogDir,`${t}${p}`),{catalog:i,result:o}=e.s(X(r,n.format),l,{stripFuzzy:g});Z(r,h?Object.fromEntries(Object.entries(i).filter(([,e])=>!e.obsolete)):i,n.format);let s=h?`${o.obsolete} removed`:`${o.obsolete} obsolete`;u.default.success(`${t}: ${o.added} added, ${o.unchanged} unchanged, ${s}`)}for(let e of n.plugins??[])await e.onAfterExtract?.({messages:new Map(l.map(e=>[e.id,e])),sourceLocale:n.sourceLocale,targetLocales:o.filter(e=>e!==n.sourceLocale),config:n})}});function xe(e){let t={};for(let[n,r]of Object.entries(e))r.translation&&r.translation.length>0?t[n]=r.translation:r.message&&(t[n]=r.message);return t}async function Q(e,t,n){let r=xe(e);for(let e of n)e.transformMessages&&(r=await e.transformMessages(r,t));let i={};for(let[t,n]of Object.entries(e)){let e=r[t];i[t]=e===void 0?{...n}:{...n,translation:e}}return i}function $(e,t,n,r){let i={};for(let[e,n]of Object.entries(t))n.translation&&n.translation.length>0?i[e]=n.translation:n.message&&(i[e]=n.message);return{locale:e,messages:i,outDir:n,config:r}}var Se=(0,l.defineCommand)({meta:{name:`compile`,description:`Compile message catalogs to JS modules`},args:{config:{type:`string`,description:`Path to config file`},"skip-fuzzy":{type:`boolean`,description:`Exclude fuzzy entries from compilation`,default:!1},"no-cache":{type:`boolean`,description:`Disable compilation cache`,default:!1},parallel:{type:`boolean`,description:`Enable parallel compilation using worker threads`,default:!1},concurrency:{type:`string`,description:`Max number of worker threads (default: auto)`}},async run({args:t}){let o=await(0,m.loadConfig)(t.config),s=(0,r.resolveLocaleCodes)(o.locales),c=o.format===`json`?`.json`:`.po`;(0,i.mkdirSync)(o.compileOutDir,{recursive:!0});let l={},d={};for(let t of s){let n=(0,a.resolve)(o.catalogDir,`${t}${c}`);if((0,i.existsSync)(n)){let r=(0,i.readFileSync)(n,`utf-8`);d[t]=r,l[t]=o.format===`json`?e.a(r):e.r(r)}else d[t]=``,l[t]={}}let f=n.t(l);u.default.info(`Compiling ${f.length} messages across ${s.length} locales`);let p=t[`skip-fuzzy`]??!1,h=t[`no-cache`]??!1?null:new A(o.catalogDir,Y(process.cwd())),g=t.parallel??!1,_=t.concurrency?parseInt(t.concurrency,10):void 0;if(_!==void 0&&(isNaN(_)||_<1)){u.default.error(`Invalid --concurrency. Must be a positive integer.`),process.exitCode=1;return}let v=0,y=!1,b=[];for(let e of s){if(h&&h.isUpToDate(e,d[e])&&(0,i.existsSync)((0,a.resolve)(o.compileOutDir,`${e}.js`))){v++;continue}b.push(e)}if(b.length>0&&(y=!0),g&&b.length>1){let t=o.plugins??[],n={};for(let e of b){for(let n of t)await n.onBeforeCompile?.($(e,l[e],o.compileOutDir,o));n[e]=t.length>0?await Q(l[e],e,t):l[e]}let r=await e.n(b.map(e=>({locale:e,catalog:n[e],allIds:f,sourceLocale:o.sourceLocale,options:{skipFuzzy:p}})),_);for(let e of r){let t=(0,a.resolve)(o.compileOutDir,`${e.locale}.js`);if((0,i.writeFileSync)(t,e.code,`utf-8`),h&&h.set(e.locale,d[e.locale]),e.stats.missing.length>0){u.default.warn(`${e.locale}: ${e.stats.compiled} compiled, ${e.stats.missing.length} missing translations`);for(let t of e.stats.missing)u.default.warn(` ⤷ ${t}`)}else u.default.success(`Compiled ${e.locale}: ${e.stats.compiled} messages → ${t}`)}for(let e of b)for(let r of t)await r.onAfterCompile?.($(e,n[e],o.compileOutDir,o))}else{let e=o.plugins??[];for(let t of b){let r=(0,a.resolve)(o.compileOutDir,`${t}.js`);for(let n of e)await n.onBeforeCompile?.($(t,l[t],o.compileOutDir,o));let s=e.length>0?await Q(l[t],t,e):l[t],{code:c,stats:m}=n.n(s,t,f,o.sourceLocale,{skipFuzzy:p});if((0,i.writeFileSync)(r,c,`utf-8`),h&&h.set(t,d[t]),m.missing.length>0){u.default.warn(`${t}: ${m.compiled} compiled, ${m.missing.length} missing translations`);for(let e of m.missing)u.default.warn(` ⤷ ${e}`)}else u.default.success(`Compiled ${t}: ${m.compiled} messages → ${r}`);for(let n of e)await n.onAfterCompile?.($(t,s,o.compileOutDir,o))}}v>0&&u.default.info(`${v} locale(s) unchanged — skipped`),h&&h.save();let x=(0,a.resolve)(o.compileOutDir,`index.js`),S=(0,a.resolve)(o.compileOutDir,`messages.d.ts`);(y||!(0,i.existsSync)(x))&&((0,i.writeFileSync)(x,n.r(s,o.compileOutDir),`utf-8`),u.default.success(`Generated index → ${x}`)),(y||!(0,i.existsSync)(S))&&((0,i.writeFileSync)(S,n.i(f,l,o.sourceLocale),`utf-8`),u.default.success(`Generated types → ${S}`))}}),Ce=(0,l.defineCommand)({meta:{name:`stats`,description:`Show translation progress`},args:{config:{type:`string`,description:`Path to config file`}},async run({args:e}){let t=await(0,m.loadConfig)(e.config),n=(0,r.resolveLocaleCodes)(t.locales),i=t.format===`json`?`.json`:`.po`,o=[];for(let e of n){let n=X((0,a.resolve)(t.catalogDir,`${e}${i}`),t.format),r=Object.values(n).filter(e=>!e.obsolete),s=r.length,c=r.filter(e=>e.translation&&e.translation.length>0).length,l=s>0?(c/s*100).toFixed(1)+`%`:`—`;o.push({locale:e,total:s,translated:c,pct:l})}u.default.log(``),u.default.log(` Locale │ Total │ Translated │ Progress`),u.default.log(` ────────┼───────┼────────────┼─────────────────────────────`);for(let e of o)u.default.log(y(e.locale,e.total,e.translated));u.default.log(``)}}),we=(0,l.defineCommand)({meta:{name:`lint`,description:`Check translation quality (missing, inconsistent placeholders, fuzzy)`},args:{config:{type:`string`,description:`Path to config file`},strict:{type:`boolean`,description:`Treat warnings as errors`,default:!1},locale:{type:`string`,description:`Lint a specific locale only`}},async run({args:e}){let t=await(0,m.loadConfig)(e.config),n=(0,r.resolveLocaleCodes)(t.locales),i=t.format===`json`?`.json`:`.po`,o={};for(let e of n)o[e]=X((0,a.resolve)(t.catalogDir,`${e}${i}`),t.format);let s=e.locale?[e.locale]:void 0;u.default.info(`Linting ${s?s.join(`, `):`all locales`} (source: ${t.sourceLocale})`);let c={sourceLocale:t.sourceLocale,strict:e.strict??!1};s&&(c.locales=s);let l=w(o,c);u.default.log(``),u.default.log(T(l)),u.default.log(``);let d=l.filter(e=>e.severity===`error`),f=l.filter(e=>e.severity===`warning`);(d.length>0||e.strict&&f.length>0)&&(process.exitCode=1)}}),Te=(0,l.defineCommand)({meta:{name:`check`,description:`Check translation coverage for CI`},args:{config:{type:`string`,description:`Path to config file`},ci:{type:`boolean`,description:`Alias for --format github`,default:!1},"min-coverage":{type:`string`,description:`Minimum coverage percentage (0-100)`,default:`100`},format:{type:`string`,description:`Output format: text | json | github`},locale:{type:`string`,description:`Check a specific locale only`}},async run({args:e}){let t=await(0,m.loadConfig)(e.config),n=(0,r.resolveLocaleCodes)(t.locales),i=t.format===`json`?`.json`:`.po`,o={};for(let e of n)o[e]=X((0,a.resolve)(t.catalogDir,`${e}${i}`),t.format);let s=parseFloat(e[`min-coverage`]??`100`);if(isNaN(s)||s<0||s>100){u.default.error(`Invalid --min-coverage. Must be a number between 0 and 100.`),process.exitCode=1;return}let c=e.format??(e.ci?`github`:`text`),l={sourceLocale:t.sourceLocale,minCoverage:s,format:c};e.locale&&(l.locale=e.locale);let d=E(o,l);switch(c){case`json`:u.default.log(O(d));break;case`github`:u.default.log(D(d,t.catalogDir,t.format));break;default:u.default.log(``),u.default.log(ne(d)),u.default.log(``);break}d.passed||(process.exitCode=1)}});async function Ee(e,t,n){let r=Array(e.length),i=0,a=Array.from({length:Math.min(n,e.length)},async()=>{for(;i<e.length;){let n=i++;r[n]=await t(e[n])}});return await Promise.all(a),r}var De=(0,l.defineCommand)({meta:{name:`translate`,description:`Translate messages using AI (Claude Code or Codex CLI)`},args:{config:{type:`string`,description:`Path to config file`},provider:{type:`string`,description:`AI provider: claude or codex`,default:`claude`},locale:{type:`string`,description:`Translate a specific locale only`},"batch-size":{type:`string`,description:`Messages per batch`,default:`50`},"dry-run":{type:`boolean`,description:`Preview translation results without writing files`,default:!1},context:{type:`string`,description:`Project context description to improve translation quality`},glossary:{type:`string`,description:`Path to glossary JSON file`},concurrency:{type:`string`,description:`Max parallel locale translations (default: 3)`},timeout:{type:`string`,description:`AI call timeout in seconds (default: 120)`}},async run({args:e}){let t=await(0,m.loadConfig)(e.config),n=(0,r.resolveLocaleCodes)(t.locales),i=e.provider;if(i!==`claude`&&i!==`codex`){u.default.error(`Invalid provider "${i}". Use "claude" or "codex".`);return}let o=parseInt(e[`batch-size`]??`50`,10);if(isNaN(o)||o<1){u.default.error(`Invalid batch-size. Must be a positive integer.`);return}let s=e.glossary?N((0,a.resolve)(e.glossary)):void 0,c=e.concurrency?parseInt(e.concurrency,10):3;if(isNaN(c)||c<1){u.default.error(`Invalid concurrency. Must be a positive integer.`);return}let l=e.timeout?parseInt(e.timeout,10):120;if(isNaN(l)||l<1){u.default.error(`Invalid timeout. Must be a positive integer (seconds).`);return}let d=l*1e3,f=e.locale?[e.locale]:n.filter(e=>e!==t.sourceLocale);if(f.length===0){u.default.warn(`No target locales to translate.`);return}u.default.info(`Translating with ${i} (batch size: ${o})`);let p=t.format===`json`?`.json`:`.po`;await Ee(f,async n=>{u.default.info(`\n[${n}]`);let r=(0,a.resolve)(t.catalogDir,`${n}${p}`),c=X(r,t.format);if(e[`dry-run`]){let e=Object.entries(c).filter(([,e])=>!e.obsolete&&(!e.translation||e.translation.length===0));if(e.length>0){for(let[t,n]of e)u.default.log(` ${t}: ${n.message??t}`);u.default.success(` ${n}: ${e.length} messages would be translated (dry-run)`)}else u.default.success(` ${n}: already fully translated`);return}let l=s?P(s,n):void 0,{catalog:f,translated:m,warnings:h}=await U({provider:i,sourceLocale:t.sourceLocale,targetLocale:n,catalog:c,batchSize:o,glossary:l,timeoutMs:d,...e.context?{context:e.context}:{}});m>0?(Z(r,f,t.format),u.default.success(` ${n}: ${m} messages translated`)):u.default.success(` ${n}: already fully translated`),h.length>0&&u.default.warn(` ${n}: ${h.length} QA warnings`)},c)}}),Oe=(0,l.defineCommand)({meta:{name:`migrate`,description:`Migrate from another i18n library using AI`},args:{from:{type:`string`,description:`Source library: vue-i18n, nuxt-i18n, react-i18next, next-intl, next-i18next, lingui`,required:!0},provider:{type:`string`,description:`AI provider: claude or codex`,default:`claude`},write:{type:`boolean`,description:`Write generated files to disk`,default:!1}},async run({args:e}){let t=e.provider;if(t!==`claude`&&t!==`codex`){u.default.error(`Invalid provider "${t}". Use "claude" or "codex".`);return}await pe({from:e.from,provider:t,write:e.write??!1})}});(0,l.runMain)((0,l.defineCommand)({meta:{name:`fluenti`,version:`0.0.1`,description:`Compile-time i18n for modern frameworks`},subCommands:{init:(0,l.defineCommand)({meta:{name:`init`,description:`Initialize Fluenti in your project`},args:{},async run(){await ve({cwd:process.cwd()})}}),extract:be,compile:Se,stats:Ce,lint:we,check:Te,translate:De,migrate:Oe}}));
23
24
  //# sourceMappingURL=cli.cjs.map