@fluenti/cli 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/cli/src/ai-provider.d.ts +0 -1
  2. package/dist/cli/src/catalog.d.ts +1 -2
  3. package/dist/cli/src/check.d.ts +0 -1
  4. package/dist/cli/src/cli.d.ts +0 -1
  5. package/dist/cli/src/codemod.d.ts +0 -1
  6. package/dist/cli/src/compile-cache.d.ts +0 -1
  7. package/dist/cli/src/compile-runner.d.ts +0 -1
  8. package/dist/cli/src/compile-worker.d.ts +0 -1
  9. package/dist/cli/src/compile.d.ts +0 -1
  10. package/dist/cli/src/config-loader.d.ts +1 -2
  11. package/dist/cli/src/config.d.ts +1 -2
  12. package/dist/cli/src/doctor.d.ts +0 -1
  13. package/dist/cli/src/extract-cache.d.ts +1 -2
  14. package/dist/cli/src/extract-runner.d.ts +0 -1
  15. package/dist/cli/src/glossary.d.ts +0 -1
  16. package/dist/cli/src/index.d.ts +3 -4
  17. package/dist/cli/src/init.d.ts +0 -1
  18. package/dist/cli/src/json-format.d.ts +0 -1
  19. package/dist/cli/src/lint.d.ts +0 -1
  20. package/dist/cli/src/migrate.d.ts +0 -1
  21. package/dist/cli/src/parallel-compile.d.ts +0 -1
  22. package/dist/cli/src/po-format.d.ts +0 -1
  23. package/dist/cli/src/stats-format.d.ts +0 -1
  24. package/dist/cli/src/translate-parse.d.ts +0 -1
  25. package/dist/cli/src/translate-prompt.d.ts +0 -1
  26. package/dist/cli/src/translate.d.ts +0 -1
  27. package/dist/cli/src/tsx-extractor.d.ts +1 -2
  28. package/dist/cli/src/validation.d.ts +0 -1
  29. package/dist/cli/src/vue-extractor.d.ts +1 -2
  30. package/dist/cli.cjs +1 -2
  31. package/dist/cli.js +0 -2
  32. package/dist/compile-C3VLvhUf.cjs +1 -2
  33. package/dist/compile-CZVpE5Md.js +0 -2
  34. package/dist/compile-worker.cjs +1 -2
  35. package/dist/compile-worker.js +0 -2
  36. package/dist/doctor-BqXXxyST.js +0 -2
  37. package/dist/doctor-xp8WS8sr.cjs +1 -2
  38. package/dist/index.cjs +1 -2
  39. package/dist/index.js +28 -13
  40. package/dist/tsx-extractor-AOjsbOmp.cjs +1 -2
  41. package/dist/tsx-extractor-C-HZNobu.js +0 -2
  42. package/dist/vue-extractor.cjs +1 -2
  43. package/dist/vue-extractor.js +0 -2
  44. package/package.json +2 -2
  45. package/dist/cli/src/ai-provider.d.ts.map +0 -1
  46. package/dist/cli/src/catalog.d.ts.map +0 -1
  47. package/dist/cli/src/check.d.ts.map +0 -1
  48. package/dist/cli/src/cli.d.ts.map +0 -1
  49. package/dist/cli/src/codemod.d.ts.map +0 -1
  50. package/dist/cli/src/compile-cache.d.ts.map +0 -1
  51. package/dist/cli/src/compile-runner.d.ts.map +0 -1
  52. package/dist/cli/src/compile-worker.d.ts.map +0 -1
  53. package/dist/cli/src/compile.d.ts.map +0 -1
  54. package/dist/cli/src/config-loader.d.ts.map +0 -1
  55. package/dist/cli/src/config.d.ts.map +0 -1
  56. package/dist/cli/src/doctor.d.ts.map +0 -1
  57. package/dist/cli/src/extract-cache.d.ts.map +0 -1
  58. package/dist/cli/src/extract-runner.d.ts.map +0 -1
  59. package/dist/cli/src/glossary.d.ts.map +0 -1
  60. package/dist/cli/src/index.d.ts.map +0 -1
  61. package/dist/cli/src/init.d.ts.map +0 -1
  62. package/dist/cli/src/json-format.d.ts.map +0 -1
  63. package/dist/cli/src/lint.d.ts.map +0 -1
  64. package/dist/cli/src/migrate.d.ts.map +0 -1
  65. package/dist/cli/src/parallel-compile.d.ts.map +0 -1
  66. package/dist/cli/src/po-format.d.ts.map +0 -1
  67. package/dist/cli/src/stats-format.d.ts.map +0 -1
  68. package/dist/cli/src/translate-parse.d.ts.map +0 -1
  69. package/dist/cli/src/translate-prompt.d.ts.map +0 -1
  70. package/dist/cli/src/translate.d.ts.map +0 -1
  71. package/dist/cli/src/tsx-extractor.d.ts.map +0 -1
  72. package/dist/cli/src/validation.d.ts.map +0 -1
  73. package/dist/cli/src/vue-extractor.d.ts.map +0 -1
  74. package/dist/cli.cjs.map +0 -1
  75. package/dist/cli.js.map +0 -1
  76. package/dist/compile-C3VLvhUf.cjs.map +0 -1
  77. package/dist/compile-CZVpE5Md.js.map +0 -1
  78. package/dist/compile-worker.cjs.map +0 -1
  79. package/dist/compile-worker.js.map +0 -1
  80. package/dist/doctor-BqXXxyST.js.map +0 -1
  81. package/dist/doctor-xp8WS8sr.cjs.map +0 -1
  82. package/dist/index.cjs.map +0 -1
  83. package/dist/index.js.map +0 -1
  84. package/dist/tsx-extractor-AOjsbOmp.cjs.map +0 -1
  85. package/dist/tsx-extractor-C-HZNobu.js.map +0 -1
  86. package/dist/vue-extractor.cjs.map +0 -1
  87. package/dist/vue-extractor.js.map +0 -1
