@fluenti/cli 0.1.0 → 0.1.2
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 +2 -2
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +23 -11
- package/dist/cli.js.map +1 -1
- package/dist/compile-DK1UYkah.cjs +13 -0
- package/dist/compile-DK1UYkah.cjs.map +1 -0
- package/dist/{compile-McMlpGSK.js → compile-DuHUSzlx.js} +31 -17
- package/dist/compile-DuHUSzlx.js.map +1 -0
- package/dist/compile.d.ts +8 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/tsx-extractor.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/compile-CzSTgY0T.cjs +0 -13
- package/dist/compile-CzSTgY0T.cjs.map +0 -1
- package/dist/compile-McMlpGSK.js.map +0 -1
package/dist/cli.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const e=require(`./compile-
|
|
2
|
+
const e=require(`./compile-DK1UYkah.cjs`);let t=require(`citty`),n=require(`consola`);n=e.d(n);let r=require(`fast-glob`);r=e.d(r);let i=require(`node:fs`),a=require(`node:path`),o=require(`node:child_process`),s=require(`node:util`);var c=(0,s.promisify)(o.execFile);function l(e,t,n){let r=JSON.stringify(n,null,2);return[`You are a professional translator. Translate the following messages from "${e}" to "${t}".`,``,`Input (JSON):`,r,``,`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(`
|
|
3
3
|
`)}async function u(e,t){let n=10*1024*1024;try{if(e===`claude`){let{stdout:e}=await c(`claude`,[`-p`,t],{maxBuffer:n});return e}else{let{stdout:e}=await c(`codex`,[`-p`,t,`--full-auto`],{maxBuffer:n});return e}}catch(t){throw t.code===`ENOENT`?Error(`"${e}" CLI not found. Please install it first:\n`+(e===`claude`?` npm install -g @anthropic-ai/claude-code`:` npm install -g @openai/codex`)):t}}function d(e){let t=e.match(/\{[\s\S]*\}/);if(!t)throw Error(`No JSON object found in AI response`);let n=JSON.parse(t[0]);if(typeof n!=`object`||!n||Array.isArray(n))throw Error(`AI response is not a valid JSON object`);return n}function f(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 m(e){let{provider:t,sourceLocale:r,targetLocale:i,catalog:a,batchSize:o}=e,s=f(a),c=Object.keys(s).length;if(c===0)return{catalog:a,translated:0};n.default.info(` ${c} untranslated messages, translating with ${t}...`);let m=p(s,o),h=0;for(let e=0;e<m.length;e++){let o=m[e],s=Object.keys(o);m.length>1&&n.default.info(` Batch ${e+1}/${m.length} (${s.length} messages)`);let c=d(await u(t,l(r,i,o)));for(let e of s)c[e]&&typeof c[e]==`string`?(a[e]={...a[e],translation:c[e]},h++):n.default.warn(` Missing translation for key: ${e}`)}return{catalog:a,translated:h}}var h=(0,s.promisify)(o.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`}},_=Object.keys(g);function v(e){let t=e.toLowerCase().replace(/^@nuxtjs\//,`nuxt-`).replace(/^@/,``);return _.find(e=>e===t)}async function y(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 o=await(0,r.default)(e.localePatterns,{absolute:!1});for(let e of o.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)+`
|
|
4
4
|
... (truncated)`:n})}let s=await(0,r.default)(e.sourcePatterns,{absolute:!1});for(let e of s.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)+`
|
|
5
5
|
... (truncated)`:n})}return t}function b(e){let t=[(0,a.resolve)(`node_modules`,`@fluenti`,`cli`,`..`,`..`,e),(0,a.join)(__dirname,`..`,`..`,`..`,e),(0,a.join)(__dirname,`..`,`..`,e)];for(let e of t)if((0,i.existsSync)(e))return(0,i.readFileSync)(e,`utf-8`);return``}function x(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(`
|
|
6
6
|
`)}async function S(e,t){let n=10*1024*1024;try{if(e===`claude`){let{stdout:e}=await h(`claude`,[`-p`,t],{maxBuffer:n});return e}else{let{stdout:e}=await h(`codex`,[`-p`,t,`--full-auto`],{maxBuffer:n});return e}}catch(t){throw t.code===`ENOENT`?Error(`"${e}" CLI not found. Please install it first:\n`+(e===`claude`?` npm install -g @anthropic-ai/claude-code`:` npm install -g @openai/codex`)):t}}function C(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 w(e){let{from:t,provider:r,write:i}=e,o=v(t);if(!o){n.default.error(`Unsupported library "${t}". Supported libraries:`);for(let e of _)n.default.log(` - ${e}`);return}let s=g[o];n.default.info(`Migrating from ${s.name} (${s.framework}) to Fluenti`),n.default.info(`Scanning project for existing i18n files...`);let c=await y(s);if(c.configFiles.length===0&&c.localeFiles.length===0){n.default.warn(`No ${s.name} configuration or locale files found.`),n.default.info(`Make sure you are running this command from the project root directory.`);return}n.default.info(`Found: ${c.configFiles.length} config file(s), ${c.localeFiles.length} locale file(s), ${c.sampleSources.length} source file(s)`);let l=b(s.migrationGuide);n.default.info(`Generating migration plan with ${r}...`);let u=C(await S(r,x(s,c,l)));if(u.installCommands&&(n.default.log(``),n.default.box({title:`Install Commands`,message:u.installCommands})),u.config)if(i){let{writeFileSync:e}=await import(`node:fs`),t=(0,a.resolve)(`fluenti.config.ts`);e(t,u.config,`utf-8`),n.default.success(`Written: ${t}`)}else n.default.log(``),n.default.box({title:`fluenti.config.ts`,message:u.config});if(u.localeFiles.length>0)if(i){let{writeFileSync:e,mkdirSync:t}=await import(`node:fs`),r=`./locales`;t((0,a.resolve)(r),{recursive:!0});for(let t of u.localeFiles){let i=(0,a.resolve)(r,`${t.locale}.po`);e(i,t.content,`utf-8`),n.default.success(`Written: ${i}`)}}else for(let e of u.localeFiles)n.default.log(``),n.default.box({title:`locales/${e.locale}.po`,message:e.content.length>500?e.content.slice(0,500)+`
|
|
7
|
-
... (use --write to save full file)`:e.content});u.steps&&(n.default.log(``),n.default.box({title:`Migration Steps`,message:u.steps})),!i&&(u.config||u.localeFiles.length>0)&&(n.default.log(``),n.default.info(`Run with --write to save generated files to disk:`),n.default.log(` fluenti migrate --from ${t} --write`))}var T={sourceLocale:`en`,locales:[`en`],catalogDir:`./locales`,format:`po`,include:[`./src/**/*.{vue,tsx,jsx,ts,js}`],compileOutDir:`./locales/compiled`};async function E(e){let t=e?[(0,a.resolve)(e)]:[(0,a.resolve)(`fluenti.config.ts`),(0,a.resolve)(`fluenti.config.js`),(0,a.resolve)(`fluenti.config.mjs`)];for(let e of t)if((0,i.existsSync)(e))try{let{createJiti:t}=await import(`jiti`),n=await t({}.url).import(e),r=n.default??n;return{...T,...r}}catch{n.default.warn(`Failed to load config from ${e}, using defaults`)}return T}function D(t,n){if(!(0,i.existsSync)(t))return{};let r=(0,i.readFileSync)(t,`utf-8`);return n===`json`?e.o(r):e.i(r)}function O(t,n,r){(0,i.mkdirSync)((0,a.dirname)(t),{recursive:!0}),(0,i.writeFileSync)(t,r===`json`?e.s(n):e.a(n),`utf-8`)}function k(t,n){return(0,a.extname)(t)===`.vue`?e.l(n,t):e.u(n,t)}(0,t.runMain)((0,t.defineCommand)({meta:{name:`fluenti`,version:`0.0.1`,description:`Compile-time i18n for modern frameworks`},subCommands:{extract:(0,t.defineCommand)({meta:{name:`extract`,description:`Extract messages from source files`},args:{config:{type:`string`,description:`Path to config file`}},async run({args:t}){let o=await E(t.config);n.default.info(`Extracting messages from ${o.include.join(`, `)}`);let s=await(0,r.default)(o.include
|
|
7
|
+
... (use --write to save full file)`:e.content});u.steps&&(n.default.log(``),n.default.box({title:`Migration Steps`,message:u.steps})),!i&&(u.config||u.localeFiles.length>0)&&(n.default.log(``),n.default.info(`Run with --write to save generated files to disk:`),n.default.log(` fluenti migrate --from ${t} --write`))}var T={sourceLocale:`en`,locales:[`en`],catalogDir:`./locales`,format:`po`,include:[`./src/**/*.{vue,tsx,jsx,ts,js}`],compileOutDir:`./locales/compiled`};async function E(e){let t=e?[(0,a.resolve)(e)]:[(0,a.resolve)(`fluenti.config.ts`),(0,a.resolve)(`fluenti.config.js`),(0,a.resolve)(`fluenti.config.mjs`)];for(let e of t)if((0,i.existsSync)(e))try{let{createJiti:t}=await import(`jiti`),n=await t({}.url).import(e),r=n.default??n;return{...T,...r}}catch{n.default.warn(`Failed to load config from ${e}, using defaults`)}return T}function D(t,n){if(!(0,i.existsSync)(t))return{};let r=(0,i.readFileSync)(t,`utf-8`);return n===`json`?e.o(r):e.i(r)}function O(t,n,r){(0,i.mkdirSync)((0,a.dirname)(t),{recursive:!0}),(0,i.writeFileSync)(t,r===`json`?e.s(n):e.a(n),`utf-8`)}function k(t,n){return(0,a.extname)(t)===`.vue`?e.l(n,t):e.u(n,t)}(0,t.runMain)((0,t.defineCommand)({meta:{name:`fluenti`,version:`0.0.1`,description:`Compile-time i18n for modern frameworks`},subCommands:{extract:(0,t.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}},async run({args:t}){let o=await E(t.config);n.default.info(`Extracting messages from ${o.include.join(`, `)}`);let s=await(0,r.default)(o.include),c=[];for(let e of s){let t=k(e,(0,i.readFileSync)(e,`utf-8`));c.push(...t)}n.default.info(`Found ${c.length} messages in ${s.length} files`);let l=o.format===`json`?`.json`:`.po`,u=t.clean??!1;for(let t of o.locales){let r=(0,a.resolve)(o.catalogDir,`${t}${l}`),{catalog:i,result:s}=e.c(D(r,o.format),c);O(r,u?Object.fromEntries(Object.entries(i).filter(([,e])=>!e.obsolete)):i,o.format);let d=u?`${s.obsolete} removed`:`${s.obsolete} obsolete`;n.default.success(`${t}: ${s.added} added, ${s.unchanged} unchanged, ${d}`)}}}),compile:(0,t.defineCommand)({meta:{name:`compile`,description:`Compile message catalogs to JS modules`},args:{config:{type:`string`,description:`Path to config file`}},async run({args:t}){let r=await E(t.config),o=r.format===`json`?`.json`:`.po`;(0,i.mkdirSync)(r.compileOutDir,{recursive:!0});let s={};for(let e of r.locales)s[e]=D((0,a.resolve)(r.catalogDir,`${e}${o}`),r.format);let c=e.t(s);n.default.info(`Compiling ${c.length} messages across ${r.locales.length} locales`);for(let t of r.locales){let{code:o,stats:l}=e.n(s[t],t,c,r.sourceLocale),u=(0,a.resolve)(r.compileOutDir,`${t}.js`);if((0,i.writeFileSync)(u,o,`utf-8`),l.missing.length>0){n.default.warn(`${t}: ${l.compiled} compiled, ${l.missing.length} missing translations`);for(let e of l.missing)n.default.warn(` ⤷ ${e}`)}else n.default.success(`Compiled ${t}: ${l.compiled} messages → ${u}`)}let l=e.r(r.locales,r.compileOutDir),u=(0,a.resolve)(r.compileOutDir,`index.js`);(0,i.writeFileSync)(u,l,`utf-8`),n.default.success(`Generated index → ${u}`)}}),stats:(0,t.defineCommand)({meta:{name:`stats`,description:`Show translation progress`},args:{config:{type:`string`,description:`Path to config file`}},async run({args:e}){let t=await E(e.config),r=t.format===`json`?`.json`:`.po`,i=[];for(let e of t.locales){let n=D((0,a.resolve)(t.catalogDir,`${e}${r}`),t.format),o=Object.values(n).filter(e=>!e.obsolete),s=o.length,c=o.filter(e=>e.translation&&e.translation.length>0).length,l=s>0?(c/s*100).toFixed(1)+`%`:`—`;i.push({locale:e,total:s,translated:c,pct:l})}n.default.log(``),n.default.log(` Locale │ Total │ Translated │ Progress`),n.default.log(` ────────┼───────┼────────────┼─────────`);for(let e of i)n.default.log(` ${e.locale.padEnd(8)}│ ${String(e.total).padStart(5)} │ ${String(e.translated).padStart(10)} │ ${e.pct}`);n.default.log(``)}}),translate:(0,t.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`}},async run({args:e}){let t=await E(e.config),r=e.provider;if(r!==`claude`&&r!==`codex`){n.default.error(`Invalid provider "${r}". Use "claude" or "codex".`);return}let i=parseInt(e[`batch-size`]??`50`,10);if(isNaN(i)||i<1){n.default.error(`Invalid batch-size. Must be a positive integer.`);return}let o=e.locale?[e.locale]:t.locales.filter(e=>e!==t.sourceLocale);if(o.length===0){n.default.warn(`No target locales to translate.`);return}n.default.info(`Translating with ${r} (batch size: ${i})`);let s=t.format===`json`?`.json`:`.po`;for(let e of o){n.default.info(`\n[${e}]`);let o=(0,a.resolve)(t.catalogDir,`${e}${s}`),c=D(o,t.format),{catalog:l,translated:u}=await m({provider:r,sourceLocale:t.sourceLocale,targetLocale:e,catalog:c,batchSize:i});u>0?(O(o,l,t.format),n.default.success(` ${e}: ${u} messages translated`)):n.default.success(` ${e}: already fully translated`)}}}),migrate:(0,t.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`){n.default.error(`Invalid provider "${t}". Use "claude" or "codex".`);return}await w({from:e.from,provider:t,write:e.write??!1})}})}}));
|
|
8
8
|
//# sourceMappingURL=cli.cjs.map
|
package/dist/cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.cjs","names":[],"sources":["../src/translate.ts","../src/migrate.ts","../src/cli.ts"],"sourcesContent":["import { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport consola from 'consola'\nimport type { CatalogData } from './catalog'\n\nconst execFileAsync = promisify(execFile)\n\nexport type AIProvider = 'claude' | 'codex'\n\nfunction buildPrompt(\n sourceLocale: string,\n targetLocale: string,\n messages: Record<string, string>,\n): string {\n const json = JSON.stringify(messages, null, 2)\n return [\n `You are a professional translator. Translate the following messages from \"${sourceLocale}\" to \"${targetLocale}\".`,\n '',\n `Input (JSON):`,\n json,\n '',\n 'Rules:',\n '- Output ONLY valid JSON with the same keys and translated values.',\n '- Keep ICU MessageFormat placeholders like {name}, {count}, {gender} unchanged.',\n '- Keep HTML tags unchanged.',\n '- Do not add any explanation or markdown formatting, output raw JSON only.',\n ].join('\\n')\n}\n\nasync function invokeAI(provider: AIProvider, prompt: string): Promise<string> {\n const maxBuffer = 10 * 1024 * 1024\n\n try {\n if (provider === 'claude') {\n const { stdout } = await execFileAsync('claude', ['-p', prompt], { maxBuffer })\n return stdout\n } else {\n const { stdout } = await execFileAsync('codex', ['-p', prompt, '--full-auto'], { maxBuffer })\n return stdout\n }\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n if (err.code === 'ENOENT') {\n throw new Error(\n `\"${provider}\" CLI not found. Please install it first:\\n` +\n (provider === 'claude'\n ? ' npm install -g @anthropic-ai/claude-code'\n : ' npm install -g @openai/codex'),\n )\n }\n throw error\n }\n}\n\nfunction extractJSON(text: string): Record<string, string> {\n // Try to find a JSON object in the response\n const match = text.match(/\\{[\\s\\S]*\\}/)\n if (!match) {\n throw new Error('No JSON object found in AI response')\n }\n const parsed = JSON.parse(match[0])\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new Error('AI response is not a valid JSON object')\n }\n return parsed as Record<string, string>\n}\n\nfunction getUntranslatedEntries(catalog: CatalogData): Record<string, string> {\n const entries: Record<string, string> = {}\n for (const [id, entry] of Object.entries(catalog)) {\n if (entry.obsolete) continue\n if (!entry.translation || entry.translation.length === 0) {\n entries[id] = entry.message ?? id\n }\n }\n return entries\n}\n\nfunction chunkEntries(\n entries: Record<string, string>,\n batchSize: number,\n): Array<Record<string, string>> {\n const keys = Object.keys(entries)\n const chunks: Array<Record<string, string>> = []\n\n for (let i = 0; i < keys.length; i += batchSize) {\n const chunk: Record<string, string> = {}\n for (const key of keys.slice(i, i + batchSize)) {\n chunk[key] = entries[key]!\n }\n chunks.push(chunk)\n }\n\n return chunks\n}\n\nexport interface TranslateOptions {\n provider: AIProvider\n sourceLocale: string\n targetLocale: string\n catalog: CatalogData\n batchSize: number\n}\n\nexport async function translateCatalog(options: TranslateOptions): Promise<{\n catalog: CatalogData\n translated: number\n}> {\n const { provider, sourceLocale, targetLocale, catalog, batchSize } = options\n\n const untranslated = getUntranslatedEntries(catalog)\n const count = Object.keys(untranslated).length\n\n if (count === 0) {\n return { catalog, translated: 0 }\n }\n\n consola.info(` ${count} untranslated messages, translating with ${provider}...`)\n\n const batches = chunkEntries(untranslated, batchSize)\n let totalTranslated = 0\n\n for (let i = 0; i < batches.length; i++) {\n const batch = batches[i]!\n const batchKeys = Object.keys(batch)\n\n if (batches.length > 1) {\n consola.info(` Batch ${i + 1}/${batches.length} (${batchKeys.length} messages)`)\n }\n\n const prompt = buildPrompt(sourceLocale, targetLocale, batch)\n const response = await invokeAI(provider, prompt)\n const translations = extractJSON(response)\n\n for (const key of batchKeys) {\n if (translations[key] && typeof translations[key] === 'string') {\n catalog[key] = {\n ...catalog[key],\n translation: translations[key],\n }\n totalTranslated++\n } else {\n consola.warn(` Missing translation for key: ${key}`)\n }\n }\n }\n\n return { catalog, translated: totalTranslated }\n}\n","import { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport { readFileSync, existsSync } from 'node:fs'\nimport { resolve, join } from 'node:path'\nimport fg from 'fast-glob'\nimport consola from 'consola'\nimport type { AIProvider } from './translate'\n\nconst execFileAsync = promisify(execFile)\n\nexport type SupportedLibrary =\n | 'vue-i18n'\n | 'nuxt-i18n'\n | 'react-i18next'\n | 'next-intl'\n | 'next-i18next'\n | 'lingui'\n\ninterface LibraryInfo {\n name: SupportedLibrary\n framework: string\n configPatterns: string[]\n localePatterns: string[]\n sourcePatterns: string[]\n migrationGuide: string // relative path from packages/\n}\n\nconst LIBRARY_INFO: Record<SupportedLibrary, LibraryInfo> = {\n 'vue-i18n': {\n name: 'vue-i18n',\n framework: 'Vue',\n 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'],\n localePatterns: ['locales/*.json', 'src/locales/*.json', 'i18n/*.json', 'src/i18n/*.json', 'lang/*.json', 'src/lang/*.json', 'locales/*.yaml', 'locales/*.yml'],\n sourcePatterns: ['src/**/*.vue'],\n migrationGuide: 'vue/llms-migration.txt',\n },\n 'nuxt-i18n': {\n name: 'nuxt-i18n',\n framework: 'Nuxt',\n configPatterns: ['nuxt.config.ts', 'nuxt.config.js', 'i18n.config.ts', 'i18n.config.js'],\n localePatterns: ['locales/*.json', 'lang/*.json', 'i18n/*.json', 'locales/*.yaml', 'locales/*.yml'],\n sourcePatterns: ['pages/**/*.vue', 'components/**/*.vue', 'layouts/**/*.vue'],\n migrationGuide: 'nuxt/llms-migration.txt',\n },\n 'react-i18next': {\n name: 'react-i18next',\n framework: 'React',\n configPatterns: ['i18n.ts', 'i18n.js', 'src/i18n.ts', 'src/i18n.js', 'src/i18n/index.ts', 'src/i18n/config.ts'],\n localePatterns: ['locales/*.json', 'src/locales/*.json', 'public/locales/**/*.json', 'translations/*.json', 'src/translations/*.json'],\n sourcePatterns: ['src/**/*.tsx', 'src/**/*.jsx', 'src/**/*.ts'],\n migrationGuide: 'react/llms-migration.txt',\n },\n 'next-intl': {\n name: 'next-intl',\n framework: 'Next.js',\n configPatterns: ['next.config.ts', 'next.config.js', 'next.config.mjs', 'i18n.ts', 'src/i18n.ts', 'i18n/request.ts', 'src/i18n/request.ts'],\n localePatterns: ['messages/*.json', 'locales/*.json', 'src/messages/*.json', 'src/locales/*.json'],\n sourcePatterns: ['app/**/*.tsx', 'src/app/**/*.tsx', 'pages/**/*.tsx', 'components/**/*.tsx'],\n migrationGuide: 'next-plugin/llms-migration.txt',\n },\n 'next-i18next': {\n name: 'next-i18next',\n framework: 'Next.js',\n configPatterns: ['next-i18next.config.js', 'next-i18next.config.mjs', 'next.config.ts', 'next.config.js'],\n localePatterns: ['public/locales/**/*.json'],\n sourcePatterns: ['pages/**/*.tsx', 'src/pages/**/*.tsx', 'components/**/*.tsx', 'src/components/**/*.tsx'],\n migrationGuide: 'next-plugin/llms-migration.txt',\n },\n 'lingui': {\n name: 'lingui',\n framework: 'React',\n configPatterns: ['lingui.config.ts', 'lingui.config.js', '.linguirc'],\n localePatterns: ['locales/*.po', 'src/locales/*.po', 'locales/*/messages.po', 'src/locales/*/messages.po'],\n sourcePatterns: ['src/**/*.tsx', 'src/**/*.jsx', 'src/**/*.ts'],\n migrationGuide: 'react/llms-migration.txt',\n },\n}\n\nconst SUPPORTED_NAMES = Object.keys(LIBRARY_INFO) as SupportedLibrary[]\n\nfunction resolveLibrary(from: string): SupportedLibrary | undefined {\n const normalized = from.toLowerCase().replace(/^@nuxtjs\\//, 'nuxt-').replace(/^@/, '')\n return SUPPORTED_NAMES.find((name) => name === normalized)\n}\n\ninterface DetectedFiles {\n configFiles: Array<{ path: string; content: string }>\n localeFiles: Array<{ path: string; content: string }>\n sampleSources: Array<{ path: string; content: string }>\n packageJson: string | undefined\n}\n\nasync function detectFiles(info: LibraryInfo): Promise<DetectedFiles> {\n const result: DetectedFiles = {\n configFiles: [],\n localeFiles: [],\n sampleSources: [],\n packageJson: undefined,\n }\n\n // Read package.json\n const pkgPath = resolve('package.json')\n if (existsSync(pkgPath)) {\n result.packageJson = readFileSync(pkgPath, 'utf-8')\n }\n\n // Find config files\n for (const pattern of info.configPatterns) {\n const fullPath = resolve(pattern)\n if (existsSync(fullPath)) {\n result.configFiles.push({\n path: pattern,\n content: readFileSync(fullPath, 'utf-8'),\n })\n }\n }\n\n // Find locale files (limit to 10 to avoid huge prompts)\n const localeGlobs = await fg(info.localePatterns, { absolute: false })\n for (const file of localeGlobs.slice(0, 10)) {\n const fullPath = resolve(file)\n const content = readFileSync(fullPath, 'utf-8')\n // Truncate large files\n result.localeFiles.push({\n path: file,\n content: content.length > 5000 ? content.slice(0, 5000) + '\\n... (truncated)' : content,\n })\n }\n\n // Find sample source files (limit to 5 for prompt size)\n const sourceGlobs = await fg(info.sourcePatterns, { absolute: false })\n for (const file of sourceGlobs.slice(0, 5)) {\n const fullPath = resolve(file)\n const content = readFileSync(fullPath, 'utf-8')\n result.sampleSources.push({\n path: file,\n content: content.length > 3000 ? content.slice(0, 3000) + '\\n... (truncated)' : content,\n })\n }\n\n return result\n}\n\nfunction loadMigrationGuide(guidePath: string): string {\n // Try to find the migration guide relative to the CLI package\n const candidates = [\n resolve('node_modules', '@fluenti', 'cli', '..', '..', guidePath),\n join(__dirname, '..', '..', '..', guidePath),\n join(__dirname, '..', '..', guidePath),\n ]\n\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return readFileSync(candidate, 'utf-8')\n }\n }\n\n return ''\n}\n\nfunction buildMigratePrompt(\n library: LibraryInfo,\n detected: DetectedFiles,\n migrationGuide: string,\n): string {\n const sections: string[] = []\n\n sections.push(\n `You are a migration assistant helping convert a ${library.framework} project from \"${library.name}\" to Fluenti (@fluenti).`,\n '',\n 'Your task:',\n '1. Generate a `fluenti.config.ts` file based on the existing i18n configuration',\n '2. Convert each locale/translation file to Fluenti PO format',\n '3. List the code changes needed (file by file) to migrate source code from the old API to Fluenti API',\n '',\n )\n\n if (migrationGuide) {\n sections.push(\n '=== MIGRATION GUIDE ===',\n migrationGuide,\n '',\n )\n }\n\n if (detected.packageJson) {\n sections.push(\n '=== package.json ===',\n detected.packageJson,\n '',\n )\n }\n\n if (detected.configFiles.length > 0) {\n sections.push('=== EXISTING CONFIG FILES ===')\n for (const file of detected.configFiles) {\n sections.push(`--- ${file.path} ---`, file.content, '')\n }\n }\n\n if (detected.localeFiles.length > 0) {\n sections.push('=== EXISTING LOCALE FILES ===')\n for (const file of detected.localeFiles) {\n sections.push(`--- ${file.path} ---`, file.content, '')\n }\n }\n\n if (detected.sampleSources.length > 0) {\n sections.push('=== SAMPLE SOURCE FILES ===')\n for (const file of detected.sampleSources) {\n sections.push(`--- ${file.path} ---`, file.content, '')\n }\n }\n\n sections.push(\n '',\n '=== OUTPUT FORMAT ===',\n 'Respond with the following sections, each starting with the exact header shown:',\n '',\n '### FLUENTI_CONFIG',\n '```ts',\n '// The fluenti.config.ts content',\n '```',\n '',\n '### LOCALE_FILES',\n 'For each locale file, output:',\n '#### LOCALE: {locale_code}',\n '```po',\n '// The PO file content',\n '```',\n '',\n '### MIGRATION_STEPS',\n 'A numbered checklist of specific code changes needed, with before/after examples.',\n '',\n '### INSTALL_COMMANDS',\n '```bash',\n '// The install and uninstall commands',\n '```',\n )\n\n return sections.join('\\n')\n}\n\nasync function invokeAI(provider: AIProvider, prompt: string): Promise<string> {\n const maxBuffer = 10 * 1024 * 1024\n\n try {\n if (provider === 'claude') {\n const { stdout } = await execFileAsync('claude', ['-p', prompt], { maxBuffer })\n return stdout\n } else {\n const { stdout } = await execFileAsync('codex', ['-p', prompt, '--full-auto'], { maxBuffer })\n return stdout\n }\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n if (err.code === 'ENOENT') {\n throw new Error(\n `\"${provider}\" CLI not found. Please install it first:\\n` +\n (provider === 'claude'\n ? ' npm install -g @anthropic-ai/claude-code'\n : ' npm install -g @openai/codex'),\n )\n }\n throw error\n }\n}\n\ninterface MigrateResult {\n config: string | undefined\n localeFiles: Array<{ locale: string; content: string }>\n steps: string | undefined\n installCommands: string | undefined\n}\n\nfunction parseResponse(response: string): MigrateResult {\n const result: MigrateResult = {\n config: undefined,\n localeFiles: [],\n steps: undefined,\n installCommands: undefined,\n }\n\n // Extract fluenti.config.ts\n const configMatch = response.match(/### FLUENTI_CONFIG[\\s\\S]*?```(?:ts|typescript)?\\n([\\s\\S]*?)```/)\n if (configMatch) {\n result.config = configMatch[1]!.trim()\n }\n\n // Extract locale files\n const localeSection = response.match(/### LOCALE_FILES([\\s\\S]*?)(?=### MIGRATION_STEPS|### INSTALL_COMMANDS|$)/)\n if (localeSection) {\n const localeRegex = /#### LOCALE:\\s*(\\S+)\\s*\\n```(?:po)?\\n([\\s\\S]*?)```/g\n let match\n while ((match = localeRegex.exec(localeSection[1]!)) !== null) {\n result.localeFiles.push({\n locale: match[1]!,\n content: match[2]!.trim(),\n })\n }\n }\n\n // Extract migration steps\n const stepsMatch = response.match(/### MIGRATION_STEPS\\s*\\n([\\s\\S]*?)(?=### INSTALL_COMMANDS|$)/)\n if (stepsMatch) {\n result.steps = stepsMatch[1]!.trim()\n }\n\n // Extract install commands\n const installMatch = response.match(/### INSTALL_COMMANDS[\\s\\S]*?```(?:bash|sh)?\\n([\\s\\S]*?)```/)\n if (installMatch) {\n result.installCommands = installMatch[1]!.trim()\n }\n\n return result\n}\n\nexport interface MigrateOptions {\n from: string\n provider: AIProvider\n write: boolean\n}\n\nexport async function runMigrate(options: MigrateOptions): Promise<void> {\n const { from, provider, write } = options\n\n const library = resolveLibrary(from)\n if (!library) {\n consola.error(`Unsupported library \"${from}\". Supported libraries:`)\n for (const name of SUPPORTED_NAMES) {\n consola.log(` - ${name}`)\n }\n return\n }\n\n const info = LIBRARY_INFO[library]\n consola.info(`Migrating from ${info.name} (${info.framework}) to Fluenti`)\n\n // Detect existing files\n consola.info('Scanning project for existing i18n files...')\n const detected = await detectFiles(info)\n\n if (detected.configFiles.length === 0 && detected.localeFiles.length === 0) {\n consola.warn(`No ${info.name} configuration or locale files found.`)\n consola.info('Make sure you are running this command from the project root directory.')\n return\n }\n\n consola.info(`Found: ${detected.configFiles.length} config file(s), ${detected.localeFiles.length} locale file(s), ${detected.sampleSources.length} source file(s)`)\n\n // Load migration guide\n const migrationGuide = loadMigrationGuide(info.migrationGuide)\n\n // Build prompt and invoke AI\n consola.info(`Generating migration plan with ${provider}...`)\n const prompt = buildMigratePrompt(info, detected, migrationGuide)\n const response = await invokeAI(provider, prompt)\n const result = parseResponse(response)\n\n // Display install commands\n if (result.installCommands) {\n consola.log('')\n consola.box({\n title: 'Install Commands',\n message: result.installCommands,\n })\n }\n\n // Write or display fluenti.config.ts\n if (result.config) {\n if (write) {\n const { writeFileSync } = await import('node:fs')\n const configPath = resolve('fluenti.config.ts')\n writeFileSync(configPath, result.config, 'utf-8')\n consola.success(`Written: ${configPath}`)\n } else {\n consola.log('')\n consola.box({\n title: 'fluenti.config.ts',\n message: result.config,\n })\n }\n }\n\n // Write or display locale files\n if (result.localeFiles.length > 0) {\n if (write) {\n const { writeFileSync, mkdirSync } = await import('node:fs')\n const catalogDir = './locales'\n mkdirSync(resolve(catalogDir), { recursive: true })\n for (const file of result.localeFiles) {\n const outPath = resolve(catalogDir, `${file.locale}.po`)\n writeFileSync(outPath, file.content, 'utf-8')\n consola.success(`Written: ${outPath}`)\n }\n } else {\n for (const file of result.localeFiles) {\n consola.log('')\n consola.box({\n title: `locales/${file.locale}.po`,\n message: file.content.length > 500\n ? file.content.slice(0, 500) + '\\n... (use --write to save full file)'\n : file.content,\n })\n }\n }\n }\n\n // Display migration steps\n if (result.steps) {\n consola.log('')\n consola.box({\n title: 'Migration Steps',\n message: result.steps,\n })\n }\n\n if (!write && (result.config || result.localeFiles.length > 0)) {\n consola.log('')\n consola.info('Run with --write to save generated files to disk:')\n consola.log(` fluenti migrate --from ${from} --write`)\n }\n}\n","#!/usr/bin/env node\nimport { defineCommand, runMain } from 'citty'\nimport consola from 'consola'\nimport fg from 'fast-glob'\nimport { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'\nimport { resolve, dirname, extname } from 'node:path'\nimport { extractFromVue } from './vue-extractor'\nimport { extractFromTsx } from './tsx-extractor'\nimport { updateCatalog } from './catalog'\nimport type { CatalogData } from './catalog'\nimport { readJsonCatalog, writeJsonCatalog } from './json-format'\nimport { readPoCatalog, writePoCatalog } from './po-format'\nimport { compileCatalog, compileIndex, collectAllIds } from './compile'\nimport { translateCatalog } from './translate'\nimport type { AIProvider } from './translate'\nimport { runMigrate } from './migrate'\nimport type { ExtractedMessage, FluentiConfig } from '@fluenti/core'\n\nconst defaultConfig: FluentiConfig = {\n sourceLocale: 'en',\n locales: ['en'],\n catalogDir: './locales',\n format: 'po',\n include: ['./src/**/*.{vue,tsx,jsx,ts,js}'],\n compileOutDir: './locales/compiled',\n}\n\nasync function loadConfig(configPath?: string): Promise<FluentiConfig> {\n const paths = configPath\n ? [resolve(configPath)]\n : [\n resolve('fluenti.config.ts'),\n resolve('fluenti.config.js'),\n resolve('fluenti.config.mjs'),\n ]\n\n for (const p of paths) {\n if (existsSync(p)) {\n try {\n const { createJiti } = await import('jiti')\n const jiti = createJiti(import.meta.url)\n const mod = await jiti.import(p) as { default?: Partial<FluentiConfig> }\n const userConfig = mod.default ?? mod as unknown as Partial<FluentiConfig>\n return { ...defaultConfig, ...userConfig }\n } catch {\n consola.warn(`Failed to load config from ${p}, using defaults`)\n }\n }\n }\n\n return defaultConfig\n}\n\nfunction readCatalog(filePath: string, format: 'json' | 'po'): CatalogData {\n if (!existsSync(filePath)) return {}\n const content = readFileSync(filePath, 'utf-8')\n return format === 'json' ? readJsonCatalog(content) : readPoCatalog(content)\n}\n\nfunction writeCatalog(filePath: string, catalog: CatalogData, format: 'json' | 'po'): void {\n mkdirSync(dirname(filePath), { recursive: true })\n const content = format === 'json' ? writeJsonCatalog(catalog) : writePoCatalog(catalog)\n writeFileSync(filePath, content, 'utf-8')\n}\n\nfunction extractFromFile(filePath: string, code: string): ExtractedMessage[] {\n const ext = extname(filePath)\n if (ext === '.vue') return extractFromVue(code, filePath)\n return extractFromTsx(code, filePath)\n}\n\nconst extract = defineCommand({\n meta: { name: 'extract', description: 'Extract messages from source files' },\n args: {\n config: { type: 'string', description: 'Path to config file' },\n },\n async run({ args }) {\n const config = await loadConfig(args.config)\n consola.info(`Extracting messages from ${config.include.join(', ')}`)\n\n const files = await fg(config.include, { absolute: true })\n const allMessages: ExtractedMessage[] = []\n\n for (const file of files) {\n const code = readFileSync(file, 'utf-8')\n const messages = extractFromFile(file, code)\n allMessages.push(...messages)\n }\n\n consola.info(`Found ${allMessages.length} messages in ${files.length} files`)\n\n const ext = config.format === 'json' ? '.json' : '.po'\n\n for (const locale of config.locales) {\n const catalogPath = resolve(config.catalogDir, `${locale}${ext}`)\n const existing = readCatalog(catalogPath, config.format)\n const { catalog, result } = updateCatalog(existing, allMessages)\n writeCatalog(catalogPath, catalog, config.format)\n consola.success(\n `${locale}: ${result.added} added, ${result.unchanged} unchanged, ${result.obsolete} obsolete`,\n )\n }\n },\n})\n\nconst compile = defineCommand({\n meta: { name: 'compile', description: 'Compile message catalogs to JS modules' },\n args: {\n config: { type: 'string', description: 'Path to config file' },\n },\n async run({ args }) {\n const config = await loadConfig(args.config)\n const ext = config.format === 'json' ? '.json' : '.po'\n\n mkdirSync(config.compileOutDir, { recursive: true })\n\n // Collect all catalogs and build union of IDs\n const allCatalogs: Record<string, CatalogData> = {}\n for (const locale of config.locales) {\n const catalogPath = resolve(config.catalogDir, `${locale}${ext}`)\n allCatalogs[locale] = readCatalog(catalogPath, config.format)\n }\n\n const allIds = collectAllIds(allCatalogs)\n consola.info(`Compiling ${allIds.length} messages across ${config.locales.length} locales`)\n\n for (const locale of config.locales) {\n const compiled = compileCatalog(\n allCatalogs[locale]!,\n locale,\n allIds,\n config.sourceLocale,\n )\n const outPath = resolve(config.compileOutDir, `${locale}.js`)\n writeFileSync(outPath, compiled, 'utf-8')\n consola.success(`Compiled ${locale} → ${outPath}`)\n }\n\n // Generate index.js with locale list and lazy loaders\n const indexCode = compileIndex(config.locales, config.compileOutDir)\n const indexPath = resolve(config.compileOutDir, 'index.js')\n writeFileSync(indexPath, indexCode, 'utf-8')\n consola.success(`Generated index → ${indexPath}`)\n },\n})\n\nconst stats = defineCommand({\n meta: { name: 'stats', description: 'Show translation progress' },\n args: {\n config: { type: 'string', description: 'Path to config file' },\n },\n async run({ args }) {\n const config = await loadConfig(args.config)\n const ext = config.format === 'json' ? '.json' : '.po'\n\n const rows: Array<{ locale: string; total: number; translated: number; pct: string }> = []\n\n for (const locale of config.locales) {\n const catalogPath = resolve(config.catalogDir, `${locale}${ext}`)\n const catalog = readCatalog(catalogPath, config.format)\n const entries = Object.values(catalog).filter((e) => !e.obsolete)\n const total = entries.length\n const translated = entries.filter((e) => e.translation && e.translation.length > 0).length\n const pct = total > 0 ? ((translated / total) * 100).toFixed(1) + '%' : '—'\n rows.push({ locale, total, translated, pct })\n }\n\n consola.log('')\n consola.log(' Locale │ Total │ Translated │ Progress')\n consola.log(' ────────┼───────┼────────────┼─────────')\n for (const row of rows) {\n consola.log(\n ` ${row.locale.padEnd(8)}│ ${String(row.total).padStart(5)} │ ${String(row.translated).padStart(10)} │ ${row.pct}`,\n )\n }\n consola.log('')\n },\n})\n\nconst translate = defineCommand({\n meta: { name: 'translate', description: 'Translate messages using AI (Claude Code or Codex CLI)' },\n args: {\n config: { type: 'string', description: 'Path to config file' },\n provider: { type: 'string', description: 'AI provider: claude or codex', default: 'claude' },\n locale: { type: 'string', description: 'Translate a specific locale only' },\n 'batch-size': { type: 'string', description: 'Messages per batch', default: '50' },\n },\n async run({ args }) {\n const config = await loadConfig(args.config)\n const provider = args.provider as AIProvider\n\n if (provider !== 'claude' && provider !== 'codex') {\n consola.error(`Invalid provider \"${provider}\". Use \"claude\" or \"codex\".`)\n return\n }\n\n const batchSize = parseInt(args['batch-size'] ?? '50', 10)\n if (isNaN(batchSize) || batchSize < 1) {\n consola.error('Invalid batch-size. Must be a positive integer.')\n return\n }\n\n const targetLocales = args.locale\n ? [args.locale]\n : config.locales.filter((l: string) => l !== config.sourceLocale)\n\n if (targetLocales.length === 0) {\n consola.warn('No target locales to translate.')\n return\n }\n\n consola.info(`Translating with ${provider} (batch size: ${batchSize})`)\n const ext = config.format === 'json' ? '.json' : '.po'\n\n for (const locale of targetLocales) {\n consola.info(`\\n[${locale}]`)\n const catalogPath = resolve(config.catalogDir, `${locale}${ext}`)\n const catalog = readCatalog(catalogPath, config.format)\n\n const { catalog: updated, translated } = await translateCatalog({\n provider,\n sourceLocale: config.sourceLocale,\n targetLocale: locale,\n catalog,\n batchSize,\n })\n\n if (translated > 0) {\n writeCatalog(catalogPath, updated, config.format)\n consola.success(` ${locale}: ${translated} messages translated`)\n } else {\n consola.success(` ${locale}: already fully translated`)\n }\n }\n },\n})\n\nconst migrate = defineCommand({\n meta: { name: 'migrate', description: 'Migrate from another i18n library using AI' },\n args: {\n from: { type: 'string', description: 'Source library: vue-i18n, nuxt-i18n, react-i18next, next-intl, next-i18next, lingui', required: true },\n provider: { type: 'string', description: 'AI provider: claude or codex', default: 'claude' },\n write: { type: 'boolean', description: 'Write generated files to disk', default: false },\n },\n async run({ args }) {\n const provider = args.provider as AIProvider\n if (provider !== 'claude' && provider !== 'codex') {\n consola.error(`Invalid provider \"${provider}\". Use \"claude\" or \"codex\".`)\n return\n }\n\n await runMigrate({\n from: args.from!,\n provider,\n write: args.write ?? false,\n })\n },\n})\n\nconst main = defineCommand({\n meta: {\n name: 'fluenti',\n version: '0.0.1',\n description: 'Compile-time i18n for modern frameworks',\n },\n subCommands: { extract, compile, stats, translate, migrate },\n})\n\nrunMain(main)\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.cjs","names":[],"sources":["../src/translate.ts","../src/migrate.ts","../src/cli.ts"],"sourcesContent":["import { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport consola from 'consola'\nimport type { CatalogData } from './catalog'\n\nconst execFileAsync = promisify(execFile)\n\nexport type AIProvider = 'claude' | 'codex'\n\nfunction buildPrompt(\n sourceLocale: string,\n targetLocale: string,\n messages: Record<string, string>,\n): string {\n const json = JSON.stringify(messages, null, 2)\n return [\n `You are a professional translator. Translate the following messages from \"${sourceLocale}\" to \"${targetLocale}\".`,\n '',\n `Input (JSON):`,\n json,\n '',\n 'Rules:',\n '- Output ONLY valid JSON with the same keys and translated values.',\n '- Keep ICU MessageFormat placeholders like {name}, {count}, {gender} unchanged.',\n '- Keep HTML tags unchanged.',\n '- Do not add any explanation or markdown formatting, output raw JSON only.',\n ].join('\\n')\n}\n\nasync function invokeAI(provider: AIProvider, prompt: string): Promise<string> {\n const maxBuffer = 10 * 1024 * 1024\n\n try {\n if (provider === 'claude') {\n const { stdout } = await execFileAsync('claude', ['-p', prompt], { maxBuffer })\n return stdout\n } else {\n const { stdout } = await execFileAsync('codex', ['-p', prompt, '--full-auto'], { maxBuffer })\n return stdout\n }\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n if (err.code === 'ENOENT') {\n throw new Error(\n `\"${provider}\" CLI not found. Please install it first:\\n` +\n (provider === 'claude'\n ? ' npm install -g @anthropic-ai/claude-code'\n : ' npm install -g @openai/codex'),\n )\n }\n throw error\n }\n}\n\nfunction extractJSON(text: string): Record<string, string> {\n // Try to find a JSON object in the response\n const match = text.match(/\\{[\\s\\S]*\\}/)\n if (!match) {\n throw new Error('No JSON object found in AI response')\n }\n const parsed = JSON.parse(match[0])\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new Error('AI response is not a valid JSON object')\n }\n return parsed as Record<string, string>\n}\n\nfunction getUntranslatedEntries(catalog: CatalogData): Record<string, string> {\n const entries: Record<string, string> = {}\n for (const [id, entry] of Object.entries(catalog)) {\n if (entry.obsolete) continue\n if (!entry.translation || entry.translation.length === 0) {\n entries[id] = entry.message ?? id\n }\n }\n return entries\n}\n\nfunction chunkEntries(\n entries: Record<string, string>,\n batchSize: number,\n): Array<Record<string, string>> {\n const keys = Object.keys(entries)\n const chunks: Array<Record<string, string>> = []\n\n for (let i = 0; i < keys.length; i += batchSize) {\n const chunk: Record<string, string> = {}\n for (const key of keys.slice(i, i + batchSize)) {\n chunk[key] = entries[key]!\n }\n chunks.push(chunk)\n }\n\n return chunks\n}\n\nexport interface TranslateOptions {\n provider: AIProvider\n sourceLocale: string\n targetLocale: string\n catalog: CatalogData\n batchSize: number\n}\n\nexport async function translateCatalog(options: TranslateOptions): Promise<{\n catalog: CatalogData\n translated: number\n}> {\n const { provider, sourceLocale, targetLocale, catalog, batchSize } = options\n\n const untranslated = getUntranslatedEntries(catalog)\n const count = Object.keys(untranslated).length\n\n if (count === 0) {\n return { catalog, translated: 0 }\n }\n\n consola.info(` ${count} untranslated messages, translating with ${provider}...`)\n\n const batches = chunkEntries(untranslated, batchSize)\n let totalTranslated = 0\n\n for (let i = 0; i < batches.length; i++) {\n const batch = batches[i]!\n const batchKeys = Object.keys(batch)\n\n if (batches.length > 1) {\n consola.info(` Batch ${i + 1}/${batches.length} (${batchKeys.length} messages)`)\n }\n\n const prompt = buildPrompt(sourceLocale, targetLocale, batch)\n const response = await invokeAI(provider, prompt)\n const translations = extractJSON(response)\n\n for (const key of batchKeys) {\n if (translations[key] && typeof translations[key] === 'string') {\n catalog[key] = {\n ...catalog[key],\n translation: translations[key],\n }\n totalTranslated++\n } else {\n consola.warn(` Missing translation for key: ${key}`)\n }\n }\n }\n\n return { catalog, translated: totalTranslated }\n}\n","import { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport { readFileSync, existsSync } from 'node:fs'\nimport { resolve, join } from 'node:path'\nimport fg from 'fast-glob'\nimport consola from 'consola'\nimport type { AIProvider } from './translate'\n\nconst execFileAsync = promisify(execFile)\n\nexport type SupportedLibrary =\n | 'vue-i18n'\n | 'nuxt-i18n'\n | 'react-i18next'\n | 'next-intl'\n | 'next-i18next'\n | 'lingui'\n\ninterface LibraryInfo {\n name: SupportedLibrary\n framework: string\n configPatterns: string[]\n localePatterns: string[]\n sourcePatterns: string[]\n migrationGuide: string // relative path from packages/\n}\n\nconst LIBRARY_INFO: Record<SupportedLibrary, LibraryInfo> = {\n 'vue-i18n': {\n name: 'vue-i18n',\n framework: 'Vue',\n 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'],\n localePatterns: ['locales/*.json', 'src/locales/*.json', 'i18n/*.json', 'src/i18n/*.json', 'lang/*.json', 'src/lang/*.json', 'locales/*.yaml', 'locales/*.yml'],\n sourcePatterns: ['src/**/*.vue'],\n migrationGuide: 'vue/llms-migration.txt',\n },\n 'nuxt-i18n': {\n name: 'nuxt-i18n',\n framework: 'Nuxt',\n configPatterns: ['nuxt.config.ts', 'nuxt.config.js', 'i18n.config.ts', 'i18n.config.js'],\n localePatterns: ['locales/*.json', 'lang/*.json', 'i18n/*.json', 'locales/*.yaml', 'locales/*.yml'],\n sourcePatterns: ['pages/**/*.vue', 'components/**/*.vue', 'layouts/**/*.vue'],\n migrationGuide: 'nuxt/llms-migration.txt',\n },\n 'react-i18next': {\n name: 'react-i18next',\n framework: 'React',\n configPatterns: ['i18n.ts', 'i18n.js', 'src/i18n.ts', 'src/i18n.js', 'src/i18n/index.ts', 'src/i18n/config.ts'],\n localePatterns: ['locales/*.json', 'src/locales/*.json', 'public/locales/**/*.json', 'translations/*.json', 'src/translations/*.json'],\n sourcePatterns: ['src/**/*.tsx', 'src/**/*.jsx', 'src/**/*.ts'],\n migrationGuide: 'react/llms-migration.txt',\n },\n 'next-intl': {\n name: 'next-intl',\n framework: 'Next.js',\n configPatterns: ['next.config.ts', 'next.config.js', 'next.config.mjs', 'i18n.ts', 'src/i18n.ts', 'i18n/request.ts', 'src/i18n/request.ts'],\n localePatterns: ['messages/*.json', 'locales/*.json', 'src/messages/*.json', 'src/locales/*.json'],\n sourcePatterns: ['app/**/*.tsx', 'src/app/**/*.tsx', 'pages/**/*.tsx', 'components/**/*.tsx'],\n migrationGuide: 'next-plugin/llms-migration.txt',\n },\n 'next-i18next': {\n name: 'next-i18next',\n framework: 'Next.js',\n configPatterns: ['next-i18next.config.js', 'next-i18next.config.mjs', 'next.config.ts', 'next.config.js'],\n localePatterns: ['public/locales/**/*.json'],\n sourcePatterns: ['pages/**/*.tsx', 'src/pages/**/*.tsx', 'components/**/*.tsx', 'src/components/**/*.tsx'],\n migrationGuide: 'next-plugin/llms-migration.txt',\n },\n 'lingui': {\n name: 'lingui',\n framework: 'React',\n configPatterns: ['lingui.config.ts', 'lingui.config.js', '.linguirc'],\n localePatterns: ['locales/*.po', 'src/locales/*.po', 'locales/*/messages.po', 'src/locales/*/messages.po'],\n sourcePatterns: ['src/**/*.tsx', 'src/**/*.jsx', 'src/**/*.ts'],\n migrationGuide: 'react/llms-migration.txt',\n },\n}\n\nconst SUPPORTED_NAMES = Object.keys(LIBRARY_INFO) as SupportedLibrary[]\n\nfunction resolveLibrary(from: string): SupportedLibrary | undefined {\n const normalized = from.toLowerCase().replace(/^@nuxtjs\\//, 'nuxt-').replace(/^@/, '')\n return SUPPORTED_NAMES.find((name) => name === normalized)\n}\n\ninterface DetectedFiles {\n configFiles: Array<{ path: string; content: string }>\n localeFiles: Array<{ path: string; content: string }>\n sampleSources: Array<{ path: string; content: string }>\n packageJson: string | undefined\n}\n\nasync function detectFiles(info: LibraryInfo): Promise<DetectedFiles> {\n const result: DetectedFiles = {\n configFiles: [],\n localeFiles: [],\n sampleSources: [],\n packageJson: undefined,\n }\n\n // Read package.json\n const pkgPath = resolve('package.json')\n if (existsSync(pkgPath)) {\n result.packageJson = readFileSync(pkgPath, 'utf-8')\n }\n\n // Find config files\n for (const pattern of info.configPatterns) {\n const fullPath = resolve(pattern)\n if (existsSync(fullPath)) {\n result.configFiles.push({\n path: pattern,\n content: readFileSync(fullPath, 'utf-8'),\n })\n }\n }\n\n // Find locale files (limit to 10 to avoid huge prompts)\n const localeGlobs = await fg(info.localePatterns, { absolute: false })\n for (const file of localeGlobs.slice(0, 10)) {\n const fullPath = resolve(file)\n const content = readFileSync(fullPath, 'utf-8')\n // Truncate large files\n result.localeFiles.push({\n path: file,\n content: content.length > 5000 ? content.slice(0, 5000) + '\\n... (truncated)' : content,\n })\n }\n\n // Find sample source files (limit to 5 for prompt size)\n const sourceGlobs = await fg(info.sourcePatterns, { absolute: false })\n for (const file of sourceGlobs.slice(0, 5)) {\n const fullPath = resolve(file)\n const content = readFileSync(fullPath, 'utf-8')\n result.sampleSources.push({\n path: file,\n content: content.length > 3000 ? content.slice(0, 3000) + '\\n... (truncated)' : content,\n })\n }\n\n return result\n}\n\nfunction loadMigrationGuide(guidePath: string): string {\n // Try to find the migration guide relative to the CLI package\n const candidates = [\n resolve('node_modules', '@fluenti', 'cli', '..', '..', guidePath),\n join(__dirname, '..', '..', '..', guidePath),\n join(__dirname, '..', '..', guidePath),\n ]\n\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return readFileSync(candidate, 'utf-8')\n }\n }\n\n return ''\n}\n\nfunction buildMigratePrompt(\n library: LibraryInfo,\n detected: DetectedFiles,\n migrationGuide: string,\n): string {\n const sections: string[] = []\n\n sections.push(\n `You are a migration assistant helping convert a ${library.framework} project from \"${library.name}\" to Fluenti (@fluenti).`,\n '',\n 'Your task:',\n '1. Generate a `fluenti.config.ts` file based on the existing i18n configuration',\n '2. Convert each locale/translation file to Fluenti PO format',\n '3. List the code changes needed (file by file) to migrate source code from the old API to Fluenti API',\n '',\n )\n\n if (migrationGuide) {\n sections.push(\n '=== MIGRATION GUIDE ===',\n migrationGuide,\n '',\n )\n }\n\n if (detected.packageJson) {\n sections.push(\n '=== package.json ===',\n detected.packageJson,\n '',\n )\n }\n\n if (detected.configFiles.length > 0) {\n sections.push('=== EXISTING CONFIG FILES ===')\n for (const file of detected.configFiles) {\n sections.push(`--- ${file.path} ---`, file.content, '')\n }\n }\n\n if (detected.localeFiles.length > 0) {\n sections.push('=== EXISTING LOCALE FILES ===')\n for (const file of detected.localeFiles) {\n sections.push(`--- ${file.path} ---`, file.content, '')\n }\n }\n\n if (detected.sampleSources.length > 0) {\n sections.push('=== SAMPLE SOURCE FILES ===')\n for (const file of detected.sampleSources) {\n sections.push(`--- ${file.path} ---`, file.content, '')\n }\n }\n\n sections.push(\n '',\n '=== OUTPUT FORMAT ===',\n 'Respond with the following sections, each starting with the exact header shown:',\n '',\n '### FLUENTI_CONFIG',\n '```ts',\n '// The fluenti.config.ts content',\n '```',\n '',\n '### LOCALE_FILES',\n 'For each locale file, output:',\n '#### LOCALE: {locale_code}',\n '```po',\n '// The PO file content',\n '```',\n '',\n '### MIGRATION_STEPS',\n 'A numbered checklist of specific code changes needed, with before/after examples.',\n '',\n '### INSTALL_COMMANDS',\n '```bash',\n '// The install and uninstall commands',\n '```',\n )\n\n return sections.join('\\n')\n}\n\nasync function invokeAI(provider: AIProvider, prompt: string): Promise<string> {\n const maxBuffer = 10 * 1024 * 1024\n\n try {\n if (provider === 'claude') {\n const { stdout } = await execFileAsync('claude', ['-p', prompt], { maxBuffer })\n return stdout\n } else {\n const { stdout } = await execFileAsync('codex', ['-p', prompt, '--full-auto'], { maxBuffer })\n return stdout\n }\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n if (err.code === 'ENOENT') {\n throw new Error(\n `\"${provider}\" CLI not found. Please install it first:\\n` +\n (provider === 'claude'\n ? ' npm install -g @anthropic-ai/claude-code'\n : ' npm install -g @openai/codex'),\n )\n }\n throw error\n }\n}\n\ninterface MigrateResult {\n config: string | undefined\n localeFiles: Array<{ locale: string; content: string }>\n steps: string | undefined\n installCommands: string | undefined\n}\n\nfunction parseResponse(response: string): MigrateResult {\n const result: MigrateResult = {\n config: undefined,\n localeFiles: [],\n steps: undefined,\n installCommands: undefined,\n }\n\n // Extract fluenti.config.ts\n const configMatch = response.match(/### FLUENTI_CONFIG[\\s\\S]*?```(?:ts|typescript)?\\n([\\s\\S]*?)```/)\n if (configMatch) {\n result.config = configMatch[1]!.trim()\n }\n\n // Extract locale files\n const localeSection = response.match(/### LOCALE_FILES([\\s\\S]*?)(?=### MIGRATION_STEPS|### INSTALL_COMMANDS|$)/)\n if (localeSection) {\n const localeRegex = /#### LOCALE:\\s*(\\S+)\\s*\\n```(?:po)?\\n([\\s\\S]*?)```/g\n let match\n while ((match = localeRegex.exec(localeSection[1]!)) !== null) {\n result.localeFiles.push({\n locale: match[1]!,\n content: match[2]!.trim(),\n })\n }\n }\n\n // Extract migration steps\n const stepsMatch = response.match(/### MIGRATION_STEPS\\s*\\n([\\s\\S]*?)(?=### INSTALL_COMMANDS|$)/)\n if (stepsMatch) {\n result.steps = stepsMatch[1]!.trim()\n }\n\n // Extract install commands\n const installMatch = response.match(/### INSTALL_COMMANDS[\\s\\S]*?```(?:bash|sh)?\\n([\\s\\S]*?)```/)\n if (installMatch) {\n result.installCommands = installMatch[1]!.trim()\n }\n\n return result\n}\n\nexport interface MigrateOptions {\n from: string\n provider: AIProvider\n write: boolean\n}\n\nexport async function runMigrate(options: MigrateOptions): Promise<void> {\n const { from, provider, write } = options\n\n const library = resolveLibrary(from)\n if (!library) {\n consola.error(`Unsupported library \"${from}\". Supported libraries:`)\n for (const name of SUPPORTED_NAMES) {\n consola.log(` - ${name}`)\n }\n return\n }\n\n const info = LIBRARY_INFO[library]\n consola.info(`Migrating from ${info.name} (${info.framework}) to Fluenti`)\n\n // Detect existing files\n consola.info('Scanning project for existing i18n files...')\n const detected = await detectFiles(info)\n\n if (detected.configFiles.length === 0 && detected.localeFiles.length === 0) {\n consola.warn(`No ${info.name} configuration or locale files found.`)\n consola.info('Make sure you are running this command from the project root directory.')\n return\n }\n\n consola.info(`Found: ${detected.configFiles.length} config file(s), ${detected.localeFiles.length} locale file(s), ${detected.sampleSources.length} source file(s)`)\n\n // Load migration guide\n const migrationGuide = loadMigrationGuide(info.migrationGuide)\n\n // Build prompt and invoke AI\n consola.info(`Generating migration plan with ${provider}...`)\n const prompt = buildMigratePrompt(info, detected, migrationGuide)\n const response = await invokeAI(provider, prompt)\n const result = parseResponse(response)\n\n // Display install commands\n if (result.installCommands) {\n consola.log('')\n consola.box({\n title: 'Install Commands',\n message: result.installCommands,\n })\n }\n\n // Write or display fluenti.config.ts\n if (result.config) {\n if (write) {\n const { writeFileSync } = await import('node:fs')\n const configPath = resolve('fluenti.config.ts')\n writeFileSync(configPath, result.config, 'utf-8')\n consola.success(`Written: ${configPath}`)\n } else {\n consola.log('')\n consola.box({\n title: 'fluenti.config.ts',\n message: result.config,\n })\n }\n }\n\n // Write or display locale files\n if (result.localeFiles.length > 0) {\n if (write) {\n const { writeFileSync, mkdirSync } = await import('node:fs')\n const catalogDir = './locales'\n mkdirSync(resolve(catalogDir), { recursive: true })\n for (const file of result.localeFiles) {\n const outPath = resolve(catalogDir, `${file.locale}.po`)\n writeFileSync(outPath, file.content, 'utf-8')\n consola.success(`Written: ${outPath}`)\n }\n } else {\n for (const file of result.localeFiles) {\n consola.log('')\n consola.box({\n title: `locales/${file.locale}.po`,\n message: file.content.length > 500\n ? file.content.slice(0, 500) + '\\n... (use --write to save full file)'\n : file.content,\n })\n }\n }\n }\n\n // Display migration steps\n if (result.steps) {\n consola.log('')\n consola.box({\n title: 'Migration Steps',\n message: result.steps,\n })\n }\n\n if (!write && (result.config || result.localeFiles.length > 0)) {\n consola.log('')\n consola.info('Run with --write to save generated files to disk:')\n consola.log(` fluenti migrate --from ${from} --write`)\n }\n}\n","#!/usr/bin/env node\nimport { defineCommand, runMain } from 'citty'\nimport consola from 'consola'\nimport fg from 'fast-glob'\nimport { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'\nimport { resolve, dirname, extname } from 'node:path'\nimport { extractFromVue } from './vue-extractor'\nimport { extractFromTsx } from './tsx-extractor'\nimport { updateCatalog } from './catalog'\nimport type { CatalogData } from './catalog'\nimport { readJsonCatalog, writeJsonCatalog } from './json-format'\nimport { readPoCatalog, writePoCatalog } from './po-format'\nimport { compileCatalog, compileIndex, collectAllIds } from './compile'\nimport { translateCatalog } from './translate'\nimport type { AIProvider } from './translate'\nimport { runMigrate } from './migrate'\nimport type { ExtractedMessage, FluentiConfig } from '@fluenti/core'\n\nconst defaultConfig: FluentiConfig = {\n sourceLocale: 'en',\n locales: ['en'],\n catalogDir: './locales',\n format: 'po',\n include: ['./src/**/*.{vue,tsx,jsx,ts,js}'],\n compileOutDir: './locales/compiled',\n}\n\nasync function loadConfig(configPath?: string): Promise<FluentiConfig> {\n const paths = configPath\n ? [resolve(configPath)]\n : [\n resolve('fluenti.config.ts'),\n resolve('fluenti.config.js'),\n resolve('fluenti.config.mjs'),\n ]\n\n for (const p of paths) {\n if (existsSync(p)) {\n try {\n const { createJiti } = await import('jiti')\n const jiti = createJiti(import.meta.url)\n const mod = await jiti.import(p) as { default?: Partial<FluentiConfig> }\n const userConfig = mod.default ?? mod as unknown as Partial<FluentiConfig>\n return { ...defaultConfig, ...userConfig }\n } catch {\n consola.warn(`Failed to load config from ${p}, using defaults`)\n }\n }\n }\n\n return defaultConfig\n}\n\nfunction readCatalog(filePath: string, format: 'json' | 'po'): CatalogData {\n if (!existsSync(filePath)) return {}\n const content = readFileSync(filePath, 'utf-8')\n return format === 'json' ? readJsonCatalog(content) : readPoCatalog(content)\n}\n\nfunction writeCatalog(filePath: string, catalog: CatalogData, format: 'json' | 'po'): void {\n mkdirSync(dirname(filePath), { recursive: true })\n const content = format === 'json' ? writeJsonCatalog(catalog) : writePoCatalog(catalog)\n writeFileSync(filePath, content, 'utf-8')\n}\n\nfunction extractFromFile(filePath: string, code: string): ExtractedMessage[] {\n const ext = extname(filePath)\n if (ext === '.vue') return extractFromVue(code, filePath)\n return extractFromTsx(code, filePath)\n}\n\nconst extract = defineCommand({\n meta: { name: 'extract', description: 'Extract messages from source files' },\n args: {\n config: { type: 'string', description: 'Path to config file' },\n clean: { type: 'boolean', description: 'Remove obsolete entries instead of marking them', default: false },\n },\n async run({ args }) {\n const config = await loadConfig(args.config)\n consola.info(`Extracting messages from ${config.include.join(', ')}`)\n\n const files = await fg(config.include)\n const allMessages: ExtractedMessage[] = []\n\n for (const file of files) {\n const code = readFileSync(file, 'utf-8')\n const messages = extractFromFile(file, code)\n allMessages.push(...messages)\n }\n\n consola.info(`Found ${allMessages.length} messages in ${files.length} files`)\n\n const ext = config.format === 'json' ? '.json' : '.po'\n const clean = args.clean ?? false\n\n for (const locale of config.locales) {\n const catalogPath = resolve(config.catalogDir, `${locale}${ext}`)\n const existing = readCatalog(catalogPath, config.format)\n const { catalog, result } = updateCatalog(existing, allMessages)\n\n const finalCatalog = clean\n ? Object.fromEntries(Object.entries(catalog).filter(([, entry]) => !entry.obsolete))\n : catalog\n\n writeCatalog(catalogPath, finalCatalog, config.format)\n\n const obsoleteLabel = clean\n ? `${result.obsolete} removed`\n : `${result.obsolete} obsolete`\n consola.success(\n `${locale}: ${result.added} added, ${result.unchanged} unchanged, ${obsoleteLabel}`,\n )\n }\n },\n})\n\nconst compile = defineCommand({\n meta: { name: 'compile', description: 'Compile message catalogs to JS modules' },\n args: {\n config: { type: 'string', description: 'Path to config file' },\n },\n async run({ args }) {\n const config = await loadConfig(args.config)\n const ext = config.format === 'json' ? '.json' : '.po'\n\n mkdirSync(config.compileOutDir, { recursive: true })\n\n // Collect all catalogs and build union of IDs\n const allCatalogs: Record<string, CatalogData> = {}\n for (const locale of config.locales) {\n const catalogPath = resolve(config.catalogDir, `${locale}${ext}`)\n allCatalogs[locale] = readCatalog(catalogPath, config.format)\n }\n\n const allIds = collectAllIds(allCatalogs)\n consola.info(`Compiling ${allIds.length} messages across ${config.locales.length} locales`)\n\n for (const locale of config.locales) {\n const { code, stats } = compileCatalog(\n allCatalogs[locale]!,\n locale,\n allIds,\n config.sourceLocale,\n )\n const outPath = resolve(config.compileOutDir, `${locale}.js`)\n writeFileSync(outPath, code, 'utf-8')\n\n if (stats.missing.length > 0) {\n consola.warn(\n `${locale}: ${stats.compiled} compiled, ${stats.missing.length} missing translations`,\n )\n for (const id of stats.missing) {\n consola.warn(` ⤷ ${id}`)\n }\n } else {\n consola.success(`Compiled ${locale}: ${stats.compiled} messages → ${outPath}`)\n }\n }\n\n // Generate index.js with locale list and lazy loaders\n const indexCode = compileIndex(config.locales, config.compileOutDir)\n const indexPath = resolve(config.compileOutDir, 'index.js')\n writeFileSync(indexPath, indexCode, 'utf-8')\n consola.success(`Generated index → ${indexPath}`)\n },\n})\n\nconst stats = defineCommand({\n meta: { name: 'stats', description: 'Show translation progress' },\n args: {\n config: { type: 'string', description: 'Path to config file' },\n },\n async run({ args }) {\n const config = await loadConfig(args.config)\n const ext = config.format === 'json' ? '.json' : '.po'\n\n const rows: Array<{ locale: string; total: number; translated: number; pct: string }> = []\n\n for (const locale of config.locales) {\n const catalogPath = resolve(config.catalogDir, `${locale}${ext}`)\n const catalog = readCatalog(catalogPath, config.format)\n const entries = Object.values(catalog).filter((e) => !e.obsolete)\n const total = entries.length\n const translated = entries.filter((e) => e.translation && e.translation.length > 0).length\n const pct = total > 0 ? ((translated / total) * 100).toFixed(1) + '%' : '—'\n rows.push({ locale, total, translated, pct })\n }\n\n consola.log('')\n consola.log(' Locale │ Total │ Translated │ Progress')\n consola.log(' ────────┼───────┼────────────┼─────────')\n for (const row of rows) {\n consola.log(\n ` ${row.locale.padEnd(8)}│ ${String(row.total).padStart(5)} │ ${String(row.translated).padStart(10)} │ ${row.pct}`,\n )\n }\n consola.log('')\n },\n})\n\nconst translate = defineCommand({\n meta: { name: 'translate', description: 'Translate messages using AI (Claude Code or Codex CLI)' },\n args: {\n config: { type: 'string', description: 'Path to config file' },\n provider: { type: 'string', description: 'AI provider: claude or codex', default: 'claude' },\n locale: { type: 'string', description: 'Translate a specific locale only' },\n 'batch-size': { type: 'string', description: 'Messages per batch', default: '50' },\n },\n async run({ args }) {\n const config = await loadConfig(args.config)\n const provider = args.provider as AIProvider\n\n if (provider !== 'claude' && provider !== 'codex') {\n consola.error(`Invalid provider \"${provider}\". Use \"claude\" or \"codex\".`)\n return\n }\n\n const batchSize = parseInt(args['batch-size'] ?? '50', 10)\n if (isNaN(batchSize) || batchSize < 1) {\n consola.error('Invalid batch-size. Must be a positive integer.')\n return\n }\n\n const targetLocales = args.locale\n ? [args.locale]\n : config.locales.filter((l: string) => l !== config.sourceLocale)\n\n if (targetLocales.length === 0) {\n consola.warn('No target locales to translate.')\n return\n }\n\n consola.info(`Translating with ${provider} (batch size: ${batchSize})`)\n const ext = config.format === 'json' ? '.json' : '.po'\n\n for (const locale of targetLocales) {\n consola.info(`\\n[${locale}]`)\n const catalogPath = resolve(config.catalogDir, `${locale}${ext}`)\n const catalog = readCatalog(catalogPath, config.format)\n\n const { catalog: updated, translated } = await translateCatalog({\n provider,\n sourceLocale: config.sourceLocale,\n targetLocale: locale,\n catalog,\n batchSize,\n })\n\n if (translated > 0) {\n writeCatalog(catalogPath, updated, config.format)\n consola.success(` ${locale}: ${translated} messages translated`)\n } else {\n consola.success(` ${locale}: already fully translated`)\n }\n }\n },\n})\n\nconst migrate = defineCommand({\n meta: { name: 'migrate', description: 'Migrate from another i18n library using AI' },\n args: {\n from: { type: 'string', description: 'Source library: vue-i18n, nuxt-i18n, react-i18next, next-intl, next-i18next, lingui', required: true },\n provider: { type: 'string', description: 'AI provider: claude or codex', default: 'claude' },\n write: { type: 'boolean', description: 'Write generated files to disk', default: false },\n },\n async run({ args }) {\n const provider = args.provider as AIProvider\n if (provider !== 'claude' && provider !== 'codex') {\n consola.error(`Invalid provider \"${provider}\". Use \"claude\" or \"codex\".`)\n return\n }\n\n await runMigrate({\n from: args.from!,\n provider,\n write: args.write ?? false,\n })\n },\n})\n\nconst main = defineCommand({\n meta: {\n name: 'fluenti',\n version: '0.0.1',\n description: 'Compile-time i18n for modern frameworks',\n },\n subCommands: { extract, compile, stats, translate, migrate },\n})\n\nrunMain(main)\n"],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as e, c as t, i as n, l as r, n as i, o as a, r as o, s, t as c, u as l } from "./compile-
|
|
2
|
+
import { a as e, c as t, i as n, l as r, n as i, o as a, r as o, s, t as c, u as l } from "./compile-DuHUSzlx.js";
|
|
3
3
|
import { defineCommand as u, runMain as d } from "citty";
|
|
4
4
|
import f from "consola";
|
|
5
5
|
import p from "fast-glob";
|
|
@@ -423,23 +423,32 @@ d(u({
|
|
|
423
423
|
name: "extract",
|
|
424
424
|
description: "Extract messages from source files"
|
|
425
425
|
},
|
|
426
|
-
args: {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
426
|
+
args: {
|
|
427
|
+
config: {
|
|
428
|
+
type: "string",
|
|
429
|
+
description: "Path to config file"
|
|
430
|
+
},
|
|
431
|
+
clean: {
|
|
432
|
+
type: "boolean",
|
|
433
|
+
description: "Remove obsolete entries instead of marking them",
|
|
434
|
+
default: !1
|
|
435
|
+
}
|
|
436
|
+
},
|
|
430
437
|
async run({ args: e }) {
|
|
431
438
|
let n = await H(e.config);
|
|
432
439
|
f.info(`Extracting messages from ${n.include.join(", ")}`);
|
|
433
|
-
let r = await p(n.include
|
|
440
|
+
let r = await p(n.include), i = [];
|
|
434
441
|
for (let e of r) {
|
|
435
442
|
let t = G(e, g(e, "utf-8"));
|
|
436
443
|
i.push(...t);
|
|
437
444
|
}
|
|
438
445
|
f.info(`Found ${i.length} messages in ${r.length} files`);
|
|
439
|
-
let a = n.format === "json" ? ".json" : ".po";
|
|
446
|
+
let a = n.format === "json" ? ".json" : ".po", o = e.clean ?? !1;
|
|
440
447
|
for (let e of n.locales) {
|
|
441
|
-
let r = x(n.catalogDir, `${e}${a}`), { catalog:
|
|
442
|
-
W(r, o
|
|
448
|
+
let r = x(n.catalogDir, `${e}${a}`), { catalog: s, result: c } = t(U(r, n.format), i);
|
|
449
|
+
W(r, o ? Object.fromEntries(Object.entries(s).filter(([, e]) => !e.obsolete)) : s, n.format);
|
|
450
|
+
let l = o ? `${c.obsolete} removed` : `${c.obsolete} obsolete`;
|
|
451
|
+
f.success(`${e}: ${c.added} added, ${c.unchanged} unchanged, ${l}`);
|
|
443
452
|
}
|
|
444
453
|
}
|
|
445
454
|
}),
|
|
@@ -460,8 +469,11 @@ d(u({
|
|
|
460
469
|
let a = c(r);
|
|
461
470
|
f.info(`Compiling ${a.length} messages across ${t.locales.length} locales`);
|
|
462
471
|
for (let e of t.locales) {
|
|
463
|
-
let n = i(r[e], e, a, t.sourceLocale),
|
|
464
|
-
_(
|
|
472
|
+
let { code: n, stats: o } = i(r[e], e, a, t.sourceLocale), s = x(t.compileOutDir, `${e}.js`);
|
|
473
|
+
if (_(s, n, "utf-8"), o.missing.length > 0) {
|
|
474
|
+
f.warn(`${e}: ${o.compiled} compiled, ${o.missing.length} missing translations`);
|
|
475
|
+
for (let e of o.missing) f.warn(` ⤷ ${e}`);
|
|
476
|
+
} else f.success(`Compiled ${e}: ${o.compiled} messages → ${s}`);
|
|
465
477
|
}
|
|
466
478
|
let s = o(t.locales, t.compileOutDir), l = x(t.compileOutDir, "index.js");
|
|
467
479
|
_(l, s, "utf-8"), f.success(`Generated index → ${l}`);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":[],"sources":["../src/translate.ts","../src/migrate.ts","../src/cli.ts"],"sourcesContent":["import { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport consola from 'consola'\nimport type { CatalogData } from './catalog'\n\nconst execFileAsync = promisify(execFile)\n\nexport type AIProvider = 'claude' | 'codex'\n\nfunction buildPrompt(\n sourceLocale: string,\n targetLocale: string,\n messages: Record<string, string>,\n): string {\n const json = JSON.stringify(messages, null, 2)\n return [\n `You are a professional translator. Translate the following messages from \"${sourceLocale}\" to \"${targetLocale}\".`,\n '',\n `Input (JSON):`,\n json,\n '',\n 'Rules:',\n '- Output ONLY valid JSON with the same keys and translated values.',\n '- Keep ICU MessageFormat placeholders like {name}, {count}, {gender} unchanged.',\n '- Keep HTML tags unchanged.',\n '- Do not add any explanation or markdown formatting, output raw JSON only.',\n ].join('\\n')\n}\n\nasync function invokeAI(provider: AIProvider, prompt: string): Promise<string> {\n const maxBuffer = 10 * 1024 * 1024\n\n try {\n if (provider === 'claude') {\n const { stdout } = await execFileAsync('claude', ['-p', prompt], { maxBuffer })\n return stdout\n } else {\n const { stdout } = await execFileAsync('codex', ['-p', prompt, '--full-auto'], { maxBuffer })\n return stdout\n }\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n if (err.code === 'ENOENT') {\n throw new Error(\n `\"${provider}\" CLI not found. Please install it first:\\n` +\n (provider === 'claude'\n ? ' npm install -g @anthropic-ai/claude-code'\n : ' npm install -g @openai/codex'),\n )\n }\n throw error\n }\n}\n\nfunction extractJSON(text: string): Record<string, string> {\n // Try to find a JSON object in the response\n const match = text.match(/\\{[\\s\\S]*\\}/)\n if (!match) {\n throw new Error('No JSON object found in AI response')\n }\n const parsed = JSON.parse(match[0])\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new Error('AI response is not a valid JSON object')\n }\n return parsed as Record<string, string>\n}\n\nfunction getUntranslatedEntries(catalog: CatalogData): Record<string, string> {\n const entries: Record<string, string> = {}\n for (const [id, entry] of Object.entries(catalog)) {\n if (entry.obsolete) continue\n if (!entry.translation || entry.translation.length === 0) {\n entries[id] = entry.message ?? id\n }\n }\n return entries\n}\n\nfunction chunkEntries(\n entries: Record<string, string>,\n batchSize: number,\n): Array<Record<string, string>> {\n const keys = Object.keys(entries)\n const chunks: Array<Record<string, string>> = []\n\n for (let i = 0; i < keys.length; i += batchSize) {\n const chunk: Record<string, string> = {}\n for (const key of keys.slice(i, i + batchSize)) {\n chunk[key] = entries[key]!\n }\n chunks.push(chunk)\n }\n\n return chunks\n}\n\nexport interface TranslateOptions {\n provider: AIProvider\n sourceLocale: string\n targetLocale: string\n catalog: CatalogData\n batchSize: number\n}\n\nexport async function translateCatalog(options: TranslateOptions): Promise<{\n catalog: CatalogData\n translated: number\n}> {\n const { provider, sourceLocale, targetLocale, catalog, batchSize } = options\n\n const untranslated = getUntranslatedEntries(catalog)\n const count = Object.keys(untranslated).length\n\n if (count === 0) {\n return { catalog, translated: 0 }\n }\n\n consola.info(` ${count} untranslated messages, translating with ${provider}...`)\n\n const batches = chunkEntries(untranslated, batchSize)\n let totalTranslated = 0\n\n for (let i = 0; i < batches.length; i++) {\n const batch = batches[i]!\n const batchKeys = Object.keys(batch)\n\n if (batches.length > 1) {\n consola.info(` Batch ${i + 1}/${batches.length} (${batchKeys.length} messages)`)\n }\n\n const prompt = buildPrompt(sourceLocale, targetLocale, batch)\n const response = await invokeAI(provider, prompt)\n const translations = extractJSON(response)\n\n for (const key of batchKeys) {\n if (translations[key] && typeof translations[key] === 'string') {\n catalog[key] = {\n ...catalog[key],\n translation: translations[key],\n }\n totalTranslated++\n } else {\n consola.warn(` Missing translation for key: ${key}`)\n }\n }\n }\n\n return { catalog, translated: totalTranslated }\n}\n","import { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport { readFileSync, existsSync } from 'node:fs'\nimport { resolve, join } from 'node:path'\nimport fg from 'fast-glob'\nimport consola from 'consola'\nimport type { AIProvider } from './translate'\n\nconst execFileAsync = promisify(execFile)\n\nexport type SupportedLibrary =\n | 'vue-i18n'\n | 'nuxt-i18n'\n | 'react-i18next'\n | 'next-intl'\n | 'next-i18next'\n | 'lingui'\n\ninterface LibraryInfo {\n name: SupportedLibrary\n framework: string\n configPatterns: string[]\n localePatterns: string[]\n sourcePatterns: string[]\n migrationGuide: string // relative path from packages/\n}\n\nconst LIBRARY_INFO: Record<SupportedLibrary, LibraryInfo> = {\n 'vue-i18n': {\n name: 'vue-i18n',\n framework: 'Vue',\n 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'],\n localePatterns: ['locales/*.json', 'src/locales/*.json', 'i18n/*.json', 'src/i18n/*.json', 'lang/*.json', 'src/lang/*.json', 'locales/*.yaml', 'locales/*.yml'],\n sourcePatterns: ['src/**/*.vue'],\n migrationGuide: 'vue/llms-migration.txt',\n },\n 'nuxt-i18n': {\n name: 'nuxt-i18n',\n framework: 'Nuxt',\n configPatterns: ['nuxt.config.ts', 'nuxt.config.js', 'i18n.config.ts', 'i18n.config.js'],\n localePatterns: ['locales/*.json', 'lang/*.json', 'i18n/*.json', 'locales/*.yaml', 'locales/*.yml'],\n sourcePatterns: ['pages/**/*.vue', 'components/**/*.vue', 'layouts/**/*.vue'],\n migrationGuide: 'nuxt/llms-migration.txt',\n },\n 'react-i18next': {\n name: 'react-i18next',\n framework: 'React',\n configPatterns: ['i18n.ts', 'i18n.js', 'src/i18n.ts', 'src/i18n.js', 'src/i18n/index.ts', 'src/i18n/config.ts'],\n localePatterns: ['locales/*.json', 'src/locales/*.json', 'public/locales/**/*.json', 'translations/*.json', 'src/translations/*.json'],\n sourcePatterns: ['src/**/*.tsx', 'src/**/*.jsx', 'src/**/*.ts'],\n migrationGuide: 'react/llms-migration.txt',\n },\n 'next-intl': {\n name: 'next-intl',\n framework: 'Next.js',\n configPatterns: ['next.config.ts', 'next.config.js', 'next.config.mjs', 'i18n.ts', 'src/i18n.ts', 'i18n/request.ts', 'src/i18n/request.ts'],\n localePatterns: ['messages/*.json', 'locales/*.json', 'src/messages/*.json', 'src/locales/*.json'],\n sourcePatterns: ['app/**/*.tsx', 'src/app/**/*.tsx', 'pages/**/*.tsx', 'components/**/*.tsx'],\n migrationGuide: 'next-plugin/llms-migration.txt',\n },\n 'next-i18next': {\n name: 'next-i18next',\n framework: 'Next.js',\n configPatterns: ['next-i18next.config.js', 'next-i18next.config.mjs', 'next.config.ts', 'next.config.js'],\n localePatterns: ['public/locales/**/*.json'],\n sourcePatterns: ['pages/**/*.tsx', 'src/pages/**/*.tsx', 'components/**/*.tsx', 'src/components/**/*.tsx'],\n migrationGuide: 'next-plugin/llms-migration.txt',\n },\n 'lingui': {\n name: 'lingui',\n framework: 'React',\n configPatterns: ['lingui.config.ts', 'lingui.config.js', '.linguirc'],\n localePatterns: ['locales/*.po', 'src/locales/*.po', 'locales/*/messages.po', 'src/locales/*/messages.po'],\n sourcePatterns: ['src/**/*.tsx', 'src/**/*.jsx', 'src/**/*.ts'],\n migrationGuide: 'react/llms-migration.txt',\n },\n}\n\nconst SUPPORTED_NAMES = Object.keys(LIBRARY_INFO) as SupportedLibrary[]\n\nfunction resolveLibrary(from: string): SupportedLibrary | undefined {\n const normalized = from.toLowerCase().replace(/^@nuxtjs\\//, 'nuxt-').replace(/^@/, '')\n return SUPPORTED_NAMES.find((name) => name === normalized)\n}\n\ninterface DetectedFiles {\n configFiles: Array<{ path: string; content: string }>\n localeFiles: Array<{ path: string; content: string }>\n sampleSources: Array<{ path: string; content: string }>\n packageJson: string | undefined\n}\n\nasync function detectFiles(info: LibraryInfo): Promise<DetectedFiles> {\n const result: DetectedFiles = {\n configFiles: [],\n localeFiles: [],\n sampleSources: [],\n packageJson: undefined,\n }\n\n // Read package.json\n const pkgPath = resolve('package.json')\n if (existsSync(pkgPath)) {\n result.packageJson = readFileSync(pkgPath, 'utf-8')\n }\n\n // Find config files\n for (const pattern of info.configPatterns) {\n const fullPath = resolve(pattern)\n if (existsSync(fullPath)) {\n result.configFiles.push({\n path: pattern,\n content: readFileSync(fullPath, 'utf-8'),\n })\n }\n }\n\n // Find locale files (limit to 10 to avoid huge prompts)\n const localeGlobs = await fg(info.localePatterns, { absolute: false })\n for (const file of localeGlobs.slice(0, 10)) {\n const fullPath = resolve(file)\n const content = readFileSync(fullPath, 'utf-8')\n // Truncate large files\n result.localeFiles.push({\n path: file,\n content: content.length > 5000 ? content.slice(0, 5000) + '\\n... (truncated)' : content,\n })\n }\n\n // Find sample source files (limit to 5 for prompt size)\n const sourceGlobs = await fg(info.sourcePatterns, { absolute: false })\n for (const file of sourceGlobs.slice(0, 5)) {\n const fullPath = resolve(file)\n const content = readFileSync(fullPath, 'utf-8')\n result.sampleSources.push({\n path: file,\n content: content.length > 3000 ? content.slice(0, 3000) + '\\n... (truncated)' : content,\n })\n }\n\n return result\n}\n\nfunction loadMigrationGuide(guidePath: string): string {\n // Try to find the migration guide relative to the CLI package\n const candidates = [\n resolve('node_modules', '@fluenti', 'cli', '..', '..', guidePath),\n join(__dirname, '..', '..', '..', guidePath),\n join(__dirname, '..', '..', guidePath),\n ]\n\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return readFileSync(candidate, 'utf-8')\n }\n }\n\n return ''\n}\n\nfunction buildMigratePrompt(\n library: LibraryInfo,\n detected: DetectedFiles,\n migrationGuide: string,\n): string {\n const sections: string[] = []\n\n sections.push(\n `You are a migration assistant helping convert a ${library.framework} project from \"${library.name}\" to Fluenti (@fluenti).`,\n '',\n 'Your task:',\n '1. Generate a `fluenti.config.ts` file based on the existing i18n configuration',\n '2. Convert each locale/translation file to Fluenti PO format',\n '3. List the code changes needed (file by file) to migrate source code from the old API to Fluenti API',\n '',\n )\n\n if (migrationGuide) {\n sections.push(\n '=== MIGRATION GUIDE ===',\n migrationGuide,\n '',\n )\n }\n\n if (detected.packageJson) {\n sections.push(\n '=== package.json ===',\n detected.packageJson,\n '',\n )\n }\n\n if (detected.configFiles.length > 0) {\n sections.push('=== EXISTING CONFIG FILES ===')\n for (const file of detected.configFiles) {\n sections.push(`--- ${file.path} ---`, file.content, '')\n }\n }\n\n if (detected.localeFiles.length > 0) {\n sections.push('=== EXISTING LOCALE FILES ===')\n for (const file of detected.localeFiles) {\n sections.push(`--- ${file.path} ---`, file.content, '')\n }\n }\n\n if (detected.sampleSources.length > 0) {\n sections.push('=== SAMPLE SOURCE FILES ===')\n for (const file of detected.sampleSources) {\n sections.push(`--- ${file.path} ---`, file.content, '')\n }\n }\n\n sections.push(\n '',\n '=== OUTPUT FORMAT ===',\n 'Respond with the following sections, each starting with the exact header shown:',\n '',\n '### FLUENTI_CONFIG',\n '```ts',\n '// The fluenti.config.ts content',\n '```',\n '',\n '### LOCALE_FILES',\n 'For each locale file, output:',\n '#### LOCALE: {locale_code}',\n '```po',\n '// The PO file content',\n '```',\n '',\n '### MIGRATION_STEPS',\n 'A numbered checklist of specific code changes needed, with before/after examples.',\n '',\n '### INSTALL_COMMANDS',\n '```bash',\n '// The install and uninstall commands',\n '```',\n )\n\n return sections.join('\\n')\n}\n\nasync function invokeAI(provider: AIProvider, prompt: string): Promise<string> {\n const maxBuffer = 10 * 1024 * 1024\n\n try {\n if (provider === 'claude') {\n const { stdout } = await execFileAsync('claude', ['-p', prompt], { maxBuffer })\n return stdout\n } else {\n const { stdout } = await execFileAsync('codex', ['-p', prompt, '--full-auto'], { maxBuffer })\n return stdout\n }\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n if (err.code === 'ENOENT') {\n throw new Error(\n `\"${provider}\" CLI not found. Please install it first:\\n` +\n (provider === 'claude'\n ? ' npm install -g @anthropic-ai/claude-code'\n : ' npm install -g @openai/codex'),\n )\n }\n throw error\n }\n}\n\ninterface MigrateResult {\n config: string | undefined\n localeFiles: Array<{ locale: string; content: string }>\n steps: string | undefined\n installCommands: string | undefined\n}\n\nfunction parseResponse(response: string): MigrateResult {\n const result: MigrateResult = {\n config: undefined,\n localeFiles: [],\n steps: undefined,\n installCommands: undefined,\n }\n\n // Extract fluenti.config.ts\n const configMatch = response.match(/### FLUENTI_CONFIG[\\s\\S]*?```(?:ts|typescript)?\\n([\\s\\S]*?)```/)\n if (configMatch) {\n result.config = configMatch[1]!.trim()\n }\n\n // Extract locale files\n const localeSection = response.match(/### LOCALE_FILES([\\s\\S]*?)(?=### MIGRATION_STEPS|### INSTALL_COMMANDS|$)/)\n if (localeSection) {\n const localeRegex = /#### LOCALE:\\s*(\\S+)\\s*\\n```(?:po)?\\n([\\s\\S]*?)```/g\n let match\n while ((match = localeRegex.exec(localeSection[1]!)) !== null) {\n result.localeFiles.push({\n locale: match[1]!,\n content: match[2]!.trim(),\n })\n }\n }\n\n // Extract migration steps\n const stepsMatch = response.match(/### MIGRATION_STEPS\\s*\\n([\\s\\S]*?)(?=### INSTALL_COMMANDS|$)/)\n if (stepsMatch) {\n result.steps = stepsMatch[1]!.trim()\n }\n\n // Extract install commands\n const installMatch = response.match(/### INSTALL_COMMANDS[\\s\\S]*?```(?:bash|sh)?\\n([\\s\\S]*?)```/)\n if (installMatch) {\n result.installCommands = installMatch[1]!.trim()\n }\n\n return result\n}\n\nexport interface MigrateOptions {\n from: string\n provider: AIProvider\n write: boolean\n}\n\nexport async function runMigrate(options: MigrateOptions): Promise<void> {\n const { from, provider, write } = options\n\n const library = resolveLibrary(from)\n if (!library) {\n consola.error(`Unsupported library \"${from}\". Supported libraries:`)\n for (const name of SUPPORTED_NAMES) {\n consola.log(` - ${name}`)\n }\n return\n }\n\n const info = LIBRARY_INFO[library]\n consola.info(`Migrating from ${info.name} (${info.framework}) to Fluenti`)\n\n // Detect existing files\n consola.info('Scanning project for existing i18n files...')\n const detected = await detectFiles(info)\n\n if (detected.configFiles.length === 0 && detected.localeFiles.length === 0) {\n consola.warn(`No ${info.name} configuration or locale files found.`)\n consola.info('Make sure you are running this command from the project root directory.')\n return\n }\n\n consola.info(`Found: ${detected.configFiles.length} config file(s), ${detected.localeFiles.length} locale file(s), ${detected.sampleSources.length} source file(s)`)\n\n // Load migration guide\n const migrationGuide = loadMigrationGuide(info.migrationGuide)\n\n // Build prompt and invoke AI\n consola.info(`Generating migration plan with ${provider}...`)\n const prompt = buildMigratePrompt(info, detected, migrationGuide)\n const response = await invokeAI(provider, prompt)\n const result = parseResponse(response)\n\n // Display install commands\n if (result.installCommands) {\n consola.log('')\n consola.box({\n title: 'Install Commands',\n message: result.installCommands,\n })\n }\n\n // Write or display fluenti.config.ts\n if (result.config) {\n if (write) {\n const { writeFileSync } = await import('node:fs')\n const configPath = resolve('fluenti.config.ts')\n writeFileSync(configPath, result.config, 'utf-8')\n consola.success(`Written: ${configPath}`)\n } else {\n consola.log('')\n consola.box({\n title: 'fluenti.config.ts',\n message: result.config,\n })\n }\n }\n\n // Write or display locale files\n if (result.localeFiles.length > 0) {\n if (write) {\n const { writeFileSync, mkdirSync } = await import('node:fs')\n const catalogDir = './locales'\n mkdirSync(resolve(catalogDir), { recursive: true })\n for (const file of result.localeFiles) {\n const outPath = resolve(catalogDir, `${file.locale}.po`)\n writeFileSync(outPath, file.content, 'utf-8')\n consola.success(`Written: ${outPath}`)\n }\n } else {\n for (const file of result.localeFiles) {\n consola.log('')\n consola.box({\n title: `locales/${file.locale}.po`,\n message: file.content.length > 500\n ? file.content.slice(0, 500) + '\\n... (use --write to save full file)'\n : file.content,\n })\n }\n }\n }\n\n // Display migration steps\n if (result.steps) {\n consola.log('')\n consola.box({\n title: 'Migration Steps',\n message: result.steps,\n })\n }\n\n if (!write && (result.config || result.localeFiles.length > 0)) {\n consola.log('')\n consola.info('Run with --write to save generated files to disk:')\n consola.log(` fluenti migrate --from ${from} --write`)\n }\n}\n","#!/usr/bin/env node\nimport { defineCommand, runMain } from 'citty'\nimport consola from 'consola'\nimport fg from 'fast-glob'\nimport { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'\nimport { resolve, dirname, extname } from 'node:path'\nimport { extractFromVue } from './vue-extractor'\nimport { extractFromTsx } from './tsx-extractor'\nimport { updateCatalog } from './catalog'\nimport type { CatalogData } from './catalog'\nimport { readJsonCatalog, writeJsonCatalog } from './json-format'\nimport { readPoCatalog, writePoCatalog } from './po-format'\nimport { compileCatalog, compileIndex, collectAllIds } from './compile'\nimport { translateCatalog } from './translate'\nimport type { AIProvider } from './translate'\nimport { runMigrate } from './migrate'\nimport type { ExtractedMessage, FluentiConfig } from '@fluenti/core'\n\nconst defaultConfig: FluentiConfig = {\n sourceLocale: 'en',\n locales: ['en'],\n catalogDir: './locales',\n format: 'po',\n include: ['./src/**/*.{vue,tsx,jsx,ts,js}'],\n compileOutDir: './locales/compiled',\n}\n\nasync function loadConfig(configPath?: string): Promise<FluentiConfig> {\n const paths = configPath\n ? [resolve(configPath)]\n : [\n resolve('fluenti.config.ts'),\n resolve('fluenti.config.js'),\n resolve('fluenti.config.mjs'),\n ]\n\n for (const p of paths) {\n if (existsSync(p)) {\n try {\n const { createJiti } = await import('jiti')\n const jiti = createJiti(import.meta.url)\n const mod = await jiti.import(p) as { default?: Partial<FluentiConfig> }\n const userConfig = mod.default ?? mod as unknown as Partial<FluentiConfig>\n return { ...defaultConfig, ...userConfig }\n } catch {\n consola.warn(`Failed to load config from ${p}, using defaults`)\n }\n }\n }\n\n return defaultConfig\n}\n\nfunction readCatalog(filePath: string, format: 'json' | 'po'): CatalogData {\n if (!existsSync(filePath)) return {}\n const content = readFileSync(filePath, 'utf-8')\n return format === 'json' ? readJsonCatalog(content) : readPoCatalog(content)\n}\n\nfunction writeCatalog(filePath: string, catalog: CatalogData, format: 'json' | 'po'): void {\n mkdirSync(dirname(filePath), { recursive: true })\n const content = format === 'json' ? writeJsonCatalog(catalog) : writePoCatalog(catalog)\n writeFileSync(filePath, content, 'utf-8')\n}\n\nfunction extractFromFile(filePath: string, code: string): ExtractedMessage[] {\n const ext = extname(filePath)\n if (ext === '.vue') return extractFromVue(code, filePath)\n return extractFromTsx(code, filePath)\n}\n\nconst extract = defineCommand({\n meta: { name: 'extract', description: 'Extract messages from source files' },\n args: {\n config: { type: 'string', description: 'Path to config file' },\n },\n async run({ args }) {\n const config = await loadConfig(args.config)\n consola.info(`Extracting messages from ${config.include.join(', ')}`)\n\n const files = await fg(config.include, { absolute: true })\n const allMessages: ExtractedMessage[] = []\n\n for (const file of files) {\n const code = readFileSync(file, 'utf-8')\n const messages = extractFromFile(file, code)\n allMessages.push(...messages)\n }\n\n consola.info(`Found ${allMessages.length} messages in ${files.length} files`)\n\n const ext = config.format === 'json' ? '.json' : '.po'\n\n for (const locale of config.locales) {\n const catalogPath = resolve(config.catalogDir, `${locale}${ext}`)\n const existing = readCatalog(catalogPath, config.format)\n const { catalog, result } = updateCatalog(existing, allMessages)\n writeCatalog(catalogPath, catalog, config.format)\n consola.success(\n `${locale}: ${result.added} added, ${result.unchanged} unchanged, ${result.obsolete} obsolete`,\n )\n }\n },\n})\n\nconst compile = defineCommand({\n meta: { name: 'compile', description: 'Compile message catalogs to JS modules' },\n args: {\n config: { type: 'string', description: 'Path to config file' },\n },\n async run({ args }) {\n const config = await loadConfig(args.config)\n const ext = config.format === 'json' ? '.json' : '.po'\n\n mkdirSync(config.compileOutDir, { recursive: true })\n\n // Collect all catalogs and build union of IDs\n const allCatalogs: Record<string, CatalogData> = {}\n for (const locale of config.locales) {\n const catalogPath = resolve(config.catalogDir, `${locale}${ext}`)\n allCatalogs[locale] = readCatalog(catalogPath, config.format)\n }\n\n const allIds = collectAllIds(allCatalogs)\n consola.info(`Compiling ${allIds.length} messages across ${config.locales.length} locales`)\n\n for (const locale of config.locales) {\n const compiled = compileCatalog(\n allCatalogs[locale]!,\n locale,\n allIds,\n config.sourceLocale,\n )\n const outPath = resolve(config.compileOutDir, `${locale}.js`)\n writeFileSync(outPath, compiled, 'utf-8')\n consola.success(`Compiled ${locale} → ${outPath}`)\n }\n\n // Generate index.js with locale list and lazy loaders\n const indexCode = compileIndex(config.locales, config.compileOutDir)\n const indexPath = resolve(config.compileOutDir, 'index.js')\n writeFileSync(indexPath, indexCode, 'utf-8')\n consola.success(`Generated index → ${indexPath}`)\n },\n})\n\nconst stats = defineCommand({\n meta: { name: 'stats', description: 'Show translation progress' },\n args: {\n config: { type: 'string', description: 'Path to config file' },\n },\n async run({ args }) {\n const config = await loadConfig(args.config)\n const ext = config.format === 'json' ? '.json' : '.po'\n\n const rows: Array<{ locale: string; total: number; translated: number; pct: string }> = []\n\n for (const locale of config.locales) {\n const catalogPath = resolve(config.catalogDir, `${locale}${ext}`)\n const catalog = readCatalog(catalogPath, config.format)\n const entries = Object.values(catalog).filter((e) => !e.obsolete)\n const total = entries.length\n const translated = entries.filter((e) => e.translation && e.translation.length > 0).length\n const pct = total > 0 ? ((translated / total) * 100).toFixed(1) + '%' : '—'\n rows.push({ locale, total, translated, pct })\n }\n\n consola.log('')\n consola.log(' Locale │ Total │ Translated │ Progress')\n consola.log(' ────────┼───────┼────────────┼─────────')\n for (const row of rows) {\n consola.log(\n ` ${row.locale.padEnd(8)}│ ${String(row.total).padStart(5)} │ ${String(row.translated).padStart(10)} │ ${row.pct}`,\n )\n }\n consola.log('')\n },\n})\n\nconst translate = defineCommand({\n meta: { name: 'translate', description: 'Translate messages using AI (Claude Code or Codex CLI)' },\n args: {\n config: { type: 'string', description: 'Path to config file' },\n provider: { type: 'string', description: 'AI provider: claude or codex', default: 'claude' },\n locale: { type: 'string', description: 'Translate a specific locale only' },\n 'batch-size': { type: 'string', description: 'Messages per batch', default: '50' },\n },\n async run({ args }) {\n const config = await loadConfig(args.config)\n const provider = args.provider as AIProvider\n\n if (provider !== 'claude' && provider !== 'codex') {\n consola.error(`Invalid provider \"${provider}\". Use \"claude\" or \"codex\".`)\n return\n }\n\n const batchSize = parseInt(args['batch-size'] ?? '50', 10)\n if (isNaN(batchSize) || batchSize < 1) {\n consola.error('Invalid batch-size. Must be a positive integer.')\n return\n }\n\n const targetLocales = args.locale\n ? [args.locale]\n : config.locales.filter((l: string) => l !== config.sourceLocale)\n\n if (targetLocales.length === 0) {\n consola.warn('No target locales to translate.')\n return\n }\n\n consola.info(`Translating with ${provider} (batch size: ${batchSize})`)\n const ext = config.format === 'json' ? '.json' : '.po'\n\n for (const locale of targetLocales) {\n consola.info(`\\n[${locale}]`)\n const catalogPath = resolve(config.catalogDir, `${locale}${ext}`)\n const catalog = readCatalog(catalogPath, config.format)\n\n const { catalog: updated, translated } = await translateCatalog({\n provider,\n sourceLocale: config.sourceLocale,\n targetLocale: locale,\n catalog,\n batchSize,\n })\n\n if (translated > 0) {\n writeCatalog(catalogPath, updated, config.format)\n consola.success(` ${locale}: ${translated} messages translated`)\n } else {\n consola.success(` ${locale}: already fully translated`)\n }\n }\n },\n})\n\nconst migrate = defineCommand({\n meta: { name: 'migrate', description: 'Migrate from another i18n library using AI' },\n args: {\n from: { type: 'string', description: 'Source library: vue-i18n, nuxt-i18n, react-i18next, next-intl, next-i18next, lingui', required: true },\n provider: { type: 'string', description: 'AI provider: claude or codex', default: 'claude' },\n write: { type: 'boolean', description: 'Write generated files to disk', default: false },\n },\n async run({ args }) {\n const provider = args.provider as AIProvider\n if (provider !== 'claude' && provider !== 'codex') {\n consola.error(`Invalid provider \"${provider}\". Use \"claude\" or \"codex\".`)\n return\n }\n\n await runMigrate({\n from: args.from!,\n provider,\n write: args.write ?? false,\n })\n },\n})\n\nconst main = defineCommand({\n meta: {\n name: 'fluenti',\n version: '0.0.1',\n description: 'Compile-time i18n for modern frameworks',\n },\n subCommands: { extract, compile, stats, translate, migrate },\n})\n\nrunMain(main)\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/translate.ts","../src/migrate.ts","../src/cli.ts"],"sourcesContent":["import { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport consola from 'consola'\nimport type { CatalogData } from './catalog'\n\nconst execFileAsync = promisify(execFile)\n\nexport type AIProvider = 'claude' | 'codex'\n\nfunction buildPrompt(\n sourceLocale: string,\n targetLocale: string,\n messages: Record<string, string>,\n): string {\n const json = JSON.stringify(messages, null, 2)\n return [\n `You are a professional translator. Translate the following messages from \"${sourceLocale}\" to \"${targetLocale}\".`,\n '',\n `Input (JSON):`,\n json,\n '',\n 'Rules:',\n '- Output ONLY valid JSON with the same keys and translated values.',\n '- Keep ICU MessageFormat placeholders like {name}, {count}, {gender} unchanged.',\n '- Keep HTML tags unchanged.',\n '- Do not add any explanation or markdown formatting, output raw JSON only.',\n ].join('\\n')\n}\n\nasync function invokeAI(provider: AIProvider, prompt: string): Promise<string> {\n const maxBuffer = 10 * 1024 * 1024\n\n try {\n if (provider === 'claude') {\n const { stdout } = await execFileAsync('claude', ['-p', prompt], { maxBuffer })\n return stdout\n } else {\n const { stdout } = await execFileAsync('codex', ['-p', prompt, '--full-auto'], { maxBuffer })\n return stdout\n }\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n if (err.code === 'ENOENT') {\n throw new Error(\n `\"${provider}\" CLI not found. Please install it first:\\n` +\n (provider === 'claude'\n ? ' npm install -g @anthropic-ai/claude-code'\n : ' npm install -g @openai/codex'),\n )\n }\n throw error\n }\n}\n\nfunction extractJSON(text: string): Record<string, string> {\n // Try to find a JSON object in the response\n const match = text.match(/\\{[\\s\\S]*\\}/)\n if (!match) {\n throw new Error('No JSON object found in AI response')\n }\n const parsed = JSON.parse(match[0])\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new Error('AI response is not a valid JSON object')\n }\n return parsed as Record<string, string>\n}\n\nfunction getUntranslatedEntries(catalog: CatalogData): Record<string, string> {\n const entries: Record<string, string> = {}\n for (const [id, entry] of Object.entries(catalog)) {\n if (entry.obsolete) continue\n if (!entry.translation || entry.translation.length === 0) {\n entries[id] = entry.message ?? id\n }\n }\n return entries\n}\n\nfunction chunkEntries(\n entries: Record<string, string>,\n batchSize: number,\n): Array<Record<string, string>> {\n const keys = Object.keys(entries)\n const chunks: Array<Record<string, string>> = []\n\n for (let i = 0; i < keys.length; i += batchSize) {\n const chunk: Record<string, string> = {}\n for (const key of keys.slice(i, i + batchSize)) {\n chunk[key] = entries[key]!\n }\n chunks.push(chunk)\n }\n\n return chunks\n}\n\nexport interface TranslateOptions {\n provider: AIProvider\n sourceLocale: string\n targetLocale: string\n catalog: CatalogData\n batchSize: number\n}\n\nexport async function translateCatalog(options: TranslateOptions): Promise<{\n catalog: CatalogData\n translated: number\n}> {\n const { provider, sourceLocale, targetLocale, catalog, batchSize } = options\n\n const untranslated = getUntranslatedEntries(catalog)\n const count = Object.keys(untranslated).length\n\n if (count === 0) {\n return { catalog, translated: 0 }\n }\n\n consola.info(` ${count} untranslated messages, translating with ${provider}...`)\n\n const batches = chunkEntries(untranslated, batchSize)\n let totalTranslated = 0\n\n for (let i = 0; i < batches.length; i++) {\n const batch = batches[i]!\n const batchKeys = Object.keys(batch)\n\n if (batches.length > 1) {\n consola.info(` Batch ${i + 1}/${batches.length} (${batchKeys.length} messages)`)\n }\n\n const prompt = buildPrompt(sourceLocale, targetLocale, batch)\n const response = await invokeAI(provider, prompt)\n const translations = extractJSON(response)\n\n for (const key of batchKeys) {\n if (translations[key] && typeof translations[key] === 'string') {\n catalog[key] = {\n ...catalog[key],\n translation: translations[key],\n }\n totalTranslated++\n } else {\n consola.warn(` Missing translation for key: ${key}`)\n }\n }\n }\n\n return { catalog, translated: totalTranslated }\n}\n","import { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport { readFileSync, existsSync } from 'node:fs'\nimport { resolve, join } from 'node:path'\nimport fg from 'fast-glob'\nimport consola from 'consola'\nimport type { AIProvider } from './translate'\n\nconst execFileAsync = promisify(execFile)\n\nexport type SupportedLibrary =\n | 'vue-i18n'\n | 'nuxt-i18n'\n | 'react-i18next'\n | 'next-intl'\n | 'next-i18next'\n | 'lingui'\n\ninterface LibraryInfo {\n name: SupportedLibrary\n framework: string\n configPatterns: string[]\n localePatterns: string[]\n sourcePatterns: string[]\n migrationGuide: string // relative path from packages/\n}\n\nconst LIBRARY_INFO: Record<SupportedLibrary, LibraryInfo> = {\n 'vue-i18n': {\n name: 'vue-i18n',\n framework: 'Vue',\n 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'],\n localePatterns: ['locales/*.json', 'src/locales/*.json', 'i18n/*.json', 'src/i18n/*.json', 'lang/*.json', 'src/lang/*.json', 'locales/*.yaml', 'locales/*.yml'],\n sourcePatterns: ['src/**/*.vue'],\n migrationGuide: 'vue/llms-migration.txt',\n },\n 'nuxt-i18n': {\n name: 'nuxt-i18n',\n framework: 'Nuxt',\n configPatterns: ['nuxt.config.ts', 'nuxt.config.js', 'i18n.config.ts', 'i18n.config.js'],\n localePatterns: ['locales/*.json', 'lang/*.json', 'i18n/*.json', 'locales/*.yaml', 'locales/*.yml'],\n sourcePatterns: ['pages/**/*.vue', 'components/**/*.vue', 'layouts/**/*.vue'],\n migrationGuide: 'nuxt/llms-migration.txt',\n },\n 'react-i18next': {\n name: 'react-i18next',\n framework: 'React',\n configPatterns: ['i18n.ts', 'i18n.js', 'src/i18n.ts', 'src/i18n.js', 'src/i18n/index.ts', 'src/i18n/config.ts'],\n localePatterns: ['locales/*.json', 'src/locales/*.json', 'public/locales/**/*.json', 'translations/*.json', 'src/translations/*.json'],\n sourcePatterns: ['src/**/*.tsx', 'src/**/*.jsx', 'src/**/*.ts'],\n migrationGuide: 'react/llms-migration.txt',\n },\n 'next-intl': {\n name: 'next-intl',\n framework: 'Next.js',\n configPatterns: ['next.config.ts', 'next.config.js', 'next.config.mjs', 'i18n.ts', 'src/i18n.ts', 'i18n/request.ts', 'src/i18n/request.ts'],\n localePatterns: ['messages/*.json', 'locales/*.json', 'src/messages/*.json', 'src/locales/*.json'],\n sourcePatterns: ['app/**/*.tsx', 'src/app/**/*.tsx', 'pages/**/*.tsx', 'components/**/*.tsx'],\n migrationGuide: 'next-plugin/llms-migration.txt',\n },\n 'next-i18next': {\n name: 'next-i18next',\n framework: 'Next.js',\n configPatterns: ['next-i18next.config.js', 'next-i18next.config.mjs', 'next.config.ts', 'next.config.js'],\n localePatterns: ['public/locales/**/*.json'],\n sourcePatterns: ['pages/**/*.tsx', 'src/pages/**/*.tsx', 'components/**/*.tsx', 'src/components/**/*.tsx'],\n migrationGuide: 'next-plugin/llms-migration.txt',\n },\n 'lingui': {\n name: 'lingui',\n framework: 'React',\n configPatterns: ['lingui.config.ts', 'lingui.config.js', '.linguirc'],\n localePatterns: ['locales/*.po', 'src/locales/*.po', 'locales/*/messages.po', 'src/locales/*/messages.po'],\n sourcePatterns: ['src/**/*.tsx', 'src/**/*.jsx', 'src/**/*.ts'],\n migrationGuide: 'react/llms-migration.txt',\n },\n}\n\nconst SUPPORTED_NAMES = Object.keys(LIBRARY_INFO) as SupportedLibrary[]\n\nfunction resolveLibrary(from: string): SupportedLibrary | undefined {\n const normalized = from.toLowerCase().replace(/^@nuxtjs\\//, 'nuxt-').replace(/^@/, '')\n return SUPPORTED_NAMES.find((name) => name === normalized)\n}\n\ninterface DetectedFiles {\n configFiles: Array<{ path: string; content: string }>\n localeFiles: Array<{ path: string; content: string }>\n sampleSources: Array<{ path: string; content: string }>\n packageJson: string | undefined\n}\n\nasync function detectFiles(info: LibraryInfo): Promise<DetectedFiles> {\n const result: DetectedFiles = {\n configFiles: [],\n localeFiles: [],\n sampleSources: [],\n packageJson: undefined,\n }\n\n // Read package.json\n const pkgPath = resolve('package.json')\n if (existsSync(pkgPath)) {\n result.packageJson = readFileSync(pkgPath, 'utf-8')\n }\n\n // Find config files\n for (const pattern of info.configPatterns) {\n const fullPath = resolve(pattern)\n if (existsSync(fullPath)) {\n result.configFiles.push({\n path: pattern,\n content: readFileSync(fullPath, 'utf-8'),\n })\n }\n }\n\n // Find locale files (limit to 10 to avoid huge prompts)\n const localeGlobs = await fg(info.localePatterns, { absolute: false })\n for (const file of localeGlobs.slice(0, 10)) {\n const fullPath = resolve(file)\n const content = readFileSync(fullPath, 'utf-8')\n // Truncate large files\n result.localeFiles.push({\n path: file,\n content: content.length > 5000 ? content.slice(0, 5000) + '\\n... (truncated)' : content,\n })\n }\n\n // Find sample source files (limit to 5 for prompt size)\n const sourceGlobs = await fg(info.sourcePatterns, { absolute: false })\n for (const file of sourceGlobs.slice(0, 5)) {\n const fullPath = resolve(file)\n const content = readFileSync(fullPath, 'utf-8')\n result.sampleSources.push({\n path: file,\n content: content.length > 3000 ? content.slice(0, 3000) + '\\n... (truncated)' : content,\n })\n }\n\n return result\n}\n\nfunction loadMigrationGuide(guidePath: string): string {\n // Try to find the migration guide relative to the CLI package\n const candidates = [\n resolve('node_modules', '@fluenti', 'cli', '..', '..', guidePath),\n join(__dirname, '..', '..', '..', guidePath),\n join(__dirname, '..', '..', guidePath),\n ]\n\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return readFileSync(candidate, 'utf-8')\n }\n }\n\n return ''\n}\n\nfunction buildMigratePrompt(\n library: LibraryInfo,\n detected: DetectedFiles,\n migrationGuide: string,\n): string {\n const sections: string[] = []\n\n sections.push(\n `You are a migration assistant helping convert a ${library.framework} project from \"${library.name}\" to Fluenti (@fluenti).`,\n '',\n 'Your task:',\n '1. Generate a `fluenti.config.ts` file based on the existing i18n configuration',\n '2. Convert each locale/translation file to Fluenti PO format',\n '3. List the code changes needed (file by file) to migrate source code from the old API to Fluenti API',\n '',\n )\n\n if (migrationGuide) {\n sections.push(\n '=== MIGRATION GUIDE ===',\n migrationGuide,\n '',\n )\n }\n\n if (detected.packageJson) {\n sections.push(\n '=== package.json ===',\n detected.packageJson,\n '',\n )\n }\n\n if (detected.configFiles.length > 0) {\n sections.push('=== EXISTING CONFIG FILES ===')\n for (const file of detected.configFiles) {\n sections.push(`--- ${file.path} ---`, file.content, '')\n }\n }\n\n if (detected.localeFiles.length > 0) {\n sections.push('=== EXISTING LOCALE FILES ===')\n for (const file of detected.localeFiles) {\n sections.push(`--- ${file.path} ---`, file.content, '')\n }\n }\n\n if (detected.sampleSources.length > 0) {\n sections.push('=== SAMPLE SOURCE FILES ===')\n for (const file of detected.sampleSources) {\n sections.push(`--- ${file.path} ---`, file.content, '')\n }\n }\n\n sections.push(\n '',\n '=== OUTPUT FORMAT ===',\n 'Respond with the following sections, each starting with the exact header shown:',\n '',\n '### FLUENTI_CONFIG',\n '```ts',\n '// The fluenti.config.ts content',\n '```',\n '',\n '### LOCALE_FILES',\n 'For each locale file, output:',\n '#### LOCALE: {locale_code}',\n '```po',\n '// The PO file content',\n '```',\n '',\n '### MIGRATION_STEPS',\n 'A numbered checklist of specific code changes needed, with before/after examples.',\n '',\n '### INSTALL_COMMANDS',\n '```bash',\n '// The install and uninstall commands',\n '```',\n )\n\n return sections.join('\\n')\n}\n\nasync function invokeAI(provider: AIProvider, prompt: string): Promise<string> {\n const maxBuffer = 10 * 1024 * 1024\n\n try {\n if (provider === 'claude') {\n const { stdout } = await execFileAsync('claude', ['-p', prompt], { maxBuffer })\n return stdout\n } else {\n const { stdout } = await execFileAsync('codex', ['-p', prompt, '--full-auto'], { maxBuffer })\n return stdout\n }\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n if (err.code === 'ENOENT') {\n throw new Error(\n `\"${provider}\" CLI not found. Please install it first:\\n` +\n (provider === 'claude'\n ? ' npm install -g @anthropic-ai/claude-code'\n : ' npm install -g @openai/codex'),\n )\n }\n throw error\n }\n}\n\ninterface MigrateResult {\n config: string | undefined\n localeFiles: Array<{ locale: string; content: string }>\n steps: string | undefined\n installCommands: string | undefined\n}\n\nfunction parseResponse(response: string): MigrateResult {\n const result: MigrateResult = {\n config: undefined,\n localeFiles: [],\n steps: undefined,\n installCommands: undefined,\n }\n\n // Extract fluenti.config.ts\n const configMatch = response.match(/### FLUENTI_CONFIG[\\s\\S]*?```(?:ts|typescript)?\\n([\\s\\S]*?)```/)\n if (configMatch) {\n result.config = configMatch[1]!.trim()\n }\n\n // Extract locale files\n const localeSection = response.match(/### LOCALE_FILES([\\s\\S]*?)(?=### MIGRATION_STEPS|### INSTALL_COMMANDS|$)/)\n if (localeSection) {\n const localeRegex = /#### LOCALE:\\s*(\\S+)\\s*\\n```(?:po)?\\n([\\s\\S]*?)```/g\n let match\n while ((match = localeRegex.exec(localeSection[1]!)) !== null) {\n result.localeFiles.push({\n locale: match[1]!,\n content: match[2]!.trim(),\n })\n }\n }\n\n // Extract migration steps\n const stepsMatch = response.match(/### MIGRATION_STEPS\\s*\\n([\\s\\S]*?)(?=### INSTALL_COMMANDS|$)/)\n if (stepsMatch) {\n result.steps = stepsMatch[1]!.trim()\n }\n\n // Extract install commands\n const installMatch = response.match(/### INSTALL_COMMANDS[\\s\\S]*?```(?:bash|sh)?\\n([\\s\\S]*?)```/)\n if (installMatch) {\n result.installCommands = installMatch[1]!.trim()\n }\n\n return result\n}\n\nexport interface MigrateOptions {\n from: string\n provider: AIProvider\n write: boolean\n}\n\nexport async function runMigrate(options: MigrateOptions): Promise<void> {\n const { from, provider, write } = options\n\n const library = resolveLibrary(from)\n if (!library) {\n consola.error(`Unsupported library \"${from}\". Supported libraries:`)\n for (const name of SUPPORTED_NAMES) {\n consola.log(` - ${name}`)\n }\n return\n }\n\n const info = LIBRARY_INFO[library]\n consola.info(`Migrating from ${info.name} (${info.framework}) to Fluenti`)\n\n // Detect existing files\n consola.info('Scanning project for existing i18n files...')\n const detected = await detectFiles(info)\n\n if (detected.configFiles.length === 0 && detected.localeFiles.length === 0) {\n consola.warn(`No ${info.name} configuration or locale files found.`)\n consola.info('Make sure you are running this command from the project root directory.')\n return\n }\n\n consola.info(`Found: ${detected.configFiles.length} config file(s), ${detected.localeFiles.length} locale file(s), ${detected.sampleSources.length} source file(s)`)\n\n // Load migration guide\n const migrationGuide = loadMigrationGuide(info.migrationGuide)\n\n // Build prompt and invoke AI\n consola.info(`Generating migration plan with ${provider}...`)\n const prompt = buildMigratePrompt(info, detected, migrationGuide)\n const response = await invokeAI(provider, prompt)\n const result = parseResponse(response)\n\n // Display install commands\n if (result.installCommands) {\n consola.log('')\n consola.box({\n title: 'Install Commands',\n message: result.installCommands,\n })\n }\n\n // Write or display fluenti.config.ts\n if (result.config) {\n if (write) {\n const { writeFileSync } = await import('node:fs')\n const configPath = resolve('fluenti.config.ts')\n writeFileSync(configPath, result.config, 'utf-8')\n consola.success(`Written: ${configPath}`)\n } else {\n consola.log('')\n consola.box({\n title: 'fluenti.config.ts',\n message: result.config,\n })\n }\n }\n\n // Write or display locale files\n if (result.localeFiles.length > 0) {\n if (write) {\n const { writeFileSync, mkdirSync } = await import('node:fs')\n const catalogDir = './locales'\n mkdirSync(resolve(catalogDir), { recursive: true })\n for (const file of result.localeFiles) {\n const outPath = resolve(catalogDir, `${file.locale}.po`)\n writeFileSync(outPath, file.content, 'utf-8')\n consola.success(`Written: ${outPath}`)\n }\n } else {\n for (const file of result.localeFiles) {\n consola.log('')\n consola.box({\n title: `locales/${file.locale}.po`,\n message: file.content.length > 500\n ? file.content.slice(0, 500) + '\\n... (use --write to save full file)'\n : file.content,\n })\n }\n }\n }\n\n // Display migration steps\n if (result.steps) {\n consola.log('')\n consola.box({\n title: 'Migration Steps',\n message: result.steps,\n })\n }\n\n if (!write && (result.config || result.localeFiles.length > 0)) {\n consola.log('')\n consola.info('Run with --write to save generated files to disk:')\n consola.log(` fluenti migrate --from ${from} --write`)\n }\n}\n","#!/usr/bin/env node\nimport { defineCommand, runMain } from 'citty'\nimport consola from 'consola'\nimport fg from 'fast-glob'\nimport { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'\nimport { resolve, dirname, extname } from 'node:path'\nimport { extractFromVue } from './vue-extractor'\nimport { extractFromTsx } from './tsx-extractor'\nimport { updateCatalog } from './catalog'\nimport type { CatalogData } from './catalog'\nimport { readJsonCatalog, writeJsonCatalog } from './json-format'\nimport { readPoCatalog, writePoCatalog } from './po-format'\nimport { compileCatalog, compileIndex, collectAllIds } from './compile'\nimport { translateCatalog } from './translate'\nimport type { AIProvider } from './translate'\nimport { runMigrate } from './migrate'\nimport type { ExtractedMessage, FluentiConfig } from '@fluenti/core'\n\nconst defaultConfig: FluentiConfig = {\n sourceLocale: 'en',\n locales: ['en'],\n catalogDir: './locales',\n format: 'po',\n include: ['./src/**/*.{vue,tsx,jsx,ts,js}'],\n compileOutDir: './locales/compiled',\n}\n\nasync function loadConfig(configPath?: string): Promise<FluentiConfig> {\n const paths = configPath\n ? [resolve(configPath)]\n : [\n resolve('fluenti.config.ts'),\n resolve('fluenti.config.js'),\n resolve('fluenti.config.mjs'),\n ]\n\n for (const p of paths) {\n if (existsSync(p)) {\n try {\n const { createJiti } = await import('jiti')\n const jiti = createJiti(import.meta.url)\n const mod = await jiti.import(p) as { default?: Partial<FluentiConfig> }\n const userConfig = mod.default ?? mod as unknown as Partial<FluentiConfig>\n return { ...defaultConfig, ...userConfig }\n } catch {\n consola.warn(`Failed to load config from ${p}, using defaults`)\n }\n }\n }\n\n return defaultConfig\n}\n\nfunction readCatalog(filePath: string, format: 'json' | 'po'): CatalogData {\n if (!existsSync(filePath)) return {}\n const content = readFileSync(filePath, 'utf-8')\n return format === 'json' ? readJsonCatalog(content) : readPoCatalog(content)\n}\n\nfunction writeCatalog(filePath: string, catalog: CatalogData, format: 'json' | 'po'): void {\n mkdirSync(dirname(filePath), { recursive: true })\n const content = format === 'json' ? writeJsonCatalog(catalog) : writePoCatalog(catalog)\n writeFileSync(filePath, content, 'utf-8')\n}\n\nfunction extractFromFile(filePath: string, code: string): ExtractedMessage[] {\n const ext = extname(filePath)\n if (ext === '.vue') return extractFromVue(code, filePath)\n return extractFromTsx(code, filePath)\n}\n\nconst extract = defineCommand({\n meta: { name: 'extract', description: 'Extract messages from source files' },\n args: {\n config: { type: 'string', description: 'Path to config file' },\n clean: { type: 'boolean', description: 'Remove obsolete entries instead of marking them', default: false },\n },\n async run({ args }) {\n const config = await loadConfig(args.config)\n consola.info(`Extracting messages from ${config.include.join(', ')}`)\n\n const files = await fg(config.include)\n const allMessages: ExtractedMessage[] = []\n\n for (const file of files) {\n const code = readFileSync(file, 'utf-8')\n const messages = extractFromFile(file, code)\n allMessages.push(...messages)\n }\n\n consola.info(`Found ${allMessages.length} messages in ${files.length} files`)\n\n const ext = config.format === 'json' ? '.json' : '.po'\n const clean = args.clean ?? false\n\n for (const locale of config.locales) {\n const catalogPath = resolve(config.catalogDir, `${locale}${ext}`)\n const existing = readCatalog(catalogPath, config.format)\n const { catalog, result } = updateCatalog(existing, allMessages)\n\n const finalCatalog = clean\n ? Object.fromEntries(Object.entries(catalog).filter(([, entry]) => !entry.obsolete))\n : catalog\n\n writeCatalog(catalogPath, finalCatalog, config.format)\n\n const obsoleteLabel = clean\n ? `${result.obsolete} removed`\n : `${result.obsolete} obsolete`\n consola.success(\n `${locale}: ${result.added} added, ${result.unchanged} unchanged, ${obsoleteLabel}`,\n )\n }\n },\n})\n\nconst compile = defineCommand({\n meta: { name: 'compile', description: 'Compile message catalogs to JS modules' },\n args: {\n config: { type: 'string', description: 'Path to config file' },\n },\n async run({ args }) {\n const config = await loadConfig(args.config)\n const ext = config.format === 'json' ? '.json' : '.po'\n\n mkdirSync(config.compileOutDir, { recursive: true })\n\n // Collect all catalogs and build union of IDs\n const allCatalogs: Record<string, CatalogData> = {}\n for (const locale of config.locales) {\n const catalogPath = resolve(config.catalogDir, `${locale}${ext}`)\n allCatalogs[locale] = readCatalog(catalogPath, config.format)\n }\n\n const allIds = collectAllIds(allCatalogs)\n consola.info(`Compiling ${allIds.length} messages across ${config.locales.length} locales`)\n\n for (const locale of config.locales) {\n const { code, stats } = compileCatalog(\n allCatalogs[locale]!,\n locale,\n allIds,\n config.sourceLocale,\n )\n const outPath = resolve(config.compileOutDir, `${locale}.js`)\n writeFileSync(outPath, code, 'utf-8')\n\n if (stats.missing.length > 0) {\n consola.warn(\n `${locale}: ${stats.compiled} compiled, ${stats.missing.length} missing translations`,\n )\n for (const id of stats.missing) {\n consola.warn(` ⤷ ${id}`)\n }\n } else {\n consola.success(`Compiled ${locale}: ${stats.compiled} messages → ${outPath}`)\n }\n }\n\n // Generate index.js with locale list and lazy loaders\n const indexCode = compileIndex(config.locales, config.compileOutDir)\n const indexPath = resolve(config.compileOutDir, 'index.js')\n writeFileSync(indexPath, indexCode, 'utf-8')\n consola.success(`Generated index → ${indexPath}`)\n },\n})\n\nconst stats = defineCommand({\n meta: { name: 'stats', description: 'Show translation progress' },\n args: {\n config: { type: 'string', description: 'Path to config file' },\n },\n async run({ args }) {\n const config = await loadConfig(args.config)\n const ext = config.format === 'json' ? '.json' : '.po'\n\n const rows: Array<{ locale: string; total: number; translated: number; pct: string }> = []\n\n for (const locale of config.locales) {\n const catalogPath = resolve(config.catalogDir, `${locale}${ext}`)\n const catalog = readCatalog(catalogPath, config.format)\n const entries = Object.values(catalog).filter((e) => !e.obsolete)\n const total = entries.length\n const translated = entries.filter((e) => e.translation && e.translation.length > 0).length\n const pct = total > 0 ? ((translated / total) * 100).toFixed(1) + '%' : '—'\n rows.push({ locale, total, translated, pct })\n }\n\n consola.log('')\n consola.log(' Locale │ Total │ Translated │ Progress')\n consola.log(' ────────┼───────┼────────────┼─────────')\n for (const row of rows) {\n consola.log(\n ` ${row.locale.padEnd(8)}│ ${String(row.total).padStart(5)} │ ${String(row.translated).padStart(10)} │ ${row.pct}`,\n )\n }\n consola.log('')\n },\n})\n\nconst translate = defineCommand({\n meta: { name: 'translate', description: 'Translate messages using AI (Claude Code or Codex CLI)' },\n args: {\n config: { type: 'string', description: 'Path to config file' },\n provider: { type: 'string', description: 'AI provider: claude or codex', default: 'claude' },\n locale: { type: 'string', description: 'Translate a specific locale only' },\n 'batch-size': { type: 'string', description: 'Messages per batch', default: '50' },\n },\n async run({ args }) {\n const config = await loadConfig(args.config)\n const provider = args.provider as AIProvider\n\n if (provider !== 'claude' && provider !== 'codex') {\n consola.error(`Invalid provider \"${provider}\". Use \"claude\" or \"codex\".`)\n return\n }\n\n const batchSize = parseInt(args['batch-size'] ?? '50', 10)\n if (isNaN(batchSize) || batchSize < 1) {\n consola.error('Invalid batch-size. Must be a positive integer.')\n return\n }\n\n const targetLocales = args.locale\n ? [args.locale]\n : config.locales.filter((l: string) => l !== config.sourceLocale)\n\n if (targetLocales.length === 0) {\n consola.warn('No target locales to translate.')\n return\n }\n\n consola.info(`Translating with ${provider} (batch size: ${batchSize})`)\n const ext = config.format === 'json' ? '.json' : '.po'\n\n for (const locale of targetLocales) {\n consola.info(`\\n[${locale}]`)\n const catalogPath = resolve(config.catalogDir, `${locale}${ext}`)\n const catalog = readCatalog(catalogPath, config.format)\n\n const { catalog: updated, translated } = await translateCatalog({\n provider,\n sourceLocale: config.sourceLocale,\n targetLocale: locale,\n catalog,\n batchSize,\n })\n\n if (translated > 0) {\n writeCatalog(catalogPath, updated, config.format)\n consola.success(` ${locale}: ${translated} messages translated`)\n } else {\n consola.success(` ${locale}: already fully translated`)\n }\n }\n },\n})\n\nconst migrate = defineCommand({\n meta: { name: 'migrate', description: 'Migrate from another i18n library using AI' },\n args: {\n from: { type: 'string', description: 'Source library: vue-i18n, nuxt-i18n, react-i18next, next-intl, next-i18next, lingui', required: true },\n provider: { type: 'string', description: 'AI provider: claude or codex', default: 'claude' },\n write: { type: 'boolean', description: 'Write generated files to disk', default: false },\n },\n async run({ args }) {\n const provider = args.provider as AIProvider\n if (provider !== 'claude' && provider !== 'codex') {\n consola.error(`Invalid provider \"${provider}\". Use \"claude\" or \"codex\".`)\n return\n }\n\n await runMigrate({\n from: args.from!,\n provider,\n write: args.write ?? false,\n })\n },\n})\n\nconst main = defineCommand({\n meta: {\n name: 'fluenti',\n version: '0.0.1',\n description: 'Compile-time i18n for modern frameworks',\n },\n subCommands: { extract, compile, stats, translate, migrate },\n})\n\nrunMain(main)\n"],"mappings":"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"}
|