@fluenti/cli 0.4.0-rc.4 → 0.5.0
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.
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +4 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.cjs
CHANGED
|
@@ -21,5 +21,5 @@ export default defineConfig({
|
|
|
21
21
|
})
|
|
22
22
|
`}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)+`
|
|
23
23
|
`,`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(`
|
|
24
|
-
`)})}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}}));
|
|
24
|
+
`)})}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??[],absolute:!1}),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}}));
|
|
25
25
|
//# sourceMappingURL=cli.cjs.map
|