package/dist/cli.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli.js","names":[],"sources":["../src/stats-format.ts","../src/validation.ts","../src/lint.ts","../src/check.ts","../src/compile-cache.ts","../src/ai-provider.ts","../src/glossary.ts","../src/translate-prompt.ts","../src/translate-parse.ts","../src/translate.ts","../src/migrate.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","export interface ValidationResult {\n valid: boolean\n missingPlaceholders: string[]\n extraPlaceholders: string[]\n missingHtmlTags: string[]\n extraHtmlTags: string[]\n syntaxErrors: string[]\n}\n\nimport { parse } from '@fluenti/core/compiler'\nimport type { ASTNode } from '@fluenti/core/compiler'\n\nconst ICU_PLURAL_SELECT_RE = /\\{(\\w+),\\s*(plural|select|selectordinal)\\s*,/\n\n/** Extract unique ICU placeholder names from a message, sorted alphabetically.\n * Uses the ICU parser so only top-level variables are returned — select/plural case\n * body words (e.g. the `he`/`she` in `{g, select, male {he} other {she}}`) are not\n * mistakenly treated as placeholder names. */\nexport function extractPlaceholders(message: string): string[] {\n try {\n const ast = parse(message)\n const seen = new Set<string>()\n collectVariableNames(ast, seen)\n return [...seen].sort()\n } catch {\n // Fallback for unparseable messages: extract only top-level {word} patterns\n const seen = new Set<string>()\n // Match {word} at depth 0 — skip nested braces inside plural/select case bodies\n let depth = 0\n let i = 0\n while (i < message.length) {\n if (message[i] === '{') {\n depth++\n if (depth === 1) {\n const end = message.indexOf('}', i + 1)\n if (end !== -1) {\n const inner = message.slice(i + 1, end).trim()\n const nameMatch = /^(\\w+)/.exec(inner)\n if (nameMatch) seen.add(nameMatch[1]!)\n }\n }\n } else if (message[i] === '}') {\n depth--\n }\n i++\n }\n return [...seen].sort()\n }\n}\n\nfunction collectVariableNames(nodes: ASTNode[], seen: Set<string>): void {\n for (const node of nodes) {\n if (node.type === 'variable' && node.name !== '#') {\n seen.add(node.name)\n } else if (node.type === 'plural' || node.type === 'select') {\n seen.add(node.variable)\n for (const branch of Object.values(node.options)) {\n collectVariableNames(branch, seen)\n }\n } else if (node.type === 'function') {\n seen.add(node.variable)\n }\n }\n}\n\n/** Extract unique HTML tag names from a message, lowercased and sorted */\nexport function extractHtmlTags(message: string): string[] {\n const seen = new Set<string>()\n const regex = /<\\/?([a-zA-Z][\\w-]*)[^>]*>/g\n let match\n while ((match = regex.exec(message)) !== null) {\n seen.add(match[1]!.toLowerCase())\n }\n return [...seen].sort()\n}\n\n/** Validate that a translation preserves placeholders and HTML tags from the source */\nexport function validateTranslation(source: string, translation: string): ValidationResult {\n const sourcePlaceholders = extractPlaceholders(source)\n const translationPlaceholders = extractPlaceholders(translation)\n const sourceHtmlTags = extractHtmlTags(source)\n const translationHtmlTags = extractHtmlTags(translation)\n\n const missingPlaceholders = sourcePlaceholders.filter(p => !translationPlaceholders.includes(p))\n const extraPlaceholders = translationPlaceholders.filter(p => !sourcePlaceholders.includes(p))\n const missingHtmlTags = sourceHtmlTags.filter(t => !translationHtmlTags.includes(t))\n const extraHtmlTags = translationHtmlTags.filter(t => !sourceHtmlTags.includes(t))\n\n const syntaxErrors: string[] = []\n if (ICU_PLURAL_SELECT_RE.test(translation)) {\n try {\n parse(translation)\n } catch (err) {\n syntaxErrors.push((err as Error).message)\n }\n }\n\n return {\n valid: missingPlaceholders.length === 0\n && extraPlaceholders.length === 0\n && missingHtmlTags.length === 0\n && extraHtmlTags.length === 0\n && syntaxErrors.length === 0,\n missingPlaceholders,\n extraPlaceholders,\n missingHtmlTags,\n extraHtmlTags,\n syntaxErrors,\n }\n}\n\n/** Validate a batch of translations, returning only invalid entries */\nexport function validateBatch(\n sources: Record<string, string>,\n translations: Record<string, string>,\n): Record<string, ValidationResult> {\n const results: Record<string, ValidationResult> = {}\n\n for (const key of Object.keys(sources)) {\n const translation = translations[key]\n if (translation === undefined) continue\n\n const result = validateTranslation(sources[key]!, translation)\n if (!result.valid) {\n results[key] = result\n }\n }\n\n return results\n}\n","import type { CatalogData } from './catalog'\nimport { extractPlaceholders } from './validation.js'\n\n/** Severity levels for lint diagnostics */\nexport type LintSeverity = 'error' | 'warning' | 'info'\n\n/** A single lint diagnostic */\nexport interface LintDiagnostic {\n /** Lint rule that produced this diagnostic */\n rule: string\n /** Severity level */\n severity: LintSeverity\n /** Human-readable message */\n message: string\n /** Affected message ID */\n messageId?: string\n /** Affected locale */\n locale?: string\n}\n\n/** Options for configuring lint behavior */\nexport interface LintOptions {\n /** Locales to lint (default: all) */\n locales?: string[]\n /** Source locale for comparison */\n sourceLocale: string\n /** Whether to fail on warnings (default: false) */\n strict?: boolean\n}\n\n/**\n * Run all lint rules against the provided catalogs.\n *\n * Returns an array of diagnostics. Empty array = all checks passed.\n */\nexport function lintCatalogs(\n catalogs: Record<string, CatalogData>,\n options: LintOptions,\n): LintDiagnostic[] {\n const diagnostics: LintDiagnostic[] = []\n const { sourceLocale } = options\n const locales = options.locales ?? Object.keys(catalogs)\n const sourceCatalog = catalogs[sourceLocale]\n\n if (!sourceCatalog) {\n diagnostics.push({\n rule: 'missing-source',\n severity: 'error',\n message: `Source locale catalog \"${sourceLocale}\" not found`,\n })\n return diagnostics\n }\n\n // Collect non-obsolete source IDs\n const sourceIds = Object.entries(sourceCatalog)\n .filter(([, entry]) => !entry.obsolete)\n .map(([id]) => id)\n\n for (const locale of locales) {\n if (locale === sourceLocale) continue\n const catalog = catalogs[locale]\n if (!catalog) {\n diagnostics.push({\n rule: 'missing-locale',\n severity: 'error',\n message: `Catalog for locale \"${locale}\" not found`,\n locale,\n })\n continue\n }\n\n for (const id of sourceIds) {\n const sourceEntry = sourceCatalog[id]!\n const targetEntry = catalog[id]\n\n // Rule: missing-translation\n if (!targetEntry || !targetEntry.translation || targetEntry.translation.length === 0) {\n diagnostics.push({\n rule: 'missing-translation',\n severity: 'error',\n message: `Missing translation for \"${id}\" in locale \"${locale}\"`,\n messageId: id,\n locale,\n })\n continue\n }\n\n // Rule: inconsistent-placeholders\n const sourceMessage = sourceEntry.message ?? id\n const sourcePlaceholders = extractPlaceholders(sourceMessage)\n const targetPlaceholders = extractPlaceholders(targetEntry.translation)\n\n const missingInTarget = sourcePlaceholders.filter((p) => !targetPlaceholders.includes(p))\n const extraInTarget = targetPlaceholders.filter((p) => !sourcePlaceholders.includes(p))\n\n if (missingInTarget.length > 0) {\n diagnostics.push({\n rule: 'inconsistent-placeholders',\n severity: 'error',\n message: `Translation for \"${id}\" in \"${locale}\" is missing placeholders: ${missingInTarget.map((p) => `{${p}}`).join(', ')}`,\n messageId: id,\n locale,\n })\n }\n\n if (extraInTarget.length > 0) {\n diagnostics.push({\n rule: 'inconsistent-placeholders',\n severity: 'warning',\n message: `Translation for \"${id}\" in \"${locale}\" has extra placeholders: ${extraInTarget.map((p) => `{${p}}`).join(', ')}`,\n messageId: id,\n locale,\n })\n }\n\n // Rule: fuzzy-translation\n if (targetEntry.fuzzy) {\n diagnostics.push({\n rule: 'fuzzy-translation',\n severity: 'warning',\n message: `Translation for \"${id}\" in \"${locale}\" is marked as fuzzy`,\n messageId: id,\n locale,\n })\n }\n }\n\n // Rule: orphan-translation (target has entries not in source)\n for (const [id, entry] of Object.entries(catalog)) {\n if (entry.obsolete) continue\n if (!sourceCatalog[id] || sourceCatalog[id]!.obsolete) {\n diagnostics.push({\n rule: 'orphan-translation',\n severity: 'warning',\n message: `Translation \"${id}\" in \"${locale}\" has no corresponding source message`,\n messageId: id,\n locale,\n })\n }\n }\n }\n\n // Rule: duplicate-messages (same translation text for different IDs in source)\n const messageToIds = new Map<string, string[]>()\n for (const [id, entry] of Object.entries(sourceCatalog)) {\n if (entry.obsolete) continue\n const msg = entry.message ?? id\n const existing = messageToIds.get(msg)\n if (existing) {\n existing.push(id)\n } else {\n messageToIds.set(msg, [id])\n }\n }\n for (const [msg, ids] of messageToIds) {\n if (ids.length > 1) {\n diagnostics.push({\n rule: 'duplicate-message',\n severity: 'info',\n message: `Duplicate source message \"${msg.slice(0, 60)}${msg.length > 60 ? '...' : ''}\" used by ${ids.length} entries: ${ids.join(', ')}`,\n locale: sourceLocale,\n })\n }\n }\n\n return diagnostics\n}\n\n\n/**\n * Format lint diagnostics for console output.\n */\nexport function formatDiagnostics(diagnostics: LintDiagnostic[]): string {\n if (diagnostics.length === 0) return ' ✓ All checks passed'\n\n const lines: string[] = []\n const grouped = groupBy(diagnostics, (d) => d.rule)\n\n for (const [rule, items] of Object.entries(grouped)) {\n lines.push(` ${rule} (${items.length}):`)\n for (const d of items) {\n const icon = d.severity === 'error' ? '✗' : d.severity === 'warning' ? '⚠' : 'ℹ'\n lines.push(` ${icon} ${d.message}`)\n }\n }\n\n const errors = diagnostics.filter((d) => d.severity === 'error').length\n const warnings = diagnostics.filter((d) => d.severity === 'warning').length\n const infos = diagnostics.filter((d) => d.severity === 'info').length\n\n lines.push('')\n lines.push(` Summary: ${errors} errors, ${warnings} warnings, ${infos} info`)\n\n return lines.join('\\n')\n}\n\nfunction groupBy<T>(items: T[], key: (item: T) => string): Record<string, T[]> {\n const result: Record<string, T[]> = {}\n for (const item of items) {\n const k = key(item)\n ;(result[k] ?? (result[k] = [])).push(item)\n }\n return result\n}\n","import type { CatalogData } from './catalog'\nimport { lintCatalogs } from './lint'\nimport type { LintDiagnostic } from './lint'\n\n/** Per-locale coverage result */\nexport interface CheckResult {\n locale: string\n total: number\n translated: number\n missing: number\n fuzzy: number\n coverage: number // 0-100\n}\n\n/** Options for the check command */\nexport interface CheckOptions {\n /** Source locale for comparison */\n sourceLocale: string\n /** Minimum coverage percentage (0-100) */\n minCoverage: number\n /** Check only a specific locale */\n locale?: string\n /** Output format */\n format: 'text' | 'json' | 'github'\n}\n\n/** Full check result */\nexport interface CheckOutput {\n results: CheckResult[]\n passed: boolean\n minCoverage: number\n actualCoverage: number\n diagnostics: LintDiagnostic[]\n}\n\n/**\n * Check translation coverage across all locale catalogs.\n */\nexport function checkCoverage(\n catalogs: Record<string, CatalogData>,\n options: CheckOptions,\n): CheckOutput {\n const { sourceLocale, minCoverage, locale: targetLocale } = options\n const sourceCatalog = catalogs[sourceLocale]\n\n if (!sourceCatalog) {\n return {\n results: [],\n passed: false,\n minCoverage,\n actualCoverage: 0,\n diagnostics: [{\n rule: 'missing-source',\n severity: 'error',\n message: `Source locale catalog \"${sourceLocale}\" not found`,\n }],\n }\n }\n\n // Count non-obsolete source entries\n const sourceIds = Object.entries(sourceCatalog)\n .filter(([, entry]) => !entry.obsolete)\n .map(([id]) => id)\n\n const total = sourceIds.length\n\n // Determine which locales to check\n const localesToCheck = targetLocale\n ? [targetLocale]\n : Object.keys(catalogs).filter((l) => l !== sourceLocale)\n\n const results: CheckResult[] = []\n\n for (const locale of localesToCheck) {\n const catalog = catalogs[locale]\n if (!catalog) {\n results.push({\n locale,\n total,\n translated: 0,\n missing: total,\n fuzzy: 0,\n coverage: 0,\n })\n continue\n }\n\n let translated = 0\n let missing = 0\n let fuzzy = 0\n\n for (const id of sourceIds) {\n const entry = catalog[id]\n if (!entry || !entry.translation || entry.translation.length === 0) {\n missing++\n } else {\n translated++\n if (entry.fuzzy) {\n fuzzy++\n }\n }\n }\n\n const coverage = total > 0 ? (translated / total) * 100 : 100\n results.push({ locale, total, translated, missing, fuzzy, coverage })\n }\n\n // Calculate overall coverage\n const totalTranslated = results.reduce((sum, r) => sum + r.translated, 0)\n const totalEntries = results.reduce((sum, r) => sum + r.total, 0)\n const actualCoverage = totalEntries > 0\n ? (totalTranslated / totalEntries) * 100\n : 100\n\n const passed = results.every((r) => r.coverage >= minCoverage)\n\n // Run lint diagnostics for missing translations\n const lintOpts: Parameters<typeof lintCatalogs>[1] = { sourceLocale }\n if (targetLocale) {\n lintOpts.locales = [sourceLocale, targetLocale]\n }\n\n const diagnostics = lintCatalogs(catalogs, lintOpts)\n\n return { results, passed, minCoverage, actualCoverage, diagnostics }\n}\n\n/**\n * Format check output as plain text.\n */\nexport function formatCheckText(output: CheckOutput): string {\n const lines: string[] = []\n\n for (const r of output.results) {\n const icon = r.coverage >= output.minCoverage ? '✓' : '✗'\n const pct = r.coverage.toFixed(1)\n const details = r.missing > 0 ? ` — ${r.missing} missing` : ''\n const fuzzyNote = r.fuzzy > 0 ? `, ${r.fuzzy} fuzzy` : ''\n lines.push(`${icon} ${r.locale}: ${pct}% (${r.translated}/${r.total})${details}${fuzzyNote}`)\n }\n\n lines.push('')\n const overallPct = output.actualCoverage.toFixed(1)\n const status = output.passed ? 'PASSED' : 'FAILED'\n lines.push(`Coverage: ${overallPct}% (min: ${output.minCoverage}%) — ${status}`)\n\n return lines.join('\\n')\n}\n\n/**\n * Format check output as GitHub Actions annotations.\n */\nexport function formatCheckGitHub(\n output: CheckOutput,\n catalogDir: string,\n format: 'json' | 'po',\n): string {\n const lines: string[] = []\n const ext = format === 'json' ? '.json' : '.po'\n\n for (const r of output.results) {\n if (r.coverage < output.minCoverage) {\n const file = `${catalogDir}/${r.locale}${ext}`\n lines.push(\n `::error file=${file}::Translation coverage ${r.coverage.toFixed(1)}% below minimum ${output.minCoverage}%`,\n )\n }\n }\n\n // Add individual missing translation warnings\n const missingDiags = output.diagnostics.filter((d) => d.rule === 'missing-translation')\n for (const d of missingDiags) {\n if (d.locale) {\n const file = `${catalogDir}/${d.locale}${ext}`\n lines.push(`::warning file=${file}::${d.message}`)\n }\n }\n\n return lines.join('\\n')\n}\n\n/**\n * Format check output as JSON.\n */\nexport function formatCheckJson(output: CheckOutput): string {\n return JSON.stringify({\n results: output.results,\n passed: output.passed,\n minCoverage: output.minCoverage,\n actualCoverage: Math.round(output.actualCoverage * 10) / 10,\n }, null, 2)\n}\n","import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs'\nimport { createHash } from 'node:crypto'\nimport { dirname, resolve } from 'node:path'\n\n/** Cache format version — bump when the structure changes */\nconst CACHE_VERSION = '1'\n\ninterface CompileCacheEntry {\n inputHash: string\n}\n\ninterface CompileCacheData {\n version: string\n entries: Record<string, CompileCacheEntry> // keyed by locale\n}\n\n/**\n * Compile cache that skips re-compilation when PO/JSON files haven't changed.\n *\n * Uses MD5 of input file content as change detection (non-cryptographic, fast).\n */\nexport class CompileCache {\n private data: CompileCacheData\n private cachePath: string\n private dirty = false\n\n constructor(catalogDir: string, projectId?: string) {\n const cacheDir = projectId\n ? resolve(catalogDir, '.cache', projectId)\n : resolve(catalogDir, '.cache')\n this.cachePath = resolve(cacheDir, 'compile-cache.json')\n this.data = this.load()\n }\n\n /**\n * Check if a locale's catalog has changed since last compile.\n * Returns true if unchanged (cache hit), false if re-compilation needed.\n */\n isUpToDate(locale: string, catalogContent: string): boolean {\n const entry = this.data.entries[locale]\n if (!entry) return false\n\n const currentHash = hashContent(catalogContent)\n return entry.inputHash === currentHash\n }\n\n /**\n * Update the cache after a successful compilation.\n */\n set(locale: string, catalogContent: string): void {\n this.data.entries[locale] = {\n inputHash: hashContent(catalogContent),\n }\n this.dirty = true\n }\n\n /**\n * Write the cache to disk if any changes were made.\n */\n save(): void {\n if (!this.dirty) return\n\n try {\n mkdirSync(dirname(this.cachePath), { recursive: true })\n writeFileSync(this.cachePath, JSON.stringify(this.data), 'utf-8')\n this.dirty = false\n } catch {\n // Cache write failure is non-fatal — next run will re-compile\n }\n }\n\n private load(): CompileCacheData {\n try {\n if (existsSync(this.cachePath)) {\n const raw = readFileSync(this.cachePath, 'utf-8')\n const parsed = JSON.parse(raw) as CompileCacheData\n if (parsed.version === CACHE_VERSION) {\n return parsed\n }\n }\n } catch {\n // Corrupt or unreadable cache — start fresh\n }\n\n return { version: CACHE_VERSION, entries: {} }\n }\n}\n\nfunction hashContent(content: string): string {\n return createHash('md5').update(content).digest('hex')\n}\n","import { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport { setTimeout as sleep } from 'node:timers/promises'\n\nconst execFileAsync = promisify(execFile)\n\nexport type AIProvider = 'claude' | 'codex'\n\nexport interface AIInvokeOptions {\n readonly provider: AIProvider\n readonly prompt: string\n readonly maxRetries?: number | undefined\n readonly initialDelayMs?: number | undefined\n readonly maxBuffer?: number | undefined\n readonly timeoutMs?: number | undefined // default: 120_000 (2 minutes)\n}\n\nexport interface AIInvokeResult {\n readonly stdout: string\n readonly attempts: number\n}\n\nconst INSTALL_COMMANDS: Record<AIProvider, string> = {\n claude: 'npm install -g @anthropic-ai/claude-code',\n codex: 'npm install -g @openai/codex',\n}\n\nfunction buildArgs(provider: AIProvider, prompt: string): readonly string[] {\n return provider === 'claude'\n ? ['-p', prompt]\n : ['-p', prompt, '--full-auto']\n}\n\nfunction isEnoent(error: unknown): boolean {\n return (error as Error & { code?: string }).code === 'ENOENT'\n}\n\nexport async function invokeAI(options: AIInvokeOptions): Promise<AIInvokeResult> {\n const {\n provider,\n prompt,\n maxRetries = 3,\n initialDelayMs = 1000,\n maxBuffer = 10 * 1024 * 1024,\n timeoutMs = 120_000,\n } = options\n\n const args = buildArgs(provider, prompt)\n const command = provider === 'claude' ? 'claude' : 'codex'\n let lastError: unknown\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const execPromise = execFileAsync(command, [...args], { maxBuffer })\n const { stdout } = await Promise.race([\n execPromise,\n sleep(timeoutMs).then<never>(() => {\n throw new Error(`AI provider timed out after ${timeoutMs}ms`)\n }),\n ])\n return { stdout, attempts: attempt + 1 }\n } catch (error: unknown) {\n if (isEnoent(error)) {\n throw new Error(\n `\"${provider}\" CLI not found. Please install it first:\\n ${INSTALL_COMMANDS[provider]}`,\n )\n }\n if (error instanceof Error && error.message.includes('timed out')) {\n throw error\n }\n lastError = error\n if (attempt < maxRetries) {\n const delay = initialDelayMs * 2 ** attempt\n await sleep(delay)\n }\n }\n }\n\n throw lastError\n}\n","import { readFileSync, existsSync, statSync } from 'node:fs'\n\nexport type GlossaryData = Record<string, Record<string, string>>\n\nconst MAX_GLOSSARY_SIZE = 1_048_576 // 1MB\n\nexport function loadGlossary(glossaryPath: string): GlossaryData {\n if (!existsSync(glossaryPath)) {\n return {}\n }\n\n const fileSize = statSync(glossaryPath).size\n if (fileSize > MAX_GLOSSARY_SIZE) {\n throw new Error(\n `Glossary file exceeds maximum size of ${MAX_GLOSSARY_SIZE} bytes (got ${fileSize} bytes)`,\n )\n }\n\n let parsed: unknown\n try {\n const content = readFileSync(glossaryPath, 'utf-8')\n parsed = JSON.parse(content) as unknown\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n throw new Error(`Invalid glossary format: failed to parse JSON — ${message}`)\n }\n\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new Error('Invalid glossary format: root must be a plain object')\n }\n\n for (const [term, mappings] of Object.entries(parsed as Record<string, unknown>)) {\n if (typeof mappings !== 'object' || mappings === null || Array.isArray(mappings)) {\n throw new Error(`Invalid glossary format: value for \"${term}\" must be a plain object`)\n }\n for (const [locale, translation] of Object.entries(mappings as Record<string, unknown>)) {\n if (typeof translation !== 'string') {\n throw new Error(\n `Invalid glossary format: \"${term}.${locale}\" must be a string, got ${typeof translation}`,\n )\n }\n }\n }\n\n return parsed as GlossaryData\n}\n\nexport function getGlossaryForLocale(\n glossary: GlossaryData,\n locale: string,\n): Record<string, string> {\n const result: Record<string, string> = {}\n for (const [term, mappings] of Object.entries(glossary)) {\n if (locale in mappings) {\n result[term] = mappings[locale]!\n }\n }\n return result\n}\n\n/** Escape control characters and quotes to prevent prompt injection in AI prompts */\nfunction sanitizePromptValue(value: string): string {\n return value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/[\\n\\r]/g, ' ')\n .replace(/\\t/g, ' ')\n}\n\nexport function buildGlossaryPromptSection(terms: Record<string, string>): string {\n const entries = Object.entries(terms)\n if (entries.length === 0) {\n return ''\n }\n\n const sorted = [...entries].sort(([a], [b]) => a.localeCompare(b))\n const lines = sorted.map(([source, target]) => `\"${sanitizePromptValue(source)}\" → \"${sanitizePromptValue(target)}\"`)\n\n return `=== GLOSSARY (use these exact translations) ===\\n${lines.join('\\n')}`\n}\n","import { buildGlossaryPromptSection } from './glossary'\n\nexport interface PromptOptions {\n readonly sourceLocale: string\n readonly targetLocale: string\n readonly messages: Record<string, string>\n readonly glossary?: Record<string, string> | undefined\n readonly context?: string | undefined\n}\n\nexport function buildTranslatePrompt(options: PromptOptions): string {\n const { sourceLocale, targetLocale, messages, glossary, context } = options\n const json = JSON.stringify(messages, null, 2)\n\n const sections: string[] = [\n `You are a professional translator. Translate the following messages from \"${sourceLocale}\" to \"${targetLocale}\".`,\n '',\n 'Rules:',\n '1. Output ONLY a valid JSON object with the same keys and translated values.',\n '2. Keep ICU MessageFormat placeholders unchanged: {name}, {count, plural, ...}, {val, number}, etc.',\n '3. Keep HTML tags unchanged: <b>, <a href=\"...\">, </span>, <br/>, etc.',\n '4. Keep numbered rich-text tags unchanged: <0>, </0>, <1/>, etc.',\n '5. Do not add any explanation, markdown formatting, or code fences — output raw JSON only.',\n ]\n\n if (glossary && Object.keys(glossary).length > 0) {\n const glossarySection = buildGlossaryPromptSection(glossary)\n if (glossarySection) {\n sections.push('', glossarySection)\n }\n }\n\n if (context) {\n sections.push('', `=== PROJECT CONTEXT ===`, context)\n }\n\n sections.push('', 'Input (JSON):', json)\n\n return sections.join('\\n')\n}\n","import { validateTranslation } from './validation'\n\nexport interface ParseResult {\n readonly translations: Record<string, string>\n readonly warnings: string[]\n}\n\n/**\n * Extract the first complete JSON object from text using bracket counting.\n * Handles strings with escaped characters correctly.\n */\nfunction extractFirstJsonObject(text: string): string {\n const start = text.indexOf('{')\n if (start === -1) {\n throw new Error('No JSON object found in AI response')\n }\n let depth = 0\n let inString = false\n let escape = false\n for (let i = start; i < text.length; i++) {\n const ch = text[i]!\n if (escape) { escape = false; continue }\n if (ch === '\\\\' && inString) { escape = true; continue }\n if (ch === '\"' && !escape) { inString = !inString; continue }\n if (inString) continue\n if (ch === '{') depth++\n if (ch === '}') {\n depth--\n if (depth === 0) return text.slice(start, i + 1)\n }\n }\n throw new Error('Unterminated JSON object in AI response')\n}\n\n/**\n * Strip markdown code fences from AI response.\n * Handles ```json ... ``` and ``` ... ``` patterns.\n */\nfunction stripCodeFence(text: string): string {\n const fenceMatch = text.match(/```(?:json)?\\s*\\n?([\\s\\S]*?)```/)\n return fenceMatch ? fenceMatch[1]! : text\n}\n\n/**\n * Parse AI translation response and validate against source messages.\n * Returns translations with warnings for any QA issues.\n */\nexport function parseTranslateResponse(\n response: string,\n sourceMessages: Record<string, string>,\n): ParseResult {\n const warnings: string[] = []\n\n // Step 1: Strip markdown code fence if present\n const cleaned = stripCodeFence(response)\n\n // Step 2: Extract first JSON object using bracket counting\n const jsonStr = extractFirstJsonObject(cleaned)\n\n // Step 3: Parse JSON\n let parsed: unknown\n try {\n parsed = JSON.parse(jsonStr)\n } catch {\n throw new Error(`Failed to parse JSON from AI response: ${jsonStr.slice(0, 200)}`)\n }\n\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new Error('AI response is not a valid JSON object')\n }\n\n const rawTranslations = parsed as Record<string, unknown>\n const translations: Record<string, string> = {}\n const sourceKeys = new Set(Object.keys(sourceMessages))\n\n // Step 4: Validate each key\n for (const [key, value] of Object.entries(rawTranslations)) {\n // Skip keys not in source\n if (!sourceKeys.has(key)) {\n warnings.push(`Extra key in AI response (ignored): \"${key}\"`)\n continue\n }\n\n // Must be a string value\n if (typeof value !== 'string') {\n warnings.push(`Non-string value for key \"${key}\" (ignored)`)\n continue\n }\n\n translations[key] = value\n\n // QA validation: check placeholder and HTML tag consistency\n const source = sourceMessages[key]!\n const validation = validateTranslation(source, value)\n if (!validation.valid) {\n const issues: string[] = []\n if (validation.missingPlaceholders.length > 0) {\n issues.push(`missing placeholders: ${validation.missingPlaceholders.join(', ')}`)\n }\n if (validation.extraPlaceholders.length > 0) {\n issues.push(`extra placeholders: ${validation.extraPlaceholders.join(', ')}`)\n }\n if (validation.missingHtmlTags.length > 0) {\n issues.push(`missing HTML tags: ${validation.missingHtmlTags.join(', ')}`)\n }\n if (validation.extraHtmlTags.length > 0) {\n issues.push(`extra HTML tags: ${validation.extraHtmlTags.join(', ')}`)\n }\n if (validation.syntaxErrors.length > 0) {\n issues.push(`ICU syntax errors: ${validation.syntaxErrors.join('; ')}`)\n }\n warnings.push(`QA issue for \"${key}\": ${issues.join('; ')}`)\n }\n }\n\n // Step 5: Check for missing keys\n for (const key of sourceKeys) {\n if (!(key in translations)) {\n warnings.push(`Missing translation for key: \"${key}\"`)\n }\n }\n\n return { translations, warnings }\n}\n","import consola from 'consola'\nimport type { CatalogData } from './catalog'\nimport { invokeAI } from './ai-provider'\nimport type { AIProvider } from './ai-provider'\nimport { buildTranslatePrompt } from './translate-prompt'\nimport { parseTranslateResponse } from './translate-parse'\n\nexport type { AIProvider } from './ai-provider'\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 readonly provider: AIProvider\n readonly sourceLocale: string\n readonly targetLocale: string\n readonly catalog: CatalogData\n readonly batchSize: number\n readonly context?: string | undefined\n readonly glossary?: Record<string, string> | undefined\n readonly timeoutMs?: number | undefined\n}\n\nexport async function translateCatalog(options: TranslateOptions): Promise<{\n catalog: CatalogData\n translated: number\n warnings: string[]\n}> {\n const { provider, sourceLocale, targetLocale, catalog, batchSize, context, glossary, timeoutMs } = 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, warnings: [] }\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 const allWarnings: string[] = []\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 try {\n const prompt = buildTranslatePrompt({\n sourceLocale,\n targetLocale,\n messages: batch,\n glossary,\n context,\n })\n const { stdout: response } = await invokeAI({ provider, prompt, timeoutMs })\n const { translations, warnings } = parseTranslateResponse(response, batch)\n\n for (const warning of warnings) {\n allWarnings.push(`[${targetLocale}] ${warning}`)\n consola.warn(` ${warning}`)\n }\n\n for (const key of batchKeys) {\n if (key in translations) {\n result[key] = {\n ...result[key],\n translation: translations[key],\n }\n totalTranslated++\n }\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n allWarnings.push(`[${targetLocale}] Batch ${i + 1} failed: ${msg}`)\n consola.error(` Batch ${i + 1} failed: ${msg}`)\n }\n }\n\n return { catalog: result, translated: totalTranslated, warnings: allWarnings }\n}\n","import { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport { readFileSync, existsSync } from 'node:fs'\nimport { resolve, join, dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\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 const thisDir = typeof __dirname !== 'undefined'\n ? __dirname\n : dirname(fileURLToPath(import.meta.url))\n // Try to find the migration guide relative to the CLI package\n const candidates = [\n resolve('node_modules', '@fluenti', 'cli', '..', '..', guidePath),\n join(thisDir, '..', '..', '..', guidePath),\n join(thisDir, '..', '..', 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 tool converting a ${library.framework} project from \"${library.name}\" to Fluenti (@fluenti).`,\n '',\n 'Tasks:',\n '1. Generate a `fluenti.config.ts` based on the existing i18n configuration',\n '2. Convert each locale/translation file to standard gettext PO format',\n '3. Generate unified diff patches for every source file that needs changes',\n '4. Generate install/uninstall commands',\n '',\n '=== TRANSLATION API RULES ===',\n 'Fluenti provides a compile-time `t` tagged template that does NOT require useI18n():',\n '',\n ' import { t } from \\'@fluenti/react\\' // or @fluenti/vue, @fluenti/solid',\n ' const name = \"World\"',\n ' t`Hello, ${name}!`',\n '',\n 'Use `import { t }` for ALL translation calls. Only use `useI18n()` when you need:',\n '- `d()` / `n()` for date/number formatting',\n '- `setLocale()` for locale switching',\n '- `locale` for reading the current locale reactively',\n '',\n '=== SOURCE CODE REWRITING RULES ===',\n 'Imports:',\n `- ${library.name}: remove all imports from \"${library.name}\" (and related packages)`,\n `- Add: import { t } from '@fluenti/${library.framework === 'Vue' ? 'vue' : library.framework === 'Next.js' ? 'react' : library.framework.toLowerCase()}'`,\n '- Only add useI18n import if d()/n()/setLocale is needed in that file',\n '',\n 'Translation calls:',\n '- t(\\'key\\') → t`Source text` (tagged template with the actual source text, not the key)',\n '- t(\\'key\\', { name }) → t`Hello, ${name}` (interpolate directly in template)',\n '- $t(\\'key\\') → t`Source text` (Vue template)',\n '- Remove useI18n()/useTranslation()/useTranslations() destructuring if only t was used',\n '',\n 'Components:',\n '- <i18n-t keypath=\"key\"> → <Trans>Source text</Trans>',\n '- <Trans i18nKey=\"key\"> → <Trans>Source text</Trans>',\n '',\n 'ICU syntax conversion:',\n '- {{variable}} (double braces) → {variable} (single braces)',\n '- _one/_other suffixes → ICU {count, plural, one {...} other {...}}',\n '- @:key references → inline the referenced text directly',\n '- Pipe-separated plurals → ICU plural',\n '',\n '=== PO FORMAT RULES ===',\n 'Each PO file must have a standard header:',\n ' msgid \"\"',\n ' msgstr \"\"',\n ' \"Content-Type: text/plain; charset=UTF-8\\\\n\"',\n ' \"Content-Transfer-Encoding: 8bit\\\\n\"',\n ' \"Language: {locale}\\\\n\"',\n '',\n 'Message entries: msgid is the source text (English), msgstr is the translation.',\n 'Flatten nested JSON keys: \"home.title\" → use the actual source text as msgid.',\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 'Output ONLY the following sections. No explanations, no commentary.',\n '',\n '### FLUENTI_CONFIG',\n '```ts',\n '// Complete fluenti.config.ts',\n '```',\n '',\n '### LOCALE_FILES',\n '#### LOCALE: {locale_code}',\n '```po',\n '// Complete PO file with standard header',\n '```',\n '(repeat for each locale)',\n '',\n '### SOURCE_PATCHES',\n '#### FILE: {relative_file_path}',\n '```diff',\n '--- a/{file_path}',\n '+++ b/{file_path}',\n '@@ ... @@',\n ' context line',\n '-removed line',\n '+added line',\n '```',\n '(repeat for each file that needs changes)',\n '',\n '### INSTALL_COMMANDS',\n '```bash',\n '// install + 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' || err.code === 'EPERM' || err.code === 'EACCES') {\n throw new Error(\n `\"${provider}\" CLI not found or not executable. 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 sourcePatches: Array<{ file: string; patch: string }>\n steps: string | undefined\n installCommands: string | undefined\n}\n\nexport function parseResponse(response: string): MigrateResult {\n const MAX_RESPONSE_LENGTH = 500_000 // 500KB\n const safeResponse = response.length > MAX_RESPONSE_LENGTH\n ? response.slice(0, MAX_RESPONSE_LENGTH)\n : response\n\n const result: MigrateResult = {\n config: undefined,\n localeFiles: [],\n sourcePatches: [],\n steps: undefined,\n installCommands: undefined,\n }\n\n // Extract fluenti.config.ts\n const configMatch = safeResponse.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 = safeResponse.match(/### LOCALE_FILES([\\s\\S]*?)(?=### SOURCE_PATCHES|### 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 source patches (new format)\n const patchSection = safeResponse.match(/### SOURCE_PATCHES([\\s\\S]*?)(?=### INSTALL_COMMANDS|$)/)\n if (patchSection) {\n const patchRegex = /#### FILE:\\s*(\\S+)\\s*\\n```(?:diff)?\\n([\\s\\S]*?)```/g\n let match\n while ((match = patchRegex.exec(patchSection[1]!)) !== null) {\n result.sourcePatches.push({\n file: match[1]!,\n patch: match[2]!.trim(),\n })\n }\n }\n\n // Extract migration steps (legacy format — still supported for backward compat)\n const stepsMatch = safeResponse.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 = safeResponse.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 or apply source patches\n if (result.sourcePatches.length > 0) {\n if (write) {\n consola.log('')\n consola.info(`Generated ${result.sourcePatches.length} source patch(es). Apply with:`)\n for (const patch of result.sourcePatches) {\n const patchPath = resolve(`.fluenti-migrate-${patch.file.replace(/[/\\\\]/g, '-')}.patch`)\n const { writeFileSync } = await import('node:fs')\n writeFileSync(patchPath, patch.patch, 'utf-8')\n consola.success(`Patch written: ${patchPath}`)\n consola.log(` patch -p1 < ${patchPath}`)\n }\n } else {\n for (const patch of result.sourcePatches) {\n consola.log('')\n consola.box({\n title: `Patch: ${patch.file}`,\n message: patch.patch.length > 800\n ? patch.patch.slice(0, 800) + '\\n... (use --write to save full patch)'\n : patch.patch,\n })\n }\n }\n }\n\n // Display migration steps (legacy format fallback)\n if (result.steps && result.sourcePatches.length === 0) {\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 || result.sourcePatches.length > 0)) {\n consola.log('')\n consola.info('Run with --write to save generated files and patches to disk:')\n consola.log(` fluenti migrate --from ${from} --write`)\n }\n}\n","#!/usr/bin/env node\nimport { defineCommand, runMain } from 'citty'\nimport consola from 'consola'\nimport fg from 'fast-glob'\nimport { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'\nimport { resolve, dirname, extname } from 'node:path'\nimport { 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 { parallelCompile } from './parallel-compile'\nimport { formatStatsRow } from './stats-format'\nimport { lintCatalogs, formatDiagnostics } from './lint'\nimport { checkCoverage, formatCheckText, formatCheckGitHub, formatCheckJson } from './check'\nimport { ExtractCache } from './extract-cache'\nimport { CompileCache } from './compile-cache'\nimport { translateCatalog } from './translate'\nimport type { AIProvider } from './translate'\nimport { loadGlossary, getGlossaryForLocale } from './glossary'\nimport { runMigrate } from './migrate'\nimport { runInit } from './init'\nimport { loadConfig } from './config-loader'\nimport { runCodemod } from './codemod'\nimport { formatDoctorReport, runDoctor } from './doctor'\nimport { createHash } from 'node:crypto'\nimport type { ExtractedMessage } from '@fluenti/core/compiler'\nimport { resolveLocaleCodes } from '@fluenti/core/compiler'\nimport type { FluentiPlugin, PluginCompileContext, FluentiBuildConfig } from '@fluenti/core/compiler'\n\nfunction deriveProjectId(cwd: string): string {\n return createHash('md5').update(cwd).digest('hex').slice(0, 8)\n}\n\nfunction readCatalog(filePath: string, format: 'json' | 'po'): CatalogData {\n if (!existsSync(filePath)) return {}\n const content = readFileSync(filePath, 'utf-8')\n return format === 'json' ? readJsonCatalog(content) : readPoCatalog(content)\n}\n\nfunction writeCatalog(filePath: string, catalog: CatalogData, format: 'json' | 'po'): void {\n mkdirSync(dirname(filePath), { recursive: true })\n const content = format === 'json' ? writeJsonCatalog(catalog) : writePoCatalog(catalog)\n writeFileSync(filePath, content, 'utf-8')\n}\n\nasync function extractFromFile(\n filePath: string,\n code: string,\n idGenerator?: (message: string, context?: string) => string,\n): 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, idGenerator)\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, idGenerator)\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 'no-cache': { type: 'boolean', description: 'Disable incremental extraction cache', default: false },\n },\n async run({ args }) {\n const config = await loadConfig(args.config)\n const localeCodes = resolveLocaleCodes(config.locales)\n consola.info(`Extracting messages from ${config.include.join(', ')}`)\n\n const files = await fg(config.include, { ignore: config.exclude ?? [], absolute: false })\n const allMessages: ExtractedMessage[] = []\n const useCache = !(args['no-cache'] ?? false)\n const cache = useCache ? new ExtractCache(config.catalogDir, deriveProjectId(process.cwd())) : null\n\n let cacheHits = 0\n\n for (const file of files) {\n if (cache) {\n const cached = cache.get(file)\n if (cached) {\n allMessages.push(...cached)\n cacheHits++\n continue\n }\n }\n\n const code = readFileSync(file, 'utf-8')\n const messages = await extractFromFile(file, code, config.idGenerator)\n allMessages.push(...messages)\n\n if (cache) {\n cache.set(file, messages)\n }\n }\n\n // Prune cache entries for deleted files\n if (cache) {\n cache.prune(new Set(files))\n cache.save()\n }\n\n if (cacheHits > 0) {\n consola.info(`Found ${allMessages.length} messages in ${files.length} files (${cacheHits} cached)`)\n } else {\n consola.info(`Found ${allMessages.length} messages in ${files.length} files`)\n }\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 localeCodes) {\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 // Run plugin onAfterExtract hooks\n for (const plugin of config.plugins ?? []) {\n await plugin.onAfterExtract?.({\n messages: new Map(allMessages.map((m) => [m.id, m])),\n sourceLocale: config.sourceLocale,\n targetLocales: localeCodes.filter((l: string) => l !== config.sourceLocale),\n config,\n })\n }\n },\n})\n\n/** Build a Record<string, string> of raw message strings from a catalog for transformMessages hooks */\nfunction catalogToRawMessages(catalog: CatalogData): Record<string, string> {\n const result: Record<string, string> = {}\n for (const [id, entry] of Object.entries(catalog)) {\n if (entry.translation && entry.translation.length > 0) {\n result[id] = entry.translation\n } else if (entry.message) {\n result[id] = entry.message\n }\n }\n return result\n}\n\n/** Apply transformMessages hooks from all plugins and return an updated catalog */\nasync function applyTransformMessages(\n catalog: CatalogData,\n locale: string,\n plugins: readonly FluentiPlugin[],\n): Promise<CatalogData> {\n let rawMessages = catalogToRawMessages(catalog)\n\n for (const plugin of plugins) {\n if (plugin.transformMessages) {\n rawMessages = await plugin.transformMessages(rawMessages, locale)\n }\n }\n\n // Build a new catalog with transformed translations\n const updated: CatalogData = {}\n for (const [id, entry] of Object.entries(catalog)) {\n const transformed = rawMessages[id]\n updated[id] = transformed !== undefined\n ? { ...entry, translation: transformed }\n : { ...entry }\n }\n return updated\n}\n\n/** Build a PluginCompileContext for hook invocation */\nfunction buildCompileContext(\n locale: string,\n catalog: CatalogData,\n outDir: string,\n config: FluentiBuildConfig,\n): PluginCompileContext {\n const messages: Record<string, string> = {}\n for (const [id, entry] of Object.entries(catalog)) {\n if (entry.translation && entry.translation.length > 0) {\n messages[id] = entry.translation\n } else if (entry.message) {\n messages[id] = entry.message\n }\n }\n return { locale, messages, outDir, config }\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 'no-cache': { type: 'boolean', description: 'Disable compilation cache', default: false },\n parallel: { type: 'boolean', description: 'Enable parallel compilation using worker threads', default: false },\n concurrency: { type: 'string', description: 'Max number of worker threads (default: auto)' },\n },\n async run({ args }) {\n const config = await loadConfig(args.config)\n const localeCodes = resolveLocaleCodes(config.locales)\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 const catalogContents: Record<string, string> = {}\n for (const locale of localeCodes) {\n const catalogPath = resolve(config.catalogDir, `${locale}${ext}`)\n if (existsSync(catalogPath)) {\n const content = readFileSync(catalogPath, 'utf-8')\n catalogContents[locale] = content\n allCatalogs[locale] = config.format === 'json'\n ? readJsonCatalog(content)\n : readPoCatalog(content)\n } else {\n catalogContents[locale] = ''\n allCatalogs[locale] = {}\n }\n }\n\n const allIds = collectAllIds(allCatalogs)\n consola.info(`Compiling ${allIds.length} messages across ${localeCodes.length} locales`)\n\n const skipFuzzy = args['skip-fuzzy'] ?? false\n const useCache = !(args['no-cache'] ?? false)\n const cache = useCache ? new CompileCache(config.catalogDir, deriveProjectId(process.cwd())) : null\n const useParallel = args.parallel ?? false\n const concurrency = args.concurrency ? parseInt(args.concurrency, 10) : undefined\n\n if (concurrency !== undefined && (isNaN(concurrency) || concurrency < 1)) {\n consola.error('Invalid --concurrency. Must be a positive integer.')\n process.exitCode = 1\n return\n }\n\n let skipped = 0\n let needsRegenIndex = false\n\n // Filter locales that need compilation (cache check)\n const localesToCompile: string[] = []\n for (const locale of localeCodes) {\n if (cache && cache.isUpToDate(locale, catalogContents[locale]!)) {\n const outPath = resolve(config.compileOutDir, `${locale}.js`)\n if (existsSync(outPath)) {\n skipped++\n continue\n }\n }\n localesToCompile.push(locale)\n }\n\n if (localesToCompile.length > 0) {\n needsRegenIndex = true\n }\n\n if (useParallel && localesToCompile.length > 1) {\n // Parallel compilation via worker threads\n const plugins = config.plugins ?? []\n\n // Apply onBeforeCompile and transformMessages hooks before parallel dispatch\n const transformedCatalogs: Record<string, CatalogData> = {}\n for (const locale of localesToCompile) {\n for (const plugin of plugins) {\n await plugin.onBeforeCompile?.(\n buildCompileContext(locale, allCatalogs[locale]!, config.compileOutDir, config),\n )\n }\n transformedCatalogs[locale] = plugins.length > 0\n ? await applyTransformMessages(allCatalogs[locale]!, locale, plugins)\n : allCatalogs[locale]!\n }\n\n const tasks = localesToCompile.map((locale) => ({\n locale,\n catalog: transformedCatalogs[locale]!,\n allIds,\n sourceLocale: config.sourceLocale,\n options: { skipFuzzy },\n }))\n\n const results = await parallelCompile(tasks, concurrency)\n\n for (const result of results) {\n const outPath = resolve(config.compileOutDir, `${result.locale}.js`)\n writeFileSync(outPath, result.code, 'utf-8')\n\n if (cache) {\n cache.set(result.locale, catalogContents[result.locale]!)\n }\n\n if (result.stats.missing.length > 0) {\n consola.warn(\n `${result.locale}: ${result.stats.compiled} compiled, ${result.stats.missing.length} missing translations`,\n )\n for (const id of result.stats.missing) {\n consola.warn(` ⤷ ${id}`)\n }\n } else {\n consola.success(`Compiled ${result.locale}: ${result.stats.compiled} messages → ${outPath}`)\n }\n }\n\n // onAfterCompile hooks after parallel results\n for (const locale of localesToCompile) {\n for (const plugin of plugins) {\n await plugin.onAfterCompile?.(\n buildCompileContext(locale, transformedCatalogs[locale]!, config.compileOutDir, config),\n )\n }\n }\n } else {\n // Serial compilation\n const plugins = config.plugins ?? []\n\n for (const locale of localesToCompile) {\n const outPath = resolve(config.compileOutDir, `${locale}.js`)\n\n // onBeforeCompile hooks\n for (const plugin of plugins) {\n await plugin.onBeforeCompile?.(\n buildCompileContext(locale, allCatalogs[locale]!, config.compileOutDir, config),\n )\n }\n\n // transformMessages hooks\n const catalogForLocale = plugins.length > 0\n ? await applyTransformMessages(allCatalogs[locale]!, locale, plugins)\n : allCatalogs[locale]!\n\n const { code, stats } = compileCatalog(\n catalogForLocale,\n locale,\n allIds,\n config.sourceLocale,\n { skipFuzzy },\n )\n writeFileSync(outPath, code, 'utf-8')\n\n if (cache) {\n cache.set(locale, catalogContents[locale]!)\n }\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 // onAfterCompile hooks\n for (const plugin of plugins) {\n await plugin.onAfterCompile?.(\n buildCompileContext(locale, catalogForLocale, config.compileOutDir, config),\n )\n }\n }\n }\n\n if (skipped > 0) {\n consola.info(`${skipped} locale(s) unchanged — skipped`)\n }\n\n if (cache) {\n cache.save()\n }\n\n // Generate index.js and types when any locale changed or outputs don't exist\n const indexPath = resolve(config.compileOutDir, 'index.js')\n const typesPath = resolve(config.compileOutDir, 'messages.d.ts')\n\n if (needsRegenIndex || !existsSync(indexPath)) {\n const indexCode = compileIndex(localeCodes, config.compileOutDir)\n writeFileSync(indexPath, indexCode, 'utf-8')\n consola.success(`Generated index → ${indexPath}`)\n }\n\n if (needsRegenIndex || !existsSync(typesPath)) {\n const typesCode = compileTypeDeclaration(allIds, allCatalogs, config.sourceLocale)\n writeFileSync(typesPath, typesCode, 'utf-8')\n consola.success(`Generated types → ${typesPath}`)\n }\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 localeCodes = resolveLocaleCodes(config.locales)\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 localeCodes) {\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 lint = defineCommand({\n meta: { name: 'lint', description: 'Check translation quality (missing, inconsistent placeholders, fuzzy)' },\n args: {\n config: { type: 'string', description: 'Path to config file' },\n strict: { type: 'boolean', description: 'Treat warnings as errors', default: false },\n locale: { type: 'string', description: 'Lint a specific locale only' },\n },\n async run({ args }) {\n const config = await loadConfig(args.config)\n const localeCodes = resolveLocaleCodes(config.locales)\n const ext = config.format === 'json' ? '.json' : '.po'\n\n const allCatalogs: Record<string, CatalogData> = {}\n for (const locale of localeCodes) {\n const catalogPath = resolve(config.catalogDir, `${locale}${ext}`)\n allCatalogs[locale] = readCatalog(catalogPath, config.format)\n }\n\n const targetLocales = args.locale\n ? [args.locale]\n : undefined\n\n consola.info(`Linting ${targetLocales ? targetLocales.join(', ') : 'all locales'} (source: ${config.sourceLocale})`)\n\n const lintOpts: Parameters<typeof lintCatalogs>[1] = {\n sourceLocale: config.sourceLocale,\n strict: args.strict ?? false,\n }\n if (targetLocales) lintOpts.locales = targetLocales\n const diagnostics = lintCatalogs(allCatalogs, lintOpts)\n\n consola.log('')\n consola.log(formatDiagnostics(diagnostics))\n consola.log('')\n\n const errors = diagnostics.filter((d) => d.severity === 'error')\n const warnings = diagnostics.filter((d) => d.severity === 'warning')\n\n if (errors.length > 0) {\n process.exitCode = 1\n } else if (args.strict && warnings.length > 0) {\n process.exitCode = 1\n }\n },\n})\n\nconst check = defineCommand({\n meta: { name: 'check', description: 'Check translation coverage for CI' },\n args: {\n config: { type: 'string', description: 'Path to config file' },\n ci: { type: 'boolean', description: 'Alias for --format github', default: false },\n 'min-coverage': { type: 'string', description: 'Minimum coverage percentage (0-100)', default: '100' },\n format: { type: 'string', description: 'Output format: text | json | github' },\n locale: { type: 'string', description: 'Check a specific locale only' },\n },\n async run({ args }) {\n const config = await loadConfig(args.config)\n const localeCodes = resolveLocaleCodes(config.locales)\n const ext = config.format === 'json' ? '.json' : '.po'\n\n const allCatalogs: Record<string, CatalogData> = {}\n for (const locale of localeCodes) {\n const catalogPath = resolve(config.catalogDir, `${locale}${ext}`)\n allCatalogs[locale] = readCatalog(catalogPath, config.format)\n }\n\n const minCoverage = parseFloat(args['min-coverage'] ?? '100')\n if (isNaN(minCoverage) || minCoverage < 0 || minCoverage > 100) {\n consola.error('Invalid --min-coverage. Must be a number between 0 and 100.')\n process.exitCode = 1\n return\n }\n\n const outputFormat = args.format ?? (args.ci ? 'github' : 'text')\n\n const checkOpts: Parameters<typeof checkCoverage>[1] = {\n sourceLocale: config.sourceLocale,\n minCoverage,\n format: outputFormat as 'text' | 'json' | 'github',\n }\n if (args.locale) checkOpts.locale = args.locale\n\n const output = checkCoverage(allCatalogs, checkOpts)\n\n switch (outputFormat) {\n case 'json':\n consola.log(formatCheckJson(output))\n break\n case 'github':\n consola.log(formatCheckGitHub(output, config.catalogDir, config.format))\n break\n default:\n consola.log('')\n consola.log(formatCheckText(output))\n consola.log('')\n break\n }\n\n if (!output.passed) {\n process.exitCode = 1\n }\n },\n})\n\nasync function mapConcurrent<T, R>(\n items: readonly T[],\n fn: (item: T) => Promise<R>,\n concurrency: number,\n): Promise<R[]> {\n const results: R[] = new Array(items.length)\n let index = 0\n const workers = Array.from(\n { length: Math.min(concurrency, items.length) },\n async () => {\n while (index < items.length) {\n const i = index++\n results[i] = await fn(items[i]!)\n }\n },\n )\n await Promise.all(workers)\n return results\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 'glossary': { type: 'string', description: 'Path to glossary JSON file' },\n 'concurrency': { type: 'string', description: 'Max parallel locale translations (default: 3)' },\n 'timeout': { type: 'string', description: 'AI call timeout in seconds (default: 120)' },\n },\n async run({ args }) {\n const config = await loadConfig(args.config)\n const localeCodes = resolveLocaleCodes(config.locales)\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 glossary = args.glossary ? loadGlossary(resolve(args.glossary)) : undefined\n\n const concurrency = args.concurrency ? parseInt(args.concurrency, 10) : 3\n if (isNaN(concurrency) || concurrency < 1) {\n consola.error('Invalid concurrency. Must be a positive integer.')\n return\n }\n\n const timeoutSec = args.timeout ? parseInt(args.timeout, 10) : 120\n if (isNaN(timeoutSec) || timeoutSec < 1) {\n consola.error('Invalid timeout. Must be a positive integer (seconds).')\n return\n }\n const timeoutMs = timeoutSec * 1000\n\n const targetLocales = args.locale\n ? [args.locale]\n : localeCodes.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 await mapConcurrent(targetLocales, async (locale) => {\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 return\n }\n\n const terms = glossary ? getGlossaryForLocale(glossary, locale) : undefined\n const { catalog: updated, translated, warnings } = await translateCatalog({\n provider,\n sourceLocale: config.sourceLocale,\n targetLocale: locale,\n catalog,\n batchSize,\n glossary: terms,\n timeoutMs,\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 if (warnings.length > 0) {\n consola.warn(` ${locale}: ${warnings.length} QA warnings`)\n }\n }, concurrency)\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 doctor = defineCommand({\n meta: { name: 'doctor', description: 'Diagnose Fluenti setup and vNext import issues' },\n args: {\n config: { type: 'string', description: 'Optional path to fluenti config file' },\n strict: { type: 'boolean', description: 'Treat warnings as errors', default: false },\n },\n async run({ args }) {\n const report = await runDoctor({ cwd: process.cwd(), ...(args.config ? { config: args.config } : {}) })\n const output = formatDoctorReport(report)\n consola.log(output)\n const hasError = report.findings.some((finding) => finding.severity === 'error')\n const hasWarning = report.findings.some((finding) => finding.severity === 'warning')\n if (hasError || ((args.strict ?? false) && hasWarning)) {\n process.exitCode = 1\n }\n },\n})\n\nconst codemod = defineCommand({\n meta: { name: 'codemod', description: 'Rewrite imports to the vNext Fluenti entry layout' },\n args: {\n write: { type: 'boolean', description: 'Write modified files to disk', default: false },\n include: { type: 'string', description: 'Comma-separated glob list to scan' },\n },\n async run({ args }) {\n const include = args.include\n ? args.include.split(',').map((entry) => entry.trim()).filter(Boolean)\n : undefined\n const result = await runCodemod({\n cwd: process.cwd(),\n ...(include ? { include } : {}),\n write: args.write ?? false,\n })\n\n if (result.changedCount === 0) {\n consola.success('No files need import rewrites.')\n return\n }\n\n for (const file of result.changedFiles) {\n consola.log(`${args.write ? 'updated' : 'would update'} ${file.file}`)\n }\n consola.success(`${args.write ? 'Updated' : 'Detected'} ${result.changedCount} file(s) with Fluenti import rewrites.`)\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, doctor, codemod, extract, compile, stats, lint, check, translate, migrate },\n})\n\nrunMain(main)\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"compile-C3VLvhUf.cjs","names":[],"sources":["../src/compile.ts"],"sourcesContent":["import type { CatalogData } from './catalog'\nimport { hashMessage, parse } from '@fluenti/core/compiler'\nimport type { ASTNode, PluralNode, SelectNode, VariableNode, FunctionNode } from '@fluenti/core/compiler'\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 valid identifiers, `v['name']` for others */\nfunction propAccess(obj: string, name: string): string {\n // Names starting with a digit are not valid JS identifiers; use quoted bracket notation.\n // e.g. {0} → v['0'], {1st} → v['1st']. Pure integers (v['0']) work the same as v[0].\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\n/**\n * Convert a simple ICU message (only `{varName}` placeholders) into a JS template literal body.\n * Static segments are escaped independently so literal `${`, backticks, etc. are preserved\n * without interfering with the ICU variable interpolations that are inserted afterwards.\n */\nfunction messageToTemplateString(message: string): string {\n ICU_VAR_REGEX.lastIndex = 0\n const parts: string[] = []\n let lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = ICU_VAR_REGEX.exec(message)) !== null) {\n parts.push(escapeTemplateLiteral(message.slice(lastIndex, m.index)))\n parts.push(`\\${${propAccess('v', m[1]!)}}`)\n lastIndex = ICU_VAR_REGEX.lastIndex\n }\n parts.push(escapeTemplateLiteral(message.slice(lastIndex)))\n return parts.join('')\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 === '${escapeStringLiteral(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 lines.push(`// @ts-nocheck`)\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 let ast\n try {\n ast = parse(translated)\n } catch (err) {\n console.warn(\n `[fluenti] Skipping malformed ICU translation for \"${id}\" (${locale}): ${(err as Error).message}`,\n )\n lines.push(`export const ${exportName} = undefined`)\n missing.push(id)\n exportNames.push({ id, exportName })\n continue\n }\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(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 lines.push('export type { LocalizedString } from \\'@fluenti/core\\'')\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 // Module augmentation: auto-wire MessageId and MessageValues into CompileTimeT\n // Locale union from catalog keys\n const localeKeys = Object.keys(catalogs).map((l) => `'${escapeStringLiteral(l)}'`).join(' | ')\n\n lines.push('// Auto-wiring: narrows t() and setLocale() to compiled types')\n lines.push(\"declare module '@fluenti/core' {\")\n lines.push(' interface FluentiTypeConfig {')\n lines.push(` locale: ${localeKeys || 'string'}`)\n lines.push(' messageIds: MessageId')\n lines.push(' messageValues: MessageValues')\n lines.push(' }')\n lines.push('}')\n lines.push('')\n\n return lines.join('\\n')\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"compile-CZVpE5Md.js","names":[],"sources":["../src/compile.ts"],"sourcesContent":["import type { CatalogData } from './catalog'\nimport { hashMessage, parse } from '@fluenti/core/compiler'\nimport type { ASTNode, PluralNode, SelectNode, VariableNode, FunctionNode } from '@fluenti/core/compiler'\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 valid identifiers, `v['name']` for others */\nfunction propAccess(obj: string, name: string): string {\n // Names starting with a digit are not valid JS identifiers; use quoted bracket notation.\n // e.g. {0} → v['0'], {1st} → v['1st']. Pure integers (v['0']) work the same as v[0].\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\n/**\n * Convert a simple ICU message (only `{varName}` placeholders) into a JS template literal body.\n * Static segments are escaped independently so literal `${`, backticks, etc. are preserved\n * without interfering with the ICU variable interpolations that are inserted afterwards.\n */\nfunction messageToTemplateString(message: string): string {\n ICU_VAR_REGEX.lastIndex = 0\n const parts: string[] = []\n let lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = ICU_VAR_REGEX.exec(message)) !== null) {\n parts.push(escapeTemplateLiteral(message.slice(lastIndex, m.index)))\n parts.push(`\\${${propAccess('v', m[1]!)}}`)\n lastIndex = ICU_VAR_REGEX.lastIndex\n }\n parts.push(escapeTemplateLiteral(message.slice(lastIndex)))\n return parts.join('')\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 === '${escapeStringLiteral(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 lines.push(`// @ts-nocheck`)\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 let ast\n try {\n ast = parse(translated)\n } catch (err) {\n console.warn(\n `[fluenti] Skipping malformed ICU translation for \"${id}\" (${locale}): ${(err as Error).message}`,\n )\n lines.push(`export const ${exportName} = undefined`)\n missing.push(id)\n exportNames.push({ id, exportName })\n continue\n }\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(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 lines.push('export type { LocalizedString } from \\'@fluenti/core\\'')\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 // Module augmentation: auto-wire MessageId and MessageValues into CompileTimeT\n // Locale union from catalog keys\n const localeKeys = Object.keys(catalogs).map((l) => `'${escapeStringLiteral(l)}'`).join(' | ')\n\n lines.push('// Auto-wiring: narrows t() and setLocale() to compiled types')\n lines.push(\"declare module '@fluenti/core' {\")\n lines.push(' interface FluentiTypeConfig {')\n lines.push(` locale: ${localeKeys || 'string'}`)\n lines.push(' messageIds: MessageId')\n lines.push(' messageValues: MessageValues')\n lines.push(' }')\n lines.push('}')\n lines.push('')\n\n return lines.join('\\n')\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"compile-worker.cjs","names":[],"sources":["../src/compile-worker.ts"],"sourcesContent":["import { parentPort } from 'node:worker_threads'\nimport { compileCatalog } from './compile'\nimport type { CatalogData } from './catalog'\nimport type { CompileOptions } from './compile'\n\nexport interface CompileWorkerRequest {\n locale: string\n catalog: CatalogData\n allIds: string[]\n sourceLocale: string\n options?: CompileOptions | undefined\n}\n\nexport interface CompileWorkerResponse {\n locale: string\n code: string\n stats: { compiled: number; missing: string[] }\n error?: string\n}\n\nparentPort!.on('message', (req: CompileWorkerRequest) => {\n try {\n const { code, stats } = compileCatalog(req.catalog, req.locale, req.allIds, req.sourceLocale, req.options)\n parentPort!.postMessage({ locale: req.locale, code, stats } satisfies CompileWorkerResponse)\n } catch (err) {\n parentPort!.postMessage({\n locale: req.locale,\n code: '',\n stats: { compiled: 0, missing: [] },\n error: err instanceof Error ? err.message : String(err),\n } satisfies CompileWorkerResponse)\n }\n})\n"],"mappings":"gHAoBA,EAAA,WAAY,GAAG,UAAY,GAA8B,CACvD,GAAI,CACF,GAAM,CAAE,OAAM,SAAU,EAAA,EAAe,EAAI,QAAS,EAAI,OAAQ,EAAI,OAAQ,EAAI,aAAc,EAAI,QAAQ,CAC1G,EAAA,WAAY,YAAY,CAAE,OAAQ,EAAI,OAAQ,OAAM,QAAO,CAAiC,OACrF,EAAK,CACZ,EAAA,WAAY,YAAY,CACtB,OAAQ,EAAI,OACZ,KAAM,GACN,MAAO,CAAE,SAAU,EAAG,QAAS,EAAE,CAAE,CACnC,MAAO,aAAe,MAAQ,EAAI,QAAU,OAAO,EAAI,CACxD,CAAiC,GAEpC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"compile-worker.js","names":[],"sources":["../src/compile-worker.ts"],"sourcesContent":["import { parentPort } from 'node:worker_threads'\nimport { compileCatalog } from './compile'\nimport type { CatalogData } from './catalog'\nimport type { CompileOptions } from './compile'\n\nexport interface CompileWorkerRequest {\n locale: string\n catalog: CatalogData\n allIds: string[]\n sourceLocale: string\n options?: CompileOptions | undefined\n}\n\nexport interface CompileWorkerResponse {\n locale: string\n code: string\n stats: { compiled: number; missing: string[] }\n error?: string\n}\n\nparentPort!.on('message', (req: CompileWorkerRequest) => {\n try {\n const { code, stats } = compileCatalog(req.catalog, req.locale, req.allIds, req.sourceLocale, req.options)\n parentPort!.postMessage({ locale: req.locale, code, stats } satisfies CompileWorkerResponse)\n } catch (err) {\n parentPort!.postMessage({\n locale: req.locale,\n code: '',\n stats: { compiled: 0, missing: [] },\n error: err instanceof Error ? err.message : String(err),\n } satisfies CompileWorkerResponse)\n }\n})\n"],"mappings":";;;AAoBA,EAAY,GAAG,YAAY,MAA8B;AACvD,KAAI;EACF,IAAM,EAAE,SAAM,aAAU,EAAe,EAAI,SAAS,EAAI,QAAQ,EAAI,QAAQ,EAAI,cAAc,EAAI,QAAQ;AAC1G,IAAY,YAAY;GAAE,QAAQ,EAAI;GAAQ;GAAM;GAAO,CAAiC;UACrF,GAAK;AACZ,IAAY,YAAY;GACtB,QAAQ,EAAI;GACZ,MAAM;GACN,OAAO;IAAE,UAAU;IAAG,SAAS,EAAE;IAAE;GACnC,OAAO,aAAe,QAAQ,EAAI,UAAU,OAAO,EAAI;GACxD,CAAiC;;EAEpC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"doctor-BqXXxyST.js","names":[],"sources":["../src/catalog.ts","../src/json-format.ts","../src/po-format.ts","../src/parallel-compile.ts","../src/extract-cache.ts","../src/codemod.ts","../src/init.ts","../src/doctor.ts"],"sourcesContent":["import type { ExtractedMessage } from '@fluenti/core/compiler'\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/compiler'\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 obsolete?: Record<string, Record<string, POTranslation>>\n}\n\ninterface ParsedExtractedComment {\n comment?: string\n customId?: string\n sourceMessage?: string\n}\n\nfunction processPoEntries(\n contextMap: Record<string, Record<string, POTranslation>>,\n catalog: CatalogData,\n isObsolete: boolean,\n): void {\n for (const [contextKey, entries] of Object.entries(contextMap)) {\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 = !isObsolete && (entry.comments?.flag?.includes('fuzzy') ?? false)\n const { comment, customId, sourceMessage } = parseExtractedComment(entry.comments?.extracted)\n // When a custom ID is present, the msgid may be the custom key (not source text),\n // so accept sourceMessage directly without requiring a hash match.\n // For hash-ID entries (no customId), verify the hash to prevent double-hashing.\n const resolvedSourceMessage = sourceMessage\n && (customId !== undefined || 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 ...(isObsolete ? { obsolete: true } : {}),\n }\n }\n }\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\n processPoEntries(po.translations ?? {}, catalog, false)\n if (po.obsolete) {\n processPoEntries(po.obsolete, catalog, true)\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 const obsolete: POData['obsolete'] = {}\n\n for (const [id, entry] of Object.entries(catalog)) {\n // Use custom ID as msgid for non-hash entries to prevent collision when two\n // entries share the same source message but have different custom IDs.\n // Hash-ID entries keep source message as msgid (PO-friendly for translators).\n const isHashId = entry.message !== undefined && hashMessage(entry.message, entry.context) === id\n const msgid = isHashId ? (entry.message ?? id) : id\n const poEntry: POTranslation = {\n msgid,\n ...(entry.context !== undefined ? { msgctxt: entry.context } : {}),\n msgstr: [entry.translation ?? ''],\n }\n\n const comments: POTranslation['comments'] = {}\n if (entry.origin && !entry.obsolete) {\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 && !entry.obsolete) {\n comments.flag = 'fuzzy'\n }\n if (comments.reference || comments.extracted || comments.flag) {\n poEntry.comments = comments\n }\n\n if (entry.obsolete) {\n const contextKey = entry.context ?? ''\n obsolete[contextKey] ??= {}\n obsolete[contextKey]![poEntry.msgid] = poEntry\n } else {\n const contextKey = entry.context ?? ''\n translations[contextKey] ??= {}\n translations[contextKey]![poEntry.msgid] = poEntry\n }\n }\n\n const poData: POData = {\n headers: {\n 'Content-Type': 'text/plain; charset=UTF-8',\n },\n translations,\n ...(Object.keys(obsolete).length > 0 ? { obsolete } : {}),\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 // Preserve source message so round-trip works when msgid is the custom ID (not source text)\n lines.push(`msg\\`${message}\\``)\n }\n\n return lines.length > 0 ? lines.join('\\n') : undefined\n}\n","import { Worker } from 'node:worker_threads'\nimport { availableParallelism } from 'node:os'\nimport { existsSync } from 'node:fs'\nimport { resolve, dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { CatalogData } from './catalog'\nimport type { CompileOptions, CompileStats } from './compile'\nimport type { CompileWorkerRequest, CompileWorkerResponse } from './compile-worker'\n\nexport interface ParallelCompileTask {\n locale: string\n catalog: CatalogData\n allIds: string[]\n sourceLocale: string\n options?: CompileOptions\n}\n\nexport interface ParallelCompileResult {\n locale: string\n code: string\n stats: CompileStats\n}\n\n/** Default timeout per worker in milliseconds (30 seconds) */\nconst WORKER_TIMEOUT_MS = 30_000\n\nfunction getWorkerPath(): string {\n const thisDir = typeof __dirname !== 'undefined'\n ? __dirname\n : dirname(fileURLToPath(import.meta.url))\n return resolve(thisDir, 'compile-worker.js')\n}\n\n/**\n * Compile multiple locales in parallel using worker threads.\n *\n * - Single task → returns result directly (no worker overhead)\n * - Multiple tasks → spawns min(tasks, availableParallelism()) workers\n * - Workers are created per-call and destroyed after completion\n *\n * Falls back to in-process compilation when the compiled worker is unavailable\n * (e.g. when running from TypeScript source in development/tests).\n */\nexport async function parallelCompile(\n tasks: ParallelCompileTask[],\n concurrency?: number,\n): Promise<ParallelCompileResult[]> {\n if (tasks.length === 0) return []\n\n // Single task: compile in-process, no worker overhead\n if (tasks.length === 1) {\n const { compileCatalog } = await import('./compile')\n const task = tasks[0]!\n const { code, stats } = compileCatalog(task.catalog, task.locale, task.allIds, task.sourceLocale, task.options)\n return [{ locale: task.locale, code, stats }]\n }\n\n const workerPath = getWorkerPath()\n\n // If compiled worker doesn't exist (dev/test), fall back to concurrent in-process compilation\n if (!existsSync(workerPath)) {\n return inProcessParallelCompile(tasks)\n }\n\n const maxWorkers = concurrency ?? Math.min(tasks.length, availableParallelism())\n const results: ParallelCompileResult[] = []\n const queue = [...tasks]\n let rejected = false\n\n return new Promise<ParallelCompileResult[]>((resolveAll, rejectAll) => {\n let activeWorkers = 0\n\n function spawnNext(): void {\n if (rejected) return\n const task = queue.shift()\n if (!task) {\n if (activeWorkers === 0) {\n resolveAll(results)\n }\n return\n }\n\n activeWorkers++\n const worker = new Worker(workerPath)\n\n const timer = setTimeout(() => {\n if (!rejected) {\n rejected = true\n activeWorkers--\n worker.terminate()\n rejectAll(new Error(`Worker timed out after ${WORKER_TIMEOUT_MS}ms compiling locale \"${task.locale}\"`))\n }\n }, WORKER_TIMEOUT_MS)\n\n worker.on('message', (response: CompileWorkerResponse) => {\n clearTimeout(timer)\n if (rejected) return\n if (response.error) {\n rejected = true\n activeWorkers--\n worker.terminate()\n rejectAll(new Error(`Failed to compile locale \"${response.locale}\": ${response.error}`))\n return\n }\n results.push({\n locale: response.locale,\n code: response.code,\n stats: response.stats,\n })\n activeWorkers--\n worker.terminate()\n spawnNext()\n })\n\n worker.on('error', (err: Error) => {\n clearTimeout(timer)\n if (!rejected) {\n rejected = true\n worker.terminate()\n rejectAll(new Error(`Worker error compiling locale \"${task.locale}\": ${err.message}`))\n }\n })\n\n worker.on('exit', (code) => {\n clearTimeout(timer)\n if (code !== 0 && !rejected) {\n rejected = true\n rejectAll(new Error(`Worker exited with code ${code} while compiling locale \"${task.locale}\"`))\n }\n })\n\n const request: CompileWorkerRequest = {\n locale: task.locale,\n catalog: task.catalog,\n allIds: task.allIds,\n sourceLocale: task.sourceLocale,\n options: task.options,\n }\n worker.postMessage(request)\n }\n\n // Start initial batch of workers\n const initialBatch = Math.min(maxWorkers, queue.length)\n for (let i = 0; i < initialBatch; i++) {\n spawnNext()\n }\n })\n}\n\n/**\n * In-process fallback for parallel compilation.\n * Uses Promise.all for concurrency when workers are unavailable.\n */\nasync function inProcessParallelCompile(\n tasks: ParallelCompileTask[],\n): Promise<ParallelCompileResult[]> {\n const { compileCatalog } = await import('./compile')\n return Promise.all(\n tasks.map((task) => {\n const { code, stats } = compileCatalog(task.catalog, task.locale, task.allIds, task.sourceLocale, task.options)\n return { locale: task.locale, code, stats }\n }),\n )\n}\n","import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport type { ExtractedMessage } from '@fluenti/core/compiler'\n\n/** Cache format version — bump when the structure changes */\nconst CACHE_VERSION = '1'\n\ninterface ExtractCacheEntry {\n mtime: number\n size: number\n messages: ExtractedMessage[]\n}\n\ninterface ExtractCacheData {\n version: string\n entries: Record<string, ExtractCacheEntry>\n}\n\n/**\n * File-level extract cache that skips re-extraction for unchanged files.\n *\n * Cache is keyed by file path, with mtime + size as change detection.\n */\nexport class ExtractCache {\n private data: ExtractCacheData\n private cachePath: string\n private dirty = false\n\n constructor(catalogDir: string, projectId?: string) {\n const cacheDir = projectId\n ? resolve(catalogDir, '.cache', projectId)\n : resolve(catalogDir, '.cache')\n this.cachePath = resolve(cacheDir, 'extract-cache.json')\n this.data = this.load()\n }\n\n /**\n * Check if a file has changed since the last extraction.\n * Returns cached messages if unchanged, undefined if re-extraction needed.\n */\n get(filePath: string): ExtractedMessage[] | undefined {\n const entry = this.data.entries[filePath]\n if (!entry) return undefined\n\n try {\n const stat = statSync(filePath)\n if (stat.mtimeMs === entry.mtime && stat.size === entry.size) {\n return entry.messages\n }\n } catch {\n // File no longer exists or can't be stat'd — cache miss\n }\n\n return undefined\n }\n\n /**\n * Update the cache for a file after extraction.\n */\n set(filePath: string, messages: ExtractedMessage[]): void {\n try {\n const stat = statSync(filePath)\n this.data.entries[filePath] = {\n mtime: stat.mtimeMs,\n size: stat.size,\n messages,\n }\n this.dirty = true\n } catch {\n // File doesn't exist — skip caching\n }\n }\n\n /**\n * Remove entries for files that no longer exist in the file list.\n */\n prune(currentFiles: Set<string>): void {\n for (const filePath of Object.keys(this.data.entries)) {\n if (!currentFiles.has(filePath)) {\n delete this.data.entries[filePath]\n this.dirty = true\n }\n }\n }\n\n /**\n * Write the cache to disk if any changes were made.\n */\n save(): void {\n if (!this.dirty) return\n\n try {\n mkdirSync(dirname(this.cachePath), { recursive: true })\n writeFileSync(this.cachePath, JSON.stringify(this.data), 'utf-8')\n this.dirty = false\n } catch {\n // Cache write failure is non-fatal — next run will re-extract\n }\n }\n\n /** Number of cached entries */\n get size(): number {\n return Object.keys(this.data.entries).length\n }\n\n private load(): ExtractCacheData {\n try {\n if (existsSync(this.cachePath)) {\n const raw = readFileSync(this.cachePath, 'utf-8')\n const parsed = JSON.parse(raw) as ExtractCacheData\n if (parsed.version === CACHE_VERSION) {\n return parsed\n }\n }\n } catch {\n // Corrupt or unreadable cache — start fresh\n }\n\n return { version: CACHE_VERSION, entries: {} }\n }\n}\n","import { readFileSync, writeFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport fg from 'fast-glob'\n\nconst FRAMEWORKS = ['react', 'vue', 'solid'] as const\nconst COMPONENT_EXPORTS = new Set(['Trans', 'Plural', 'Select', 'DateTime', 'NumberFormat'])\n\ntype FrameworkName = (typeof FRAMEWORKS)[number]\n\nexport interface CodemodOptions {\n cwd: string\n include?: string[]\n write?: boolean\n}\n\nexport interface CodemodFileResult {\n file: string\n changed: boolean\n}\n\nexport interface CodemodResult {\n changedFiles: CodemodFileResult[]\n changedCount: number\n}\n\ninterface ParsedSpecifier {\n imported: string\n local: string\n isType: boolean\n}\n\nconst DEFAULT_GLOBS = [\n 'src/**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs,vue,mdx}',\n 'app/**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs,vue,mdx}',\n 'pages/**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs,vue,mdx}',\n 'components/**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs,vue,mdx}',\n 'layouts/**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs,vue,mdx}',\n]\n\nfunction parseSpecifier(raw: string): ParsedSpecifier {\n const trimmed = raw.trim()\n const isType = trimmed.startsWith('type ')\n const normalized = isType ? trimmed.slice(5).trim() : trimmed\n const parts = normalized.split(/\\s+as\\s+/)\n const imported = parts[0]!.trim()\n const local = (parts[1] ?? parts[0])!.trim()\n return {\n imported,\n local,\n isType,\n }\n}\n\nfunction formatSpecifier(specifier: ParsedSpecifier): string {\n const prefix = specifier.isType ? 'type ' : ''\n if (specifier.imported === specifier.local) {\n return `${prefix}${specifier.imported}`\n }\n return `${prefix}${specifier.imported} as ${specifier.local}`\n}\n\nfunction dedupeSpecifiers(specifiers: ParsedSpecifier[]): ParsedSpecifier[] {\n const seen = new Set<string>()\n const result: ParsedSpecifier[] = []\n for (const specifier of specifiers) {\n const key = `${specifier.isType ? 'type:' : 'value:'}${specifier.imported}:${specifier.local}`\n if (seen.has(key)) continue\n seen.add(key)\n result.push(specifier)\n }\n return result\n}\n\nfunction mergeNamedImports(code: string, source: string): string {\n const pattern = new RegExp(`import\\\\s*\\\\{([\\\\s\\\\S]*?)\\\\}\\\\s*from\\\\s*['\"]${source.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}['\"];?\\\\n?`, 'g')\n const matches = [...code.matchAll(pattern)]\n if (matches.length <= 1) return code\n\n const specifiers = dedupeSpecifiers(\n matches.flatMap((match) => match[1]!.split(',').map((entry) => parseSpecifier(entry)).filter((entry) => entry.imported.length > 0)),\n )\n const mergedImport = `import { ${specifiers.map(formatSpecifier).join(', ')} } from '${source}'\\n`\n\n let first = true\n return code.replace(pattern, () => {\n if (first) {\n first = false\n return mergedImport\n }\n return ''\n })\n}\n\nexport function rewriteFluentiImports(source: string): { code: string; changed: boolean } {\n let code = source\n let changed = false\n let renameSolidFactory = false\n\n const importPattern = /import\\s*\\{([\\s\\S]*?)\\}\\s*from\\s*['\"](@fluenti\\/(react|vue|solid)(?:\\/components)?)['\"];?/g\n\n code = code.replace(importPattern, (full, specifiersRaw: string, sourcePath: string, framework: FrameworkName) => {\n const specifiers = specifiersRaw\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean)\n .map(parseSpecifier)\n\n const main: ParsedSpecifier[] = []\n const components: ParsedSpecifier[] = []\n const runtime: ParsedSpecifier[] = []\n\n for (const specifier of specifiers) {\n const next = { ...specifier }\n if (framework === 'solid' && next.imported === 'createFluentiContext') {\n next.imported = 'createFluenti'\n if (next.local === 'createFluentiContext') {\n next.local = 'createFluenti'\n renameSolidFactory = true\n }\n }\n\n if (next.imported === 'interpolate') {\n runtime.push({ ...next, isType: false })\n continue\n }\n\n if (!next.isType && COMPONENT_EXPORTS.has(next.imported) && sourcePath.endsWith('/components')) {\n components.push(next)\n continue\n }\n\n main.push(next)\n }\n\n const nextImports: string[] = []\n if (main.length > 0 && !sourcePath.endsWith('/components')) {\n nextImports.push(`import { ${dedupeSpecifiers(main).map(formatSpecifier).join(', ')} } from '@fluenti/${framework}'`)\n }\n if (components.length > 0) {\n nextImports.push(`import { ${dedupeSpecifiers(components).map(formatSpecifier).join(', ')} } from '@fluenti/${framework}/components'`)\n }\n if (runtime.length > 0) {\n nextImports.push(`import { ${dedupeSpecifiers(runtime).map(formatSpecifier).join(', ')} } from '@fluenti/core/runtime'`)\n }\n if (main.length > 0 && sourcePath.endsWith('/components')) {\n nextImports.unshift(`import { ${dedupeSpecifiers(main).map(formatSpecifier).join(', ')} } from '@fluenti/${framework}'`)\n }\n\n const replacement = nextImports.join('\\n')\n if (replacement !== full) {\n changed = true\n }\n return replacement\n })\n\n if (renameSolidFactory) {\n const renamed = code.replace(/\\bcreateFluentiContext\\b/g, 'createFluenti')\n if (renamed !== code) {\n code = renamed\n changed = true\n }\n }\n\n for (const sourcePath of [\n '@fluenti/react',\n '@fluenti/react/components',\n '@fluenti/vue',\n '@fluenti/vue/components',\n '@fluenti/solid',\n '@fluenti/solid/components',\n '@fluenti/core/runtime',\n ]) {\n const merged = mergeNamedImports(code, sourcePath)\n if (merged !== code) {\n code = merged\n changed = true\n }\n }\n\n return { code, changed }\n}\n\nexport async function runCodemod(options: CodemodOptions): Promise<CodemodResult> {\n const include = options.include && options.include.length > 0 ? options.include : DEFAULT_GLOBS\n const files = await fg(include, {\n cwd: options.cwd,\n absolute: false,\n ignore: ['**/node_modules/**', '**/dist/**', '**/.fluenti/**'],\n })\n\n const changedFiles: CodemodFileResult[] = []\n\n for (const file of files) {\n const path = resolve(options.cwd, file)\n const current = readFileSync(path, 'utf-8')\n const rewritten = rewriteFluentiImports(current)\n if (!rewritten.changed) continue\n\n if (options.write) {\n writeFileSync(path, rewritten.code, 'utf-8')\n }\n\n changedFiles.push({ file, changed: true })\n }\n\n return {\n changedFiles,\n changedCount: changedFiles.length,\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) => JSON.stringify(l)).join(', ')\n return `import { defineConfig } from '@fluenti/cli'\n\nexport default defineConfig({\n sourceLocale: ${JSON.stringify(opts.sourceLocale)},\n locales: [${localesList}],\n catalogDir: './locales',\n format: ${JSON.stringify(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","import { existsSync, readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport fg from 'fast-glob'\nimport { detectFramework, type DetectedFramework } from './init'\nimport { loadConfig } from './config-loader'\n\nexport type DoctorSeverity = 'error' | 'warning' | 'info'\n\nexport interface DoctorFinding {\n severity: DoctorSeverity\n code: string\n message: string\n}\n\nexport interface DoctorReport {\n framework: DetectedFramework['name']\n findings: DoctorFinding[]\n configPath?: string\n}\n\nexport interface DoctorOptions {\n cwd: string\n config?: string\n}\n\nconst SOURCE_GLOBS = [\n 'src/**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs,vue,mdx}',\n 'app/**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs,vue,mdx}',\n 'pages/**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs,vue,mdx}',\n 'components/**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs,vue,mdx}',\n 'layouts/**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs,vue,mdx}',\n]\n\nconst MAIN_INTERPOLATE_IMPORT_RE = /import\\s*\\{[\\s\\S]*?\\binterpolate\\b[\\s\\S]*?\\}\\s*from\\s*['\"]@fluenti\\/(react|vue|solid)(?:\\/components)?['\"]/g\nconst INTERNAL_IMPORT_RE = /from\\s*['\"]@fluenti\\/core\\/internal['\"]/g\n\nfunction readIfExists(path: string): string | undefined {\n return existsSync(path) ? readFileSync(path, 'utf-8') : undefined\n}\n\nfunction detectConfigPath(cwd: string, explicit?: string): string | undefined {\n if (explicit) return resolve(cwd, explicit)\n\n for (const candidate of ['fluenti.config.ts', 'fluenti.config.mts', 'fluenti.config.js', 'fluenti.config.mjs']) {\n const resolved = resolve(cwd, candidate)\n if (existsSync(resolved)) return resolved\n }\n\n return undefined\n}\n\nasync function collectSourceFiles(cwd: string): Promise<string[]> {\n return fg(SOURCE_GLOBS, {\n cwd,\n absolute: false,\n ignore: ['**/node_modules/**', '**/dist/**', '**/.fluenti/**'],\n })\n}\n\nfunction pushFinding(findings: DoctorFinding[], severity: DoctorSeverity, code: string, message: string): void {\n findings.push({ severity, code, message })\n}\n\nfunction getPackageJson(cwd: string): { dependencies?: Record<string, string>; devDependencies?: Record<string, string> } | undefined {\n const pkgPath = resolve(cwd, 'package.json')\n const content = readIfExists(pkgPath)\n if (!content) return undefined\n return JSON.parse(content) as { dependencies?: Record<string, string>; devDependencies?: Record<string, string> }\n}\n\nfunction hasVitePlugin(content: string | undefined): boolean {\n if (!content) return false\n return content.includes('@fluenti/') && content.includes('vite-plugin')\n}\n\nfunction countMatches(content: string, pattern: RegExp): number {\n return [...content.matchAll(pattern)].length\n}\n\nexport async function runDoctor(options: DoctorOptions): Promise<DoctorReport> {\n const findings: DoctorFinding[] = []\n const pkg = getPackageJson(options.cwd)\n\n if (!pkg) {\n return {\n framework: 'unknown',\n findings: [{ severity: 'error', code: 'missing-package-json', message: 'No package.json found in the current directory.' }],\n }\n }\n\n const framework = detectFramework({ ...pkg.dependencies, ...pkg.devDependencies })\n const configPath = detectConfigPath(options.cwd, options.config)\n const files = await collectSourceFiles(options.cwd)\n\n let mainInterpolateImports = 0\n let internalImports = 0\n let importsFluenti = false\n let usesCompileTimeT = false\n\n for (const file of files) {\n const content = readFileSync(resolve(options.cwd, file), 'utf-8')\n mainInterpolateImports += countMatches(content, MAIN_INTERPOLATE_IMPORT_RE)\n internalImports += countMatches(content, INTERNAL_IMPORT_RE)\n importsFluenti = importsFluenti || content.includes('@fluenti/')\n usesCompileTimeT = usesCompileTimeT || content.includes(\"from '@fluenti/\") && content.includes(' t`')\n }\n\n if (mainInterpolateImports > 0) {\n pushFinding(\n findings,\n 'warning',\n 'main-entry-interpolate',\n `Found ${mainInterpolateImports} import(s) of interpolate from framework packages. Import interpolate from @fluenti/core/runtime instead.`,\n )\n }\n\n if (internalImports > 0) {\n pushFinding(\n findings,\n 'warning',\n 'core-internal-imports',\n `Found ${internalImports} import(s) from @fluenti/core/internal. Use @fluenti/core/runtime or @fluenti/core/compiler.`,\n )\n }\n\n if (!configPath) {\n pushFinding(findings, 'warning', 'missing-config', 'No fluenti.config.* file found.')\n }\n\n if (framework.name === 'nextjs') {\n const nextConfig = ['next.config.ts', 'next.config.mjs', 'next.config.js']\n .map((name) => resolve(options.cwd, name))\n .find((candidate) => existsSync(candidate))\n const nextContent = nextConfig ? readIfExists(nextConfig) : undefined\n if (importsFluenti && !nextContent?.includes('withFluenti(')) {\n pushFinding(findings, 'error', 'missing-with-fluenti', 'Next.js project imports Fluenti but next.config does not call withFluenti().')\n }\n }\n\n if (framework.name === 'react' || framework.name === 'vue' || framework.name === 'solid') {\n const viteConfig = ['vite.config.ts', 'vite.config.mts', 'vite.config.js', 'vite.config.mjs']\n .map((name) => resolve(options.cwd, name))\n .find((candidate) => existsSync(candidate))\n const viteContent = viteConfig ? readIfExists(viteConfig) : undefined\n if ((importsFluenti || usesCompileTimeT) && !hasVitePlugin(viteContent)) {\n pushFinding(findings, 'warning', 'missing-vite-plugin', 'Project imports Fluenti but vite.config does not appear to include a Fluenti Vite plugin.')\n }\n }\n\n if (configPath && existsSync(configPath)) {\n try {\n const config = await loadConfig(configPath)\n const sourceCatalogExt = config.format === 'json' ? '.json' : '.po'\n const sourceCatalog = resolve(options.cwd, config.catalogDir, `${config.sourceLocale}${sourceCatalogExt}`)\n if (!existsSync(sourceCatalog)) {\n pushFinding(findings, 'warning', 'missing-source-catalog', `Source catalog not found: ${sourceCatalog}`)\n }\n\n const compiledIndex = resolve(options.cwd, config.compileOutDir, 'index.js')\n if (!existsSync(compiledIndex)) {\n pushFinding(findings, 'warning', 'missing-compiled-catalogs', `Compiled catalogs not found at ${compiledIndex}. Run \"fluenti compile\".`)\n }\n } catch (error) {\n pushFinding(\n findings,\n 'error',\n 'invalid-config',\n `Failed to load Fluenti config: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n }\n\n if (findings.length === 0) {\n pushFinding(findings, 'info', 'ok', 'No Fluenti migration or configuration issues detected.')\n }\n\n return {\n framework: framework.name,\n findings,\n ...(configPath ? { configPath } : {}),\n }\n}\n\nexport function formatDoctorReport(report: DoctorReport): string {\n const lines: string[] = [`Framework: ${report.framework}`]\n if (report.configPath) {\n lines.push(`Config: ${report.configPath}`)\n }\n lines.push('')\n\n for (const finding of report.findings) {\n const prefix = finding.severity === 'error'\n ? '✖'\n : finding.severity === 'warning'\n ? '⚠'\n : '•'\n lines.push(`${prefix} [${finding.code}] ${finding.message}`)\n }\n\n return lines.join('\\n')\n}\n"],"mappings":"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"}