@concircle/i18n-ai-translator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +475 -0
- package/bin/i18n-ai-translator.mjs +6 -0
- package/dist/cli.cjs +6 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +21 -0
- package/dist/cli.d.ts +21 -0
- package/dist/cli.mjs +6 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.cjs +6 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +149 -0
- package/dist/index.d.ts +149 -0
- package/dist/index.mjs +6 -0
- package/dist/index.mjs.map +1 -0
- package/dist/types-DF4QMkU1.d.cts +151 -0
- package/dist/types-DF4QMkU1.d.ts +151 -0
- package/package.json +76 -0
package/dist/cli.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/translator.ts","../src/cache/fileCache.ts","../src/glossary/glossaryManager.ts","../src/parsers/placeholderExtractor.ts","../src/parsers/propertiesParser.ts","../src/providers/openai/client.ts","../src/providers/base.ts","../src/utils/config.ts","../src/utils/logger.ts"],"sourcesContent":["import { translateProject } from './translator.js';\nimport { TranslationMode } from './types.js';\n\ninterface CliIO {\n log(message: string): void;\n error(message: string): void;\n}\n\nconst SUPPORTED_MODES: TranslationMode[] = ['missing', 'overwrite'];\nconst SUPPORTED_PROVIDERS = ['openai'] as const;\n\nexport async function runCli(\n argv: string[],\n io: CliIO = {\n log: console.log,\n error: console.error,\n }\n ): Promise<number> {\n let args;\n try {\n args = parseArgs(argv);\n } catch (error) {\n io.error(error instanceof Error ? error.message : String(error));\n io.log(getHelpText());\n return 1;\n }\n\n if (args.help || argv.length === 0) {\n io.log(getHelpText());\n return 0;\n }\n\n try {\n const result = await translateProject({\n configPath: args.config,\n inputPath: args.input,\n languages: args.languages,\n mode: args.mode,\n provider: args.provider as 'openai' | undefined,\n model: args.model,\n dryRun: args.dryRun,\n encodeUnicode: args.encodeUnicode,\n verbose: args.verbose,\n });\n\n io.log(JSON.stringify(result, null, 2));\n return 0;\n } catch (error) {\n io.error(error instanceof Error ? error.message : String(error));\n return 1;\n }\n}\n\nexport function parseArgs(argv: string[]): {\n config?: string;\n input?: string;\n languages?: string[];\n mode?: TranslationMode;\n provider?: string;\n model?: string;\n dryRun: boolean;\n encodeUnicode?: boolean;\n verbose: boolean;\n help: boolean;\n} {\n const result = {\n dryRun: false,\n verbose: false,\n help: false,\n } as {\n config?: string;\n input?: string;\n languages?: string[];\n mode?: TranslationMode;\n provider?: string;\n model?: string;\n dryRun: boolean;\n encodeUnicode?: boolean;\n verbose: boolean;\n help: boolean;\n };\n\n const tokens = [...argv];\n if (tokens[0] === 'help') {\n tokens.shift();\n result.help = true;\n }\n\n while (tokens.length > 0) {\n const token = tokens.shift()!;\n\n switch (token) {\n case '--config':\n result.config = readOptionValue(tokens, '--config');\n break;\n case '--input':\n result.input = readOptionValue(tokens, '--input');\n break;\n case '--languages':\n result.languages = readOptionValue(tokens, '--languages')\n .split(',')\n .map((value) => value.trim())\n .filter(Boolean);\n break;\n case '--mode': {\n const value = readOptionValue(tokens, '--mode');\n if (!SUPPORTED_MODES.includes(value as TranslationMode)) {\n throw new Error(\n `Unknown value \"${value}\" for option \"--mode\". Expected one of: ${SUPPORTED_MODES.join(', ')}.`\n );\n }\n result.mode = value as TranslationMode;\n break;\n }\n case '--provider': {\n const value = readOptionValue(tokens, '--provider');\n if (!SUPPORTED_PROVIDERS.includes(value as (typeof SUPPORTED_PROVIDERS)[number])) {\n throw new Error(\n `Unknown value \"${value}\" for option \"--provider\". Expected one of: ${SUPPORTED_PROVIDERS.join(', ')}.`\n );\n }\n result.provider = value;\n break;\n }\n case '--model':\n result.model = readOptionValue(tokens, '--model');\n break;\n case '--dry-run':\n result.dryRun = true;\n break;\n case '--encode-unicode':\n result.encodeUnicode = true;\n break;\n case '--verbose':\n result.verbose = true;\n break;\n case '--help':\n case '-h':\n result.help = true;\n break;\n default:\n if (token.startsWith('-')) {\n throw new Error(`Unknown option \"${token}\". Use --help to see supported options.`);\n }\n throw new Error(`Unknown argument: ${token}`);\n }\n }\n\n return result;\n}\n\nfunction readOptionValue(tokens: string[], option: string): string {\n const value = tokens.shift();\n if (!value || value.startsWith('-')) {\n throw new Error(`Missing value for option \"${option}\".`);\n }\n\n return value;\n}\n\nfunction getHelpText(): string {\n return [\n 'i18n-ai-translator [options]',\n '',\n 'Options:',\n ' --config <path> Path to i18n-ai config file',\n ' --input <path> Source i18n.properties file',\n ' --languages <list> Comma-separated target languages',\n ' --mode <mode> missing | overwrite',\n ' --provider <name> Reserved for future providers',\n ' --model <name> Override provider model',\n ' --dry-run Do not write files',\n ' --encode-unicode Write non-ASCII characters as \\\\uXXXX escapes',\n ' --verbose Enable verbose logs',\n ' --help Show this help text',\n ].join('\\n');\n}\n","import path from 'path';\nimport { FileCache } from './cache/fileCache.js';\nimport { GlossaryManager } from './glossary/glossaryManager.js';\nimport {\n maskPlaceholders,\n restorePlaceholders,\n validatePlaceholderIntegrity,\n} from './parsers/placeholderExtractor.js';\nimport {\n cloneDocument,\n createEmptyPropertiesDocument,\n getLanguageFilePath,\n listPropertiesEntries,\n readPropertiesDocument,\n upsertPropertiesValue,\n writePropertiesDocument,\n} from './parsers/propertiesParser.js';\nimport { OpenAIProvider } from './providers/openai/client.js';\nimport {\n AIProvider,\n GlossaryConfig,\n TranslationBatchResponseItem,\n TranslationFileOptions,\n TranslationProjectOptions,\n TranslationResult,\n TranslatorConfig,\n} from './types.js';\nimport { loadTranslatorConfig, resolveTranslationMode, validateConfig } from './utils/config.js';\nimport { ConsoleLogger, SilentLogger } from './utils/logger.js';\n\nexport class Translator {\n private readonly config: TranslatorConfig;\n private readonly provider: AIProvider;\n private readonly glossaryManager: GlossaryManager;\n private readonly cache: FileCache;\n private readonly logger: ConsoleLogger | SilentLogger;\n\n constructor(config: TranslatorConfig) {\n validateConfig(config);\n this.config = config;\n this.provider = createProvider(config);\n this.glossaryManager = new GlossaryManager(config.glossary as GlossaryConfig | undefined);\n this.cache = new FileCache(config.cache);\n this.logger = config.verbose\n ? new ConsoleLogger('@concircle/i18n-ai-translator', true)\n : new SilentLogger();\n }\n\n static async fromConfig(options?: {\n configPath?: string;\n cwd?: string;\n }): Promise<Translator> {\n const config = await loadTranslatorConfig(options);\n return new Translator(config);\n }\n\n async translateProject(options?: TranslationProjectOptions): Promise<TranslationResult> {\n const config =\n options?.configPath || options?.cwd\n ? await loadTranslatorConfig({\n configPath: options.configPath,\n cwd: options.cwd,\n overrides: {\n input: options.inputPath,\n targetLanguages: options.languages,\n translationMode: options.mode,\n encodeUnicode: options.encodeUnicode,\n provider: options.provider,\n model: options.model,\n verbose: options.verbose,\n },\n })\n : this.config;\n\n const translator =\n config === this.config ? this : new Translator(config);\n\n return translator.translateFile({\n inputPath: options?.inputPath,\n languages: options?.languages,\n mode: options?.mode,\n dryRun: options?.dryRun,\n });\n }\n\n async translateFile(options?: TranslationFileOptions): Promise<TranslationResult> {\n const inputPath = path.resolve(\n process.cwd(),\n options?.inputPath ?? this.config.files?.input ?? 'i18n/i18n.properties'\n );\n const sourceDocument = readPropertiesDocument(inputPath);\n const sourceEntries = listPropertiesEntries(sourceDocument);\n const sourceKeys = Object.keys(sourceEntries);\n const languages = options?.languages ?? this.config.targetLanguages;\n const translationMode = resolveTranslationMode(\n options?.mode,\n this.config.translationMode\n );\n\n const result: TranslationResult = {\n sourceFile: inputPath,\n translationMode,\n translations: {},\n };\n\n for (const language of languages) {\n this.logger.info('Translating language', { language });\n const targetPath = getLanguageFilePath(\n inputPath,\n language,\n this.config.files?.languageFilePattern,\n this.config.files?.outputDir\n );\n const targetDocument = readPropertiesDocument(targetPath);\n const targetEntries = listPropertiesEntries(targetDocument);\n const keysToTranslate = sourceKeys.filter((key) => {\n if (translationMode === 'overwrite') {\n return true;\n }\n\n const existingValue = targetEntries[key];\n return existingValue === undefined || existingValue === '';\n });\n\n const translatedMap = await this.translateEntries(sourceEntries, keysToTranslate, language);\n const outputDocument =\n targetDocument.lines.length > 0\n ? cloneDocument(targetDocument)\n : cloneDocument(sourceDocument);\n\n for (const [key, translatedValue] of Object.entries(translatedMap)) {\n upsertPropertiesValue(outputDocument, key, translatedValue);\n }\n\n const errors: string[] = [];\n if (!options?.dryRun) {\n writePropertiesDocument(targetPath, outputDocument, {\n encodeUnicode: this.resolveEncodeUnicode(language),\n });\n }\n\n result.translations[language] = {\n outputFile: targetPath,\n success: errors.length === 0,\n translatedKeysCount: Object.keys(translatedMap).length,\n skippedKeysCount: sourceKeys.length - keysToTranslate.length,\n errors,\n dryRun: options?.dryRun ?? false,\n };\n }\n\n return result;\n }\n\n clearCache(): void {\n this.cache.clear();\n }\n\n getCacheStats() {\n return this.cache.getStats();\n }\n\n getGlossaryTerms(language: string) {\n return this.glossaryManager.getTermsForLanguage(language);\n }\n\n private async translateEntries(\n sourceEntries: Record<string, string>,\n keys: string[],\n language: string\n ): Promise<Record<string, string>> {\n const glossaryTerms = this.glossaryManager.getTermsForLanguage(language);\n const rules = this.config.rules ?? [];\n const batchSize = this.config.batchSize ?? 20;\n const translatedMap: Record<string, string> = {};\n\n for (let start = 0; start < keys.length; start += batchSize) {\n const batchKeys = keys.slice(start, start + batchSize);\n const uncachedItems: Array<{\n key: string;\n sourceValue: string;\n maskedValue: string;\n placeholders: ReturnType<typeof maskPlaceholders>['tokens'];\n }> = [];\n\n for (const key of batchKeys) {\n const sourceValue = sourceEntries[key];\n const placeholderInfo = maskPlaceholders(sourceValue);\n const cacheKey = FileCache.createKey({\n provider: this.provider.getName(),\n model: this.config.providerOptions?.model ?? 'default',\n sourceLanguage: this.config.sourceLanguage ?? 'en',\n targetLanguage: language,\n sourceValue,\n glossaryTerms,\n rules,\n });\n const cached = this.cache.get(cacheKey);\n\n if (cached) {\n translatedMap[key] = cached;\n continue;\n }\n\n uncachedItems.push({\n key,\n sourceValue,\n maskedValue: placeholderInfo.masked,\n placeholders: placeholderInfo.tokens,\n });\n }\n\n if (uncachedItems.length === 0) {\n continue;\n }\n\n const response = await this.provider.translateBatch({\n sourceLanguage: this.config.sourceLanguage ?? 'en',\n targetLanguage: language,\n items: uncachedItems,\n glossaryTerms,\n rules,\n model: this.config.providerOptions?.model,\n });\n\n for (const item of uncachedItems) {\n const translated = findResponseItem(response.items, item.key);\n const restored = restorePlaceholders(\n translated.translatedValue,\n item.placeholders\n );\n const validation = validatePlaceholderIntegrity(item.sourceValue, restored);\n\n if (!validation.valid) {\n throw new Error(\n `Placeholder validation failed for key \"${item.key}\" in ${language}: expected ${validation.expected.join(\n ', '\n )}, got ${validation.actual.join(', ')}`\n );\n }\n\n translatedMap[item.key] = restored;\n\n const cacheKey = FileCache.createKey({\n provider: this.provider.getName(),\n model: this.config.providerOptions?.model ?? 'default',\n sourceLanguage: this.config.sourceLanguage ?? 'en',\n targetLanguage: language,\n sourceValue: item.sourceValue,\n glossaryTerms,\n rules,\n });\n this.cache.set(cacheKey, restored);\n }\n }\n\n return translatedMap;\n }\n\n private resolveEncodeUnicode(language: string): boolean {\n const languageOverride = this.config.languageOptions?.[language]?.encodeUnicode;\n return languageOverride ?? this.config.encodeUnicode ?? false;\n }\n}\n\nexport async function translateProject(\n options?: TranslationProjectOptions\n): Promise<TranslationResult> {\n const config = await loadTranslatorConfig({\n configPath: options?.configPath,\n cwd: options?.cwd,\n overrides: {\n input: options?.inputPath,\n targetLanguages: options?.languages,\n translationMode: options?.mode,\n encodeUnicode: options?.encodeUnicode,\n provider: options?.provider,\n model: options?.model,\n verbose: options?.verbose,\n },\n });\n\n const translator = new Translator(config);\n return translator.translateProject(options);\n}\n\nexport async function translateFile(\n config: TranslatorConfig,\n options?: TranslationFileOptions\n): Promise<TranslationResult> {\n const translator = new Translator(config);\n return translator.translateFile(options);\n}\n\nfunction createProvider(config: TranslatorConfig): AIProvider {\n if (config.provider === 'openai') {\n return new OpenAIProvider(config.providerOptions ?? {});\n }\n\n throw new Error(`Unsupported provider: ${config.provider}`);\n}\n\nfunction findResponseItem(\n items: TranslationBatchResponseItem[],\n key: string\n): TranslationBatchResponseItem {\n const item = items.find((candidate) => candidate.key === key);\n if (!item) {\n throw new Error(`Provider response is missing translation for key \"${key}\"`);\n }\n\n return item;\n}\n\nexport function createNewTranslationDocument() {\n return createEmptyPropertiesDocument();\n}\n","import crypto from 'crypto';\nimport fs from 'fs';\nimport path from 'path';\nimport { CacheOptions, GlossaryTerm } from '../types.js';\n\ninterface CacheEntry {\n translation: string;\n createdAt: number;\n}\n\nexport class FileCache {\n private readonly enabled: boolean;\n private readonly ttlMs: number;\n private readonly cacheDir: string;\n\n constructor(options?: CacheOptions) {\n this.enabled = options?.enabled ?? true;\n this.ttlMs = options?.ttlMs ?? 7 * 24 * 60 * 60 * 1000;\n const homeDir = process.env.HOME || process.env.USERPROFILE || '/tmp';\n this.cacheDir =\n options?.dir ?? path.join(homeDir, '.i18n-ai-translator-cache');\n\n if (this.enabled && !fs.existsSync(this.cacheDir)) {\n fs.mkdirSync(this.cacheDir, { recursive: true });\n }\n }\n\n get(cacheKey: string): string | null {\n if (!this.enabled) {\n return null;\n }\n\n const filePath = path.join(this.cacheDir, `${cacheKey}.json`);\n if (!fs.existsSync(filePath)) {\n return null;\n }\n\n try {\n const entry = JSON.parse(fs.readFileSync(filePath, 'utf-8')) as CacheEntry;\n if (Date.now() - entry.createdAt > this.ttlMs) {\n fs.unlinkSync(filePath);\n return null;\n }\n\n return entry.translation;\n } catch {\n return null;\n }\n }\n\n set(cacheKey: string, translation: string): void {\n if (!this.enabled) {\n return;\n }\n\n const filePath = path.join(this.cacheDir, `${cacheKey}.json`);\n const entry: CacheEntry = {\n translation,\n createdAt: Date.now(),\n };\n\n try {\n fs.writeFileSync(filePath, JSON.stringify(entry), 'utf-8');\n } catch {\n // Best-effort cache only.\n }\n }\n\n clear(): void {\n if (!this.enabled || !fs.existsSync(this.cacheDir)) {\n return;\n }\n\n for (const fileName of fs.readdirSync(this.cacheDir)) {\n if (fileName.endsWith('.json')) {\n fs.unlinkSync(path.join(this.cacheDir, fileName));\n }\n }\n }\n\n getStats(): { enabled: boolean; ttlMs: number; cacheDir: string; entriesCount: number } {\n let entriesCount = 0;\n if (this.enabled && fs.existsSync(this.cacheDir)) {\n entriesCount = fs\n .readdirSync(this.cacheDir)\n .filter((fileName) => fileName.endsWith('.json')).length;\n }\n\n return {\n enabled: this.enabled,\n ttlMs: this.ttlMs,\n cacheDir: this.cacheDir,\n entriesCount,\n };\n }\n\n static createKey(input: {\n provider: string;\n model: string;\n sourceLanguage: string;\n targetLanguage: string;\n sourceValue: string;\n glossaryTerms: GlossaryTerm[];\n rules: string[];\n }): string {\n const json = JSON.stringify(input);\n return crypto.createHash('sha256').update(json).digest('hex');\n }\n}\n","import fs from 'fs';\nimport { GlossaryConfig, GlossaryTerm } from '../types.js';\n\nexport class GlossaryManager {\n private glossary: GlossaryConfig;\n\n constructor(glossary?: GlossaryConfig) {\n this.glossary = glossary ?? {};\n }\n\n static loadGlossary(source?: GlossaryConfig | string): GlossaryConfig {\n if (!source) {\n return {};\n }\n\n if (typeof source === 'string') {\n return GlossaryManager.loadFromFile(source);\n }\n\n GlossaryManager.validateGlossary(source);\n return source;\n }\n\n static loadFromFile(filePath: string): GlossaryConfig {\n if (!fs.existsSync(filePath)) {\n throw new Error(`Glossary file not found: ${filePath}`);\n }\n\n const content = fs.readFileSync(filePath, 'utf-8');\n const parsed = JSON.parse(content) as GlossaryConfig;\n GlossaryManager.validateGlossary(parsed);\n return parsed;\n }\n\n static validateGlossary(glossary: unknown): void {\n if (!glossary || typeof glossary !== 'object' || Array.isArray(glossary)) {\n throw new Error('Glossary must be an object');\n }\n\n const typed = glossary as GlossaryConfig;\n GlossaryManager.validateTerms(typed.shared, 'shared');\n\n if (typed.languages) {\n for (const [language, terms] of Object.entries(typed.languages)) {\n GlossaryManager.validateTerms(terms, `languages.${language}`);\n }\n }\n }\n\n private static validateTerms(terms: GlossaryTerm[] | undefined, label: string): void {\n if (!terms) {\n return;\n }\n\n if (!Array.isArray(terms)) {\n throw new Error(`${label} glossary terms must be an array`);\n }\n\n for (const term of terms) {\n if (!term || typeof term !== 'object') {\n throw new Error(`${label} glossary term must be an object`);\n }\n\n if (!term.source || typeof term.source !== 'string') {\n throw new Error(`${label} glossary term requires a string source value`);\n }\n\n if (term.target !== undefined && typeof term.target !== 'string') {\n throw new Error(`${label} glossary term target must be a string`);\n }\n\n if (term.context !== undefined && typeof term.context !== 'string') {\n throw new Error(`${label} glossary term context must be a string`);\n }\n\n if (\n term.doNotTranslate !== undefined &&\n typeof term.doNotTranslate !== 'boolean'\n ) {\n throw new Error(`${label} glossary term doNotTranslate must be a boolean`);\n }\n }\n }\n\n getGlossary(): GlossaryConfig {\n return this.glossary;\n }\n\n getTermsForLanguage(language: string): GlossaryTerm[] {\n return [\n ...(this.glossary.shared ?? []),\n ...(this.glossary.languages?.[language] ?? []),\n ];\n }\n\n hasTerms(language: string): boolean {\n return this.getTermsForLanguage(language).length > 0;\n }\n\n toJSON(): string {\n return JSON.stringify(this.glossary, null, 2);\n }\n}\n","import { PlaceholderInfo, PlaceholderToken } from '../types.js';\n\nconst PLACEHOLDER_PATTERN =\n /\\{\\d+\\}|\\{[a-zA-Z_][\\w.-]*\\}|\\$\\{[a-zA-Z_][\\w.-]*\\}|%\\d+\\$[sdif]|%[sdif]/g;\nconst PROTECTED_SEQUENCE_PATTERN = /\\\\[nrtf]/g;\nconst MASKABLE_PATTERN = new RegExp(\n `${PLACEHOLDER_PATTERN.source}|${PROTECTED_SEQUENCE_PATTERN.source}`,\n 'g'\n);\n\nexport function extractPlaceholders(text: string): string[] {\n return Array.from(text.matchAll(MASKABLE_PATTERN), (match) => match[0]);\n}\n\nexport function hasPlaceholders(text: string): boolean {\n return new RegExp(MASKABLE_PATTERN).test(text);\n}\n\nexport function maskPlaceholders(text: string): PlaceholderInfo {\n const tokens: PlaceholderToken[] = [];\n let index = 0;\n\n const masked = text.replace(MASKABLE_PATTERN, (placeholder) => {\n const token = `__I18N_PH_${index}__`;\n index += 1;\n tokens.push({ token, placeholder });\n return token;\n });\n\n return {\n original: text,\n masked,\n tokens,\n };\n}\n\nexport function restorePlaceholders(\n text: string,\n tokens: PlaceholderToken[]\n): string {\n return tokens.reduce(\n (current, token) => current.split(token.token).join(token.placeholder),\n text\n );\n}\n\nexport function validatePlaceholderIntegrity(\n sourceText: string,\n translatedText: string\n): { valid: boolean; expected: string[]; actual: string[] } {\n const expected = extractPlaceholders(sourceText).sort();\n const actual = extractPlaceholders(translatedText).sort();\n\n return {\n valid:\n expected.length === actual.length &&\n expected.every((placeholder, index) => placeholder === actual[index]),\n expected,\n actual,\n };\n}\n","import fs from 'fs';\nimport path from 'path';\nimport {\n PropertiesDocument,\n PropertiesLine,\n PropertiesSerializationOptions,\n} from '../types.js';\n\nexport function parsePropertiesDocument(content: string): PropertiesDocument {\n const eolMatch = content.match(/\\r\\n|\\n/);\n const eol = eolMatch?.[0] ?? '\\n';\n const hasTrailingNewline = content.length > 0 && content.endsWith(eol);\n const rawLines = content.length === 0 ? [] : content.split(/\\r?\\n/);\n if (hasTrailingNewline && rawLines[rawLines.length - 1] === '') {\n rawLines.pop();\n }\n const lines = rawLines.map(parseLine);\n\n return {\n lines,\n eol,\n hasTrailingNewline,\n };\n}\n\nexport function readPropertiesDocument(filePath: string): PropertiesDocument {\n if (!fs.existsSync(filePath)) {\n return createEmptyPropertiesDocument();\n }\n\n return parsePropertiesDocument(fs.readFileSync(filePath, 'utf-8'));\n}\n\nexport function createEmptyPropertiesDocument(): PropertiesDocument {\n return {\n lines: [],\n eol: '\\n',\n hasTrailingNewline: true,\n };\n}\n\nexport function listPropertiesEntries(document: PropertiesDocument): Record<string, string> {\n const entries: Record<string, string> = {};\n\n for (const line of document.lines) {\n if (line.type === 'entry') {\n entries[line.key] = line.value;\n }\n }\n\n return entries;\n}\n\nexport function cloneDocument(document: PropertiesDocument): PropertiesDocument {\n return {\n eol: document.eol,\n hasTrailingNewline: document.hasTrailingNewline,\n lines: document.lines.map((line) => ({ ...line })),\n };\n}\n\nexport function upsertPropertiesValue(\n document: PropertiesDocument,\n key: string,\n value: string\n): void {\n const existing = findEntryLine(document, key);\n\n if (existing) {\n existing.value = value;\n existing.modified = true;\n return;\n }\n\n if (document.lines.length > 0 && document.lines[document.lines.length - 1].type !== 'blank') {\n document.lines.push({ type: 'blank', raw: '' });\n }\n\n document.lines.push({\n type: 'entry',\n raw: '',\n key,\n value,\n separator: '=',\n leadingWhitespace: '',\n modified: true,\n });\n}\n\nexport function serializePropertiesDocument(\n document: PropertiesDocument,\n options: PropertiesSerializationOptions = {}\n): string {\n const serializedLines = document.lines.map((line) => serializeLine(line, options));\n const body = serializedLines.join(document.eol);\n\n if (body.length === 0) {\n return '';\n }\n\n return document.hasTrailingNewline ? `${body}${document.eol}` : body;\n}\n\nexport function writePropertiesDocument(\n filePath: string,\n document: PropertiesDocument,\n options: PropertiesSerializationOptions = {}\n): void {\n const dir = path.dirname(filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n fs.writeFileSync(filePath, serializePropertiesDocument(document, options), 'utf-8');\n}\n\nexport function createDocumentFromEntries(\n entries: Record<string, string>\n): PropertiesDocument {\n const lines: PropertiesLine[] = Object.entries(entries).map(([key, value]) => ({\n type: 'entry',\n raw: '',\n key,\n value,\n separator: '=',\n leadingWhitespace: '',\n modified: true,\n }));\n\n return {\n lines,\n eol: '\\n',\n hasTrailingNewline: true,\n };\n}\n\nexport function getLanguageFilePath(\n sourceFilePath: string,\n language: string,\n pattern?: string,\n outputDir?: string\n): string {\n const sourceDir = outputDir ?? path.dirname(sourceFilePath);\n const ext = path.extname(sourceFilePath) || '.properties';\n const baseName = path.basename(sourceFilePath, ext);\n\n if (pattern) {\n const fileName = pattern\n .replace('{baseName}', baseName)\n .replace('{language}', language)\n .replace('{ext}', ext);\n return path.join(sourceDir, fileName);\n }\n\n const fileName = baseName === 'i18n' ? `i18n_${language}${ext}` : `${baseName}_${language}${ext}`;\n return path.join(sourceDir, fileName);\n}\n\nfunction parseLine(raw: string): PropertiesLine {\n if (raw.trim() === '') {\n return { type: 'blank', raw };\n }\n\n if (/^\\s*[#!]/.test(raw)) {\n return { type: 'comment', raw };\n }\n\n const leadingWhitespace = raw.match(/^\\s*/)![0];\n const body = raw.slice(leadingWhitespace.length);\n\n let separatorIndex = -1;\n let separator = '=';\n let valueStartIndex = -1;\n\n for (let index = 0; index < body.length; index += 1) {\n const char = body[index];\n const previous = index > 0 ? body[index - 1] : '';\n if (previous === '\\\\') {\n continue;\n }\n\n if (char === '=' || char === ':') {\n separatorIndex = index;\n separator = char;\n break;\n }\n\n if (char === ' ' || char === '\\t' || char === '\\f') {\n separatorIndex = index;\n separator = char;\n\n let lookahead = index;\n while (\n lookahead < body.length &&\n (body[lookahead] === ' ' || body[lookahead] === '\\t' || body[lookahead] === '\\f')\n ) {\n lookahead += 1;\n }\n\n if (lookahead < body.length && (body[lookahead] === '=' || body[lookahead] === ':')) {\n separator = body[lookahead];\n valueStartIndex = lookahead + 1;\n }\n break;\n }\n }\n\n const rawKey = separatorIndex >= 0 ? body.slice(0, separatorIndex) : body;\n const rawValue =\n separatorIndex >= 0\n ? body\n .slice(valueStartIndex >= 0 ? valueStartIndex : separatorIndex + 1)\n .replace(/^[ \\t\\f]*/, '')\n : '';\n\n return {\n type: 'entry',\n raw,\n key: decodePropertiesToken(rawKey.trimEnd()),\n value: decodePropertiesToken(rawValue),\n separator,\n leadingWhitespace,\n modified: false,\n };\n}\n\nfunction serializeLine(\n line: PropertiesLine,\n options: PropertiesSerializationOptions\n): string {\n if (line.type !== 'entry') {\n return line.raw;\n }\n\n if (!line.modified && line.raw) {\n return line.raw;\n }\n\n return `${line.leadingWhitespace}${escapePropertiesToken(line.key)}${line.separator}${escapePropertiesValue(\n line.value,\n options\n )}`;\n}\n\nfunction findEntryLine(document: PropertiesDocument, key: string): Extract<PropertiesLine, { type: 'entry' }> | undefined {\n return document.lines.find(\n (line): line is Extract<PropertiesLine, { type: 'entry' }> =>\n line.type === 'entry' && line.key === key\n );\n}\n\nfunction decodePropertiesToken(value: string): string {\n return value\n .replace(/\\\\u([0-9a-fA-F]{4})/g, (_, hex) =>\n String.fromCharCode(parseInt(hex, 16))\n )\n .replace(/\\\\t/g, '\\t')\n .replace(/\\\\r/g, '\\r')\n .replace(/\\\\n/g, '\\n')\n .replace(/\\\\f/g, '\\f')\n .replace(/\\\\:/g, ':')\n .replace(/\\\\=/g, '=')\n .replace(/\\\\\\\\/g, '\\\\');\n}\n\nfunction escapePropertiesToken(value: string): string {\n return value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/:/g, '\\\\:')\n .replace(/=/g, '\\\\=');\n}\n\nfunction escapePropertiesValue(\n value: string,\n options: PropertiesSerializationOptions\n): string {\n const escaped = value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r')\n .replace(/\\t/g, '\\\\t');\n\n if (!options.encodeUnicode) {\n return escaped;\n }\n\n return Array.from(escaped, (char) =>\n char.charCodeAt(0) > 0x7f\n ? `\\\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`\n : char\n ).join('');\n}\n","import OpenAI from 'openai';\nimport { BaseAIProvider } from '../base.js';\nimport {\n OpenAIProviderOptions,\n TranslationBatchRequest,\n TranslationBatchResponse,\n} from '../../types.js';\n\nconst TRANSLATION_SCHEMA = {\n type: 'object',\n additionalProperties: false,\n required: ['items'],\n properties: {\n items: {\n type: 'array',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['key', 'translatedValue'],\n properties: {\n key: { type: 'string' },\n translatedValue: { type: 'string' },\n },\n },\n },\n },\n} satisfies Record<string, unknown>;\n\nexport class OpenAIProvider extends BaseAIProvider {\n private readonly client: OpenAI;\n private readonly options: Required<\n Pick<OpenAIProviderOptions, 'model' | 'temperature' | 'maxOutputTokens'>\n > &\n Omit<OpenAIProviderOptions, 'model' | 'temperature' | 'maxOutputTokens'>;\n\n constructor(options: OpenAIProviderOptions) {\n super();\n\n if (!options.apiKey) {\n throw new Error('OpenAI API key is required');\n }\n\n this.client = new OpenAI({\n apiKey: options.apiKey,\n baseURL: options.baseURL,\n organization: options.organization,\n });\n\n this.options = {\n ...options,\n model: options.model ?? 'gpt-4.1-mini',\n temperature: options.temperature ?? 0,\n maxOutputTokens: options.maxOutputTokens ?? 4000,\n };\n }\n\n getName(): 'openai' {\n return 'openai';\n }\n\n async translateBatch(\n request: TranslationBatchRequest\n ): Promise<TranslationBatchResponse> {\n const response = await this.client.responses.create({\n model: request.model ?? this.options.model,\n temperature: this.options.temperature,\n max_output_tokens: this.options.maxOutputTokens,\n text: {\n format: {\n type: 'json_schema',\n name: 'translation_batch',\n schema: TRANSLATION_SCHEMA,\n strict: true,\n },\n },\n input: [\n {\n role: 'system',\n content: buildSystemPrompt(),\n },\n {\n role: 'user',\n content: JSON.stringify(buildPayload(request)),\n },\n ],\n });\n\n const rawResponse = response.output_text?.trim();\n if (!rawResponse) {\n throw new Error('OpenAI Responses API returned an empty response');\n }\n\n const parsed = JSON.parse(rawResponse) as {\n items?: Array<{ key?: string; translatedValue?: string }>;\n };\n\n if (!Array.isArray(parsed.items)) {\n throw new Error('OpenAI response is missing items array');\n }\n\n return {\n provider: 'openai',\n rawResponse,\n items: parsed.items.map((item) => {\n if (!item.key || typeof item.translatedValue !== 'string') {\n throw new Error('OpenAI response contains an invalid translation item');\n }\n\n return {\n key: item.key,\n translatedValue: item.translatedValue,\n };\n }),\n };\n }\n}\n\nfunction buildSystemPrompt(): string {\n return [\n 'You translate SAP UI5 i18n property values.',\n 'Return JSON only.',\n 'Translate values, never keys.',\n 'Do not add explanations.',\n 'Keep placeholder tokens like __I18N_PH_0__ unchanged.',\n 'Preserve surrounding punctuation and semantic meaning.',\n 'Apply glossary terms strictly when provided.',\n ].join(' ');\n}\n\nfunction buildPayload(request: TranslationBatchRequest): Record<string, unknown> {\n return {\n sourceLanguage: request.sourceLanguage,\n targetLanguage: request.targetLanguage,\n glossaryTerms: request.glossaryTerms,\n rules: request.rules,\n items: request.items.map((item) => ({\n key: item.key,\n value: item.maskedValue,\n placeholders: item.placeholders.map((placeholder) => placeholder.placeholder),\n })),\n };\n}\n","import { AIProvider, SupportedProvider, TranslationBatchRequest, TranslationBatchResponse } from '../types.js';\n\nexport abstract class BaseAIProvider implements AIProvider {\n abstract getName(): SupportedProvider;\n abstract translateBatch(\n request: TranslationBatchRequest\n ): Promise<TranslationBatchResponse>;\n}\n","import fs from 'fs';\nimport path from 'path';\nimport { pathToFileURL } from 'url';\nimport dotenv from 'dotenv';\nimport { GlossaryManager } from '../glossary/glossaryManager.js';\nimport {\n LanguageConfig,\n TranslationMode,\n TranslatorConfig,\n TranslatorConfigOverrides,\n} from '../types.js';\n\nconst DEFAULT_CONFIG_FILES = [\n 'i18n-ai.config.json',\n 'i18n-ai.config.mjs',\n 'i18n-ai.config.js',\n 'ui5-ai-i18n.config.json',\n 'ui5-ai-i18n.config.mjs',\n 'ui5-ai-i18n.config.js',\n];\n\nconst TOP_LEVEL_CONFIG_KEYS = [\n 'sourceLanguage',\n 'targetLanguages',\n 'translationMode',\n 'encodeUnicode',\n 'languageOptions',\n 'provider',\n 'providerOptions',\n 'files',\n 'glossary',\n 'cache',\n 'rules',\n 'batchSize',\n 'verbose',\n] as const;\n\nconst PROVIDER_OPTIONS_KEYS = [\n 'apiKey',\n 'model',\n 'baseURL',\n 'organization',\n 'temperature',\n 'maxOutputTokens',\n] as const;\n\nconst FILE_CONFIG_KEYS = ['input', 'outputDir', 'languageFilePattern'] as const;\nconst CACHE_CONFIG_KEYS = ['enabled', 'ttlMs', 'dir'] as const;\nconst LANGUAGE_CONFIG_KEYS = ['encodeUnicode'] as const;\n\nexport async function loadTranslatorConfig(options?: {\n configPath?: string;\n cwd?: string;\n overrides?: TranslatorConfigOverrides;\n}): Promise<TranslatorConfig> {\n const cwd = options?.cwd ?? process.cwd();\n loadEnvFiles(cwd);\n const configPath = options?.configPath\n ? path.resolve(cwd, options.configPath)\n : findDefaultConfigPath(cwd);\n\n const loaded = configPath ? await loadConfigFile(configPath) : {};\n if (configPath) {\n validateLoadedConfigShape(loaded, configPath);\n }\n const merged = applyOverrides(\n applyEnvDefaults({\n sourceLanguage: 'en',\n targetLanguages: [],\n translationMode: 'missing',\n encodeUnicode: false,\n provider: 'openai',\n batchSize: 20,\n verbose: false,\n files: {\n input: 'i18n/i18n.properties',\n },\n cache: {\n enabled: true,\n },\n rules: [],\n ...loaded,\n }),\n options?.overrides\n );\n\n if (merged.glossary) {\n merged.glossary = GlossaryManager.loadGlossary(merged.glossary);\n }\n\n validateConfig(merged);\n return merged;\n}\n\nexport function validateConfig(config: TranslatorConfig): void {\n if (!config.targetLanguages || config.targetLanguages.length === 0) {\n throw new Error('At least one target language is required');\n }\n\n if (config.provider !== 'openai') {\n throw new Error(`Unsupported provider: ${config.provider}`);\n }\n\n if (!config.files?.input) {\n throw new Error('files.input is required');\n }\n\n const mode = config.translationMode ?? 'missing';\n if (mode !== 'missing' && mode !== 'overwrite') {\n throw new Error(`Unsupported translationMode: ${mode}`);\n }\n\n if (config.batchSize !== undefined && config.batchSize < 1) {\n throw new Error('batchSize must be at least 1');\n }\n\n if (!config.providerOptions?.apiKey) {\n throw new Error('OpenAI API key is required via config.providerOptions.apiKey or OPENAI_API_KEY');\n }\n}\n\nexport function createDefaultConfig(\n apiKey: string,\n targetLanguages: string[]\n): TranslatorConfig {\n return {\n sourceLanguage: 'en',\n targetLanguages,\n translationMode: 'missing',\n encodeUnicode: false,\n provider: 'openai',\n providerOptions: {\n apiKey,\n model: 'gpt-4.1-mini',\n temperature: 0,\n maxOutputTokens: 4000,\n },\n files: {\n input: 'i18n/i18n.properties',\n },\n cache: {\n enabled: true,\n },\n rules: [],\n batchSize: 20,\n verbose: false,\n };\n}\n\nfunction applyEnvDefaults(config: TranslatorConfig): TranslatorConfig {\n const providerOptions = {\n ...config.providerOptions,\n };\n\n if (!providerOptions.apiKey && process.env.OPENAI_API_KEY) {\n providerOptions.apiKey = process.env.OPENAI_API_KEY;\n }\n\n if (!providerOptions.model && process.env.OPENAI_MODEL) {\n providerOptions.model = process.env.OPENAI_MODEL;\n }\n\n return {\n ...config,\n providerOptions,\n };\n}\n\nfunction applyOverrides(\n config: TranslatorConfig,\n overrides?: TranslatorConfigOverrides\n): TranslatorConfig {\n if (!overrides) {\n return config;\n }\n\n return {\n ...config,\n provider: overrides.provider ?? config.provider,\n translationMode: overrides.translationMode ?? config.translationMode,\n encodeUnicode: overrides.encodeUnicode ?? config.encodeUnicode,\n languageOptions: config.languageOptions,\n targetLanguages: overrides.targetLanguages ?? config.targetLanguages,\n verbose: overrides.verbose ?? config.verbose,\n files: {\n ...config.files,\n input: overrides.input ?? config.files?.input,\n },\n providerOptions: {\n ...config.providerOptions,\n model: overrides.model ?? config.providerOptions?.model,\n },\n };\n}\n\nfunction findDefaultConfigPath(cwd: string): string | undefined {\n for (const fileName of DEFAULT_CONFIG_FILES) {\n const candidate = path.join(cwd, fileName);\n if (fs.existsSync(candidate)) {\n return candidate;\n }\n }\n\n return undefined;\n}\n\nasync function loadConfigFile(configPath: string): Promise<Partial<TranslatorConfig>> {\n if (!fs.existsSync(configPath)) {\n throw new Error(`Config file not found: ${configPath}`);\n }\n\n if (configPath.endsWith('.json')) {\n return JSON.parse(fs.readFileSync(configPath, 'utf-8')) as Partial<TranslatorConfig>;\n }\n\n if (configPath.endsWith('.js') || configPath.endsWith('.mjs')) {\n const loaded = await import(pathToFileURL(configPath).href);\n return (loaded.default ?? loaded) as Partial<TranslatorConfig>;\n }\n\n throw new Error(`Unsupported config format: ${configPath}`);\n}\n\nfunction validateLoadedConfigShape(\n config: Partial<TranslatorConfig>,\n configPath: string\n): void {\n assertPlainObject(config, 'Config file', configPath);\n validateAllowedKeys(config, TOP_LEVEL_CONFIG_KEYS, 'Config file', configPath);\n\n if (config.providerOptions !== undefined) {\n assertPlainObject(config.providerOptions, 'config.providerOptions', configPath);\n validateAllowedKeys(\n config.providerOptions,\n PROVIDER_OPTIONS_KEYS,\n 'config.providerOptions',\n configPath\n );\n }\n\n if (config.files !== undefined) {\n assertPlainObject(config.files, 'config.files', configPath);\n validateAllowedKeys(config.files, FILE_CONFIG_KEYS, 'config.files', configPath);\n }\n\n if (config.cache !== undefined) {\n assertPlainObject(config.cache, 'config.cache', configPath);\n validateAllowedKeys(config.cache, CACHE_CONFIG_KEYS, 'config.cache', configPath);\n }\n\n if (config.languageOptions !== undefined) {\n assertPlainObject(config.languageOptions, 'config.languageOptions', configPath);\n for (const [language, value] of Object.entries(config.languageOptions)) {\n assertPlainObject(value, `config.languageOptions.${language}`, configPath);\n validateAllowedKeys(\n value as LanguageConfig,\n LANGUAGE_CONFIG_KEYS,\n `config.languageOptions.${language}`,\n configPath\n );\n }\n }\n}\n\nfunction validateAllowedKeys(\n value: object,\n allowedKeys: readonly string[],\n pathLabel: string,\n configPath: string\n): void {\n for (const key of Object.keys(value)) {\n if (!allowedKeys.includes(key)) {\n throw new Error(\n `Unknown option \"${key}\" in ${pathLabel} of ${configPath}.`\n );\n }\n }\n}\n\nfunction assertPlainObject(value: unknown, pathLabel: string, configPath: string): void {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(`${pathLabel} in ${configPath} must be an object.`);\n }\n}\n\nexport function resolveTranslationMode(\n preferred?: TranslationMode,\n fallback: TranslationMode = 'missing'\n): TranslationMode {\n return preferred ?? fallback;\n}\n\nfunction loadEnvFiles(cwd: string): void {\n const envPath = path.join(cwd, '.env');\n const envLocalPath = path.join(cwd, '.env.local');\n\n if (fs.existsSync(envPath)) {\n dotenv.config({ path: envPath });\n }\n\n if (fs.existsSync(envLocalPath)) {\n dotenv.config({ path: envLocalPath, override: true });\n }\n}\n","/**\n * Logging utility for debug output\n */\n\nimport { Logger } from '../types.js';\n\n/**\n * Default logger implementation\n * Uses console for output, respects DEBUG environment variable\n */\nexport class ConsoleLogger implements Logger {\n private debug_enabled: boolean;\n private prefix: string;\n\n constructor(prefix: string = '@concircle/i18n-ai-translator', debug?: boolean) {\n this.prefix = prefix;\n this.debug_enabled =\n debug ??\n (typeof process !== 'undefined' &&\n process.env.DEBUG?.includes('i18n-ai-translator')) ??\n false;\n }\n\n debug(message: string, data?: Record<string, unknown>): void {\n if (this.debug_enabled) {\n console.debug(`[${this.prefix}:DEBUG] ${message}`, data || '');\n }\n }\n\n info(message: string, data?: Record<string, unknown>): void {\n console.info(`[${this.prefix}:INFO] ${message}`, data || '');\n }\n\n warn(message: string, data?: Record<string, unknown>): void {\n console.warn(`[${this.prefix}:WARN] ${message}`, data || '');\n }\n\n error(message: string, error?: Error): void {\n console.error(`[${this.prefix}:ERROR] ${message}`, error?.message || '');\n if (error?.stack && this.debug_enabled) {\n console.error(error.stack);\n }\n }\n\n setDebug(enabled: boolean): void {\n this.debug_enabled = enabled;\n }\n\n isDebugEnabled(): boolean {\n return this.debug_enabled;\n }\n}\n\n/**\n * No-op logger (for silent mode)\n */\nexport class SilentLogger implements Logger {\n debug(_message: string, _data?: Record<string, unknown>): void {\n // no-op\n }\n\n info(_message: string, _data?: Record<string, unknown>): void {\n // no-op\n }\n\n warn(_message: string, _data?: Record<string, unknown>): void {\n // no-op\n }\n\n error(_message: string, _error?: Error): void {\n // no-op\n }\n}\n\n/**\n * Create a logger instance\n *\n * @param debug Enable debug output\n * @param silent Enable silent mode (no output)\n * @returns Logger instance\n */\nexport function createLogger(debug: boolean = false, silent: boolean = false): Logger {\n if (silent) {\n return new SilentLogger();\n }\n\n return new ConsoleLogger('@concircle/i18n-ai-translator', debug);\n}\n"],"mappings":"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","names":["cli_exports","__export","parseArgs","runCli","__toCommonJS","import_path","import_crypto","import_fs","import_path","FileCache","options","homeDir","path","fs","cacheKey","filePath","entry","translation","fileName","entriesCount","input","json","crypto","import_fs","GlossaryManager","_GlossaryManager","glossary","source","filePath","fs","content","parsed","typed","language","terms","label","term","PLACEHOLDER_PATTERN","PROTECTED_SEQUENCE_PATTERN","MASKABLE_PATTERN","extractPlaceholders","text","match","maskPlaceholders","text","tokens","index","masked","MASKABLE_PATTERN","placeholder","token","restorePlaceholders","current","validatePlaceholderIntegrity","sourceText","translatedText","expected","extractPlaceholders","actual","import_fs","import_path","parsePropertiesDocument","content","eol","hasTrailingNewline","rawLines","parseLine","readPropertiesDocument","filePath","fs","createEmptyPropertiesDocument","listPropertiesEntries","document","entries","line","cloneDocument","upsertPropertiesValue","key","value","existing","findEntryLine","serializePropertiesDocument","options","body","serializeLine","writePropertiesDocument","dir","path","getLanguageFilePath","sourceFilePath","language","pattern","outputDir","sourceDir","path","ext","baseName","fileName","parseLine","raw","leadingWhitespace","body","separatorIndex","separator","valueStartIndex","index","char","lookahead","rawKey","rawValue","decodePropertiesToken","serializeLine","line","options","escapePropertiesToken","escapePropertiesValue","findEntryLine","document","key","value","_","hex","escaped","import_openai","BaseAIProvider","TRANSLATION_SCHEMA","OpenAIProvider","BaseAIProvider","options","OpenAI","request","rawResponse","buildSystemPrompt","buildPayload","parsed","item","placeholder","import_fs","import_path","import_url","import_dotenv","DEFAULT_CONFIG_FILES","TOP_LEVEL_CONFIG_KEYS","PROVIDER_OPTIONS_KEYS","FILE_CONFIG_KEYS","CACHE_CONFIG_KEYS","LANGUAGE_CONFIG_KEYS","loadTranslatorConfig","options","cwd","loadEnvFiles","configPath","path","findDefaultConfigPath","loaded","loadConfigFile","validateLoadedConfigShape","merged","applyOverrides","applyEnvDefaults","GlossaryManager","validateConfig","config","mode","applyEnvDefaults","config","providerOptions","applyOverrides","overrides","findDefaultConfigPath","cwd","fileName","DEFAULT_CONFIG_FILES","candidate","path","fs","loadConfigFile","configPath","loaded","validateLoadedConfigShape","assertPlainObject","validateAllowedKeys","TOP_LEVEL_CONFIG_KEYS","PROVIDER_OPTIONS_KEYS","FILE_CONFIG_KEYS","CACHE_CONFIG_KEYS","language","value","LANGUAGE_CONFIG_KEYS","allowedKeys","pathLabel","key","resolveTranslationMode","preferred","fallback","loadEnvFiles","envPath","envLocalPath","dotenv","ConsoleLogger","prefix","debug","message","data","error","enabled","SilentLogger","_message","_data","_error","Translator","_Translator","config","validateConfig","createProvider","GlossaryManager","FileCache","ConsoleLogger","SilentLogger","options","loadTranslatorConfig","inputPath","path","sourceDocument","readPropertiesDocument","sourceEntries","listPropertiesEntries","sourceKeys","languages","translationMode","resolveTranslationMode","result","language","targetPath","getLanguageFilePath","targetDocument","targetEntries","keysToTranslate","key","existingValue","translatedMap","outputDocument","cloneDocument","translatedValue","upsertPropertiesValue","errors","writePropertiesDocument","keys","glossaryTerms","rules","batchSize","start","batchKeys","uncachedItems","sourceValue","placeholderInfo","maskPlaceholders","cacheKey","cached","response","item","translated","findResponseItem","restored","restorePlaceholders","validation","validatePlaceholderIntegrity","translateProject","createProvider","config","OpenAIProvider","findResponseItem","items","key","item","candidate","SUPPORTED_MODES","SUPPORTED_PROVIDERS","runCli","argv","io","args","parseArgs","error","getHelpText","result","translateProject","tokens","token","readOptionValue","value","option"]}
|
package/dist/cli.d.cts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { T as TranslationMode } from './types-DF4QMkU1.cjs';
|
|
2
|
+
|
|
3
|
+
interface CliIO {
|
|
4
|
+
log(message: string): void;
|
|
5
|
+
error(message: string): void;
|
|
6
|
+
}
|
|
7
|
+
declare function runCli(argv: string[], io?: CliIO): Promise<number>;
|
|
8
|
+
declare function parseArgs(argv: string[]): {
|
|
9
|
+
config?: string;
|
|
10
|
+
input?: string;
|
|
11
|
+
languages?: string[];
|
|
12
|
+
mode?: TranslationMode;
|
|
13
|
+
provider?: string;
|
|
14
|
+
model?: string;
|
|
15
|
+
dryRun: boolean;
|
|
16
|
+
encodeUnicode?: boolean;
|
|
17
|
+
verbose: boolean;
|
|
18
|
+
help: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export { parseArgs, runCli };
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { T as TranslationMode } from './types-DF4QMkU1.js';
|
|
2
|
+
|
|
3
|
+
interface CliIO {
|
|
4
|
+
log(message: string): void;
|
|
5
|
+
error(message: string): void;
|
|
6
|
+
}
|
|
7
|
+
declare function runCli(argv: string[], io?: CliIO): Promise<number>;
|
|
8
|
+
declare function parseArgs(argv: string[]): {
|
|
9
|
+
config?: string;
|
|
10
|
+
input?: string;
|
|
11
|
+
languages?: string[];
|
|
12
|
+
mode?: TranslationMode;
|
|
13
|
+
provider?: string;
|
|
14
|
+
model?: string;
|
|
15
|
+
dryRun: boolean;
|
|
16
|
+
encodeUnicode?: boolean;
|
|
17
|
+
verbose: boolean;
|
|
18
|
+
help: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export { parseArgs, runCli };
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import Le from"path";import ne from"crypto";import u from"fs";import S from"path";var v=class{constructor(e){this.enabled=e?.enabled??!0,this.ttlMs=e?.ttlMs??10080*60*1e3;let t=process.env.HOME||process.env.USERPROFILE||"/tmp";this.cacheDir=e?.dir??S.join(t,".i18n-ai-translator-cache"),this.enabled&&!u.existsSync(this.cacheDir)&&u.mkdirSync(this.cacheDir,{recursive:!0})}get(e){if(!this.enabled)return null;let t=S.join(this.cacheDir,`${e}.json`);if(!u.existsSync(t))return null;try{let n=JSON.parse(u.readFileSync(t,"utf-8"));return Date.now()-n.createdAt>this.ttlMs?(u.unlinkSync(t),null):n.translation}catch{return null}}set(e,t){if(!this.enabled)return;let n=S.join(this.cacheDir,`${e}.json`),o={translation:t,createdAt:Date.now()};try{u.writeFileSync(n,JSON.stringify(o),"utf-8")}catch{}}clear(){if(!(!this.enabled||!u.existsSync(this.cacheDir)))for(let e of u.readdirSync(this.cacheDir))e.endsWith(".json")&&u.unlinkSync(S.join(this.cacheDir,e))}getStats(){let e=0;return this.enabled&&u.existsSync(this.cacheDir)&&(e=u.readdirSync(this.cacheDir).filter(t=>t.endsWith(".json")).length),{enabled:this.enabled,ttlMs:this.ttlMs,cacheDir:this.cacheDir,entriesCount:e}}static createKey(e){let t=JSON.stringify(e);return ne.createHash("sha256").update(t).digest("hex")}};import F from"fs";var w=class r{constructor(e){this.glossary=e??{}}static loadGlossary(e){return e?typeof e=="string"?r.loadFromFile(e):(r.validateGlossary(e),e):{}}static loadFromFile(e){if(!F.existsSync(e))throw new Error(`Glossary file not found: ${e}`);let t=F.readFileSync(e,"utf-8"),n=JSON.parse(t);return r.validateGlossary(n),n}static validateGlossary(e){if(!e||typeof e!="object"||Array.isArray(e))throw new Error("Glossary must be an object");let t=e;if(r.validateTerms(t.shared,"shared"),t.languages)for(let[n,o]of Object.entries(t.languages))r.validateTerms(o,`languages.${n}`)}static validateTerms(e,t){if(e){if(!Array.isArray(e))throw new Error(`${t} glossary terms must be an array`);for(let n of e){if(!n||typeof n!="object")throw new Error(`${t} glossary term must be an object`);if(!n.source||typeof n.source!="string")throw new Error(`${t} glossary term requires a string source value`);if(n.target!==void 0&&typeof n.target!="string")throw new Error(`${t} glossary term target must be a string`);if(n.context!==void 0&&typeof n.context!="string")throw new Error(`${t} glossary term context must be a string`);if(n.doNotTranslate!==void 0&&typeof n.doNotTranslate!="boolean")throw new Error(`${t} glossary term doNotTranslate must be a boolean`)}}}getGlossary(){return this.glossary}getTermsForLanguage(e){return[...this.glossary.shared??[],...this.glossary.languages?.[e]??[]]}hasTerms(e){return this.getTermsForLanguage(e).length>0}toJSON(){return JSON.stringify(this.glossary,null,2)}};var oe=/\{\d+\}|\{[a-zA-Z_][\w.-]*\}|\$\{[a-zA-Z_][\w.-]*\}|%\d+\$[sdif]|%[sdif]/g,se=/\\[nrtf]/g,K=new RegExp(`${oe.source}|${se.source}`,"g");function G(r){return Array.from(r.matchAll(K),e=>e[0])}function V(r){let e=[],t=0,n=r.replace(K,o=>{let s=`__I18N_PH_${t}__`;return t+=1,e.push({token:s,placeholder:o}),s});return{original:r,masked:n,tokens:e}}function z(r,e){return e.reduce((t,n)=>t.split(n.token).join(n.placeholder),r)}function B(r,e){let t=G(r).sort(),n=G(e).sort();return{valid:t.length===n.length&&t.every((o,s)=>o===n[s]),expected:t,actual:n}}import T from"fs";import b from"path";function ie(r){let t=r.match(/\r\n|\n/)?.[0]??`
|
|
2
|
+
`,n=r.length>0&&r.endsWith(t),o=r.length===0?[]:r.split(/\r?\n/);return n&&o[o.length-1]===""&&o.pop(),{lines:o.map(le),eol:t,hasTrailingNewline:n}}function _(r){return T.existsSync(r)?ie(T.readFileSync(r,"utf-8")):J()}function J(){return{lines:[],eol:`
|
|
3
|
+
`,hasTrailingNewline:!0}}function j(r){let e={};for(let t of r.lines)t.type==="entry"&&(e[t.key]=t.value);return e}function N(r){return{eol:r.eol,hasTrailingNewline:r.hasTrailingNewline,lines:r.lines.map(e=>({...e}))}}function q(r,e,t){let n=ue(r,e);if(n){n.value=t,n.modified=!0;return}r.lines.length>0&&r.lines[r.lines.length-1].type!=="blank"&&r.lines.push({type:"blank",raw:""}),r.lines.push({type:"entry",raw:"",key:e,value:t,separator:"=",leadingWhitespace:"",modified:!0})}function ae(r,e={}){let n=r.lines.map(o=>ce(o,e)).join(r.eol);return n.length===0?"":r.hasTrailingNewline?`${n}${r.eol}`:n}function Y(r,e,t={}){let n=b.dirname(r);T.existsSync(n)||T.mkdirSync(n,{recursive:!0}),T.writeFileSync(r,ae(e,t),"utf-8")}function H(r,e,t,n){let o=n??b.dirname(r),s=b.extname(r)||".properties",g=b.basename(r,s);if(t){let i=t.replace("{baseName}",g).replace("{language}",e).replace("{ext}",s);return b.join(o,i)}let d=g==="i18n"?`i18n_${e}${s}`:`${g}_${e}${s}`;return b.join(o,d)}function le(r){if(r.trim()==="")return{type:"blank",raw:r};if(/^\s*[#!]/.test(r))return{type:"comment",raw:r};let e=r.match(/^\s*/)[0],t=r.slice(e.length),n=-1,o="=",s=-1;for(let i=0;i<t.length;i+=1){let l=t[i];if((i>0?t[i-1]:"")!=="\\"){if(l==="="||l===":"){n=i,o=l;break}if(l===" "||l===" "||l==="\f"){n=i,o=l;let a=i;for(;a<t.length&&(t[a]===" "||t[a]===" "||t[a]==="\f");)a+=1;a<t.length&&(t[a]==="="||t[a]===":")&&(o=t[a],s=a+1);break}}}let g=n>=0?t.slice(0,n):t,d=n>=0?t.slice(s>=0?s:n+1).replace(/^[ \t\f]*/,""):"";return{type:"entry",raw:r,key:W(g.trimEnd()),value:W(d),separator:o,leadingWhitespace:e,modified:!1}}function ce(r,e){return r.type!=="entry"||!r.modified&&r.raw?r.raw:`${r.leadingWhitespace}${ge(r.key)}${r.separator}${de(r.value,e)}`}function ue(r,e){return r.lines.find(t=>t.type==="entry"&&t.key===e)}function W(r){return r.replace(/\\u([0-9a-fA-F]{4})/g,(e,t)=>String.fromCharCode(parseInt(t,16))).replace(/\\t/g," ").replace(/\\r/g,"\r").replace(/\\n/g,`
|
|
4
|
+
`).replace(/\\f/g,"\f").replace(/\\:/g,":").replace(/\\=/g,"=").replace(/\\\\/g,"\\")}function ge(r){return r.replace(/\\/g,"\\\\").replace(/:/g,"\\:").replace(/=/g,"\\=")}function de(r,e){let t=r.replace(/\\/g,"\\\\").replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t");return e.encodeUnicode?Array.from(t,n=>n.charCodeAt(0)>127?`\\u${n.charCodeAt(0).toString(16).padStart(4,"0")}`:n).join(""):t}import pe from"openai";var C=class{};var fe={type:"object",additionalProperties:!1,required:["items"],properties:{items:{type:"array",items:{type:"object",additionalProperties:!1,required:["key","translatedValue"],properties:{key:{type:"string"},translatedValue:{type:"string"}}}}}},R=class extends C{constructor(e){if(super(),!e.apiKey)throw new Error("OpenAI API key is required");this.client=new pe({apiKey:e.apiKey,baseURL:e.baseURL,organization:e.organization}),this.options={...e,model:e.model??"gpt-4.1-mini",temperature:e.temperature??0,maxOutputTokens:e.maxOutputTokens??4e3}}getName(){return"openai"}async translateBatch(e){let n=(await this.client.responses.create({model:e.model??this.options.model,temperature:this.options.temperature,max_output_tokens:this.options.maxOutputTokens,text:{format:{type:"json_schema",name:"translation_batch",schema:fe,strict:!0}},input:[{role:"system",content:me()},{role:"user",content:JSON.stringify(he(e))}]})).output_text?.trim();if(!n)throw new Error("OpenAI Responses API returned an empty response");let o=JSON.parse(n);if(!Array.isArray(o.items))throw new Error("OpenAI response is missing items array");return{provider:"openai",rawResponse:n,items:o.items.map(s=>{if(!s.key||typeof s.translatedValue!="string")throw new Error("OpenAI response contains an invalid translation item");return{key:s.key,translatedValue:s.translatedValue}})}}};function me(){return["You translate SAP UI5 i18n property values.","Return JSON only.","Translate values, never keys.","Do not add explanations.","Keep placeholder tokens like __I18N_PH_0__ unchanged.","Preserve surrounding punctuation and semantic meaning.","Apply glossary terms strictly when provided."].join(" ")}function he(r){return{sourceLanguage:r.sourceLanguage,targetLanguage:r.targetLanguage,glossaryTerms:r.glossaryTerms,rules:r.rules,items:r.items.map(e=>({key:e.key,value:e.maskedValue,placeholders:e.placeholders.map(t=>t.placeholder)}))}}import k from"fs";import L from"path";import{pathToFileURL as ye}from"url";import X from"dotenv";var ve=["i18n-ai.config.json","i18n-ai.config.mjs","i18n-ai.config.js","ui5-ai-i18n.config.json","ui5-ai-i18n.config.mjs","ui5-ai-i18n.config.js"],we=["sourceLanguage","targetLanguages","translationMode","encodeUnicode","languageOptions","provider","providerOptions","files","glossary","cache","rules","batchSize","verbose"],be=["apiKey","model","baseURL","organization","temperature","maxOutputTokens"],Pe=["input","outputDir","languageFilePattern"],Oe=["enabled","ttlMs","dir"],Te=["encodeUnicode"];async function A(r){let e=r?.cwd??process.cwd();Re(e);let t=r?.configPath?L.resolve(e,r.configPath):xe(e),n=t?await Se(t):{};t&&Ce(n,t);let o=ke(Ee({sourceLanguage:"en",targetLanguages:[],translationMode:"missing",encodeUnicode:!1,provider:"openai",batchSize:20,verbose:!1,files:{input:"i18n/i18n.properties"},cache:{enabled:!0},rules:[],...n}),r?.overrides);return o.glossary&&(o.glossary=w.loadGlossary(o.glossary)),M(o),o}function M(r){if(!r.targetLanguages||r.targetLanguages.length===0)throw new Error("At least one target language is required");if(r.provider!=="openai")throw new Error(`Unsupported provider: ${r.provider}`);if(!r.files?.input)throw new Error("files.input is required");let e=r.translationMode??"missing";if(e!=="missing"&&e!=="overwrite")throw new Error(`Unsupported translationMode: ${e}`);if(r.batchSize!==void 0&&r.batchSize<1)throw new Error("batchSize must be at least 1");if(!r.providerOptions?.apiKey)throw new Error("OpenAI API key is required via config.providerOptions.apiKey or OPENAI_API_KEY")}function Ee(r){let e={...r.providerOptions};return!e.apiKey&&process.env.OPENAI_API_KEY&&(e.apiKey=process.env.OPENAI_API_KEY),!e.model&&process.env.OPENAI_MODEL&&(e.model=process.env.OPENAI_MODEL),{...r,providerOptions:e}}function ke(r,e){return e?{...r,provider:e.provider??r.provider,translationMode:e.translationMode??r.translationMode,encodeUnicode:e.encodeUnicode??r.encodeUnicode,languageOptions:r.languageOptions,targetLanguages:e.targetLanguages??r.targetLanguages,verbose:e.verbose??r.verbose,files:{...r.files,input:e.input??r.files?.input},providerOptions:{...r.providerOptions,model:e.model??r.providerOptions?.model}}:r}function xe(r){for(let e of ve){let t=L.join(r,e);if(k.existsSync(t))return t}}async function Se(r){if(!k.existsSync(r))throw new Error(`Config file not found: ${r}`);if(r.endsWith(".json"))return JSON.parse(k.readFileSync(r,"utf-8"));if(r.endsWith(".js")||r.endsWith(".mjs")){let e=await import(ye(r).href);return e.default??e}throw new Error(`Unsupported config format: ${r}`)}function Ce(r,e){if(P(r,"Config file",e),E(r,we,"Config file",e),r.providerOptions!==void 0&&(P(r.providerOptions,"config.providerOptions",e),E(r.providerOptions,be,"config.providerOptions",e)),r.files!==void 0&&(P(r.files,"config.files",e),E(r.files,Pe,"config.files",e)),r.cache!==void 0&&(P(r.cache,"config.cache",e),E(r.cache,Oe,"config.cache",e)),r.languageOptions!==void 0){P(r.languageOptions,"config.languageOptions",e);for(let[t,n]of Object.entries(r.languageOptions))P(n,`config.languageOptions.${t}`,e),E(n,Te,`config.languageOptions.${t}`,e)}}function E(r,e,t,n){for(let o of Object.keys(r))if(!e.includes(o))throw new Error(`Unknown option "${o}" in ${t} of ${n}.`)}function P(r,e,t){if(!r||typeof r!="object"||Array.isArray(r))throw new Error(`${e} in ${t} must be an object.`)}function Z(r,e="missing"){return r??e}function Re(r){let e=L.join(r,".env"),t=L.join(r,".env.local");k.existsSync(e)&&X.config({path:e}),k.existsSync(t)&&X.config({path:t,override:!0})}var D=class{constructor(e="@concircle/i18n-ai-translator",t){this.prefix=e,this.debug_enabled=t??(typeof process<"u"&&process.env.DEBUG?.includes("i18n-ai-translator"))??!1}debug(e,t){this.debug_enabled&&console.debug(`[${this.prefix}:DEBUG] ${e}`,t||"")}info(e,t){console.info(`[${this.prefix}:INFO] ${e}`,t||"")}warn(e,t){console.warn(`[${this.prefix}:WARN] ${e}`,t||"")}error(e,t){console.error(`[${this.prefix}:ERROR] ${e}`,t?.message||""),t?.stack&&this.debug_enabled&&console.error(t.stack)}setDebug(e){this.debug_enabled=e}isDebugEnabled(){return this.debug_enabled}},I=class{debug(e,t){}info(e,t){}warn(e,t){}error(e,t){}};var U=class r{constructor(e){M(e),this.config=e,this.provider=Ae(e),this.glossaryManager=new w(e.glossary),this.cache=new v(e.cache),this.logger=e.verbose?new D("@concircle/i18n-ai-translator",!0):new I}static async fromConfig(e){let t=await A(e);return new r(t)}async translateProject(e){let t=e?.configPath||e?.cwd?await A({configPath:e.configPath,cwd:e.cwd,overrides:{input:e.inputPath,targetLanguages:e.languages,translationMode:e.mode,encodeUnicode:e.encodeUnicode,provider:e.provider,model:e.model,verbose:e.verbose}}):this.config;return(t===this.config?this:new r(t)).translateFile({inputPath:e?.inputPath,languages:e?.languages,mode:e?.mode,dryRun:e?.dryRun})}async translateFile(e){let t=Le.resolve(process.cwd(),e?.inputPath??this.config.files?.input??"i18n/i18n.properties"),n=_(t),o=j(n),s=Object.keys(o),g=e?.languages??this.config.targetLanguages,d=Z(e?.mode,this.config.translationMode),i={sourceFile:t,translationMode:d,translations:{}};for(let l of g){this.logger.info("Translating language",{language:l});let p=H(t,l,this.config.files?.languageFilePattern,this.config.files?.outputDir),a=_(p),c=j(a),m=s.filter($=>{if(d==="overwrite")return!0;let x=c[$];return x===void 0||x===""}),f=await this.translateEntries(o,m,l),h=a.lines.length>0?N(a):N(n);for(let[$,x]of Object.entries(f))q(h,$,x);let y=[];e?.dryRun||Y(p,h,{encodeUnicode:this.resolveEncodeUnicode(l)}),i.translations[l]={outputFile:p,success:y.length===0,translatedKeysCount:Object.keys(f).length,skippedKeysCount:s.length-m.length,errors:y,dryRun:e?.dryRun??!1}}return i}clearCache(){this.cache.clear()}getCacheStats(){return this.cache.getStats()}getGlossaryTerms(e){return this.glossaryManager.getTermsForLanguage(e)}async translateEntries(e,t,n){let o=this.glossaryManager.getTermsForLanguage(n),s=this.config.rules??[],g=this.config.batchSize??20,d={};for(let i=0;i<t.length;i+=g){let l=t.slice(i,i+g),p=[];for(let c of l){let m=e[c],f=V(m),h=v.createKey({provider:this.provider.getName(),model:this.config.providerOptions?.model??"default",sourceLanguage:this.config.sourceLanguage??"en",targetLanguage:n,sourceValue:m,glossaryTerms:o,rules:s}),y=this.cache.get(h);if(y){d[c]=y;continue}p.push({key:c,sourceValue:m,maskedValue:f.masked,placeholders:f.tokens})}if(p.length===0)continue;let a=await this.provider.translateBatch({sourceLanguage:this.config.sourceLanguage??"en",targetLanguage:n,items:p,glossaryTerms:o,rules:s,model:this.config.providerOptions?.model});for(let c of p){let m=De(a.items,c.key),f=z(m.translatedValue,c.placeholders),h=B(c.sourceValue,f);if(!h.valid)throw new Error(`Placeholder validation failed for key "${c.key}" in ${n}: expected ${h.expected.join(", ")}, got ${h.actual.join(", ")}`);d[c.key]=f;let y=v.createKey({provider:this.provider.getName(),model:this.config.providerOptions?.model??"default",sourceLanguage:this.config.sourceLanguage??"en",targetLanguage:n,sourceValue:c.sourceValue,glossaryTerms:o,rules:s});this.cache.set(y,f)}}return d}resolveEncodeUnicode(e){return this.config.languageOptions?.[e]?.encodeUnicode??this.config.encodeUnicode??!1}};async function Q(r){let e=await A({configPath:r?.configPath,cwd:r?.cwd,overrides:{input:r?.inputPath,targetLanguages:r?.languages,translationMode:r?.mode,encodeUnicode:r?.encodeUnicode,provider:r?.provider,model:r?.model,verbose:r?.verbose}});return new U(e).translateProject(r)}function Ae(r){if(r.provider==="openai")return new R(r.providerOptions??{});throw new Error(`Unsupported provider: ${r.provider}`)}function De(r,e){let t=r.find(n=>n.key===e);if(!t)throw new Error(`Provider response is missing translation for key "${e}"`);return t}var ee=["missing","overwrite"],re=["openai"];async function gr(r,e={log:console.log,error:console.error}){let t;try{t=Ie(r)}catch(n){return e.error(n instanceof Error?n.message:String(n)),e.log(te()),1}if(t.help||r.length===0)return e.log(te()),0;try{let n=await Q({configPath:t.config,inputPath:t.input,languages:t.languages,mode:t.mode,provider:t.provider,model:t.model,dryRun:t.dryRun,encodeUnicode:t.encodeUnicode,verbose:t.verbose});return e.log(JSON.stringify(n,null,2)),0}catch(n){return e.error(n instanceof Error?n.message:String(n)),1}}function Ie(r){let e={dryRun:!1,verbose:!1,help:!1},t=[...r];for(t[0]==="help"&&(t.shift(),e.help=!0);t.length>0;){let n=t.shift();switch(n){case"--config":e.config=O(t,"--config");break;case"--input":e.input=O(t,"--input");break;case"--languages":e.languages=O(t,"--languages").split(",").map(o=>o.trim()).filter(Boolean);break;case"--mode":{let o=O(t,"--mode");if(!ee.includes(o))throw new Error(`Unknown value "${o}" for option "--mode". Expected one of: ${ee.join(", ")}.`);e.mode=o;break}case"--provider":{let o=O(t,"--provider");if(!re.includes(o))throw new Error(`Unknown value "${o}" for option "--provider". Expected one of: ${re.join(", ")}.`);e.provider=o;break}case"--model":e.model=O(t,"--model");break;case"--dry-run":e.dryRun=!0;break;case"--encode-unicode":e.encodeUnicode=!0;break;case"--verbose":e.verbose=!0;break;case"--help":case"-h":e.help=!0;break;default:throw n.startsWith("-")?new Error(`Unknown option "${n}". Use --help to see supported options.`):new Error(`Unknown argument: ${n}`)}}return e}function O(r,e){let t=r.shift();if(!t||t.startsWith("-"))throw new Error(`Missing value for option "${e}".`);return t}function te(){return["i18n-ai-translator [options]","","Options:"," --config <path> Path to i18n-ai config file"," --input <path> Source i18n.properties file"," --languages <list> Comma-separated target languages"," --mode <mode> missing | overwrite"," --provider <name> Reserved for future providers"," --model <name> Override provider model"," --dry-run Do not write files"," --encode-unicode Write non-ASCII characters as \\uXXXX escapes"," --verbose Enable verbose logs"," --help Show this help text"].join(`
|
|
5
|
+
`)}export{Ie as parseArgs,gr as runCli};
|
|
6
|
+
//# sourceMappingURL=cli.mjs.map
|
package/dist/cli.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/translator.ts","../src/cache/fileCache.ts","../src/glossary/glossaryManager.ts","../src/parsers/placeholderExtractor.ts","../src/parsers/propertiesParser.ts","../src/providers/openai/client.ts","../src/providers/base.ts","../src/utils/config.ts","../src/utils/logger.ts","../src/cli.ts"],"sourcesContent":["import path from 'path';\nimport { FileCache } from './cache/fileCache.js';\nimport { GlossaryManager } from './glossary/glossaryManager.js';\nimport {\n maskPlaceholders,\n restorePlaceholders,\n validatePlaceholderIntegrity,\n} from './parsers/placeholderExtractor.js';\nimport {\n cloneDocument,\n createEmptyPropertiesDocument,\n getLanguageFilePath,\n listPropertiesEntries,\n readPropertiesDocument,\n upsertPropertiesValue,\n writePropertiesDocument,\n} from './parsers/propertiesParser.js';\nimport { OpenAIProvider } from './providers/openai/client.js';\nimport {\n AIProvider,\n GlossaryConfig,\n TranslationBatchResponseItem,\n TranslationFileOptions,\n TranslationProjectOptions,\n TranslationResult,\n TranslatorConfig,\n} from './types.js';\nimport { loadTranslatorConfig, resolveTranslationMode, validateConfig } from './utils/config.js';\nimport { ConsoleLogger, SilentLogger } from './utils/logger.js';\n\nexport class Translator {\n private readonly config: TranslatorConfig;\n private readonly provider: AIProvider;\n private readonly glossaryManager: GlossaryManager;\n private readonly cache: FileCache;\n private readonly logger: ConsoleLogger | SilentLogger;\n\n constructor(config: TranslatorConfig) {\n validateConfig(config);\n this.config = config;\n this.provider = createProvider(config);\n this.glossaryManager = new GlossaryManager(config.glossary as GlossaryConfig | undefined);\n this.cache = new FileCache(config.cache);\n this.logger = config.verbose\n ? new ConsoleLogger('@concircle/i18n-ai-translator', true)\n : new SilentLogger();\n }\n\n static async fromConfig(options?: {\n configPath?: string;\n cwd?: string;\n }): Promise<Translator> {\n const config = await loadTranslatorConfig(options);\n return new Translator(config);\n }\n\n async translateProject(options?: TranslationProjectOptions): Promise<TranslationResult> {\n const config =\n options?.configPath || options?.cwd\n ? await loadTranslatorConfig({\n configPath: options.configPath,\n cwd: options.cwd,\n overrides: {\n input: options.inputPath,\n targetLanguages: options.languages,\n translationMode: options.mode,\n encodeUnicode: options.encodeUnicode,\n provider: options.provider,\n model: options.model,\n verbose: options.verbose,\n },\n })\n : this.config;\n\n const translator =\n config === this.config ? this : new Translator(config);\n\n return translator.translateFile({\n inputPath: options?.inputPath,\n languages: options?.languages,\n mode: options?.mode,\n dryRun: options?.dryRun,\n });\n }\n\n async translateFile(options?: TranslationFileOptions): Promise<TranslationResult> {\n const inputPath = path.resolve(\n process.cwd(),\n options?.inputPath ?? this.config.files?.input ?? 'i18n/i18n.properties'\n );\n const sourceDocument = readPropertiesDocument(inputPath);\n const sourceEntries = listPropertiesEntries(sourceDocument);\n const sourceKeys = Object.keys(sourceEntries);\n const languages = options?.languages ?? this.config.targetLanguages;\n const translationMode = resolveTranslationMode(\n options?.mode,\n this.config.translationMode\n );\n\n const result: TranslationResult = {\n sourceFile: inputPath,\n translationMode,\n translations: {},\n };\n\n for (const language of languages) {\n this.logger.info('Translating language', { language });\n const targetPath = getLanguageFilePath(\n inputPath,\n language,\n this.config.files?.languageFilePattern,\n this.config.files?.outputDir\n );\n const targetDocument = readPropertiesDocument(targetPath);\n const targetEntries = listPropertiesEntries(targetDocument);\n const keysToTranslate = sourceKeys.filter((key) => {\n if (translationMode === 'overwrite') {\n return true;\n }\n\n const existingValue = targetEntries[key];\n return existingValue === undefined || existingValue === '';\n });\n\n const translatedMap = await this.translateEntries(sourceEntries, keysToTranslate, language);\n const outputDocument =\n targetDocument.lines.length > 0\n ? cloneDocument(targetDocument)\n : cloneDocument(sourceDocument);\n\n for (const [key, translatedValue] of Object.entries(translatedMap)) {\n upsertPropertiesValue(outputDocument, key, translatedValue);\n }\n\n const errors: string[] = [];\n if (!options?.dryRun) {\n writePropertiesDocument(targetPath, outputDocument, {\n encodeUnicode: this.resolveEncodeUnicode(language),\n });\n }\n\n result.translations[language] = {\n outputFile: targetPath,\n success: errors.length === 0,\n translatedKeysCount: Object.keys(translatedMap).length,\n skippedKeysCount: sourceKeys.length - keysToTranslate.length,\n errors,\n dryRun: options?.dryRun ?? false,\n };\n }\n\n return result;\n }\n\n clearCache(): void {\n this.cache.clear();\n }\n\n getCacheStats() {\n return this.cache.getStats();\n }\n\n getGlossaryTerms(language: string) {\n return this.glossaryManager.getTermsForLanguage(language);\n }\n\n private async translateEntries(\n sourceEntries: Record<string, string>,\n keys: string[],\n language: string\n ): Promise<Record<string, string>> {\n const glossaryTerms = this.glossaryManager.getTermsForLanguage(language);\n const rules = this.config.rules ?? [];\n const batchSize = this.config.batchSize ?? 20;\n const translatedMap: Record<string, string> = {};\n\n for (let start = 0; start < keys.length; start += batchSize) {\n const batchKeys = keys.slice(start, start + batchSize);\n const uncachedItems: Array<{\n key: string;\n sourceValue: string;\n maskedValue: string;\n placeholders: ReturnType<typeof maskPlaceholders>['tokens'];\n }> = [];\n\n for (const key of batchKeys) {\n const sourceValue = sourceEntries[key];\n const placeholderInfo = maskPlaceholders(sourceValue);\n const cacheKey = FileCache.createKey({\n provider: this.provider.getName(),\n model: this.config.providerOptions?.model ?? 'default',\n sourceLanguage: this.config.sourceLanguage ?? 'en',\n targetLanguage: language,\n sourceValue,\n glossaryTerms,\n rules,\n });\n const cached = this.cache.get(cacheKey);\n\n if (cached) {\n translatedMap[key] = cached;\n continue;\n }\n\n uncachedItems.push({\n key,\n sourceValue,\n maskedValue: placeholderInfo.masked,\n placeholders: placeholderInfo.tokens,\n });\n }\n\n if (uncachedItems.length === 0) {\n continue;\n }\n\n const response = await this.provider.translateBatch({\n sourceLanguage: this.config.sourceLanguage ?? 'en',\n targetLanguage: language,\n items: uncachedItems,\n glossaryTerms,\n rules,\n model: this.config.providerOptions?.model,\n });\n\n for (const item of uncachedItems) {\n const translated = findResponseItem(response.items, item.key);\n const restored = restorePlaceholders(\n translated.translatedValue,\n item.placeholders\n );\n const validation = validatePlaceholderIntegrity(item.sourceValue, restored);\n\n if (!validation.valid) {\n throw new Error(\n `Placeholder validation failed for key \"${item.key}\" in ${language}: expected ${validation.expected.join(\n ', '\n )}, got ${validation.actual.join(', ')}`\n );\n }\n\n translatedMap[item.key] = restored;\n\n const cacheKey = FileCache.createKey({\n provider: this.provider.getName(),\n model: this.config.providerOptions?.model ?? 'default',\n sourceLanguage: this.config.sourceLanguage ?? 'en',\n targetLanguage: language,\n sourceValue: item.sourceValue,\n glossaryTerms,\n rules,\n });\n this.cache.set(cacheKey, restored);\n }\n }\n\n return translatedMap;\n }\n\n private resolveEncodeUnicode(language: string): boolean {\n const languageOverride = this.config.languageOptions?.[language]?.encodeUnicode;\n return languageOverride ?? this.config.encodeUnicode ?? false;\n }\n}\n\nexport async function translateProject(\n options?: TranslationProjectOptions\n): Promise<TranslationResult> {\n const config = await loadTranslatorConfig({\n configPath: options?.configPath,\n cwd: options?.cwd,\n overrides: {\n input: options?.inputPath,\n targetLanguages: options?.languages,\n translationMode: options?.mode,\n encodeUnicode: options?.encodeUnicode,\n provider: options?.provider,\n model: options?.model,\n verbose: options?.verbose,\n },\n });\n\n const translator = new Translator(config);\n return translator.translateProject(options);\n}\n\nexport async function translateFile(\n config: TranslatorConfig,\n options?: TranslationFileOptions\n): Promise<TranslationResult> {\n const translator = new Translator(config);\n return translator.translateFile(options);\n}\n\nfunction createProvider(config: TranslatorConfig): AIProvider {\n if (config.provider === 'openai') {\n return new OpenAIProvider(config.providerOptions ?? {});\n }\n\n throw new Error(`Unsupported provider: ${config.provider}`);\n}\n\nfunction findResponseItem(\n items: TranslationBatchResponseItem[],\n key: string\n): TranslationBatchResponseItem {\n const item = items.find((candidate) => candidate.key === key);\n if (!item) {\n throw new Error(`Provider response is missing translation for key \"${key}\"`);\n }\n\n return item;\n}\n\nexport function createNewTranslationDocument() {\n return createEmptyPropertiesDocument();\n}\n","import crypto from 'crypto';\nimport fs from 'fs';\nimport path from 'path';\nimport { CacheOptions, GlossaryTerm } from '../types.js';\n\ninterface CacheEntry {\n translation: string;\n createdAt: number;\n}\n\nexport class FileCache {\n private readonly enabled: boolean;\n private readonly ttlMs: number;\n private readonly cacheDir: string;\n\n constructor(options?: CacheOptions) {\n this.enabled = options?.enabled ?? true;\n this.ttlMs = options?.ttlMs ?? 7 * 24 * 60 * 60 * 1000;\n const homeDir = process.env.HOME || process.env.USERPROFILE || '/tmp';\n this.cacheDir =\n options?.dir ?? path.join(homeDir, '.i18n-ai-translator-cache');\n\n if (this.enabled && !fs.existsSync(this.cacheDir)) {\n fs.mkdirSync(this.cacheDir, { recursive: true });\n }\n }\n\n get(cacheKey: string): string | null {\n if (!this.enabled) {\n return null;\n }\n\n const filePath = path.join(this.cacheDir, `${cacheKey}.json`);\n if (!fs.existsSync(filePath)) {\n return null;\n }\n\n try {\n const entry = JSON.parse(fs.readFileSync(filePath, 'utf-8')) as CacheEntry;\n if (Date.now() - entry.createdAt > this.ttlMs) {\n fs.unlinkSync(filePath);\n return null;\n }\n\n return entry.translation;\n } catch {\n return null;\n }\n }\n\n set(cacheKey: string, translation: string): void {\n if (!this.enabled) {\n return;\n }\n\n const filePath = path.join(this.cacheDir, `${cacheKey}.json`);\n const entry: CacheEntry = {\n translation,\n createdAt: Date.now(),\n };\n\n try {\n fs.writeFileSync(filePath, JSON.stringify(entry), 'utf-8');\n } catch {\n // Best-effort cache only.\n }\n }\n\n clear(): void {\n if (!this.enabled || !fs.existsSync(this.cacheDir)) {\n return;\n }\n\n for (const fileName of fs.readdirSync(this.cacheDir)) {\n if (fileName.endsWith('.json')) {\n fs.unlinkSync(path.join(this.cacheDir, fileName));\n }\n }\n }\n\n getStats(): { enabled: boolean; ttlMs: number; cacheDir: string; entriesCount: number } {\n let entriesCount = 0;\n if (this.enabled && fs.existsSync(this.cacheDir)) {\n entriesCount = fs\n .readdirSync(this.cacheDir)\n .filter((fileName) => fileName.endsWith('.json')).length;\n }\n\n return {\n enabled: this.enabled,\n ttlMs: this.ttlMs,\n cacheDir: this.cacheDir,\n entriesCount,\n };\n }\n\n static createKey(input: {\n provider: string;\n model: string;\n sourceLanguage: string;\n targetLanguage: string;\n sourceValue: string;\n glossaryTerms: GlossaryTerm[];\n rules: string[];\n }): string {\n const json = JSON.stringify(input);\n return crypto.createHash('sha256').update(json).digest('hex');\n }\n}\n","import fs from 'fs';\nimport { GlossaryConfig, GlossaryTerm } from '../types.js';\n\nexport class GlossaryManager {\n private glossary: GlossaryConfig;\n\n constructor(glossary?: GlossaryConfig) {\n this.glossary = glossary ?? {};\n }\n\n static loadGlossary(source?: GlossaryConfig | string): GlossaryConfig {\n if (!source) {\n return {};\n }\n\n if (typeof source === 'string') {\n return GlossaryManager.loadFromFile(source);\n }\n\n GlossaryManager.validateGlossary(source);\n return source;\n }\n\n static loadFromFile(filePath: string): GlossaryConfig {\n if (!fs.existsSync(filePath)) {\n throw new Error(`Glossary file not found: ${filePath}`);\n }\n\n const content = fs.readFileSync(filePath, 'utf-8');\n const parsed = JSON.parse(content) as GlossaryConfig;\n GlossaryManager.validateGlossary(parsed);\n return parsed;\n }\n\n static validateGlossary(glossary: unknown): void {\n if (!glossary || typeof glossary !== 'object' || Array.isArray(glossary)) {\n throw new Error('Glossary must be an object');\n }\n\n const typed = glossary as GlossaryConfig;\n GlossaryManager.validateTerms(typed.shared, 'shared');\n\n if (typed.languages) {\n for (const [language, terms] of Object.entries(typed.languages)) {\n GlossaryManager.validateTerms(terms, `languages.${language}`);\n }\n }\n }\n\n private static validateTerms(terms: GlossaryTerm[] | undefined, label: string): void {\n if (!terms) {\n return;\n }\n\n if (!Array.isArray(terms)) {\n throw new Error(`${label} glossary terms must be an array`);\n }\n\n for (const term of terms) {\n if (!term || typeof term !== 'object') {\n throw new Error(`${label} glossary term must be an object`);\n }\n\n if (!term.source || typeof term.source !== 'string') {\n throw new Error(`${label} glossary term requires a string source value`);\n }\n\n if (term.target !== undefined && typeof term.target !== 'string') {\n throw new Error(`${label} glossary term target must be a string`);\n }\n\n if (term.context !== undefined && typeof term.context !== 'string') {\n throw new Error(`${label} glossary term context must be a string`);\n }\n\n if (\n term.doNotTranslate !== undefined &&\n typeof term.doNotTranslate !== 'boolean'\n ) {\n throw new Error(`${label} glossary term doNotTranslate must be a boolean`);\n }\n }\n }\n\n getGlossary(): GlossaryConfig {\n return this.glossary;\n }\n\n getTermsForLanguage(language: string): GlossaryTerm[] {\n return [\n ...(this.glossary.shared ?? []),\n ...(this.glossary.languages?.[language] ?? []),\n ];\n }\n\n hasTerms(language: string): boolean {\n return this.getTermsForLanguage(language).length > 0;\n }\n\n toJSON(): string {\n return JSON.stringify(this.glossary, null, 2);\n }\n}\n","import { PlaceholderInfo, PlaceholderToken } from '../types.js';\n\nconst PLACEHOLDER_PATTERN =\n /\\{\\d+\\}|\\{[a-zA-Z_][\\w.-]*\\}|\\$\\{[a-zA-Z_][\\w.-]*\\}|%\\d+\\$[sdif]|%[sdif]/g;\nconst PROTECTED_SEQUENCE_PATTERN = /\\\\[nrtf]/g;\nconst MASKABLE_PATTERN = new RegExp(\n `${PLACEHOLDER_PATTERN.source}|${PROTECTED_SEQUENCE_PATTERN.source}`,\n 'g'\n);\n\nexport function extractPlaceholders(text: string): string[] {\n return Array.from(text.matchAll(MASKABLE_PATTERN), (match) => match[0]);\n}\n\nexport function hasPlaceholders(text: string): boolean {\n return new RegExp(MASKABLE_PATTERN).test(text);\n}\n\nexport function maskPlaceholders(text: string): PlaceholderInfo {\n const tokens: PlaceholderToken[] = [];\n let index = 0;\n\n const masked = text.replace(MASKABLE_PATTERN, (placeholder) => {\n const token = `__I18N_PH_${index}__`;\n index += 1;\n tokens.push({ token, placeholder });\n return token;\n });\n\n return {\n original: text,\n masked,\n tokens,\n };\n}\n\nexport function restorePlaceholders(\n text: string,\n tokens: PlaceholderToken[]\n): string {\n return tokens.reduce(\n (current, token) => current.split(token.token).join(token.placeholder),\n text\n );\n}\n\nexport function validatePlaceholderIntegrity(\n sourceText: string,\n translatedText: string\n): { valid: boolean; expected: string[]; actual: string[] } {\n const expected = extractPlaceholders(sourceText).sort();\n const actual = extractPlaceholders(translatedText).sort();\n\n return {\n valid:\n expected.length === actual.length &&\n expected.every((placeholder, index) => placeholder === actual[index]),\n expected,\n actual,\n };\n}\n","import fs from 'fs';\nimport path from 'path';\nimport {\n PropertiesDocument,\n PropertiesLine,\n PropertiesSerializationOptions,\n} from '../types.js';\n\nexport function parsePropertiesDocument(content: string): PropertiesDocument {\n const eolMatch = content.match(/\\r\\n|\\n/);\n const eol = eolMatch?.[0] ?? '\\n';\n const hasTrailingNewline = content.length > 0 && content.endsWith(eol);\n const rawLines = content.length === 0 ? [] : content.split(/\\r?\\n/);\n if (hasTrailingNewline && rawLines[rawLines.length - 1] === '') {\n rawLines.pop();\n }\n const lines = rawLines.map(parseLine);\n\n return {\n lines,\n eol,\n hasTrailingNewline,\n };\n}\n\nexport function readPropertiesDocument(filePath: string): PropertiesDocument {\n if (!fs.existsSync(filePath)) {\n return createEmptyPropertiesDocument();\n }\n\n return parsePropertiesDocument(fs.readFileSync(filePath, 'utf-8'));\n}\n\nexport function createEmptyPropertiesDocument(): PropertiesDocument {\n return {\n lines: [],\n eol: '\\n',\n hasTrailingNewline: true,\n };\n}\n\nexport function listPropertiesEntries(document: PropertiesDocument): Record<string, string> {\n const entries: Record<string, string> = {};\n\n for (const line of document.lines) {\n if (line.type === 'entry') {\n entries[line.key] = line.value;\n }\n }\n\n return entries;\n}\n\nexport function cloneDocument(document: PropertiesDocument): PropertiesDocument {\n return {\n eol: document.eol,\n hasTrailingNewline: document.hasTrailingNewline,\n lines: document.lines.map((line) => ({ ...line })),\n };\n}\n\nexport function upsertPropertiesValue(\n document: PropertiesDocument,\n key: string,\n value: string\n): void {\n const existing = findEntryLine(document, key);\n\n if (existing) {\n existing.value = value;\n existing.modified = true;\n return;\n }\n\n if (document.lines.length > 0 && document.lines[document.lines.length - 1].type !== 'blank') {\n document.lines.push({ type: 'blank', raw: '' });\n }\n\n document.lines.push({\n type: 'entry',\n raw: '',\n key,\n value,\n separator: '=',\n leadingWhitespace: '',\n modified: true,\n });\n}\n\nexport function serializePropertiesDocument(\n document: PropertiesDocument,\n options: PropertiesSerializationOptions = {}\n): string {\n const serializedLines = document.lines.map((line) => serializeLine(line, options));\n const body = serializedLines.join(document.eol);\n\n if (body.length === 0) {\n return '';\n }\n\n return document.hasTrailingNewline ? `${body}${document.eol}` : body;\n}\n\nexport function writePropertiesDocument(\n filePath: string,\n document: PropertiesDocument,\n options: PropertiesSerializationOptions = {}\n): void {\n const dir = path.dirname(filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n fs.writeFileSync(filePath, serializePropertiesDocument(document, options), 'utf-8');\n}\n\nexport function createDocumentFromEntries(\n entries: Record<string, string>\n): PropertiesDocument {\n const lines: PropertiesLine[] = Object.entries(entries).map(([key, value]) => ({\n type: 'entry',\n raw: '',\n key,\n value,\n separator: '=',\n leadingWhitespace: '',\n modified: true,\n }));\n\n return {\n lines,\n eol: '\\n',\n hasTrailingNewline: true,\n };\n}\n\nexport function getLanguageFilePath(\n sourceFilePath: string,\n language: string,\n pattern?: string,\n outputDir?: string\n): string {\n const sourceDir = outputDir ?? path.dirname(sourceFilePath);\n const ext = path.extname(sourceFilePath) || '.properties';\n const baseName = path.basename(sourceFilePath, ext);\n\n if (pattern) {\n const fileName = pattern\n .replace('{baseName}', baseName)\n .replace('{language}', language)\n .replace('{ext}', ext);\n return path.join(sourceDir, fileName);\n }\n\n const fileName = baseName === 'i18n' ? `i18n_${language}${ext}` : `${baseName}_${language}${ext}`;\n return path.join(sourceDir, fileName);\n}\n\nfunction parseLine(raw: string): PropertiesLine {\n if (raw.trim() === '') {\n return { type: 'blank', raw };\n }\n\n if (/^\\s*[#!]/.test(raw)) {\n return { type: 'comment', raw };\n }\n\n const leadingWhitespace = raw.match(/^\\s*/)![0];\n const body = raw.slice(leadingWhitespace.length);\n\n let separatorIndex = -1;\n let separator = '=';\n let valueStartIndex = -1;\n\n for (let index = 0; index < body.length; index += 1) {\n const char = body[index];\n const previous = index > 0 ? body[index - 1] : '';\n if (previous === '\\\\') {\n continue;\n }\n\n if (char === '=' || char === ':') {\n separatorIndex = index;\n separator = char;\n break;\n }\n\n if (char === ' ' || char === '\\t' || char === '\\f') {\n separatorIndex = index;\n separator = char;\n\n let lookahead = index;\n while (\n lookahead < body.length &&\n (body[lookahead] === ' ' || body[lookahead] === '\\t' || body[lookahead] === '\\f')\n ) {\n lookahead += 1;\n }\n\n if (lookahead < body.length && (body[lookahead] === '=' || body[lookahead] === ':')) {\n separator = body[lookahead];\n valueStartIndex = lookahead + 1;\n }\n break;\n }\n }\n\n const rawKey = separatorIndex >= 0 ? body.slice(0, separatorIndex) : body;\n const rawValue =\n separatorIndex >= 0\n ? body\n .slice(valueStartIndex >= 0 ? valueStartIndex : separatorIndex + 1)\n .replace(/^[ \\t\\f]*/, '')\n : '';\n\n return {\n type: 'entry',\n raw,\n key: decodePropertiesToken(rawKey.trimEnd()),\n value: decodePropertiesToken(rawValue),\n separator,\n leadingWhitespace,\n modified: false,\n };\n}\n\nfunction serializeLine(\n line: PropertiesLine,\n options: PropertiesSerializationOptions\n): string {\n if (line.type !== 'entry') {\n return line.raw;\n }\n\n if (!line.modified && line.raw) {\n return line.raw;\n }\n\n return `${line.leadingWhitespace}${escapePropertiesToken(line.key)}${line.separator}${escapePropertiesValue(\n line.value,\n options\n )}`;\n}\n\nfunction findEntryLine(document: PropertiesDocument, key: string): Extract<PropertiesLine, { type: 'entry' }> | undefined {\n return document.lines.find(\n (line): line is Extract<PropertiesLine, { type: 'entry' }> =>\n line.type === 'entry' && line.key === key\n );\n}\n\nfunction decodePropertiesToken(value: string): string {\n return value\n .replace(/\\\\u([0-9a-fA-F]{4})/g, (_, hex) =>\n String.fromCharCode(parseInt(hex, 16))\n )\n .replace(/\\\\t/g, '\\t')\n .replace(/\\\\r/g, '\\r')\n .replace(/\\\\n/g, '\\n')\n .replace(/\\\\f/g, '\\f')\n .replace(/\\\\:/g, ':')\n .replace(/\\\\=/g, '=')\n .replace(/\\\\\\\\/g, '\\\\');\n}\n\nfunction escapePropertiesToken(value: string): string {\n return value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/:/g, '\\\\:')\n .replace(/=/g, '\\\\=');\n}\n\nfunction escapePropertiesValue(\n value: string,\n options: PropertiesSerializationOptions\n): string {\n const escaped = value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r')\n .replace(/\\t/g, '\\\\t');\n\n if (!options.encodeUnicode) {\n return escaped;\n }\n\n return Array.from(escaped, (char) =>\n char.charCodeAt(0) > 0x7f\n ? `\\\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`\n : char\n ).join('');\n}\n","import OpenAI from 'openai';\nimport { BaseAIProvider } from '../base.js';\nimport {\n OpenAIProviderOptions,\n TranslationBatchRequest,\n TranslationBatchResponse,\n} from '../../types.js';\n\nconst TRANSLATION_SCHEMA = {\n type: 'object',\n additionalProperties: false,\n required: ['items'],\n properties: {\n items: {\n type: 'array',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['key', 'translatedValue'],\n properties: {\n key: { type: 'string' },\n translatedValue: { type: 'string' },\n },\n },\n },\n },\n} satisfies Record<string, unknown>;\n\nexport class OpenAIProvider extends BaseAIProvider {\n private readonly client: OpenAI;\n private readonly options: Required<\n Pick<OpenAIProviderOptions, 'model' | 'temperature' | 'maxOutputTokens'>\n > &\n Omit<OpenAIProviderOptions, 'model' | 'temperature' | 'maxOutputTokens'>;\n\n constructor(options: OpenAIProviderOptions) {\n super();\n\n if (!options.apiKey) {\n throw new Error('OpenAI API key is required');\n }\n\n this.client = new OpenAI({\n apiKey: options.apiKey,\n baseURL: options.baseURL,\n organization: options.organization,\n });\n\n this.options = {\n ...options,\n model: options.model ?? 'gpt-4.1-mini',\n temperature: options.temperature ?? 0,\n maxOutputTokens: options.maxOutputTokens ?? 4000,\n };\n }\n\n getName(): 'openai' {\n return 'openai';\n }\n\n async translateBatch(\n request: TranslationBatchRequest\n ): Promise<TranslationBatchResponse> {\n const response = await this.client.responses.create({\n model: request.model ?? this.options.model,\n temperature: this.options.temperature,\n max_output_tokens: this.options.maxOutputTokens,\n text: {\n format: {\n type: 'json_schema',\n name: 'translation_batch',\n schema: TRANSLATION_SCHEMA,\n strict: true,\n },\n },\n input: [\n {\n role: 'system',\n content: buildSystemPrompt(),\n },\n {\n role: 'user',\n content: JSON.stringify(buildPayload(request)),\n },\n ],\n });\n\n const rawResponse = response.output_text?.trim();\n if (!rawResponse) {\n throw new Error('OpenAI Responses API returned an empty response');\n }\n\n const parsed = JSON.parse(rawResponse) as {\n items?: Array<{ key?: string; translatedValue?: string }>;\n };\n\n if (!Array.isArray(parsed.items)) {\n throw new Error('OpenAI response is missing items array');\n }\n\n return {\n provider: 'openai',\n rawResponse,\n items: parsed.items.map((item) => {\n if (!item.key || typeof item.translatedValue !== 'string') {\n throw new Error('OpenAI response contains an invalid translation item');\n }\n\n return {\n key: item.key,\n translatedValue: item.translatedValue,\n };\n }),\n };\n }\n}\n\nfunction buildSystemPrompt(): string {\n return [\n 'You translate SAP UI5 i18n property values.',\n 'Return JSON only.',\n 'Translate values, never keys.',\n 'Do not add explanations.',\n 'Keep placeholder tokens like __I18N_PH_0__ unchanged.',\n 'Preserve surrounding punctuation and semantic meaning.',\n 'Apply glossary terms strictly when provided.',\n ].join(' ');\n}\n\nfunction buildPayload(request: TranslationBatchRequest): Record<string, unknown> {\n return {\n sourceLanguage: request.sourceLanguage,\n targetLanguage: request.targetLanguage,\n glossaryTerms: request.glossaryTerms,\n rules: request.rules,\n items: request.items.map((item) => ({\n key: item.key,\n value: item.maskedValue,\n placeholders: item.placeholders.map((placeholder) => placeholder.placeholder),\n })),\n };\n}\n","import { AIProvider, SupportedProvider, TranslationBatchRequest, TranslationBatchResponse } from '../types.js';\n\nexport abstract class BaseAIProvider implements AIProvider {\n abstract getName(): SupportedProvider;\n abstract translateBatch(\n request: TranslationBatchRequest\n ): Promise<TranslationBatchResponse>;\n}\n","import fs from 'fs';\nimport path from 'path';\nimport { pathToFileURL } from 'url';\nimport dotenv from 'dotenv';\nimport { GlossaryManager } from '../glossary/glossaryManager.js';\nimport {\n LanguageConfig,\n TranslationMode,\n TranslatorConfig,\n TranslatorConfigOverrides,\n} from '../types.js';\n\nconst DEFAULT_CONFIG_FILES = [\n 'i18n-ai.config.json',\n 'i18n-ai.config.mjs',\n 'i18n-ai.config.js',\n 'ui5-ai-i18n.config.json',\n 'ui5-ai-i18n.config.mjs',\n 'ui5-ai-i18n.config.js',\n];\n\nconst TOP_LEVEL_CONFIG_KEYS = [\n 'sourceLanguage',\n 'targetLanguages',\n 'translationMode',\n 'encodeUnicode',\n 'languageOptions',\n 'provider',\n 'providerOptions',\n 'files',\n 'glossary',\n 'cache',\n 'rules',\n 'batchSize',\n 'verbose',\n] as const;\n\nconst PROVIDER_OPTIONS_KEYS = [\n 'apiKey',\n 'model',\n 'baseURL',\n 'organization',\n 'temperature',\n 'maxOutputTokens',\n] as const;\n\nconst FILE_CONFIG_KEYS = ['input', 'outputDir', 'languageFilePattern'] as const;\nconst CACHE_CONFIG_KEYS = ['enabled', 'ttlMs', 'dir'] as const;\nconst LANGUAGE_CONFIG_KEYS = ['encodeUnicode'] as const;\n\nexport async function loadTranslatorConfig(options?: {\n configPath?: string;\n cwd?: string;\n overrides?: TranslatorConfigOverrides;\n}): Promise<TranslatorConfig> {\n const cwd = options?.cwd ?? process.cwd();\n loadEnvFiles(cwd);\n const configPath = options?.configPath\n ? path.resolve(cwd, options.configPath)\n : findDefaultConfigPath(cwd);\n\n const loaded = configPath ? await loadConfigFile(configPath) : {};\n if (configPath) {\n validateLoadedConfigShape(loaded, configPath);\n }\n const merged = applyOverrides(\n applyEnvDefaults({\n sourceLanguage: 'en',\n targetLanguages: [],\n translationMode: 'missing',\n encodeUnicode: false,\n provider: 'openai',\n batchSize: 20,\n verbose: false,\n files: {\n input: 'i18n/i18n.properties',\n },\n cache: {\n enabled: true,\n },\n rules: [],\n ...loaded,\n }),\n options?.overrides\n );\n\n if (merged.glossary) {\n merged.glossary = GlossaryManager.loadGlossary(merged.glossary);\n }\n\n validateConfig(merged);\n return merged;\n}\n\nexport function validateConfig(config: TranslatorConfig): void {\n if (!config.targetLanguages || config.targetLanguages.length === 0) {\n throw new Error('At least one target language is required');\n }\n\n if (config.provider !== 'openai') {\n throw new Error(`Unsupported provider: ${config.provider}`);\n }\n\n if (!config.files?.input) {\n throw new Error('files.input is required');\n }\n\n const mode = config.translationMode ?? 'missing';\n if (mode !== 'missing' && mode !== 'overwrite') {\n throw new Error(`Unsupported translationMode: ${mode}`);\n }\n\n if (config.batchSize !== undefined && config.batchSize < 1) {\n throw new Error('batchSize must be at least 1');\n }\n\n if (!config.providerOptions?.apiKey) {\n throw new Error('OpenAI API key is required via config.providerOptions.apiKey or OPENAI_API_KEY');\n }\n}\n\nexport function createDefaultConfig(\n apiKey: string,\n targetLanguages: string[]\n): TranslatorConfig {\n return {\n sourceLanguage: 'en',\n targetLanguages,\n translationMode: 'missing',\n encodeUnicode: false,\n provider: 'openai',\n providerOptions: {\n apiKey,\n model: 'gpt-4.1-mini',\n temperature: 0,\n maxOutputTokens: 4000,\n },\n files: {\n input: 'i18n/i18n.properties',\n },\n cache: {\n enabled: true,\n },\n rules: [],\n batchSize: 20,\n verbose: false,\n };\n}\n\nfunction applyEnvDefaults(config: TranslatorConfig): TranslatorConfig {\n const providerOptions = {\n ...config.providerOptions,\n };\n\n if (!providerOptions.apiKey && process.env.OPENAI_API_KEY) {\n providerOptions.apiKey = process.env.OPENAI_API_KEY;\n }\n\n if (!providerOptions.model && process.env.OPENAI_MODEL) {\n providerOptions.model = process.env.OPENAI_MODEL;\n }\n\n return {\n ...config,\n providerOptions,\n };\n}\n\nfunction applyOverrides(\n config: TranslatorConfig,\n overrides?: TranslatorConfigOverrides\n): TranslatorConfig {\n if (!overrides) {\n return config;\n }\n\n return {\n ...config,\n provider: overrides.provider ?? config.provider,\n translationMode: overrides.translationMode ?? config.translationMode,\n encodeUnicode: overrides.encodeUnicode ?? config.encodeUnicode,\n languageOptions: config.languageOptions,\n targetLanguages: overrides.targetLanguages ?? config.targetLanguages,\n verbose: overrides.verbose ?? config.verbose,\n files: {\n ...config.files,\n input: overrides.input ?? config.files?.input,\n },\n providerOptions: {\n ...config.providerOptions,\n model: overrides.model ?? config.providerOptions?.model,\n },\n };\n}\n\nfunction findDefaultConfigPath(cwd: string): string | undefined {\n for (const fileName of DEFAULT_CONFIG_FILES) {\n const candidate = path.join(cwd, fileName);\n if (fs.existsSync(candidate)) {\n return candidate;\n }\n }\n\n return undefined;\n}\n\nasync function loadConfigFile(configPath: string): Promise<Partial<TranslatorConfig>> {\n if (!fs.existsSync(configPath)) {\n throw new Error(`Config file not found: ${configPath}`);\n }\n\n if (configPath.endsWith('.json')) {\n return JSON.parse(fs.readFileSync(configPath, 'utf-8')) as Partial<TranslatorConfig>;\n }\n\n if (configPath.endsWith('.js') || configPath.endsWith('.mjs')) {\n const loaded = await import(pathToFileURL(configPath).href);\n return (loaded.default ?? loaded) as Partial<TranslatorConfig>;\n }\n\n throw new Error(`Unsupported config format: ${configPath}`);\n}\n\nfunction validateLoadedConfigShape(\n config: Partial<TranslatorConfig>,\n configPath: string\n): void {\n assertPlainObject(config, 'Config file', configPath);\n validateAllowedKeys(config, TOP_LEVEL_CONFIG_KEYS, 'Config file', configPath);\n\n if (config.providerOptions !== undefined) {\n assertPlainObject(config.providerOptions, 'config.providerOptions', configPath);\n validateAllowedKeys(\n config.providerOptions,\n PROVIDER_OPTIONS_KEYS,\n 'config.providerOptions',\n configPath\n );\n }\n\n if (config.files !== undefined) {\n assertPlainObject(config.files, 'config.files', configPath);\n validateAllowedKeys(config.files, FILE_CONFIG_KEYS, 'config.files', configPath);\n }\n\n if (config.cache !== undefined) {\n assertPlainObject(config.cache, 'config.cache', configPath);\n validateAllowedKeys(config.cache, CACHE_CONFIG_KEYS, 'config.cache', configPath);\n }\n\n if (config.languageOptions !== undefined) {\n assertPlainObject(config.languageOptions, 'config.languageOptions', configPath);\n for (const [language, value] of Object.entries(config.languageOptions)) {\n assertPlainObject(value, `config.languageOptions.${language}`, configPath);\n validateAllowedKeys(\n value as LanguageConfig,\n LANGUAGE_CONFIG_KEYS,\n `config.languageOptions.${language}`,\n configPath\n );\n }\n }\n}\n\nfunction validateAllowedKeys(\n value: object,\n allowedKeys: readonly string[],\n pathLabel: string,\n configPath: string\n): void {\n for (const key of Object.keys(value)) {\n if (!allowedKeys.includes(key)) {\n throw new Error(\n `Unknown option \"${key}\" in ${pathLabel} of ${configPath}.`\n );\n }\n }\n}\n\nfunction assertPlainObject(value: unknown, pathLabel: string, configPath: string): void {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(`${pathLabel} in ${configPath} must be an object.`);\n }\n}\n\nexport function resolveTranslationMode(\n preferred?: TranslationMode,\n fallback: TranslationMode = 'missing'\n): TranslationMode {\n return preferred ?? fallback;\n}\n\nfunction loadEnvFiles(cwd: string): void {\n const envPath = path.join(cwd, '.env');\n const envLocalPath = path.join(cwd, '.env.local');\n\n if (fs.existsSync(envPath)) {\n dotenv.config({ path: envPath });\n }\n\n if (fs.existsSync(envLocalPath)) {\n dotenv.config({ path: envLocalPath, override: true });\n }\n}\n","/**\n * Logging utility for debug output\n */\n\nimport { Logger } from '../types.js';\n\n/**\n * Default logger implementation\n * Uses console for output, respects DEBUG environment variable\n */\nexport class ConsoleLogger implements Logger {\n private debug_enabled: boolean;\n private prefix: string;\n\n constructor(prefix: string = '@concircle/i18n-ai-translator', debug?: boolean) {\n this.prefix = prefix;\n this.debug_enabled =\n debug ??\n (typeof process !== 'undefined' &&\n process.env.DEBUG?.includes('i18n-ai-translator')) ??\n false;\n }\n\n debug(message: string, data?: Record<string, unknown>): void {\n if (this.debug_enabled) {\n console.debug(`[${this.prefix}:DEBUG] ${message}`, data || '');\n }\n }\n\n info(message: string, data?: Record<string, unknown>): void {\n console.info(`[${this.prefix}:INFO] ${message}`, data || '');\n }\n\n warn(message: string, data?: Record<string, unknown>): void {\n console.warn(`[${this.prefix}:WARN] ${message}`, data || '');\n }\n\n error(message: string, error?: Error): void {\n console.error(`[${this.prefix}:ERROR] ${message}`, error?.message || '');\n if (error?.stack && this.debug_enabled) {\n console.error(error.stack);\n }\n }\n\n setDebug(enabled: boolean): void {\n this.debug_enabled = enabled;\n }\n\n isDebugEnabled(): boolean {\n return this.debug_enabled;\n }\n}\n\n/**\n * No-op logger (for silent mode)\n */\nexport class SilentLogger implements Logger {\n debug(_message: string, _data?: Record<string, unknown>): void {\n // no-op\n }\n\n info(_message: string, _data?: Record<string, unknown>): void {\n // no-op\n }\n\n warn(_message: string, _data?: Record<string, unknown>): void {\n // no-op\n }\n\n error(_message: string, _error?: Error): void {\n // no-op\n }\n}\n\n/**\n * Create a logger instance\n *\n * @param debug Enable debug output\n * @param silent Enable silent mode (no output)\n * @returns Logger instance\n */\nexport function createLogger(debug: boolean = false, silent: boolean = false): Logger {\n if (silent) {\n return new SilentLogger();\n }\n\n return new ConsoleLogger('@concircle/i18n-ai-translator', debug);\n}\n","import { translateProject } from './translator.js';\nimport { TranslationMode } from './types.js';\n\ninterface CliIO {\n log(message: string): void;\n error(message: string): void;\n}\n\nconst SUPPORTED_MODES: TranslationMode[] = ['missing', 'overwrite'];\nconst SUPPORTED_PROVIDERS = ['openai'] as const;\n\nexport async function runCli(\n argv: string[],\n io: CliIO = {\n log: console.log,\n error: console.error,\n }\n ): Promise<number> {\n let args;\n try {\n args = parseArgs(argv);\n } catch (error) {\n io.error(error instanceof Error ? error.message : String(error));\n io.log(getHelpText());\n return 1;\n }\n\n if (args.help || argv.length === 0) {\n io.log(getHelpText());\n return 0;\n }\n\n try {\n const result = await translateProject({\n configPath: args.config,\n inputPath: args.input,\n languages: args.languages,\n mode: args.mode,\n provider: args.provider as 'openai' | undefined,\n model: args.model,\n dryRun: args.dryRun,\n encodeUnicode: args.encodeUnicode,\n verbose: args.verbose,\n });\n\n io.log(JSON.stringify(result, null, 2));\n return 0;\n } catch (error) {\n io.error(error instanceof Error ? error.message : String(error));\n return 1;\n }\n}\n\nexport function parseArgs(argv: string[]): {\n config?: string;\n input?: string;\n languages?: string[];\n mode?: TranslationMode;\n provider?: string;\n model?: string;\n dryRun: boolean;\n encodeUnicode?: boolean;\n verbose: boolean;\n help: boolean;\n} {\n const result = {\n dryRun: false,\n verbose: false,\n help: false,\n } as {\n config?: string;\n input?: string;\n languages?: string[];\n mode?: TranslationMode;\n provider?: string;\n model?: string;\n dryRun: boolean;\n encodeUnicode?: boolean;\n verbose: boolean;\n help: boolean;\n };\n\n const tokens = [...argv];\n if (tokens[0] === 'help') {\n tokens.shift();\n result.help = true;\n }\n\n while (tokens.length > 0) {\n const token = tokens.shift()!;\n\n switch (token) {\n case '--config':\n result.config = readOptionValue(tokens, '--config');\n break;\n case '--input':\n result.input = readOptionValue(tokens, '--input');\n break;\n case '--languages':\n result.languages = readOptionValue(tokens, '--languages')\n .split(',')\n .map((value) => value.trim())\n .filter(Boolean);\n break;\n case '--mode': {\n const value = readOptionValue(tokens, '--mode');\n if (!SUPPORTED_MODES.includes(value as TranslationMode)) {\n throw new Error(\n `Unknown value \"${value}\" for option \"--mode\". Expected one of: ${SUPPORTED_MODES.join(', ')}.`\n );\n }\n result.mode = value as TranslationMode;\n break;\n }\n case '--provider': {\n const value = readOptionValue(tokens, '--provider');\n if (!SUPPORTED_PROVIDERS.includes(value as (typeof SUPPORTED_PROVIDERS)[number])) {\n throw new Error(\n `Unknown value \"${value}\" for option \"--provider\". Expected one of: ${SUPPORTED_PROVIDERS.join(', ')}.`\n );\n }\n result.provider = value;\n break;\n }\n case '--model':\n result.model = readOptionValue(tokens, '--model');\n break;\n case '--dry-run':\n result.dryRun = true;\n break;\n case '--encode-unicode':\n result.encodeUnicode = true;\n break;\n case '--verbose':\n result.verbose = true;\n break;\n case '--help':\n case '-h':\n result.help = true;\n break;\n default:\n if (token.startsWith('-')) {\n throw new Error(`Unknown option \"${token}\". Use --help to see supported options.`);\n }\n throw new Error(`Unknown argument: ${token}`);\n }\n }\n\n return result;\n}\n\nfunction readOptionValue(tokens: string[], option: string): string {\n const value = tokens.shift();\n if (!value || value.startsWith('-')) {\n throw new Error(`Missing value for option \"${option}\".`);\n }\n\n return value;\n}\n\nfunction getHelpText(): string {\n return [\n 'i18n-ai-translator [options]',\n '',\n 'Options:',\n ' --config <path> Path to i18n-ai config file',\n ' --input <path> Source i18n.properties file',\n ' --languages <list> Comma-separated target languages',\n ' --mode <mode> missing | overwrite',\n ' --provider <name> Reserved for future providers',\n ' --model <name> Override provider model',\n ' --dry-run Do not write files',\n ' --encode-unicode Write non-ASCII characters as \\\\uXXXX escapes',\n ' --verbose Enable verbose logs',\n ' --help Show this help text',\n ].join('\\n');\n}\n"],"mappings":"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","names":["path","crypto","fs","path","FileCache","options","homeDir","cacheKey","filePath","entry","translation","fileName","entriesCount","input","json","fs","GlossaryManager","_GlossaryManager","glossary","source","filePath","content","parsed","typed","language","terms","label","term","PLACEHOLDER_PATTERN","PROTECTED_SEQUENCE_PATTERN","MASKABLE_PATTERN","extractPlaceholders","text","match","maskPlaceholders","text","tokens","index","masked","MASKABLE_PATTERN","placeholder","token","restorePlaceholders","current","validatePlaceholderIntegrity","sourceText","translatedText","expected","extractPlaceholders","actual","fs","path","parsePropertiesDocument","content","eol","hasTrailingNewline","rawLines","parseLine","readPropertiesDocument","filePath","createEmptyPropertiesDocument","listPropertiesEntries","document","entries","line","cloneDocument","upsertPropertiesValue","key","value","existing","findEntryLine","serializePropertiesDocument","options","body","serializeLine","writePropertiesDocument","dir","getLanguageFilePath","sourceFilePath","language","pattern","outputDir","sourceDir","path","ext","baseName","fileName","parseLine","raw","leadingWhitespace","body","separatorIndex","separator","valueStartIndex","index","char","lookahead","rawKey","rawValue","decodePropertiesToken","serializeLine","line","options","escapePropertiesToken","escapePropertiesValue","findEntryLine","document","key","value","_","hex","escaped","OpenAI","BaseAIProvider","TRANSLATION_SCHEMA","OpenAIProvider","BaseAIProvider","options","OpenAI","request","rawResponse","buildSystemPrompt","buildPayload","parsed","item","placeholder","fs","path","pathToFileURL","dotenv","DEFAULT_CONFIG_FILES","TOP_LEVEL_CONFIG_KEYS","PROVIDER_OPTIONS_KEYS","FILE_CONFIG_KEYS","CACHE_CONFIG_KEYS","LANGUAGE_CONFIG_KEYS","loadTranslatorConfig","options","cwd","loadEnvFiles","configPath","path","findDefaultConfigPath","loaded","loadConfigFile","validateLoadedConfigShape","merged","applyOverrides","applyEnvDefaults","GlossaryManager","validateConfig","config","mode","applyEnvDefaults","config","providerOptions","applyOverrides","overrides","findDefaultConfigPath","cwd","fileName","DEFAULT_CONFIG_FILES","candidate","path","fs","loadConfigFile","configPath","loaded","pathToFileURL","validateLoadedConfigShape","assertPlainObject","validateAllowedKeys","TOP_LEVEL_CONFIG_KEYS","PROVIDER_OPTIONS_KEYS","FILE_CONFIG_KEYS","CACHE_CONFIG_KEYS","language","value","LANGUAGE_CONFIG_KEYS","allowedKeys","pathLabel","key","resolveTranslationMode","preferred","fallback","loadEnvFiles","envPath","envLocalPath","dotenv","ConsoleLogger","prefix","debug","message","data","error","enabled","SilentLogger","_message","_data","_error","Translator","_Translator","config","validateConfig","createProvider","GlossaryManager","FileCache","ConsoleLogger","SilentLogger","options","loadTranslatorConfig","inputPath","path","sourceDocument","readPropertiesDocument","sourceEntries","listPropertiesEntries","sourceKeys","languages","translationMode","resolveTranslationMode","result","language","targetPath","getLanguageFilePath","targetDocument","targetEntries","keysToTranslate","key","existingValue","translatedMap","outputDocument","cloneDocument","translatedValue","upsertPropertiesValue","errors","writePropertiesDocument","keys","glossaryTerms","rules","batchSize","start","batchKeys","uncachedItems","sourceValue","placeholderInfo","maskPlaceholders","cacheKey","cached","response","item","translated","findResponseItem","restored","restorePlaceholders","validation","validatePlaceholderIntegrity","translateProject","createProvider","config","OpenAIProvider","findResponseItem","items","key","item","candidate","SUPPORTED_MODES","SUPPORTED_PROVIDERS","runCli","argv","io","args","parseArgs","error","getHelpText","result","translateProject","tokens","token","readOptionValue","value","option"]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";var pe=Object.create;var M=Object.defineProperty;var fe=Object.getOwnPropertyDescriptor;var me=Object.getOwnPropertyNames;var he=Object.getPrototypeOf,ye=Object.prototype.hasOwnProperty;var ve=(r,e)=>{for(var t in e)M(r,t,{get:e[t],enumerable:!0})},re=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of me(e))!ye.call(r,o)&&o!==t&&M(r,o,{get:()=>e[o],enumerable:!(n=fe(e,o))||n.enumerable});return r};var u=(r,e,t)=>(t=r!=null?pe(he(r)):{},re(e||!r||!r.__esModule?M(t,"default",{value:r,enumerable:!0}):t,r)),Pe=r=>re(M({},"__esModule",{value:!0}),r);var ze={};ve(ze,{BaseAIProvider:()=>x,ConsoleLogger:()=>O,FileCache:()=>v,GlossaryManager:()=>P,OpenAIProvider:()=>C,SilentLogger:()=>b,Translator:()=>A,cloneDocument:()=>j,createDefaultConfig:()=>le,createDocumentFromEntries:()=>se,createEmptyPropertiesDocument:()=>z,createLogger:()=>ce,extractPlaceholders:()=>G,getLanguageFilePath:()=>q,hasPlaceholders:()=>ne,listPropertiesEntries:()=>I,loadTranslatorConfig:()=>S,maskPlaceholders:()=>U,parsePropertiesDocument:()=>Q,readPropertiesDocument:()=>R,resolveTranslationMode:()=>J,restorePlaceholders:()=>K,serializePropertiesDocument:()=>X,translateFile:()=>de,translateProject:()=>ue,upsertPropertiesValue:()=>B,validateConfig:()=>$,validatePlaceholderIntegrity:()=>V,writePropertiesDocument:()=>W});module.exports=Pe(ze);var ge=u(require("path"),1);var te=u(require("crypto"),1),g=u(require("fs"),1),D=u(require("path"),1),v=class{constructor(e){this.enabled=e?.enabled??!0,this.ttlMs=e?.ttlMs??10080*60*1e3;let t=process.env.HOME||process.env.USERPROFILE||"/tmp";this.cacheDir=e?.dir??D.default.join(t,".i18n-ai-translator-cache"),this.enabled&&!g.default.existsSync(this.cacheDir)&&g.default.mkdirSync(this.cacheDir,{recursive:!0})}get(e){if(!this.enabled)return null;let t=D.default.join(this.cacheDir,`${e}.json`);if(!g.default.existsSync(t))return null;try{let n=JSON.parse(g.default.readFileSync(t,"utf-8"));return Date.now()-n.createdAt>this.ttlMs?(g.default.unlinkSync(t),null):n.translation}catch{return null}}set(e,t){if(!this.enabled)return;let n=D.default.join(this.cacheDir,`${e}.json`),o={translation:t,createdAt:Date.now()};try{g.default.writeFileSync(n,JSON.stringify(o),"utf-8")}catch{}}clear(){if(!(!this.enabled||!g.default.existsSync(this.cacheDir)))for(let e of g.default.readdirSync(this.cacheDir))e.endsWith(".json")&&g.default.unlinkSync(D.default.join(this.cacheDir,e))}getStats(){let e=0;return this.enabled&&g.default.existsSync(this.cacheDir)&&(e=g.default.readdirSync(this.cacheDir).filter(t=>t.endsWith(".json")).length),{enabled:this.enabled,ttlMs:this.ttlMs,cacheDir:this.cacheDir,entriesCount:e}}static createKey(e){let t=JSON.stringify(e);return te.default.createHash("sha256").update(t).digest("hex")}};var H=u(require("fs"),1),P=class r{constructor(e){this.glossary=e??{}}static loadGlossary(e){return e?typeof e=="string"?r.loadFromFile(e):(r.validateGlossary(e),e):{}}static loadFromFile(e){if(!H.default.existsSync(e))throw new Error(`Glossary file not found: ${e}`);let t=H.default.readFileSync(e,"utf-8"),n=JSON.parse(t);return r.validateGlossary(n),n}static validateGlossary(e){if(!e||typeof e!="object"||Array.isArray(e))throw new Error("Glossary must be an object");let t=e;if(r.validateTerms(t.shared,"shared"),t.languages)for(let[n,o]of Object.entries(t.languages))r.validateTerms(o,`languages.${n}`)}static validateTerms(e,t){if(e){if(!Array.isArray(e))throw new Error(`${t} glossary terms must be an array`);for(let n of e){if(!n||typeof n!="object")throw new Error(`${t} glossary term must be an object`);if(!n.source||typeof n.source!="string")throw new Error(`${t} glossary term requires a string source value`);if(n.target!==void 0&&typeof n.target!="string")throw new Error(`${t} glossary term target must be a string`);if(n.context!==void 0&&typeof n.context!="string")throw new Error(`${t} glossary term context must be a string`);if(n.doNotTranslate!==void 0&&typeof n.doNotTranslate!="boolean")throw new Error(`${t} glossary term doNotTranslate must be a boolean`)}}}getGlossary(){return this.glossary}getTermsForLanguage(e){return[...this.glossary.shared??[],...this.glossary.languages?.[e]??[]]}hasTerms(e){return this.getTermsForLanguage(e).length>0}toJSON(){return JSON.stringify(this.glossary,null,2)}};var Te=/\{\d+\}|\{[a-zA-Z_][\w.-]*\}|\$\{[a-zA-Z_][\w.-]*\}|%\d+\$[sdif]|%[sdif]/g,we=/\\[nrtf]/g,Z=new RegExp(`${Te.source}|${we.source}`,"g");function G(r){return Array.from(r.matchAll(Z),e=>e[0])}function ne(r){return new RegExp(Z).test(r)}function U(r){let e=[],t=0,n=r.replace(Z,o=>{let s=`__I18N_PH_${t}__`;return t+=1,e.push({token:s,placeholder:o}),s});return{original:r,masked:n,tokens:e}}function K(r,e){return e.reduce((t,n)=>t.split(n.token).join(n.placeholder),r)}function V(r,e){let t=G(r).sort(),n=G(e).sort();return{valid:t.length===n.length&&t.every((o,s)=>o===n[s]),expected:t,actual:n}}var E=u(require("fs"),1),w=u(require("path"),1);function Q(r){let t=r.match(/\r\n|\n/)?.[0]??`
|
|
2
|
+
`,n=r.length>0&&r.endsWith(t),o=r.length===0?[]:r.split(/\r?\n/);return n&&o[o.length-1]===""&&o.pop(),{lines:o.map(Oe),eol:t,hasTrailingNewline:n}}function R(r){return E.default.existsSync(r)?Q(E.default.readFileSync(r,"utf-8")):z()}function z(){return{lines:[],eol:`
|
|
3
|
+
`,hasTrailingNewline:!0}}function I(r){let e={};for(let t of r.lines)t.type==="entry"&&(e[t.key]=t.value);return e}function j(r){return{eol:r.eol,hasTrailingNewline:r.hasTrailingNewline,lines:r.lines.map(e=>({...e}))}}function B(r,e,t){let n=Ee(r,e);if(n){n.value=t,n.modified=!0;return}r.lines.length>0&&r.lines[r.lines.length-1].type!=="blank"&&r.lines.push({type:"blank",raw:""}),r.lines.push({type:"entry",raw:"",key:e,value:t,separator:"=",leadingWhitespace:"",modified:!0})}function X(r,e={}){let n=r.lines.map(o=>be(o,e)).join(r.eol);return n.length===0?"":r.hasTrailingNewline?`${n}${r.eol}`:n}function W(r,e,t={}){let n=w.default.dirname(r);E.default.existsSync(n)||E.default.mkdirSync(n,{recursive:!0}),E.default.writeFileSync(r,X(e,t),"utf-8")}function se(r){return{lines:Object.entries(r).map(([t,n])=>({type:"entry",raw:"",key:t,value:n,separator:"=",leadingWhitespace:"",modified:!0})),eol:`
|
|
4
|
+
`,hasTrailingNewline:!0}}function q(r,e,t,n){let o=n??w.default.dirname(r),s=w.default.extname(r)||".properties",d=w.default.basename(r,s);if(t){let i=t.replace("{baseName}",d).replace("{language}",e).replace("{ext}",s);return w.default.join(o,i)}let p=d==="i18n"?`i18n_${e}${s}`:`${d}_${e}${s}`;return w.default.join(o,p)}function Oe(r){if(r.trim()==="")return{type:"blank",raw:r};if(/^\s*[#!]/.test(r))return{type:"comment",raw:r};let e=r.match(/^\s*/)[0],t=r.slice(e.length),n=-1,o="=",s=-1;for(let i=0;i<t.length;i+=1){let l=t[i];if((i>0?t[i-1]:"")!=="\\"){if(l==="="||l===":"){n=i,o=l;break}if(l===" "||l===" "||l==="\f"){n=i,o=l;let a=i;for(;a<t.length&&(t[a]===" "||t[a]===" "||t[a]==="\f");)a+=1;a<t.length&&(t[a]==="="||t[a]===":")&&(o=t[a],s=a+1);break}}}let d=n>=0?t.slice(0,n):t,p=n>=0?t.slice(s>=0?s:n+1).replace(/^[ \t\f]*/,""):"";return{type:"entry",raw:r,key:oe(d.trimEnd()),value:oe(p),separator:o,leadingWhitespace:e,modified:!1}}function be(r,e){return r.type!=="entry"||!r.modified&&r.raw?r.raw:`${r.leadingWhitespace}${xe(r.key)}${r.separator}${Ce(r.value,e)}`}function Ee(r,e){return r.lines.find(t=>t.type==="entry"&&t.key===e)}function oe(r){return r.replace(/\\u([0-9a-fA-F]{4})/g,(e,t)=>String.fromCharCode(parseInt(t,16))).replace(/\\t/g," ").replace(/\\r/g,"\r").replace(/\\n/g,`
|
|
5
|
+
`).replace(/\\f/g,"\f").replace(/\\:/g,":").replace(/\\=/g,"=").replace(/\\\\/g,"\\")}function xe(r){return r.replace(/\\/g,"\\\\").replace(/:/g,"\\:").replace(/=/g,"\\=")}function Ce(r,e){let t=r.replace(/\\/g,"\\\\").replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t");return e.encodeUnicode?Array.from(t,n=>n.charCodeAt(0)>127?`\\u${n.charCodeAt(0).toString(16).padStart(4,"0")}`:n).join(""):t}var ie=u(require("openai"),1);var x=class{};var Le={type:"object",additionalProperties:!1,required:["items"],properties:{items:{type:"array",items:{type:"object",additionalProperties:!1,required:["key","translatedValue"],properties:{key:{type:"string"},translatedValue:{type:"string"}}}}}},C=class extends x{constructor(e){if(super(),!e.apiKey)throw new Error("OpenAI API key is required");this.client=new ie.default({apiKey:e.apiKey,baseURL:e.baseURL,organization:e.organization}),this.options={...e,model:e.model??"gpt-4.1-mini",temperature:e.temperature??0,maxOutputTokens:e.maxOutputTokens??4e3}}getName(){return"openai"}async translateBatch(e){let n=(await this.client.responses.create({model:e.model??this.options.model,temperature:this.options.temperature,max_output_tokens:this.options.maxOutputTokens,text:{format:{type:"json_schema",name:"translation_batch",schema:Le,strict:!0}},input:[{role:"system",content:ke()},{role:"user",content:JSON.stringify(Se(e))}]})).output_text?.trim();if(!n)throw new Error("OpenAI Responses API returned an empty response");let o=JSON.parse(n);if(!Array.isArray(o.items))throw new Error("OpenAI response is missing items array");return{provider:"openai",rawResponse:n,items:o.items.map(s=>{if(!s.key||typeof s.translatedValue!="string")throw new Error("OpenAI response contains an invalid translation item");return{key:s.key,translatedValue:s.translatedValue}})}}};function ke(){return["You translate SAP UI5 i18n property values.","Return JSON only.","Translate values, never keys.","Do not add explanations.","Keep placeholder tokens like __I18N_PH_0__ unchanged.","Preserve surrounding punctuation and semantic meaning.","Apply glossary terms strictly when provided."].join(" ")}function Se(r){return{sourceLanguage:r.sourceLanguage,targetLanguage:r.targetLanguage,glossaryTerms:r.glossaryTerms,rules:r.rules,items:r.items.map(e=>({key:e.key,value:e.maskedValue,placeholders:e.placeholders.map(t=>t.placeholder)}))}}var k=u(require("fs"),1),N=u(require("path"),1),ae=require("url"),ee=u(require("dotenv"),1);var Ae=["i18n-ai.config.json","i18n-ai.config.mjs","i18n-ai.config.js","ui5-ai-i18n.config.json","ui5-ai-i18n.config.mjs","ui5-ai-i18n.config.js"],De=["sourceLanguage","targetLanguages","translationMode","encodeUnicode","languageOptions","provider","providerOptions","files","glossary","cache","rules","batchSize","verbose"],Re=["apiKey","model","baseURL","organization","temperature","maxOutputTokens"],Ie=["input","outputDir","languageFilePattern"],je=["enabled","ttlMs","dir"],_e=["encodeUnicode"];async function S(r){let e=r?.cwd??process.cwd();Ue(e);let t=r?.configPath?N.default.resolve(e,r.configPath):Fe(e),n=t?await Me(t):{};t&&Ge(n,t);let o=$e(Ne({sourceLanguage:"en",targetLanguages:[],translationMode:"missing",encodeUnicode:!1,provider:"openai",batchSize:20,verbose:!1,files:{input:"i18n/i18n.properties"},cache:{enabled:!0},rules:[],...n}),r?.overrides);return o.glossary&&(o.glossary=P.loadGlossary(o.glossary)),$(o),o}function $(r){if(!r.targetLanguages||r.targetLanguages.length===0)throw new Error("At least one target language is required");if(r.provider!=="openai")throw new Error(`Unsupported provider: ${r.provider}`);if(!r.files?.input)throw new Error("files.input is required");let e=r.translationMode??"missing";if(e!=="missing"&&e!=="overwrite")throw new Error(`Unsupported translationMode: ${e}`);if(r.batchSize!==void 0&&r.batchSize<1)throw new Error("batchSize must be at least 1");if(!r.providerOptions?.apiKey)throw new Error("OpenAI API key is required via config.providerOptions.apiKey or OPENAI_API_KEY")}function le(r,e){return{sourceLanguage:"en",targetLanguages:e,translationMode:"missing",encodeUnicode:!1,provider:"openai",providerOptions:{apiKey:r,model:"gpt-4.1-mini",temperature:0,maxOutputTokens:4e3},files:{input:"i18n/i18n.properties"},cache:{enabled:!0},rules:[],batchSize:20,verbose:!1}}function Ne(r){let e={...r.providerOptions};return!e.apiKey&&process.env.OPENAI_API_KEY&&(e.apiKey=process.env.OPENAI_API_KEY),!e.model&&process.env.OPENAI_MODEL&&(e.model=process.env.OPENAI_MODEL),{...r,providerOptions:e}}function $e(r,e){return e?{...r,provider:e.provider??r.provider,translationMode:e.translationMode??r.translationMode,encodeUnicode:e.encodeUnicode??r.encodeUnicode,languageOptions:r.languageOptions,targetLanguages:e.targetLanguages??r.targetLanguages,verbose:e.verbose??r.verbose,files:{...r.files,input:e.input??r.files?.input},providerOptions:{...r.providerOptions,model:e.model??r.providerOptions?.model}}:r}function Fe(r){for(let e of Ae){let t=N.default.join(r,e);if(k.default.existsSync(t))return t}}async function Me(r){if(!k.default.existsSync(r))throw new Error(`Config file not found: ${r}`);if(r.endsWith(".json"))return JSON.parse(k.default.readFileSync(r,"utf-8"));if(r.endsWith(".js")||r.endsWith(".mjs")){let e=await import((0,ae.pathToFileURL)(r).href);return e.default??e}throw new Error(`Unsupported config format: ${r}`)}function Ge(r,e){if(L(r,"Config file",e),_(r,De,"Config file",e),r.providerOptions!==void 0&&(L(r.providerOptions,"config.providerOptions",e),_(r.providerOptions,Re,"config.providerOptions",e)),r.files!==void 0&&(L(r.files,"config.files",e),_(r.files,Ie,"config.files",e)),r.cache!==void 0&&(L(r.cache,"config.cache",e),_(r.cache,je,"config.cache",e)),r.languageOptions!==void 0){L(r.languageOptions,"config.languageOptions",e);for(let[t,n]of Object.entries(r.languageOptions))L(n,`config.languageOptions.${t}`,e),_(n,_e,`config.languageOptions.${t}`,e)}}function _(r,e,t,n){for(let o of Object.keys(r))if(!e.includes(o))throw new Error(`Unknown option "${o}" in ${t} of ${n}.`)}function L(r,e,t){if(!r||typeof r!="object"||Array.isArray(r))throw new Error(`${e} in ${t} must be an object.`)}function J(r,e="missing"){return r??e}function Ue(r){let e=N.default.join(r,".env"),t=N.default.join(r,".env.local");k.default.existsSync(e)&&ee.default.config({path:e}),k.default.existsSync(t)&&ee.default.config({path:t,override:!0})}var O=class{constructor(e="@concircle/i18n-ai-translator",t){this.prefix=e,this.debug_enabled=t??(typeof process<"u"&&process.env.DEBUG?.includes("i18n-ai-translator"))??!1}debug(e,t){this.debug_enabled&&console.debug(`[${this.prefix}:DEBUG] ${e}`,t||"")}info(e,t){console.info(`[${this.prefix}:INFO] ${e}`,t||"")}warn(e,t){console.warn(`[${this.prefix}:WARN] ${e}`,t||"")}error(e,t){console.error(`[${this.prefix}:ERROR] ${e}`,t?.message||""),t?.stack&&this.debug_enabled&&console.error(t.stack)}setDebug(e){this.debug_enabled=e}isDebugEnabled(){return this.debug_enabled}},b=class{debug(e,t){}info(e,t){}warn(e,t){}error(e,t){}};function ce(r=!1,e=!1){return e?new b:new O("@concircle/i18n-ai-translator",r)}var A=class r{constructor(e){$(e),this.config=e,this.provider=Ke(e),this.glossaryManager=new P(e.glossary),this.cache=new v(e.cache),this.logger=e.verbose?new O("@concircle/i18n-ai-translator",!0):new b}static async fromConfig(e){let t=await S(e);return new r(t)}async translateProject(e){let t=e?.configPath||e?.cwd?await S({configPath:e.configPath,cwd:e.cwd,overrides:{input:e.inputPath,targetLanguages:e.languages,translationMode:e.mode,encodeUnicode:e.encodeUnicode,provider:e.provider,model:e.model,verbose:e.verbose}}):this.config;return(t===this.config?this:new r(t)).translateFile({inputPath:e?.inputPath,languages:e?.languages,mode:e?.mode,dryRun:e?.dryRun})}async translateFile(e){let t=ge.default.resolve(process.cwd(),e?.inputPath??this.config.files?.input??"i18n/i18n.properties"),n=R(t),o=I(n),s=Object.keys(o),d=e?.languages??this.config.targetLanguages,p=J(e?.mode,this.config.translationMode),i={sourceFile:t,translationMode:p,translations:{}};for(let l of d){this.logger.info("Translating language",{language:l});let f=q(t,l,this.config.files?.languageFilePattern,this.config.files?.outputDir),a=R(f),c=I(a),h=s.filter(Y=>{if(p==="overwrite")return!0;let F=c[Y];return F===void 0||F===""}),m=await this.translateEntries(o,h,l),y=a.lines.length>0?j(a):j(n);for(let[Y,F]of Object.entries(m))B(y,Y,F);let T=[];e?.dryRun||W(f,y,{encodeUnicode:this.resolveEncodeUnicode(l)}),i.translations[l]={outputFile:f,success:T.length===0,translatedKeysCount:Object.keys(m).length,skippedKeysCount:s.length-h.length,errors:T,dryRun:e?.dryRun??!1}}return i}clearCache(){this.cache.clear()}getCacheStats(){return this.cache.getStats()}getGlossaryTerms(e){return this.glossaryManager.getTermsForLanguage(e)}async translateEntries(e,t,n){let o=this.glossaryManager.getTermsForLanguage(n),s=this.config.rules??[],d=this.config.batchSize??20,p={};for(let i=0;i<t.length;i+=d){let l=t.slice(i,i+d),f=[];for(let c of l){let h=e[c],m=U(h),y=v.createKey({provider:this.provider.getName(),model:this.config.providerOptions?.model??"default",sourceLanguage:this.config.sourceLanguage??"en",targetLanguage:n,sourceValue:h,glossaryTerms:o,rules:s}),T=this.cache.get(y);if(T){p[c]=T;continue}f.push({key:c,sourceValue:h,maskedValue:m.masked,placeholders:m.tokens})}if(f.length===0)continue;let a=await this.provider.translateBatch({sourceLanguage:this.config.sourceLanguage??"en",targetLanguage:n,items:f,glossaryTerms:o,rules:s,model:this.config.providerOptions?.model});for(let c of f){let h=Ve(a.items,c.key),m=K(h.translatedValue,c.placeholders),y=V(c.sourceValue,m);if(!y.valid)throw new Error(`Placeholder validation failed for key "${c.key}" in ${n}: expected ${y.expected.join(", ")}, got ${y.actual.join(", ")}`);p[c.key]=m;let T=v.createKey({provider:this.provider.getName(),model:this.config.providerOptions?.model??"default",sourceLanguage:this.config.sourceLanguage??"en",targetLanguage:n,sourceValue:c.sourceValue,glossaryTerms:o,rules:s});this.cache.set(T,m)}}return p}resolveEncodeUnicode(e){return this.config.languageOptions?.[e]?.encodeUnicode??this.config.encodeUnicode??!1}};async function ue(r){let e=await S({configPath:r?.configPath,cwd:r?.cwd,overrides:{input:r?.inputPath,targetLanguages:r?.languages,translationMode:r?.mode,encodeUnicode:r?.encodeUnicode,provider:r?.provider,model:r?.model,verbose:r?.verbose}});return new A(e).translateProject(r)}async function de(r,e){return new A(r).translateFile(e)}function Ke(r){if(r.provider==="openai")return new C(r.providerOptions??{});throw new Error(`Unsupported provider: ${r.provider}`)}function Ve(r,e){let t=r.find(n=>n.key===e);if(!t)throw new Error(`Provider response is missing translation for key "${e}"`);return t}0&&(module.exports={BaseAIProvider,ConsoleLogger,FileCache,GlossaryManager,OpenAIProvider,SilentLogger,Translator,cloneDocument,createDefaultConfig,createDocumentFromEntries,createEmptyPropertiesDocument,createLogger,extractPlaceholders,getLanguageFilePath,hasPlaceholders,listPropertiesEntries,loadTranslatorConfig,maskPlaceholders,parsePropertiesDocument,readPropertiesDocument,resolveTranslationMode,restorePlaceholders,serializePropertiesDocument,translateFile,translateProject,upsertPropertiesValue,validateConfig,validatePlaceholderIntegrity,writePropertiesDocument});
|
|
6
|
+
//# sourceMappingURL=index.cjs.map
|