@fluenti/cli 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +3 -3
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +105 -85
- package/dist/cli.js.map +1 -1
- package/dist/{config-loader-CcqRnMzw.js → config-loader-BgAoTfxH.js} +1 -1
- package/dist/{config-loader-CcqRnMzw.js.map → config-loader-BgAoTfxH.js.map} +1 -1
- package/dist/config-loader-D3RGkK_r.cjs +16 -0
- package/dist/{config-loader-DV5Yqrg5.cjs.map → config-loader-D3RGkK_r.cjs.map} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -25
- package/dist/index.js.map +1 -1
- package/dist/translate.d.ts +2 -1
- package/dist/translate.d.ts.map +1 -1
- package/dist/{tsx-extractor-DZrY1LMS.js → tsx-extractor-CcFjsYI-.js} +1 -1
- package/dist/{tsx-extractor-DZrY1LMS.js.map → tsx-extractor-CcFjsYI-.js.map} +1 -1
- package/dist/{tsx-extractor-DNg_iUSd.cjs → tsx-extractor-D__s_cP8.cjs} +2 -2
- package/dist/{tsx-extractor-DNg_iUSd.cjs.map → tsx-extractor-D__s_cP8.cjs.map} +1 -1
- package/dist/vue-extractor.cjs +3 -0
- package/dist/vue-extractor.cjs.map +1 -0
- package/dist/{vue-extractor-iUl6SUkv.js → vue-extractor.js} +60 -67
- package/dist/vue-extractor.js.map +1 -0
- package/package.json +27 -2
- package/dist/config-loader-DV5Yqrg5.cjs +0 -16
- package/dist/vue-extractor-DWETY0BN.cjs +0 -3
- package/dist/vue-extractor-DWETY0BN.cjs.map +0 -1
- package/dist/vue-extractor-iUl6SUkv.js.map +0 -1
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":[],"sources":["../src/stats-format.ts","../src/translate.ts","../src/migrate.ts","../src/init.ts","../src/cli.ts"],"sourcesContent":["const BLOCK_FULL = '█'\nconst BLOCK_EMPTY = '░'\n\n/**\n * Render a Unicode progress bar.\n *\n * @param pct - Percentage (0–100)\n * @param width - Character width of the bar (default 20)\n */\nexport function formatProgressBar(pct: number, width = 20): string {\n const clamped = Math.max(0, Math.min(100, pct))\n const filled = Math.round((clamped / 100) * width)\n return BLOCK_FULL.repeat(filled) + BLOCK_EMPTY.repeat(width - filled)\n}\n\n/**\n * Wrap a percentage string in ANSI colour based on value.\n *\n * - ≥90 → green (\\x1b[32m)\n * - ≥70 → yellow (\\x1b[33m)\n * - <70 → red (\\x1b[31m)\n */\nexport function colorizePercent(pct: number): string {\n const label = pct.toFixed(1) + '%'\n if (pct >= 90) return `\\x1b[32m${label}\\x1b[0m`\n if (pct >= 70) return `\\x1b[33m${label}\\x1b[0m`\n return `\\x1b[31m${label}\\x1b[0m`\n}\n\n/**\n * Format a full stats row for a single locale.\n */\nexport function formatStatsRow(\n locale: string,\n total: number,\n translated: number,\n): string {\n const pct = total > 0 ? (translated / total) * 100 : 0\n const pctDisplay = total > 0 ? colorizePercent(pct) : '—'\n const bar = total > 0 ? formatProgressBar(pct) : ''\n return ` ${locale.padEnd(8)}│ ${String(total).padStart(5)} │ ${String(translated).padStart(10)} │ ${bar} ${pctDisplay}`\n}\n","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\nexport function 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\nexport function 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\nexport function 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\nexport function 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: { ...catalog }, translated: 0 }\n }\n\n consola.info(` ${count} untranslated messages, translating with ${provider}...`)\n\n const result = { ...catalog }\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 result[key] = {\n ...result[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: result, 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\nexport function resolveLibrary(from: string): SupportedLibrary | undefined {\n const normalized = from.toLowerCase().replace(/^@nuxtjs\\//, 'nuxt-').replace(/^@/, '')\n return SUPPORTED_NAMES.find((name) => name === normalized)\n}\n\nexport interface 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\nexport function 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\nexport function 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","import { readFileSync, writeFileSync, existsSync, appendFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport consola from 'consola'\n\nconst LOCALE_PATTERN = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{1,8})*$/\n\nexport function validateLocale(locale: string): string {\n if (!LOCALE_PATTERN.test(locale)) {\n throw new Error(`Invalid locale format: \"${locale}\"`)\n }\n return locale\n}\n\nexport interface DetectedFramework {\n name: 'nextjs' | 'nuxt' | 'vue' | 'solid' | 'solidstart' | 'react' | 'unknown'\n pluginPackage: string | null\n}\n\nconst FRAMEWORK_DETECTION: Array<{\n dep: string\n name: DetectedFramework['name']\n pluginPackage: string\n}> = [\n { dep: 'next', name: 'nextjs', pluginPackage: '@fluenti/next' },\n { dep: 'nuxt', name: 'nuxt', pluginPackage: '@fluenti/vue' },\n { dep: '@solidjs/start', name: 'solidstart', pluginPackage: '@fluenti/solid' },\n { dep: 'vue', name: 'vue', pluginPackage: '@fluenti/vue' },\n { dep: 'solid-js', name: 'solid', pluginPackage: '@fluenti/solid' },\n { dep: 'react', name: 'react', pluginPackage: '@fluenti/react' },\n]\n\n/**\n * Detect the framework from package.json dependencies.\n */\nexport function detectFramework(deps: Record<string, string>): DetectedFramework {\n for (const entry of FRAMEWORK_DETECTION) {\n if (entry.dep in deps) {\n return { name: entry.name, pluginPackage: entry.pluginPackage }\n }\n }\n return { name: 'unknown', pluginPackage: null }\n}\n\n/**\n * Generate fluenti.config.ts content.\n */\nexport function generateFluentiConfig(opts: {\n sourceLocale: string\n locales: string[]\n format: 'po' | 'json'\n}): string {\n const localesList = opts.locales.map((l) => `'${l}'`).join(', ')\n return `import { defineConfig } from '@fluenti/cli'\n\nexport default defineConfig({\n sourceLocale: '${opts.sourceLocale}',\n locales: [${localesList}],\n catalogDir: './locales',\n format: '${opts.format}',\n include: ['./src/**/*.{vue,tsx,jsx,ts,js}'],\n compileOutDir: './src/locales/compiled',\n})\n`\n}\n\n/**\n * Interactive init flow.\n */\nexport async function runInit(options: { cwd: string }): Promise<void> {\n const pkgPath = resolve(options.cwd, 'package.json')\n if (!existsSync(pkgPath)) {\n consola.error('No package.json found in current directory.')\n return\n }\n\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as {\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n scripts?: Record<string, string>\n }\n const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }\n const framework = detectFramework(allDeps)\n\n consola.info(`Detected framework: ${framework.name}`)\n if (framework.pluginPackage) {\n consola.info(`Recommended plugin: ${framework.pluginPackage}`)\n }\n\n // Check if config already exists\n const configPath = resolve(options.cwd, 'fluenti.config.ts')\n if (existsSync(configPath)) {\n consola.warn('fluenti.config.ts already exists. Skipping config generation.')\n return\n }\n\n // Prompt for configuration\n const sourceLocale = await consola.prompt('Source locale?', {\n type: 'text',\n default: 'en',\n placeholder: 'en',\n }) as unknown as string\n\n if (typeof sourceLocale === 'symbol') return // user cancelled\n\n const targetLocalesInput = await consola.prompt('Target locales (comma-separated)?', {\n type: 'text',\n default: 'ja,zh-CN',\n placeholder: 'ja,zh-CN',\n }) as unknown as string\n\n if (typeof targetLocalesInput === 'symbol') return\n\n const format = await consola.prompt('Catalog format?', {\n type: 'select',\n options: ['po', 'json'],\n initial: 'po',\n }) as unknown as string\n\n if (typeof format === 'symbol') return\n\n const targetLocales = targetLocalesInput.split(',').map((l) => l.trim()).filter(Boolean)\n\n // Validate locale formats\n validateLocale(sourceLocale)\n for (const locale of targetLocales) {\n validateLocale(locale)\n }\n\n const allLocales = [sourceLocale, ...targetLocales.filter((l) => l !== sourceLocale)]\n\n // Write config\n const configContent = generateFluentiConfig({\n sourceLocale,\n locales: allLocales,\n format: format as 'po' | 'json',\n })\n writeFileSync(configPath, configContent, 'utf-8')\n consola.success('Created fluenti.config.ts')\n\n // Append to .gitignore\n const gitignorePath = resolve(options.cwd, '.gitignore')\n const gitignoreEntry = 'src/locales/compiled/'\n if (existsSync(gitignorePath)) {\n const existing = readFileSync(gitignorePath, 'utf-8')\n if (!existing.includes(gitignoreEntry)) {\n appendFileSync(gitignorePath, `\\n# Fluenti compiled catalogs\\n${gitignoreEntry}\\n`)\n consola.success('Updated .gitignore')\n }\n } else {\n writeFileSync(gitignorePath, `# Fluenti compiled catalogs\\n${gitignoreEntry}\\n`)\n consola.success('Created .gitignore')\n }\n\n // Patch package.json scripts\n const existingScripts = pkg.scripts ?? {}\n const newScripts: Record<string, string> = {}\n let scriptsChanged = false\n if (!existingScripts['i18n:extract']) {\n newScripts['i18n:extract'] = 'fluenti extract'\n scriptsChanged = true\n }\n if (!existingScripts['i18n:compile']) {\n newScripts['i18n:compile'] = 'fluenti compile'\n scriptsChanged = true\n }\n if (scriptsChanged) {\n const updatedPkg = {\n ...pkg,\n scripts: { ...existingScripts, ...newScripts },\n }\n writeFileSync(pkgPath, JSON.stringify(updatedPkg, null, 2) + '\\n', 'utf-8')\n consola.success('Added i18n:extract and i18n:compile scripts to package.json')\n }\n\n // Print next steps\n consola.log('')\n consola.box({\n title: 'Next steps',\n message: [\n framework.pluginPackage\n ? `1. Install: pnpm add -D ${framework.pluginPackage} @fluenti/cli`\n : '1. Install: pnpm add -D @fluenti/cli',\n framework.name === 'nextjs'\n ? '2. Add withFluenti() to your next.config.ts'\n : framework.name !== 'unknown'\n ? '2. Add the Vite plugin to your vite.config.ts (e.g. fluentiVue() from @fluenti/vue/vite-plugin)'\n : '2. Configure your build tool with the framework Vite plugin or @fluenti/next',\n '3. Run: npx fluenti extract',\n '4. Translate your messages',\n '5. Run: npx fluenti compile',\n ].join('\\n'),\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 { 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, compileTypeDeclaration } from './compile'\nimport { formatStatsRow } from './stats-format'\nimport { translateCatalog } from './translate'\nimport type { AIProvider } from './translate'\nimport { runMigrate } from './migrate'\nimport { runInit } from './init'\nimport { loadConfig } from './config-loader'\nimport type { ExtractedMessage } from '@fluenti/core'\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\nasync function extractFromFile(filePath: string, code: string): Promise<ExtractedMessage[]> {\n const ext = extname(filePath)\n if (ext === '.vue') {\n try {\n const { extractFromVue } = await import('./vue-extractor')\n return extractFromVue(code, filePath)\n } catch {\n consola.warn(\n `Skipping ${filePath}: install @vue/compiler-sfc to extract from .vue files`,\n )\n return []\n }\n }\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 'no-fuzzy': { type: 'boolean', description: 'Strip fuzzy flags from all entries', 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 = await 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 const stripFuzzy = args['no-fuzzy'] ?? 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, { stripFuzzy })\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 'skip-fuzzy': { type: 'boolean', description: 'Exclude fuzzy entries from compilation', default: false },\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 const skipFuzzy = args['skip-fuzzy'] ?? false\n\n for (const locale of config.locales) {\n const { code, stats } = compileCatalog(\n allCatalogs[locale]!,\n locale,\n allIds,\n config.sourceLocale,\n { skipFuzzy },\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 // Generate type declarations\n const typesCode = compileTypeDeclaration(allIds, allCatalogs, config.sourceLocale)\n const typesPath = resolve(config.compileOutDir, 'messages.d.ts')\n writeFileSync(typesPath, typesCode, 'utf-8')\n consola.success(`Generated types → ${typesPath}`)\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(formatStatsRow(row.locale, row.total, row.translated))\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 init = defineCommand({\n meta: { name: 'init', description: 'Initialize Fluenti in your project' },\n args: {},\n async run() {\n await runInit({ cwd: process.cwd() })\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: { init, extract, compile, stats, translate, migrate },\n})\n\nrunMain(main)\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/stats-format.ts","../src/translate.ts","../src/migrate.ts","../src/init.ts","../src/cli.ts"],"sourcesContent":["const BLOCK_FULL = '█'\nconst BLOCK_EMPTY = '░'\n\n/**\n * Render a Unicode progress bar.\n *\n * @param pct - Percentage (0–100)\n * @param width - Character width of the bar (default 20)\n */\nexport function formatProgressBar(pct: number, width = 20): string {\n const clamped = Math.max(0, Math.min(100, pct))\n const filled = Math.round((clamped / 100) * width)\n return BLOCK_FULL.repeat(filled) + BLOCK_EMPTY.repeat(width - filled)\n}\n\n/**\n * Wrap a percentage string in ANSI colour based on value.\n *\n * - ≥90 → green (\\x1b[32m)\n * - ≥70 → yellow (\\x1b[33m)\n * - <70 → red (\\x1b[31m)\n */\nexport function colorizePercent(pct: number): string {\n const label = pct.toFixed(1) + '%'\n if (pct >= 90) return `\\x1b[32m${label}\\x1b[0m`\n if (pct >= 70) return `\\x1b[33m${label}\\x1b[0m`\n return `\\x1b[31m${label}\\x1b[0m`\n}\n\n/**\n * Format a full stats row for a single locale.\n */\nexport function formatStatsRow(\n locale: string,\n total: number,\n translated: number,\n): string {\n const pct = total > 0 ? (translated / total) * 100 : 0\n const pctDisplay = total > 0 ? colorizePercent(pct) : '—'\n const bar = total > 0 ? formatProgressBar(pct) : ''\n return ` ${locale.padEnd(8)}│ ${String(total).padStart(5)} │ ${String(translated).padStart(10)} │ ${bar} ${pctDisplay}`\n}\n","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\nexport function buildPrompt(\n sourceLocale: string,\n targetLocale: string,\n messages: Record<string, string>,\n context?: 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 ...(context ? [`Project context: ${context}`, ''] : []),\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\nexport function 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\nexport function 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\nexport function 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 context?: string\n}\n\nexport async function translateCatalog(options: TranslateOptions): Promise<{\n catalog: CatalogData\n translated: number\n}> {\n const { provider, sourceLocale, targetLocale, catalog, batchSize, context } = options\n\n const untranslated = getUntranslatedEntries(catalog)\n const count = Object.keys(untranslated).length\n\n if (count === 0) {\n return { catalog: { ...catalog }, translated: 0 }\n }\n\n consola.info(` ${count} untranslated messages, translating with ${provider}...`)\n\n const result = { ...catalog }\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, context)\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 result[key] = {\n ...result[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: result, 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\nexport function resolveLibrary(from: string): SupportedLibrary | undefined {\n const normalized = from.toLowerCase().replace(/^@nuxtjs\\//, 'nuxt-').replace(/^@/, '')\n return SUPPORTED_NAMES.find((name) => name === normalized)\n}\n\nexport interface 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\nexport function 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\nexport function 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","import { readFileSync, writeFileSync, existsSync, appendFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport consola from 'consola'\n\nconst LOCALE_PATTERN = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{1,8})*$/\n\nexport function validateLocale(locale: string): string {\n if (!LOCALE_PATTERN.test(locale)) {\n throw new Error(`Invalid locale format: \"${locale}\"`)\n }\n return locale\n}\n\nexport interface DetectedFramework {\n name: 'nextjs' | 'nuxt' | 'vue' | 'solid' | 'solidstart' | 'react' | 'unknown'\n pluginPackage: string | null\n}\n\nconst FRAMEWORK_DETECTION: Array<{\n dep: string\n name: DetectedFramework['name']\n pluginPackage: string\n}> = [\n { dep: 'next', name: 'nextjs', pluginPackage: '@fluenti/next' },\n { dep: 'nuxt', name: 'nuxt', pluginPackage: '@fluenti/vue' },\n { dep: '@solidjs/start', name: 'solidstart', pluginPackage: '@fluenti/solid' },\n { dep: 'vue', name: 'vue', pluginPackage: '@fluenti/vue' },\n { dep: 'solid-js', name: 'solid', pluginPackage: '@fluenti/solid' },\n { dep: 'react', name: 'react', pluginPackage: '@fluenti/react' },\n]\n\n/**\n * Detect the framework from package.json dependencies.\n */\nexport function detectFramework(deps: Record<string, string>): DetectedFramework {\n for (const entry of FRAMEWORK_DETECTION) {\n if (entry.dep in deps) {\n return { name: entry.name, pluginPackage: entry.pluginPackage }\n }\n }\n return { name: 'unknown', pluginPackage: null }\n}\n\n/**\n * Generate fluenti.config.ts content.\n */\nexport function generateFluentiConfig(opts: {\n sourceLocale: string\n locales: string[]\n format: 'po' | 'json'\n}): string {\n const localesList = opts.locales.map((l) => `'${l}'`).join(', ')\n return `import { defineConfig } from '@fluenti/cli'\n\nexport default defineConfig({\n sourceLocale: '${opts.sourceLocale}',\n locales: [${localesList}],\n catalogDir: './locales',\n format: '${opts.format}',\n include: ['./src/**/*.{vue,tsx,jsx,ts,js}'],\n compileOutDir: './src/locales/compiled',\n})\n`\n}\n\n/**\n * Interactive init flow.\n */\nexport async function runInit(options: { cwd: string }): Promise<void> {\n const pkgPath = resolve(options.cwd, 'package.json')\n if (!existsSync(pkgPath)) {\n consola.error('No package.json found in current directory.')\n return\n }\n\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as {\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n scripts?: Record<string, string>\n }\n const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }\n const framework = detectFramework(allDeps)\n\n consola.info(`Detected framework: ${framework.name}`)\n if (framework.pluginPackage) {\n consola.info(`Recommended plugin: ${framework.pluginPackage}`)\n }\n\n // Check if config already exists\n const configPath = resolve(options.cwd, 'fluenti.config.ts')\n if (existsSync(configPath)) {\n consola.warn('fluenti.config.ts already exists. Skipping config generation.')\n return\n }\n\n // Prompt for configuration\n const sourceLocale = await consola.prompt('Source locale?', {\n type: 'text',\n default: 'en',\n placeholder: 'en',\n }) as unknown as string\n\n if (typeof sourceLocale === 'symbol') return // user cancelled\n\n const targetLocalesInput = await consola.prompt('Target locales (comma-separated)?', {\n type: 'text',\n default: 'ja,zh-CN',\n placeholder: 'ja,zh-CN',\n }) as unknown as string\n\n if (typeof targetLocalesInput === 'symbol') return\n\n const format = await consola.prompt('Catalog format?', {\n type: 'select',\n options: ['po', 'json'],\n initial: 'po',\n }) as unknown as string\n\n if (typeof format === 'symbol') return\n\n const targetLocales = targetLocalesInput.split(',').map((l) => l.trim()).filter(Boolean)\n\n // Validate locale formats\n validateLocale(sourceLocale)\n for (const locale of targetLocales) {\n validateLocale(locale)\n }\n\n const allLocales = [sourceLocale, ...targetLocales.filter((l) => l !== sourceLocale)]\n\n // Write config\n const configContent = generateFluentiConfig({\n sourceLocale,\n locales: allLocales,\n format: format as 'po' | 'json',\n })\n writeFileSync(configPath, configContent, 'utf-8')\n consola.success('Created fluenti.config.ts')\n\n // Append to .gitignore\n const gitignorePath = resolve(options.cwd, '.gitignore')\n const gitignoreEntry = 'src/locales/compiled/'\n if (existsSync(gitignorePath)) {\n const existing = readFileSync(gitignorePath, 'utf-8')\n if (!existing.includes(gitignoreEntry)) {\n appendFileSync(gitignorePath, `\\n# Fluenti compiled catalogs\\n${gitignoreEntry}\\n`)\n consola.success('Updated .gitignore')\n }\n } else {\n writeFileSync(gitignorePath, `# Fluenti compiled catalogs\\n${gitignoreEntry}\\n`)\n consola.success('Created .gitignore')\n }\n\n // Patch package.json scripts\n const existingScripts = pkg.scripts ?? {}\n const newScripts: Record<string, string> = {}\n let scriptsChanged = false\n if (!existingScripts['i18n:extract']) {\n newScripts['i18n:extract'] = 'fluenti extract'\n scriptsChanged = true\n }\n if (!existingScripts['i18n:compile']) {\n newScripts['i18n:compile'] = 'fluenti compile'\n scriptsChanged = true\n }\n if (scriptsChanged) {\n const updatedPkg = {\n ...pkg,\n scripts: { ...existingScripts, ...newScripts },\n }\n writeFileSync(pkgPath, JSON.stringify(updatedPkg, null, 2) + '\\n', 'utf-8')\n consola.success('Added i18n:extract and i18n:compile scripts to package.json')\n }\n\n // Print next steps\n consola.log('')\n consola.box({\n title: 'Next steps',\n message: [\n framework.pluginPackage\n ? `1. Install: pnpm add -D ${framework.pluginPackage} @fluenti/cli`\n : '1. Install: pnpm add -D @fluenti/cli',\n framework.name === 'nextjs'\n ? '2. Add withFluenti() to your next.config.ts'\n : framework.name !== 'unknown'\n ? '2. Add the Vite plugin to your vite.config.ts (e.g. fluentiVue() from @fluenti/vue/vite-plugin)'\n : '2. Configure your build tool with the framework Vite plugin or @fluenti/next',\n '3. Run: npx fluenti extract',\n '4. Translate your messages',\n '5. Run: npx fluenti compile',\n ].join('\\n'),\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 { 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, compileTypeDeclaration } from './compile'\nimport { formatStatsRow } from './stats-format'\nimport { translateCatalog } from './translate'\nimport type { AIProvider } from './translate'\nimport { runMigrate } from './migrate'\nimport { runInit } from './init'\nimport { loadConfig } from './config-loader'\nimport type { ExtractedMessage } from '@fluenti/core'\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\nasync function extractFromFile(filePath: string, code: string): Promise<ExtractedMessage[]> {\n const ext = extname(filePath)\n if (ext === '.vue') {\n try {\n const { extractFromVue } = await import('./vue-extractor')\n return extractFromVue(code, filePath)\n } catch {\n consola.warn(\n `Skipping ${filePath}: install @vue/compiler-sfc to extract from .vue files`,\n )\n return []\n }\n }\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 'no-fuzzy': { type: 'boolean', description: 'Strip fuzzy flags from all entries', 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 = await 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 const stripFuzzy = args['no-fuzzy'] ?? 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, { stripFuzzy })\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 'skip-fuzzy': { type: 'boolean', description: 'Exclude fuzzy entries from compilation', default: false },\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 const skipFuzzy = args['skip-fuzzy'] ?? false\n\n for (const locale of config.locales) {\n const { code, stats } = compileCatalog(\n allCatalogs[locale]!,\n locale,\n allIds,\n config.sourceLocale,\n { skipFuzzy },\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 // Generate type declarations\n const typesCode = compileTypeDeclaration(allIds, allCatalogs, config.sourceLocale)\n const typesPath = resolve(config.compileOutDir, 'messages.d.ts')\n writeFileSync(typesPath, typesCode, 'utf-8')\n consola.success(`Generated types → ${typesPath}`)\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(formatStatsRow(row.locale, row.total, row.translated))\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 'dry-run': { type: 'boolean', description: 'Preview translation results without writing files', default: false },\n context: { type: 'string', description: 'Project context description to improve translation quality' },\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 if (args['dry-run']) {\n const untranslated = Object.entries(catalog).filter(\n ([, entry]) => !entry.obsolete && (!entry.translation || entry.translation.length === 0),\n )\n if (untranslated.length > 0) {\n for (const [id, entry] of untranslated) {\n consola.log(` ${id}: ${entry.message ?? id}`)\n }\n consola.success(` ${locale}: ${untranslated.length} messages would be translated (dry-run)`)\n } else {\n consola.success(` ${locale}: already fully translated`)\n }\n continue\n }\n\n const { catalog: updated, translated } = await translateCatalog({\n provider,\n sourceLocale: config.sourceLocale,\n targetLocale: locale,\n catalog,\n batchSize,\n ...(args.context ? { context: args.context } : {}),\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 init = defineCommand({\n meta: { name: 'init', description: 'Initialize Fluenti in your project' },\n args: {},\n async run() {\n await runInit({ cwd: process.cwd() })\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: { init, extract, compile, stats, translate, migrate },\n})\n\nrunMain(main)\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-loader-CcqRnMzw.js","names":[],"sources":["../src/catalog.ts","../src/json-format.ts","../src/po-format.ts","../src/compile.ts","../src/config-loader.ts"],"sourcesContent":["import type { ExtractedMessage } from '@fluenti/core'\n\nexport interface CatalogEntry {\n message?: string | undefined\n context?: string | undefined\n comment?: string | undefined\n translation?: string | undefined\n origin?: string | string[] | undefined\n obsolete?: boolean | undefined\n fuzzy?: boolean | undefined\n}\n\nexport type CatalogData = Record<string, CatalogEntry>\n\nexport interface UpdateResult {\n added: number\n unchanged: number\n obsolete: number\n}\n\nexport interface UpdateCatalogOptions {\n stripFuzzy?: boolean\n}\n\n/** Update catalog with newly extracted messages */\nexport function updateCatalog(\n existing: CatalogData,\n extracted: ExtractedMessage[],\n options?: UpdateCatalogOptions,\n): { catalog: CatalogData; result: UpdateResult } {\n const extractedIds = new Set(extracted.map((m) => m.id))\n const consumedCarryForwardIds = new Set<string>()\n const catalog: CatalogData = {}\n let added = 0\n let unchanged = 0\n let obsolete = 0\n\n for (const msg of extracted) {\n const existingEntry = existing[msg.id]\n const carried = existingEntry\n ? undefined\n : findCarryForwardEntry(existing, msg, consumedCarryForwardIds)\n const origin = `${msg.origin.file}:${msg.origin.line}`\n const baseEntry = existingEntry ?? carried?.entry\n\n if (carried) {\n consumedCarryForwardIds.add(carried.id)\n }\n\n if (baseEntry) {\n catalog[msg.id] = {\n ...baseEntry,\n message: msg.message ?? baseEntry.message,\n context: msg.context,\n comment: msg.comment,\n origin,\n obsolete: false,\n }\n unchanged++\n } else if (catalog[msg.id]) {\n // Same ID already seen in this extraction batch — merge origin\n const existing = catalog[msg.id]!\n catalog[msg.id] = {\n ...existing,\n origin: mergeOrigins(existing.origin, origin),\n }\n } else {\n catalog[msg.id] = {\n message: msg.message,\n context: msg.context,\n comment: msg.comment,\n origin,\n }\n added++\n }\n\n if (options?.stripFuzzy) {\n const { fuzzy: _fuzzy, ...rest } = catalog[msg.id]!\n catalog[msg.id] = rest\n }\n }\n\n for (const [id, entry] of Object.entries(existing)) {\n if (!extractedIds.has(id)) {\n const { fuzzy: _fuzzy, ...rest } = entry\n const obsoleteEntry = options?.stripFuzzy\n ? { ...rest, obsolete: true }\n : { ...entry, obsolete: true }\n catalog[id] = obsoleteEntry\n obsolete++\n }\n }\n\n return { catalog, result: { added, unchanged, obsolete } }\n}\n\nfunction mergeOrigins(\n existing: string | string[] | undefined,\n newOrigin: string,\n): string | string[] {\n if (!existing) return newOrigin\n const existingArray = Array.isArray(existing) ? existing : [existing]\n const merged = [...new Set([...existingArray, newOrigin])]\n return merged.length === 1 ? merged[0]! : merged\n}\n\nfunction findCarryForwardEntry(\n existing: CatalogData,\n extracted: ExtractedMessage,\n consumedCarryForwardIds: Set<string>,\n): { id: string; entry: CatalogEntry } | undefined {\n if (!extracted.context) {\n return undefined\n }\n\n const extractedOrigin = `${extracted.origin.file}:${extracted.origin.line}`\n for (const [id, entry] of Object.entries(existing)) {\n if (consumedCarryForwardIds.has(id)) continue\n if (entry.context !== undefined) continue\n if (entry.message !== extracted.message) continue\n if (!sameOrigin(entry.origin, extractedOrigin)) continue\n return { id, entry }\n }\n\n return undefined\n}\n\nfunction sameOrigin(previous: string | string[] | undefined, next: string): boolean {\n if (!previous) return false\n const origins = Array.isArray(previous) ? previous : [previous]\n return origins.some((o) => o === next || originFile(o) === originFile(next))\n}\n\nfunction originFile(origin: string): string {\n const match = origin.match(/^(.*):\\d+$/)\n return match?.[1] ?? origin\n}\n","import type { CatalogData } from './catalog'\n\n/** Read a JSON catalog file */\nexport function readJsonCatalog(content: string): CatalogData {\n const raw = JSON.parse(content) as Record<string, unknown>\n const catalog: CatalogData = {}\n\n for (const [id, entry] of Object.entries(raw)) {\n if (typeof entry === 'object' && entry !== null) {\n const e = entry as Record<string, unknown>\n catalog[id] = {\n message: typeof e['message'] === 'string' ? e['message'] : undefined,\n context: typeof e['context'] === 'string' ? e['context'] : undefined,\n comment: typeof e['comment'] === 'string' ? e['comment'] : undefined,\n translation: typeof e['translation'] === 'string' ? e['translation'] : undefined,\n origin: typeof e['origin'] === 'string'\n ? e['origin']\n : Array.isArray(e['origin']) && (e['origin'] as unknown[]).every((v) => typeof v === 'string')\n ? (e['origin'] as string[])\n : undefined,\n obsolete: typeof e['obsolete'] === 'boolean' ? e['obsolete'] : undefined,\n fuzzy: typeof e['fuzzy'] === 'boolean' ? e['fuzzy'] : undefined,\n }\n }\n }\n\n return catalog\n}\n\n/** Write a catalog to JSON format */\nexport function writeJsonCatalog(catalog: CatalogData): string {\n const output: Record<string, Record<string, unknown>> = {}\n\n for (const [id, entry] of Object.entries(catalog)) {\n const obj: Record<string, unknown> = {}\n if (entry.message !== undefined) obj['message'] = entry.message\n if (entry.context !== undefined) obj['context'] = entry.context\n if (entry.comment !== undefined) obj['comment'] = entry.comment\n if (entry.translation !== undefined) obj['translation'] = entry.translation\n if (entry.origin !== undefined) obj['origin'] = entry.origin\n if (entry.obsolete) obj['obsolete'] = true\n if (entry.fuzzy) obj['fuzzy'] = true\n output[id] = obj\n }\n\n return JSON.stringify(output, null, 2) + '\\n'\n}\n","import type { CatalogData } from './catalog'\nimport { hashMessage } from '@fluenti/core'\nimport * as gettextParser from 'gettext-parser'\n\nconst CUSTOM_ID_MARKER = 'fluenti-id:'\n\ninterface POTranslation {\n msgid: string\n msgctxt?: string\n msgstr: string[]\n comments?: {\n reference?: string\n extracted?: string\n flag?: string\n translator?: string\n previous?: string\n }\n}\n\ninterface POData {\n headers?: Record<string, string>\n translations: Record<string, Record<string, POTranslation>>\n}\n\ninterface ParsedExtractedComment {\n comment?: string\n customId?: string\n sourceMessage?: string\n}\n\n/** Read a PO catalog file */\nexport function readPoCatalog(content: string): CatalogData {\n const po = gettextParser.po.parse(content) as POData\n const catalog: CatalogData = {}\n const translations = po.translations ?? {}\n\n for (const [contextKey, entries] of Object.entries(translations)) {\n for (const [msgid, entry] of Object.entries(entries)) {\n if (!msgid) continue\n\n const context = contextKey || entry.msgctxt || undefined\n const translation = entry.msgstr?.[0] ?? undefined\n const rawReference = entry.comments?.reference ?? undefined\n const origin = rawReference?.includes('\\n')\n ? rawReference.split('\\n').map((r: string) => r.trim()).filter(Boolean)\n : rawReference?.includes(' ')\n ? rawReference.split(/\\s+/).filter(Boolean)\n : rawReference\n const normalizedOrigin = Array.isArray(origin) && origin.length === 1 ? origin[0] : origin\n const isFuzzy = entry.comments?.flag?.includes('fuzzy') ?? false\n const { comment, customId, sourceMessage } = parseExtractedComment(entry.comments?.extracted)\n const resolvedSourceMessage = sourceMessage\n && hashMessage(sourceMessage, context) === msgid\n ? sourceMessage\n : undefined\n const id = customId\n ?? (resolvedSourceMessage ? msgid : hashMessage(msgid, context))\n\n catalog[id] = {\n message: resolvedSourceMessage ?? msgid,\n ...(context !== undefined ? { context } : {}),\n ...(comment !== undefined ? { comment } : {}),\n ...(translation ? { translation } : {}),\n ...(normalizedOrigin !== undefined ? { origin: normalizedOrigin } : {}),\n ...(isFuzzy ? { fuzzy: true } : {}),\n }\n }\n }\n\n return catalog\n}\n\n/** Write a catalog to PO format */\nexport function writePoCatalog(catalog: CatalogData): string {\n const translations: POData['translations'] = {\n '': {\n '': {\n msgid: '',\n msgstr: ['Content-Type: text/plain; charset=UTF-8\\n'],\n },\n },\n }\n\n for (const [id, entry] of Object.entries(catalog)) {\n const poEntry: POTranslation = {\n msgid: entry.message ?? id,\n ...(entry.context !== undefined ? { msgctxt: entry.context } : {}),\n msgstr: [entry.translation ?? ''],\n }\n\n const comments: POTranslation['comments'] = {}\n if (entry.origin) {\n comments.reference = Array.isArray(entry.origin)\n ? entry.origin.join('\\n')\n : entry.origin\n }\n const extractedComment = buildExtractedComment(id, entry.message ?? id, entry.context, entry.comment)\n if (extractedComment) {\n comments.extracted = extractedComment\n }\n if (entry.fuzzy) {\n comments.flag = 'fuzzy'\n }\n if (comments.reference || comments.extracted || comments.flag) {\n poEntry.comments = comments\n }\n\n const contextKey = entry.context ?? ''\n translations[contextKey] ??= {}\n translations[contextKey][poEntry.msgid] = poEntry\n }\n\n const poData: POData = {\n headers: {\n 'Content-Type': 'text/plain; charset=UTF-8',\n },\n translations,\n }\n\n const buffer = gettextParser.po.compile(poData as Parameters<typeof gettextParser.po.compile>[0])\n return buffer.toString()\n}\n\nfunction parseExtractedComment(\n extracted: string | undefined,\n): ParsedExtractedComment {\n if (!extracted) {\n return {}\n }\n\n const lines = extracted.split('\\n').map((line) => line.trim()).filter(Boolean)\n let customId: string | undefined\n let sourceMessage: string | undefined\n const commentLines: string[] = []\n\n for (const line of lines) {\n if (line.startsWith(CUSTOM_ID_MARKER)) {\n customId = line.slice(CUSTOM_ID_MARKER.length).trim() || undefined\n continue\n }\n if (line.startsWith('msg`') && line.endsWith('`')) {\n sourceMessage = line.slice(4, -1)\n continue\n }\n if (line.startsWith('Trans: ')) {\n sourceMessage = normalizeRichTextComment(line.slice('Trans: '.length))\n continue\n }\n commentLines.push(line)\n }\n\n return {\n ...(commentLines.length > 0 ? { comment: commentLines.join('\\n') } : {}),\n ...(customId ? { customId } : {}),\n ...(sourceMessage ? { sourceMessage } : {}),\n }\n}\n\nfunction normalizeRichTextComment(comment: string): string {\n let stack: Array<{ tag: string; index: number }> = []\n let nextIndex = 0\n\n return comment.replace(/<\\/?([a-zA-Z][\\w-]*)>/g, (match, rawTag: string) => {\n const tag = rawTag\n if (match.startsWith('</')) {\n for (let index = stack.length - 1; index >= 0; index--) {\n const entry = stack[index]\n if (entry?.tag !== tag) continue\n stack = stack.filter((_, i) => i !== index)\n return `</${entry.index}>`\n }\n return match\n }\n\n const index = nextIndex++\n stack.push({ tag, index })\n return `<${index}>`\n })\n}\n\nfunction buildExtractedComment(\n id: string,\n message: string,\n context: string | undefined,\n comment: string | undefined,\n): string | undefined {\n const lines: string[] = []\n\n if (comment) {\n lines.push(comment)\n }\n\n if (id !== hashMessage(message, context)) {\n lines.push(`${CUSTOM_ID_MARKER} ${id}`)\n }\n\n return lines.length > 0 ? lines.join('\\n') : undefined\n}\n","import type { CatalogData } from './catalog'\nimport { hashMessage } from '@fluenti/core'\nimport { parse } from '@fluenti/core'\nimport type { ASTNode, PluralNode, SelectNode, VariableNode, FunctionNode } from '@fluenti/core'\n\nconst ICU_VAR_REGEX = /\\{(\\w+)\\}/g\nconst ICU_VAR_TEST = /\\{(\\w+)\\}/\n\nfunction hasVariables(message: string): boolean {\n return ICU_VAR_TEST.test(message)\n}\n\n\nfunction escapeStringLiteral(str: string): string {\n return str\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/'/g, \"\\\\'\")\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r')\n}\n\n/** Generate safe JS property access: `v.name` for identifiers, `v[0]` for numeric names */\nfunction propAccess(obj: string, name: string): string {\n return /^\\d/.test(name) ? `${obj}[${name}]` : `${obj}.${name}`\n}\n\nfunction escapeTemplateLiteral(str: string): string {\n return str\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/`/g, '\\\\`')\n .replace(/\\$\\{/g, '\\\\${')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r')\n}\n\nfunction messageToTemplateString(message: string): string {\n return message.replace(ICU_VAR_REGEX, (_match, name: string) => `\\${${propAccess('v', name)}}`)\n}\n\n\n// ─── ICU → JS code generation for split mode ───────────────────────────────\n\nconst ICU_PLURAL_SELECT_REGEX = /\\{(\\w+),\\s*(plural|select|selectordinal)\\s*,/\n\n/** Check if message contains ICU plural/select syntax */\nfunction hasIcuPluralOrSelect(message: string): boolean {\n return ICU_PLURAL_SELECT_REGEX.test(message)\n}\n\n/**\n * Compile an ICU AST node array into a JS expression string.\n * Used for generating static code (not runtime evaluation).\n */\nfunction astToJsExpression(nodes: ASTNode[], locale: string): string {\n if (nodes.length === 0) return \"''\"\n\n const parts = nodes.map((node) => astNodeToJs(node, locale))\n\n if (parts.length === 1) return parts[0]!\n return parts.join(' + ')\n}\n\nfunction astNodeToJs(node: ASTNode, locale: string): string {\n switch (node.type) {\n case 'text':\n return `'${escapeStringLiteral(node.value)}'`\n\n case 'variable':\n if (node.name === '#') return 'String(__c)'\n return `String(${propAccess('v', node.name)} ?? '{${node.name}}')`\n\n case 'plural':\n return pluralToJs(node as PluralNode, locale)\n\n case 'select':\n return selectToJs(node as SelectNode, locale)\n\n case 'function':\n return `String(${propAccess('v', node.variable)} ?? '')`\n }\n}\n\nfunction pluralToJs(node: PluralNode, locale: string): string {\n const offset = node.offset ?? 0\n const access = propAccess('v', node.variable)\n const countExpr = offset ? `(${access} - ${offset})` : access\n\n const lines: string[] = []\n lines.push(`((c) => { const __c = c; `)\n\n // Exact matches first\n const exactKeys = Object.keys(node.options).filter((k) => k.startsWith('='))\n if (exactKeys.length > 0) {\n for (const key of exactKeys) {\n const num = key.slice(1)\n const body = astToJsExpression(node.options[key]!, locale)\n lines.push(`if (c === ${num}) return ${body}; `)\n }\n }\n\n // CLDR categories via Intl.PluralRules\n const cldrKeys = Object.keys(node.options).filter((k) => !k.startsWith('='))\n if (cldrKeys.length > 1 || (cldrKeys.length === 1 && cldrKeys[0] !== 'other')) {\n lines.push(`const __cat = new Intl.PluralRules('${escapeStringLiteral(locale)}').select(c); `)\n for (const key of cldrKeys) {\n if (key === 'other') continue\n const body = astToJsExpression(node.options[key]!, locale)\n lines.push(`if (__cat === '${key}') return ${body}; `)\n }\n }\n\n // Fallback to 'other'\n const otherBody = node.options['other']\n ? astToJsExpression(node.options['other'], locale)\n : \"''\"\n lines.push(`return ${otherBody}; `)\n lines.push(`})(${countExpr})`)\n\n return lines.join('')\n}\n\nfunction selectToJs(node: SelectNode, locale: string): string {\n const lines: string[] = []\n lines.push(`((s) => { `)\n\n const keys = Object.keys(node.options).filter((k) => k !== 'other')\n for (const key of keys) {\n const body = astToJsExpression(node.options[key]!, locale)\n lines.push(`if (s === '${escapeStringLiteral(key)}') return ${body}; `)\n }\n\n const otherBody = node.options['other']\n ? astToJsExpression(node.options['other'], locale)\n : \"''\"\n lines.push(`return ${otherBody}; `)\n lines.push(`})(String(${propAccess('v', node.variable)} ?? ''))`)\n\n return lines.join('')\n}\n\n/**\n * Compile a catalog to ES module with tree-shakeable named exports.\n * Each message becomes a `/* @__PURE__ */` annotated named export.\n * A default export maps message IDs to their compiled values for runtime lookup.\n */\n/** Catalog format version. Bump when the compiled output format changes. */\nexport const CATALOG_VERSION = 1\n\nexport interface CompileStats {\n compiled: number\n missing: string[]\n}\n\nexport interface CompileOptions {\n skipFuzzy?: boolean\n}\n\nexport function compileCatalog(\n catalog: CatalogData,\n locale: string,\n allIds: string[],\n sourceLocale?: string,\n options?: CompileOptions,\n): { code: string; stats: CompileStats } {\n const lines: string[] = []\n lines.push(`// @fluenti/compiled v${CATALOG_VERSION}`)\n const exportNames: Array<{ id: string; exportName: string }> = []\n let compiled = 0\n const missing: string[] = []\n\n const hashToId = new Map<string, string>()\n\n for (const id of allIds) {\n const hash = hashMessage(id)\n\n const existingId = hashToId.get(hash)\n if (existingId !== undefined && existingId !== id) {\n throw new Error(\n `Hash collision detected: messages \"${existingId}\" and \"${id}\" produce the same hash \"${hash}\"`,\n )\n }\n hashToId.set(hash, id)\n\n const exportName = `_${hash}`\n const entry = catalog[id]\n const translated = resolveCompiledMessage(entry, id, locale, sourceLocale, options?.skipFuzzy)\n\n if (translated === undefined) {\n lines.push(`export const ${exportName} = undefined`)\n missing.push(id)\n } else if (hasIcuPluralOrSelect(translated)) {\n // Parse ICU and compile to JS\n const ast = parse(translated)\n const jsExpr = astToJsExpression(ast, locale)\n lines.push(`export const ${exportName} = (v) => ${jsExpr}`)\n compiled++\n } else if (hasVariables(translated)) {\n const templateStr = messageToTemplateString(escapeTemplateLiteral(translated))\n lines.push(`export const ${exportName} = (v) => \\`${templateStr}\\``)\n compiled++\n } else {\n lines.push(`export const ${exportName} = '${escapeStringLiteral(translated)}'`)\n compiled++\n }\n\n exportNames.push({ id, exportName })\n }\n\n if (exportNames.length === 0) {\n return {\n code: `// @fluenti/compiled v${CATALOG_VERSION}\\n// empty catalog\\nexport default {}\\n`,\n stats: { compiled: 0, missing: [] },\n }\n }\n\n // Default export maps message IDs → compiled values for runtime lookup\n lines.push('')\n lines.push('export default {')\n for (const { id, exportName } of exportNames) {\n lines.push(` '${escapeStringLiteral(id)}': ${exportName},`)\n }\n lines.push('}')\n lines.push('')\n\n return { code: lines.join('\\n'), stats: { compiled, missing } }\n}\n\nfunction resolveCompiledMessage(\n entry: CatalogData[string] | undefined,\n id: string,\n locale: string,\n sourceLocale: string | undefined,\n skipFuzzy?: boolean,\n): string | undefined {\n const effectiveSourceLocale = sourceLocale ?? locale\n\n if (!entry) {\n return undefined\n }\n\n if (skipFuzzy && entry.fuzzy) {\n return undefined\n }\n\n if (entry.translation !== undefined && entry.translation.length > 0) {\n return entry.translation\n }\n\n if (locale === effectiveSourceLocale) {\n return entry.message ?? id\n }\n\n return undefined\n}\n\n/**\n * Generate the index module that exports locale list and lazy loaders.\n */\nexport function compileIndex(locales: string[], _catalogDir: string): string {\n const lines: string[] = []\n lines.push(`export const locales = ${JSON.stringify(locales)}`)\n lines.push('')\n lines.push('export const loaders = {')\n for (const locale of locales) {\n lines.push(` '${escapeStringLiteral(locale)}': () => import('./${escapeStringLiteral(locale)}.js'),`)\n }\n lines.push('}')\n lines.push('')\n return lines.join('\\n')\n}\n\n/**\n * Collect the union of all message IDs across all locale catalogs.\n * Ensures every locale file exports the same names.\n */\nexport function collectAllIds(catalogs: Record<string, CatalogData>): string[] {\n const idSet = new Set<string>()\n for (const catalog of Object.values(catalogs)) {\n for (const [id, entry] of Object.entries(catalog)) {\n if (!entry.obsolete) {\n idSet.add(id)\n }\n }\n }\n return [...idSet].sort()\n}\n\n// ─── Type-safe message ID generation ─────────────────────────────────────────\n\nexport interface MessageVariable {\n name: string\n type: string\n}\n\n/**\n * Extract variable names and their TypeScript types from an ICU message string.\n */\nexport function extractMessageVariables(message: string): MessageVariable[] {\n const ast = parse(message)\n const vars = new Map<string, string>()\n collectVariablesFromNodes(ast, vars)\n return [...vars.entries()]\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([name, type]) => ({ name, type }))\n}\n\nfunction collectVariablesFromNodes(nodes: ASTNode[], vars: Map<string, string>): void {\n for (const node of nodes) {\n switch (node.type) {\n case 'variable':\n if (node.name !== '#' && !vars.has(node.name)) {\n vars.set((node as VariableNode).name, 'string | number')\n }\n break\n case 'plural': {\n const pn = node as PluralNode\n // Plural variable is always a number\n vars.set(pn.variable, 'number')\n // Recurse into plural option bodies\n for (const optionNodes of Object.values(pn.options)) {\n collectVariablesFromNodes(optionNodes, vars)\n }\n break\n }\n case 'select': {\n const sn = node as SelectNode\n const keys = Object.keys(sn.options).filter((k) => k !== 'other')\n const hasOther = 'other' in sn.options\n const literalTypes = keys.map((k) => `'${k}'`).join(' | ')\n const selectType = hasOther\n ? (keys.length > 0 ? `${literalTypes} | string` : 'string')\n : (keys.length > 0 ? literalTypes : 'string')\n vars.set(sn.variable, selectType)\n // Recurse into select option bodies\n for (const optionNodes of Object.values(sn.options)) {\n collectVariablesFromNodes(optionNodes, vars)\n }\n break\n }\n case 'function':\n if (!vars.has((node as FunctionNode).variable)) {\n vars.set((node as FunctionNode).variable, 'string | number')\n }\n break\n case 'text':\n break\n }\n }\n}\n\n/**\n * Generate a TypeScript declaration file with MessageId union and MessageValues interface.\n */\nexport function compileTypeDeclaration(\n allIds: string[],\n catalogs: Record<string, CatalogData>,\n sourceLocale: string,\n): string {\n const lines: string[] = []\n lines.push('// Auto-generated by @fluenti/cli — do not edit')\n lines.push('')\n\n // MessageId union\n if (allIds.length === 0) {\n lines.push('export type MessageId = never')\n } else {\n lines.push('export type MessageId =')\n for (const id of allIds) {\n lines.push(` | '${escapeStringLiteral(id)}'`)\n }\n }\n\n lines.push('')\n\n // MessageValues interface\n lines.push('export interface MessageValues {')\n for (const id of allIds) {\n // Use source locale catalog to get the message for variable extraction\n const sourceCatalog = catalogs[sourceLocale]\n const entry = sourceCatalog?.[id]\n const message = entry?.message ?? id\n const vars = extractMessageVariables(message)\n\n const escapedId = escapeStringLiteral(id)\n if (vars.length === 0) {\n lines.push(` '${escapedId}': Record<string, never>`)\n } else {\n const fields = vars.map((v) => `${v.name}: ${v.type}`).join('; ')\n lines.push(` '${escapedId}': { ${fields} }`)\n }\n }\n lines.push('}')\n lines.push('')\n\n return lines.join('\\n')\n}\n","import { existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport type { 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: './src/locales/compiled',\n}\n\n/**\n * Load Fluenti config from the given path or auto-discover it.\n * When `cwd` is provided, config paths are resolved relative to it.\n */\nexport async function loadConfig(configPath?: string, cwd?: string): Promise<FluentiConfig> {\n const base = cwd ?? process.cwd()\n const paths = configPath\n ? [resolve(base, configPath)]\n : [\n resolve(base, 'fluenti.config.ts'),\n resolve(base, 'fluenti.config.js'),\n resolve(base, 'fluenti.config.mjs'),\n ]\n\n for (const p of paths) {\n if (existsSync(p)) {\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 }\n }\n\n return defaultConfig\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"config-loader-BgAoTfxH.js","names":[],"sources":["../src/catalog.ts","../src/json-format.ts","../src/po-format.ts","../src/compile.ts","../src/config-loader.ts"],"sourcesContent":["import type { ExtractedMessage } from '@fluenti/core'\n\nexport interface CatalogEntry {\n message?: string | undefined\n context?: string | undefined\n comment?: string | undefined\n translation?: string | undefined\n origin?: string | string[] | undefined\n obsolete?: boolean | undefined\n fuzzy?: boolean | undefined\n}\n\nexport type CatalogData = Record<string, CatalogEntry>\n\nexport interface UpdateResult {\n added: number\n unchanged: number\n obsolete: number\n}\n\nexport interface UpdateCatalogOptions {\n stripFuzzy?: boolean\n}\n\n/** Update catalog with newly extracted messages */\nexport function updateCatalog(\n existing: CatalogData,\n extracted: ExtractedMessage[],\n options?: UpdateCatalogOptions,\n): { catalog: CatalogData; result: UpdateResult } {\n const extractedIds = new Set(extracted.map((m) => m.id))\n const consumedCarryForwardIds = new Set<string>()\n const catalog: CatalogData = {}\n let added = 0\n let unchanged = 0\n let obsolete = 0\n\n for (const msg of extracted) {\n const existingEntry = existing[msg.id]\n const carried = existingEntry\n ? undefined\n : findCarryForwardEntry(existing, msg, consumedCarryForwardIds)\n const origin = `${msg.origin.file}:${msg.origin.line}`\n const baseEntry = existingEntry ?? carried?.entry\n\n if (carried) {\n consumedCarryForwardIds.add(carried.id)\n }\n\n if (baseEntry) {\n catalog[msg.id] = {\n ...baseEntry,\n message: msg.message ?? baseEntry.message,\n context: msg.context,\n comment: msg.comment,\n origin,\n obsolete: false,\n }\n unchanged++\n } else if (catalog[msg.id]) {\n // Same ID already seen in this extraction batch — merge origin\n const existing = catalog[msg.id]!\n catalog[msg.id] = {\n ...existing,\n origin: mergeOrigins(existing.origin, origin),\n }\n } else {\n catalog[msg.id] = {\n message: msg.message,\n context: msg.context,\n comment: msg.comment,\n origin,\n }\n added++\n }\n\n if (options?.stripFuzzy) {\n const { fuzzy: _fuzzy, ...rest } = catalog[msg.id]!\n catalog[msg.id] = rest\n }\n }\n\n for (const [id, entry] of Object.entries(existing)) {\n if (!extractedIds.has(id)) {\n const { fuzzy: _fuzzy, ...rest } = entry\n const obsoleteEntry = options?.stripFuzzy\n ? { ...rest, obsolete: true }\n : { ...entry, obsolete: true }\n catalog[id] = obsoleteEntry\n obsolete++\n }\n }\n\n return { catalog, result: { added, unchanged, obsolete } }\n}\n\nfunction mergeOrigins(\n existing: string | string[] | undefined,\n newOrigin: string,\n): string | string[] {\n if (!existing) return newOrigin\n const existingArray = Array.isArray(existing) ? existing : [existing]\n const merged = [...new Set([...existingArray, newOrigin])]\n return merged.length === 1 ? merged[0]! : merged\n}\n\nfunction findCarryForwardEntry(\n existing: CatalogData,\n extracted: ExtractedMessage,\n consumedCarryForwardIds: Set<string>,\n): { id: string; entry: CatalogEntry } | undefined {\n if (!extracted.context) {\n return undefined\n }\n\n const extractedOrigin = `${extracted.origin.file}:${extracted.origin.line}`\n for (const [id, entry] of Object.entries(existing)) {\n if (consumedCarryForwardIds.has(id)) continue\n if (entry.context !== undefined) continue\n if (entry.message !== extracted.message) continue\n if (!sameOrigin(entry.origin, extractedOrigin)) continue\n return { id, entry }\n }\n\n return undefined\n}\n\nfunction sameOrigin(previous: string | string[] | undefined, next: string): boolean {\n if (!previous) return false\n const origins = Array.isArray(previous) ? previous : [previous]\n return origins.some((o) => o === next || originFile(o) === originFile(next))\n}\n\nfunction originFile(origin: string): string {\n const match = origin.match(/^(.*):\\d+$/)\n return match?.[1] ?? origin\n}\n","import type { CatalogData } from './catalog'\n\n/** Read a JSON catalog file */\nexport function readJsonCatalog(content: string): CatalogData {\n const raw = JSON.parse(content) as Record<string, unknown>\n const catalog: CatalogData = {}\n\n for (const [id, entry] of Object.entries(raw)) {\n if (typeof entry === 'object' && entry !== null) {\n const e = entry as Record<string, unknown>\n catalog[id] = {\n message: typeof e['message'] === 'string' ? e['message'] : undefined,\n context: typeof e['context'] === 'string' ? e['context'] : undefined,\n comment: typeof e['comment'] === 'string' ? e['comment'] : undefined,\n translation: typeof e['translation'] === 'string' ? e['translation'] : undefined,\n origin: typeof e['origin'] === 'string'\n ? e['origin']\n : Array.isArray(e['origin']) && (e['origin'] as unknown[]).every((v) => typeof v === 'string')\n ? (e['origin'] as string[])\n : undefined,\n obsolete: typeof e['obsolete'] === 'boolean' ? e['obsolete'] : undefined,\n fuzzy: typeof e['fuzzy'] === 'boolean' ? e['fuzzy'] : undefined,\n }\n }\n }\n\n return catalog\n}\n\n/** Write a catalog to JSON format */\nexport function writeJsonCatalog(catalog: CatalogData): string {\n const output: Record<string, Record<string, unknown>> = {}\n\n for (const [id, entry] of Object.entries(catalog)) {\n const obj: Record<string, unknown> = {}\n if (entry.message !== undefined) obj['message'] = entry.message\n if (entry.context !== undefined) obj['context'] = entry.context\n if (entry.comment !== undefined) obj['comment'] = entry.comment\n if (entry.translation !== undefined) obj['translation'] = entry.translation\n if (entry.origin !== undefined) obj['origin'] = entry.origin\n if (entry.obsolete) obj['obsolete'] = true\n if (entry.fuzzy) obj['fuzzy'] = true\n output[id] = obj\n }\n\n return JSON.stringify(output, null, 2) + '\\n'\n}\n","import type { CatalogData } from './catalog'\nimport { hashMessage } from '@fluenti/core'\nimport * as gettextParser from 'gettext-parser'\n\nconst CUSTOM_ID_MARKER = 'fluenti-id:'\n\ninterface POTranslation {\n msgid: string\n msgctxt?: string\n msgstr: string[]\n comments?: {\n reference?: string\n extracted?: string\n flag?: string\n translator?: string\n previous?: string\n }\n}\n\ninterface POData {\n headers?: Record<string, string>\n translations: Record<string, Record<string, POTranslation>>\n}\n\ninterface ParsedExtractedComment {\n comment?: string\n customId?: string\n sourceMessage?: string\n}\n\n/** Read a PO catalog file */\nexport function readPoCatalog(content: string): CatalogData {\n const po = gettextParser.po.parse(content) as POData\n const catalog: CatalogData = {}\n const translations = po.translations ?? {}\n\n for (const [contextKey, entries] of Object.entries(translations)) {\n for (const [msgid, entry] of Object.entries(entries)) {\n if (!msgid) continue\n\n const context = contextKey || entry.msgctxt || undefined\n const translation = entry.msgstr?.[0] ?? undefined\n const rawReference = entry.comments?.reference ?? undefined\n const origin = rawReference?.includes('\\n')\n ? rawReference.split('\\n').map((r: string) => r.trim()).filter(Boolean)\n : rawReference?.includes(' ')\n ? rawReference.split(/\\s+/).filter(Boolean)\n : rawReference\n const normalizedOrigin = Array.isArray(origin) && origin.length === 1 ? origin[0] : origin\n const isFuzzy = entry.comments?.flag?.includes('fuzzy') ?? false\n const { comment, customId, sourceMessage } = parseExtractedComment(entry.comments?.extracted)\n const resolvedSourceMessage = sourceMessage\n && hashMessage(sourceMessage, context) === msgid\n ? sourceMessage\n : undefined\n const id = customId\n ?? (resolvedSourceMessage ? msgid : hashMessage(msgid, context))\n\n catalog[id] = {\n message: resolvedSourceMessage ?? msgid,\n ...(context !== undefined ? { context } : {}),\n ...(comment !== undefined ? { comment } : {}),\n ...(translation ? { translation } : {}),\n ...(normalizedOrigin !== undefined ? { origin: normalizedOrigin } : {}),\n ...(isFuzzy ? { fuzzy: true } : {}),\n }\n }\n }\n\n return catalog\n}\n\n/** Write a catalog to PO format */\nexport function writePoCatalog(catalog: CatalogData): string {\n const translations: POData['translations'] = {\n '': {\n '': {\n msgid: '',\n msgstr: ['Content-Type: text/plain; charset=UTF-8\\n'],\n },\n },\n }\n\n for (const [id, entry] of Object.entries(catalog)) {\n const poEntry: POTranslation = {\n msgid: entry.message ?? id,\n ...(entry.context !== undefined ? { msgctxt: entry.context } : {}),\n msgstr: [entry.translation ?? ''],\n }\n\n const comments: POTranslation['comments'] = {}\n if (entry.origin) {\n comments.reference = Array.isArray(entry.origin)\n ? entry.origin.join('\\n')\n : entry.origin\n }\n const extractedComment = buildExtractedComment(id, entry.message ?? id, entry.context, entry.comment)\n if (extractedComment) {\n comments.extracted = extractedComment\n }\n if (entry.fuzzy) {\n comments.flag = 'fuzzy'\n }\n if (comments.reference || comments.extracted || comments.flag) {\n poEntry.comments = comments\n }\n\n const contextKey = entry.context ?? ''\n translations[contextKey] ??= {}\n translations[contextKey][poEntry.msgid] = poEntry\n }\n\n const poData: POData = {\n headers: {\n 'Content-Type': 'text/plain; charset=UTF-8',\n },\n translations,\n }\n\n const buffer = gettextParser.po.compile(poData as Parameters<typeof gettextParser.po.compile>[0])\n return buffer.toString()\n}\n\nfunction parseExtractedComment(\n extracted: string | undefined,\n): ParsedExtractedComment {\n if (!extracted) {\n return {}\n }\n\n const lines = extracted.split('\\n').map((line) => line.trim()).filter(Boolean)\n let customId: string | undefined\n let sourceMessage: string | undefined\n const commentLines: string[] = []\n\n for (const line of lines) {\n if (line.startsWith(CUSTOM_ID_MARKER)) {\n customId = line.slice(CUSTOM_ID_MARKER.length).trim() || undefined\n continue\n }\n if (line.startsWith('msg`') && line.endsWith('`')) {\n sourceMessage = line.slice(4, -1)\n continue\n }\n if (line.startsWith('Trans: ')) {\n sourceMessage = normalizeRichTextComment(line.slice('Trans: '.length))\n continue\n }\n commentLines.push(line)\n }\n\n return {\n ...(commentLines.length > 0 ? { comment: commentLines.join('\\n') } : {}),\n ...(customId ? { customId } : {}),\n ...(sourceMessage ? { sourceMessage } : {}),\n }\n}\n\nfunction normalizeRichTextComment(comment: string): string {\n let stack: Array<{ tag: string; index: number }> = []\n let nextIndex = 0\n\n return comment.replace(/<\\/?([a-zA-Z][\\w-]*)>/g, (match, rawTag: string) => {\n const tag = rawTag\n if (match.startsWith('</')) {\n for (let index = stack.length - 1; index >= 0; index--) {\n const entry = stack[index]\n if (entry?.tag !== tag) continue\n stack = stack.filter((_, i) => i !== index)\n return `</${entry.index}>`\n }\n return match\n }\n\n const index = nextIndex++\n stack.push({ tag, index })\n return `<${index}>`\n })\n}\n\nfunction buildExtractedComment(\n id: string,\n message: string,\n context: string | undefined,\n comment: string | undefined,\n): string | undefined {\n const lines: string[] = []\n\n if (comment) {\n lines.push(comment)\n }\n\n if (id !== hashMessage(message, context)) {\n lines.push(`${CUSTOM_ID_MARKER} ${id}`)\n }\n\n return lines.length > 0 ? lines.join('\\n') : undefined\n}\n","import type { CatalogData } from './catalog'\nimport { hashMessage } from '@fluenti/core'\nimport { parse } from '@fluenti/core'\nimport type { ASTNode, PluralNode, SelectNode, VariableNode, FunctionNode } from '@fluenti/core'\n\nconst ICU_VAR_REGEX = /\\{(\\w+)\\}/g\nconst ICU_VAR_TEST = /\\{(\\w+)\\}/\n\nfunction hasVariables(message: string): boolean {\n return ICU_VAR_TEST.test(message)\n}\n\n\nfunction escapeStringLiteral(str: string): string {\n return str\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/'/g, \"\\\\'\")\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r')\n}\n\n/** Generate safe JS property access: `v.name` for identifiers, `v[0]` for numeric names */\nfunction propAccess(obj: string, name: string): string {\n return /^\\d/.test(name) ? `${obj}[${name}]` : `${obj}.${name}`\n}\n\nfunction escapeTemplateLiteral(str: string): string {\n return str\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/`/g, '\\\\`')\n .replace(/\\$\\{/g, '\\\\${')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r')\n}\n\nfunction messageToTemplateString(message: string): string {\n return message.replace(ICU_VAR_REGEX, (_match, name: string) => `\\${${propAccess('v', name)}}`)\n}\n\n\n// ─── ICU → JS code generation for split mode ───────────────────────────────\n\nconst ICU_PLURAL_SELECT_REGEX = /\\{(\\w+),\\s*(plural|select|selectordinal)\\s*,/\n\n/** Check if message contains ICU plural/select syntax */\nfunction hasIcuPluralOrSelect(message: string): boolean {\n return ICU_PLURAL_SELECT_REGEX.test(message)\n}\n\n/**\n * Compile an ICU AST node array into a JS expression string.\n * Used for generating static code (not runtime evaluation).\n */\nfunction astToJsExpression(nodes: ASTNode[], locale: string): string {\n if (nodes.length === 0) return \"''\"\n\n const parts = nodes.map((node) => astNodeToJs(node, locale))\n\n if (parts.length === 1) return parts[0]!\n return parts.join(' + ')\n}\n\nfunction astNodeToJs(node: ASTNode, locale: string): string {\n switch (node.type) {\n case 'text':\n return `'${escapeStringLiteral(node.value)}'`\n\n case 'variable':\n if (node.name === '#') return 'String(__c)'\n return `String(${propAccess('v', node.name)} ?? '{${node.name}}')`\n\n case 'plural':\n return pluralToJs(node as PluralNode, locale)\n\n case 'select':\n return selectToJs(node as SelectNode, locale)\n\n case 'function':\n return `String(${propAccess('v', node.variable)} ?? '')`\n }\n}\n\nfunction pluralToJs(node: PluralNode, locale: string): string {\n const offset = node.offset ?? 0\n const access = propAccess('v', node.variable)\n const countExpr = offset ? `(${access} - ${offset})` : access\n\n const lines: string[] = []\n lines.push(`((c) => { const __c = c; `)\n\n // Exact matches first\n const exactKeys = Object.keys(node.options).filter((k) => k.startsWith('='))\n if (exactKeys.length > 0) {\n for (const key of exactKeys) {\n const num = key.slice(1)\n const body = astToJsExpression(node.options[key]!, locale)\n lines.push(`if (c === ${num}) return ${body}; `)\n }\n }\n\n // CLDR categories via Intl.PluralRules\n const cldrKeys = Object.keys(node.options).filter((k) => !k.startsWith('='))\n if (cldrKeys.length > 1 || (cldrKeys.length === 1 && cldrKeys[0] !== 'other')) {\n lines.push(`const __cat = new Intl.PluralRules('${escapeStringLiteral(locale)}').select(c); `)\n for (const key of cldrKeys) {\n if (key === 'other') continue\n const body = astToJsExpression(node.options[key]!, locale)\n lines.push(`if (__cat === '${key}') return ${body}; `)\n }\n }\n\n // Fallback to 'other'\n const otherBody = node.options['other']\n ? astToJsExpression(node.options['other'], locale)\n : \"''\"\n lines.push(`return ${otherBody}; `)\n lines.push(`})(${countExpr})`)\n\n return lines.join('')\n}\n\nfunction selectToJs(node: SelectNode, locale: string): string {\n const lines: string[] = []\n lines.push(`((s) => { `)\n\n const keys = Object.keys(node.options).filter((k) => k !== 'other')\n for (const key of keys) {\n const body = astToJsExpression(node.options[key]!, locale)\n lines.push(`if (s === '${escapeStringLiteral(key)}') return ${body}; `)\n }\n\n const otherBody = node.options['other']\n ? astToJsExpression(node.options['other'], locale)\n : \"''\"\n lines.push(`return ${otherBody}; `)\n lines.push(`})(String(${propAccess('v', node.variable)} ?? ''))`)\n\n return lines.join('')\n}\n\n/**\n * Compile a catalog to ES module with tree-shakeable named exports.\n * Each message becomes a `/* @__PURE__ */` annotated named export.\n * A default export maps message IDs to their compiled values for runtime lookup.\n */\n/** Catalog format version. Bump when the compiled output format changes. */\nexport const CATALOG_VERSION = 1\n\nexport interface CompileStats {\n compiled: number\n missing: string[]\n}\n\nexport interface CompileOptions {\n skipFuzzy?: boolean\n}\n\nexport function compileCatalog(\n catalog: CatalogData,\n locale: string,\n allIds: string[],\n sourceLocale?: string,\n options?: CompileOptions,\n): { code: string; stats: CompileStats } {\n const lines: string[] = []\n lines.push(`// @fluenti/compiled v${CATALOG_VERSION}`)\n const exportNames: Array<{ id: string; exportName: string }> = []\n let compiled = 0\n const missing: string[] = []\n\n const hashToId = new Map<string, string>()\n\n for (const id of allIds) {\n const hash = hashMessage(id)\n\n const existingId = hashToId.get(hash)\n if (existingId !== undefined && existingId !== id) {\n throw new Error(\n `Hash collision detected: messages \"${existingId}\" and \"${id}\" produce the same hash \"${hash}\"`,\n )\n }\n hashToId.set(hash, id)\n\n const exportName = `_${hash}`\n const entry = catalog[id]\n const translated = resolveCompiledMessage(entry, id, locale, sourceLocale, options?.skipFuzzy)\n\n if (translated === undefined) {\n lines.push(`export const ${exportName} = undefined`)\n missing.push(id)\n } else if (hasIcuPluralOrSelect(translated)) {\n // Parse ICU and compile to JS\n const ast = parse(translated)\n const jsExpr = astToJsExpression(ast, locale)\n lines.push(`export const ${exportName} = (v) => ${jsExpr}`)\n compiled++\n } else if (hasVariables(translated)) {\n const templateStr = messageToTemplateString(escapeTemplateLiteral(translated))\n lines.push(`export const ${exportName} = (v) => \\`${templateStr}\\``)\n compiled++\n } else {\n lines.push(`export const ${exportName} = '${escapeStringLiteral(translated)}'`)\n compiled++\n }\n\n exportNames.push({ id, exportName })\n }\n\n if (exportNames.length === 0) {\n return {\n code: `// @fluenti/compiled v${CATALOG_VERSION}\\n// empty catalog\\nexport default {}\\n`,\n stats: { compiled: 0, missing: [] },\n }\n }\n\n // Default export maps message IDs → compiled values for runtime lookup\n lines.push('')\n lines.push('export default {')\n for (const { id, exportName } of exportNames) {\n lines.push(` '${escapeStringLiteral(id)}': ${exportName},`)\n }\n lines.push('}')\n lines.push('')\n\n return { code: lines.join('\\n'), stats: { compiled, missing } }\n}\n\nfunction resolveCompiledMessage(\n entry: CatalogData[string] | undefined,\n id: string,\n locale: string,\n sourceLocale: string | undefined,\n skipFuzzy?: boolean,\n): string | undefined {\n const effectiveSourceLocale = sourceLocale ?? locale\n\n if (!entry) {\n return undefined\n }\n\n if (skipFuzzy && entry.fuzzy) {\n return undefined\n }\n\n if (entry.translation !== undefined && entry.translation.length > 0) {\n return entry.translation\n }\n\n if (locale === effectiveSourceLocale) {\n return entry.message ?? id\n }\n\n return undefined\n}\n\n/**\n * Generate the index module that exports locale list and lazy loaders.\n */\nexport function compileIndex(locales: string[], _catalogDir: string): string {\n const lines: string[] = []\n lines.push(`export const locales = ${JSON.stringify(locales)}`)\n lines.push('')\n lines.push('export const loaders = {')\n for (const locale of locales) {\n lines.push(` '${escapeStringLiteral(locale)}': () => import('./${escapeStringLiteral(locale)}.js'),`)\n }\n lines.push('}')\n lines.push('')\n return lines.join('\\n')\n}\n\n/**\n * Collect the union of all message IDs across all locale catalogs.\n * Ensures every locale file exports the same names.\n */\nexport function collectAllIds(catalogs: Record<string, CatalogData>): string[] {\n const idSet = new Set<string>()\n for (const catalog of Object.values(catalogs)) {\n for (const [id, entry] of Object.entries(catalog)) {\n if (!entry.obsolete) {\n idSet.add(id)\n }\n }\n }\n return [...idSet].sort()\n}\n\n// ─── Type-safe message ID generation ─────────────────────────────────────────\n\nexport interface MessageVariable {\n name: string\n type: string\n}\n\n/**\n * Extract variable names and their TypeScript types from an ICU message string.\n */\nexport function extractMessageVariables(message: string): MessageVariable[] {\n const ast = parse(message)\n const vars = new Map<string, string>()\n collectVariablesFromNodes(ast, vars)\n return [...vars.entries()]\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([name, type]) => ({ name, type }))\n}\n\nfunction collectVariablesFromNodes(nodes: ASTNode[], vars: Map<string, string>): void {\n for (const node of nodes) {\n switch (node.type) {\n case 'variable':\n if (node.name !== '#' && !vars.has(node.name)) {\n vars.set((node as VariableNode).name, 'string | number')\n }\n break\n case 'plural': {\n const pn = node as PluralNode\n // Plural variable is always a number\n vars.set(pn.variable, 'number')\n // Recurse into plural option bodies\n for (const optionNodes of Object.values(pn.options)) {\n collectVariablesFromNodes(optionNodes, vars)\n }\n break\n }\n case 'select': {\n const sn = node as SelectNode\n const keys = Object.keys(sn.options).filter((k) => k !== 'other')\n const hasOther = 'other' in sn.options\n const literalTypes = keys.map((k) => `'${k}'`).join(' | ')\n const selectType = hasOther\n ? (keys.length > 0 ? `${literalTypes} | string` : 'string')\n : (keys.length > 0 ? literalTypes : 'string')\n vars.set(sn.variable, selectType)\n // Recurse into select option bodies\n for (const optionNodes of Object.values(sn.options)) {\n collectVariablesFromNodes(optionNodes, vars)\n }\n break\n }\n case 'function':\n if (!vars.has((node as FunctionNode).variable)) {\n vars.set((node as FunctionNode).variable, 'string | number')\n }\n break\n case 'text':\n break\n }\n }\n}\n\n/**\n * Generate a TypeScript declaration file with MessageId union and MessageValues interface.\n */\nexport function compileTypeDeclaration(\n allIds: string[],\n catalogs: Record<string, CatalogData>,\n sourceLocale: string,\n): string {\n const lines: string[] = []\n lines.push('// Auto-generated by @fluenti/cli — do not edit')\n lines.push('')\n\n // MessageId union\n if (allIds.length === 0) {\n lines.push('export type MessageId = never')\n } else {\n lines.push('export type MessageId =')\n for (const id of allIds) {\n lines.push(` | '${escapeStringLiteral(id)}'`)\n }\n }\n\n lines.push('')\n\n // MessageValues interface\n lines.push('export interface MessageValues {')\n for (const id of allIds) {\n // Use source locale catalog to get the message for variable extraction\n const sourceCatalog = catalogs[sourceLocale]\n const entry = sourceCatalog?.[id]\n const message = entry?.message ?? id\n const vars = extractMessageVariables(message)\n\n const escapedId = escapeStringLiteral(id)\n if (vars.length === 0) {\n lines.push(` '${escapedId}': Record<string, never>`)\n } else {\n const fields = vars.map((v) => `${v.name}: ${v.type}`).join('; ')\n lines.push(` '${escapedId}': { ${fields} }`)\n }\n }\n lines.push('}')\n lines.push('')\n\n return lines.join('\\n')\n}\n","import { existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport type { 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: './src/locales/compiled',\n}\n\n/**\n * Load Fluenti config from the given path or auto-discover it.\n * When `cwd` is provided, config paths are resolved relative to it.\n */\nexport async function loadConfig(configPath?: string, cwd?: string): Promise<FluentiConfig> {\n const base = cwd ?? process.cwd()\n const paths = configPath\n ? [resolve(base, configPath)]\n : [\n resolve(base, 'fluenti.config.ts'),\n resolve(base, 'fluenti.config.js'),\n resolve(base, 'fluenti.config.mjs'),\n ]\n\n for (const p of paths) {\n if (existsSync(p)) {\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 }\n }\n\n return defaultConfig\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`@fluenti/core`),l=require(`gettext-parser`);l=s(l);let u=require(`node:fs`),d=require(`node:path`);function f(e,t,n){let r=new Set(t.map(e=>e.id)),i=new Set,a={},o=0,s=0,c=0;for(let r of t){let t=e[r.id],c=t?void 0:m(e,r,i),l=`${r.origin.file}:${r.origin.line}`,u=t??c?.entry;if(c&&i.add(c.id),u)a[r.id]={...u,message:r.message??u.message,context:r.context,comment:r.comment,origin:l,obsolete:!1},s++;else if(a[r.id]){let e=a[r.id];a[r.id]={...e,origin:p(e.origin,l)}}else a[r.id]={message:r.message,context:r.context,comment:r.comment,origin:l},o++;if(n?.stripFuzzy){let{fuzzy:e,...t}=a[r.id];a[r.id]=t}}for(let[t,i]of Object.entries(e))if(!r.has(t)){let{fuzzy:e,...r}=i;a[t]=n?.stripFuzzy?{...r,obsolete:!0}:{...i,obsolete:!0},c++}return{catalog:a,result:{added:o,unchanged:s,obsolete:c}}}function p(e,t){if(!e)return t;let n=Array.isArray(e)?e:[e],r=[...new Set([...n,t])];return r.length===1?r[0]:r}function m(e,t,n){if(!t.context)return;let r=`${t.origin.file}:${t.origin.line}`;for(let[i,a]of Object.entries(e))if(!n.has(i)&&a.context===void 0&&a.message===t.message&&h(a.origin,r))return{id:i,entry:a}}function h(e,t){return e?(Array.isArray(e)?e:[e]).some(e=>e===t||g(e)===g(t)):!1}function g(e){return e.match(/^(.*):\d+$/)?.[1]??e}function _(e){let t=JSON.parse(e),n={};for(let[e,r]of Object.entries(t))if(typeof r==`object`&&r){let t=r;n[e]={message:typeof t.message==`string`?t.message:void 0,context:typeof t.context==`string`?t.context:void 0,comment:typeof t.comment==`string`?t.comment:void 0,translation:typeof t.translation==`string`?t.translation:void 0,origin:typeof t.origin==`string`||Array.isArray(t.origin)&&t.origin.every(e=>typeof e==`string`)?t.origin:void 0,obsolete:typeof t.obsolete==`boolean`?t.obsolete:void 0,fuzzy:typeof t.fuzzy==`boolean`?t.fuzzy:void 0}}return n}function v(e){let t={};for(let[n,r]of Object.entries(e)){let e={};r.message!==void 0&&(e.message=r.message),r.context!==void 0&&(e.context=r.context),r.comment!==void 0&&(e.comment=r.comment),r.translation!==void 0&&(e.translation=r.translation),r.origin!==void 0&&(e.origin=r.origin),r.obsolete&&(e.obsolete=!0),r.fuzzy&&(e.fuzzy=!0),t[n]=e}return JSON.stringify(t,null,2)+`
|
|
2
|
+
`}var y=`fluenti-id:`;function b(e){let t=l.po.parse(e),n={},r=t.translations??{};for(let[e,t]of Object.entries(r))for(let[r,i]of Object.entries(t)){if(!r)continue;let t=e||i.msgctxt||void 0,a=i.msgstr?.[0]??void 0,o=i.comments?.reference??void 0,s=o?.includes(`
|
|
3
|
+
`)?o.split(`
|
|
4
|
+
`).map(e=>e.trim()).filter(Boolean):o?.includes(` `)?o.split(/\s+/).filter(Boolean):o,l=Array.isArray(s)&&s.length===1?s[0]:s,u=i.comments?.flag?.includes(`fuzzy`)??!1,{comment:d,customId:f,sourceMessage:p}=S(i.comments?.extracted),m=p&&(0,c.hashMessage)(p,t)===r?p:void 0,h=f??(m?r:(0,c.hashMessage)(r,t));n[h]={message:m??r,...t===void 0?{}:{context:t},...d===void 0?{}:{comment:d},...a?{translation:a}:{},...l===void 0?{}:{origin:l},...u?{fuzzy:!0}:{}}}return n}function x(e){let t={"":{"":{msgid:``,msgstr:[`Content-Type: text/plain; charset=UTF-8
|
|
5
|
+
`]}}};for(let[n,r]of Object.entries(e)){let e={msgid:r.message??n,...r.context===void 0?{}:{msgctxt:r.context},msgstr:[r.translation??``]},i={};r.origin&&(i.reference=Array.isArray(r.origin)?r.origin.join(`
|
|
6
|
+
`):r.origin);let a=w(n,r.message??n,r.context,r.comment);a&&(i.extracted=a),r.fuzzy&&(i.flag=`fuzzy`),(i.reference||i.extracted||i.flag)&&(e.comments=i);let o=r.context??``;t[o]??={},t[o][e.msgid]=e}let n={headers:{"Content-Type":`text/plain; charset=UTF-8`},translations:t};return l.po.compile(n).toString()}function S(e){if(!e)return{};let t=e.split(`
|
|
7
|
+
`).map(e=>e.trim()).filter(Boolean),n,r,i=[];for(let e of t){if(e.startsWith(y)){n=e.slice(11).trim()||void 0;continue}if(e.startsWith("msg`")&&e.endsWith("`")){r=e.slice(4,-1);continue}if(e.startsWith(`Trans: `)){r=C(e.slice(7));continue}i.push(e)}return{...i.length>0?{comment:i.join(`
|
|
8
|
+
`)}:{},...n?{customId:n}:{},...r?{sourceMessage:r}:{}}}function C(e){let t=[],n=0;return e.replace(/<\/?([a-zA-Z][\w-]*)>/g,(e,r)=>{let i=r;if(e.startsWith(`</`)){for(let e=t.length-1;e>=0;e--){let n=t[e];if(n?.tag===i)return t=t.filter((t,n)=>n!==e),`</${n.index}>`}return e}let a=n++;return t.push({tag:i,index:a}),`<${a}>`})}function w(e,t,n,r){let i=[];return r&&i.push(r),e!==(0,c.hashMessage)(t,n)&&i.push(`${y} ${e}`),i.length>0?i.join(`
|
|
9
|
+
`):void 0}var T=/\{(\w+)\}/g,E=/\{(\w+)\}/;function D(e){return E.test(e)}function O(e){return e.replace(/\\/g,`\\\\`).replace(/'/g,`\\'`).replace(/\n/g,`\\n`).replace(/\r/g,`\\r`)}function k(e,t){return/^\d/.test(t)?`${e}[${t}]`:`${e}.${t}`}function A(e){return e.replace(/\\/g,`\\\\`).replace(/`/g,"\\`").replace(/\$\{/g,"\\${").replace(/\n/g,`\\n`).replace(/\r/g,`\\r`)}function j(e){return e.replace(T,(e,t)=>`\${${k(`v`,t)}}`)}var M=/\{(\w+),\s*(plural|select|selectordinal)\s*,/;function N(e){return M.test(e)}function P(e,t){if(e.length===0)return`''`;let n=e.map(e=>F(e,t));return n.length===1?n[0]:n.join(` + `)}function F(e,t){switch(e.type){case`text`:return`'${O(e.value)}'`;case`variable`:return e.name===`#`?`String(__c)`:`String(${k(`v`,e.name)} ?? '{${e.name}}')`;case`plural`:return I(e,t);case`select`:return L(e,t);case`function`:return`String(${k(`v`,e.variable)} ?? '')`}}function I(e,t){let n=e.offset??0,r=k(`v`,e.variable),i=n?`(${r} - ${n})`:r,a=[];a.push(`((c) => { const __c = c; `);let o=Object.keys(e.options).filter(e=>e.startsWith(`=`));if(o.length>0)for(let n of o){let r=n.slice(1),i=P(e.options[n],t);a.push(`if (c === ${r}) return ${i}; `)}let s=Object.keys(e.options).filter(e=>!e.startsWith(`=`));if(s.length>1||s.length===1&&s[0]!==`other`){a.push(`const __cat = new Intl.PluralRules('${O(t)}').select(c); `);for(let n of s){if(n===`other`)continue;let r=P(e.options[n],t);a.push(`if (__cat === '${n}') return ${r}; `)}}let c=e.options.other?P(e.options.other,t):`''`;return a.push(`return ${c}; `),a.push(`})(${i})`),a.join(``)}function L(e,t){let n=[];n.push(`((s) => { `);let r=Object.keys(e.options).filter(e=>e!==`other`);for(let i of r){let r=P(e.options[i],t);n.push(`if (s === '${O(i)}') return ${r}; `)}let i=e.options.other?P(e.options.other,t):`''`;return n.push(`return ${i}; `),n.push(`})(String(${k(`v`,e.variable)} ?? ''))`),n.join(``)}function R(e,t,n,r,i){let a=[];a.push(`// @fluenti/compiled v1`);let o=[],s=0,l=[],u=new Map;for(let d of n){let n=(0,c.hashMessage)(d),f=u.get(n);if(f!==void 0&&f!==d)throw Error(`Hash collision detected: messages "${f}" and "${d}" produce the same hash "${n}"`);u.set(n,d);let p=`_${n}`,m=e[d],h=z(m,d,t,r,i?.skipFuzzy);if(h===void 0)a.push(`export const ${p} = undefined`),l.push(d);else if(N(h)){let e=P((0,c.parse)(h),t);a.push(`export const ${p} = (v) => ${e}`),s++}else if(D(h)){let e=j(A(h));a.push(`export const ${p} = (v) => \`${e}\``),s++}else a.push(`export const ${p} = '${O(h)}'`),s++;o.push({id:d,exportName:p})}if(o.length===0)return{code:`// @fluenti/compiled v1
|
|
10
|
+
// empty catalog
|
|
11
|
+
export default {}
|
|
12
|
+
`,stats:{compiled:0,missing:[]}};a.push(``),a.push(`export default {`);for(let{id:e,exportName:t}of o)a.push(` '${O(e)}': ${t},`);return a.push(`}`),a.push(``),{code:a.join(`
|
|
13
|
+
`),stats:{compiled:s,missing:l}}}function z(e,t,n,r,i){let a=r??n;if(e&&!(i&&e.fuzzy)){if(e.translation!==void 0&&e.translation.length>0)return e.translation;if(n===a)return e.message??t}}function B(e,t){let n=[];n.push(`export const locales = ${JSON.stringify(e)}`),n.push(``),n.push(`export const loaders = {`);for(let t of e)n.push(` '${O(t)}': () => import('./${O(t)}.js'),`);return n.push(`}`),n.push(``),n.join(`
|
|
14
|
+
`)}function V(e){let t=new Set;for(let n of Object.values(e))for(let[e,r]of Object.entries(n))r.obsolete||t.add(e);return[...t].sort()}function H(e){let t=(0,c.parse)(e),n=new Map;return U(t,n),[...n.entries()].sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>({name:e,type:t}))}function U(e,t){for(let n of e)switch(n.type){case`variable`:n.name!==`#`&&!t.has(n.name)&&t.set(n.name,`string | number`);break;case`plural`:{let e=n;t.set(e.variable,`number`);for(let n of Object.values(e.options))U(n,t);break}case`select`:{let e=n,r=Object.keys(e.options).filter(e=>e!==`other`),i=`other`in e.options,a=r.map(e=>`'${e}'`).join(` | `),o=i?r.length>0?`${a} | string`:`string`:r.length>0?a:`string`;t.set(e.variable,o);for(let n of Object.values(e.options))U(n,t);break}case`function`:t.has(n.variable)||t.set(n.variable,`string | number`);break;case`text`:break}}function W(e,t,n){let r=[];if(r.push(`// Auto-generated by @fluenti/cli — do not edit`),r.push(``),e.length===0)r.push(`export type MessageId = never`);else{r.push(`export type MessageId =`);for(let t of e)r.push(` | '${O(t)}'`)}r.push(``),r.push(`export interface MessageValues {`);for(let i of e){let e=H(t[n]?.[i]?.message??i),a=O(i);if(e.length===0)r.push(` '${a}': Record<string, never>`);else{let t=e.map(e=>`${e.name}: ${e.type}`).join(`; `);r.push(` '${a}': { ${t} }`)}}return r.push(`}`),r.push(``),r.join(`
|
|
15
|
+
`)}var G={sourceLocale:`en`,locales:[`en`],catalogDir:`./locales`,format:`po`,include:[`./src/**/*.{vue,tsx,jsx,ts,js}`],compileOutDir:`./src/locales/compiled`};async function K(e,t){let n=t??process.cwd(),r=e?[(0,d.resolve)(n,e)]:[(0,d.resolve)(n,`fluenti.config.ts`),(0,d.resolve)(n,`fluenti.config.js`),(0,d.resolve)(n,`fluenti.config.mjs`)];for(let e of r)if((0,u.existsSync)(e)){let{createJiti:t}=await import(`jiti`),n=await t({}.url).import(e),r=n.default??n;return{...G,...r}}return G}Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return W}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return _}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return B}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return v}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return V}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return R}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return x}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return K}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return f}});
|
|
16
|
+
//# sourceMappingURL=config-loader-D3RGkK_r.cjs.map
|