@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.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":["export { Translator, translateFile, translateProject } from './translator.js';\nexport {\n createDefaultConfig,\n loadTranslatorConfig,\n resolveTranslationMode,\n validateConfig,\n} from './utils/config.js';\nexport { createLogger, ConsoleLogger, SilentLogger } from './utils/logger.js';\nexport { FileCache } from './cache/fileCache.js';\nexport { GlossaryManager } from './glossary/glossaryManager.js';\nexport {\n cloneDocument,\n createDocumentFromEntries,\n createEmptyPropertiesDocument,\n getLanguageFilePath,\n listPropertiesEntries,\n parsePropertiesDocument,\n readPropertiesDocument,\n serializePropertiesDocument,\n upsertPropertiesValue,\n writePropertiesDocument,\n} from './parsers/propertiesParser.js';\nexport {\n extractPlaceholders,\n hasPlaceholders,\n maskPlaceholders,\n restorePlaceholders,\n validatePlaceholderIntegrity,\n} from './parsers/placeholderExtractor.js';\nexport { BaseAIProvider } from './providers/base.js';\nexport { OpenAIProvider } from './providers/openai/client.js';\nexport type {\n AIProvider,\n CacheOptions,\n FileConfig,\n GlossaryConfig,\n GlossaryTerm,\n LanguageTranslationSummary,\n Logger,\n OpenAIProviderOptions,\n PlaceholderInfo,\n PlaceholderToken,\n PropertiesDocument,\n PropertiesLine,\n PropertiesSerializationOptions,\n SupportedProvider,\n TranslationBatchRequest,\n TranslationBatchResponse,\n TranslationBatchResponseItem,\n TranslationFileOptions,\n TranslationMode,\n TranslationProjectOptions,\n TranslationResult,\n TranslatorConfig,\n TranslatorConfigOverrides,\n} from './types.js';\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":["index_exports","__export","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","__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","hasPlaceholders","maskPlaceholders","tokens","index","masked","placeholder","token","restorePlaceholders","current","validatePlaceholderIntegrity","sourceText","translatedText","expected","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","createDocumentFromEntries","getLanguageFilePath","sourceFilePath","language","pattern","outputDir","sourceDir","ext","baseName","fileName","raw","leadingWhitespace","separatorIndex","separator","valueStartIndex","index","char","lookahead","rawKey","rawValue","decodePropertiesToken","escapePropertiesToken","escapePropertiesValue","_","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","createDefaultConfig","apiKey","targetLanguages","providerOptions","overrides","fileName","candidate","fs","assertPlainObject","validateAllowedKeys","language","value","allowedKeys","pathLabel","key","resolveTranslationMode","preferred","fallback","envPath","envLocalPath","dotenv","ConsoleLogger","prefix","debug","message","data","error","enabled","SilentLogger","_message","_data","_error","createLogger","silent","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","translateFile","OpenAIProvider","items","candidate"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { a as TranslatorConfig, b as TranslationProjectOptions, c as TranslationResult, d as TranslationFileOptions, G as GlossaryTerm, e as TranslatorConfigOverrides, T as TranslationMode, L as Logger, C as CacheOptions, f as GlossaryConfig, P as PropertiesDocument, g as PropertiesSerializationOptions, h as PlaceholderInfo, i as PlaceholderToken, A as AIProvider, S as SupportedProvider, j as TranslationBatchRequest, k as TranslationBatchResponse, O as OpenAIProviderOptions } from './types-DF4QMkU1.cjs';
|
|
2
|
+
export { F as FileConfig, l as LanguageTranslationSummary, m as PropertiesLine, n as TranslationBatchResponseItem } from './types-DF4QMkU1.cjs';
|
|
3
|
+
|
|
4
|
+
declare class Translator {
|
|
5
|
+
private readonly config;
|
|
6
|
+
private readonly provider;
|
|
7
|
+
private readonly glossaryManager;
|
|
8
|
+
private readonly cache;
|
|
9
|
+
private readonly logger;
|
|
10
|
+
constructor(config: TranslatorConfig);
|
|
11
|
+
static fromConfig(options?: {
|
|
12
|
+
configPath?: string;
|
|
13
|
+
cwd?: string;
|
|
14
|
+
}): Promise<Translator>;
|
|
15
|
+
translateProject(options?: TranslationProjectOptions): Promise<TranslationResult>;
|
|
16
|
+
translateFile(options?: TranslationFileOptions): Promise<TranslationResult>;
|
|
17
|
+
clearCache(): void;
|
|
18
|
+
getCacheStats(): {
|
|
19
|
+
enabled: boolean;
|
|
20
|
+
ttlMs: number;
|
|
21
|
+
cacheDir: string;
|
|
22
|
+
entriesCount: number;
|
|
23
|
+
};
|
|
24
|
+
getGlossaryTerms(language: string): GlossaryTerm[];
|
|
25
|
+
private translateEntries;
|
|
26
|
+
private resolveEncodeUnicode;
|
|
27
|
+
}
|
|
28
|
+
declare function translateProject(options?: TranslationProjectOptions): Promise<TranslationResult>;
|
|
29
|
+
declare function translateFile(config: TranslatorConfig, options?: TranslationFileOptions): Promise<TranslationResult>;
|
|
30
|
+
|
|
31
|
+
declare function loadTranslatorConfig(options?: {
|
|
32
|
+
configPath?: string;
|
|
33
|
+
cwd?: string;
|
|
34
|
+
overrides?: TranslatorConfigOverrides;
|
|
35
|
+
}): Promise<TranslatorConfig>;
|
|
36
|
+
declare function validateConfig(config: TranslatorConfig): void;
|
|
37
|
+
declare function createDefaultConfig(apiKey: string, targetLanguages: string[]): TranslatorConfig;
|
|
38
|
+
declare function resolveTranslationMode(preferred?: TranslationMode, fallback?: TranslationMode): TranslationMode;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Logging utility for debug output
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Default logger implementation
|
|
46
|
+
* Uses console for output, respects DEBUG environment variable
|
|
47
|
+
*/
|
|
48
|
+
declare class ConsoleLogger implements Logger {
|
|
49
|
+
private debug_enabled;
|
|
50
|
+
private prefix;
|
|
51
|
+
constructor(prefix?: string, debug?: boolean);
|
|
52
|
+
debug(message: string, data?: Record<string, unknown>): void;
|
|
53
|
+
info(message: string, data?: Record<string, unknown>): void;
|
|
54
|
+
warn(message: string, data?: Record<string, unknown>): void;
|
|
55
|
+
error(message: string, error?: Error): void;
|
|
56
|
+
setDebug(enabled: boolean): void;
|
|
57
|
+
isDebugEnabled(): boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* No-op logger (for silent mode)
|
|
61
|
+
*/
|
|
62
|
+
declare class SilentLogger implements Logger {
|
|
63
|
+
debug(_message: string, _data?: Record<string, unknown>): void;
|
|
64
|
+
info(_message: string, _data?: Record<string, unknown>): void;
|
|
65
|
+
warn(_message: string, _data?: Record<string, unknown>): void;
|
|
66
|
+
error(_message: string, _error?: Error): void;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Create a logger instance
|
|
70
|
+
*
|
|
71
|
+
* @param debug Enable debug output
|
|
72
|
+
* @param silent Enable silent mode (no output)
|
|
73
|
+
* @returns Logger instance
|
|
74
|
+
*/
|
|
75
|
+
declare function createLogger(debug?: boolean, silent?: boolean): Logger;
|
|
76
|
+
|
|
77
|
+
declare class FileCache {
|
|
78
|
+
private readonly enabled;
|
|
79
|
+
private readonly ttlMs;
|
|
80
|
+
private readonly cacheDir;
|
|
81
|
+
constructor(options?: CacheOptions);
|
|
82
|
+
get(cacheKey: string): string | null;
|
|
83
|
+
set(cacheKey: string, translation: string): void;
|
|
84
|
+
clear(): void;
|
|
85
|
+
getStats(): {
|
|
86
|
+
enabled: boolean;
|
|
87
|
+
ttlMs: number;
|
|
88
|
+
cacheDir: string;
|
|
89
|
+
entriesCount: number;
|
|
90
|
+
};
|
|
91
|
+
static createKey(input: {
|
|
92
|
+
provider: string;
|
|
93
|
+
model: string;
|
|
94
|
+
sourceLanguage: string;
|
|
95
|
+
targetLanguage: string;
|
|
96
|
+
sourceValue: string;
|
|
97
|
+
glossaryTerms: GlossaryTerm[];
|
|
98
|
+
rules: string[];
|
|
99
|
+
}): string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
declare class GlossaryManager {
|
|
103
|
+
private glossary;
|
|
104
|
+
constructor(glossary?: GlossaryConfig);
|
|
105
|
+
static loadGlossary(source?: GlossaryConfig | string): GlossaryConfig;
|
|
106
|
+
static loadFromFile(filePath: string): GlossaryConfig;
|
|
107
|
+
static validateGlossary(glossary: unknown): void;
|
|
108
|
+
private static validateTerms;
|
|
109
|
+
getGlossary(): GlossaryConfig;
|
|
110
|
+
getTermsForLanguage(language: string): GlossaryTerm[];
|
|
111
|
+
hasTerms(language: string): boolean;
|
|
112
|
+
toJSON(): string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
declare function parsePropertiesDocument(content: string): PropertiesDocument;
|
|
116
|
+
declare function readPropertiesDocument(filePath: string): PropertiesDocument;
|
|
117
|
+
declare function createEmptyPropertiesDocument(): PropertiesDocument;
|
|
118
|
+
declare function listPropertiesEntries(document: PropertiesDocument): Record<string, string>;
|
|
119
|
+
declare function cloneDocument(document: PropertiesDocument): PropertiesDocument;
|
|
120
|
+
declare function upsertPropertiesValue(document: PropertiesDocument, key: string, value: string): void;
|
|
121
|
+
declare function serializePropertiesDocument(document: PropertiesDocument, options?: PropertiesSerializationOptions): string;
|
|
122
|
+
declare function writePropertiesDocument(filePath: string, document: PropertiesDocument, options?: PropertiesSerializationOptions): void;
|
|
123
|
+
declare function createDocumentFromEntries(entries: Record<string, string>): PropertiesDocument;
|
|
124
|
+
declare function getLanguageFilePath(sourceFilePath: string, language: string, pattern?: string, outputDir?: string): string;
|
|
125
|
+
|
|
126
|
+
declare function extractPlaceholders(text: string): string[];
|
|
127
|
+
declare function hasPlaceholders(text: string): boolean;
|
|
128
|
+
declare function maskPlaceholders(text: string): PlaceholderInfo;
|
|
129
|
+
declare function restorePlaceholders(text: string, tokens: PlaceholderToken[]): string;
|
|
130
|
+
declare function validatePlaceholderIntegrity(sourceText: string, translatedText: string): {
|
|
131
|
+
valid: boolean;
|
|
132
|
+
expected: string[];
|
|
133
|
+
actual: string[];
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
declare abstract class BaseAIProvider implements AIProvider {
|
|
137
|
+
abstract getName(): SupportedProvider;
|
|
138
|
+
abstract translateBatch(request: TranslationBatchRequest): Promise<TranslationBatchResponse>;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
declare class OpenAIProvider extends BaseAIProvider {
|
|
142
|
+
private readonly client;
|
|
143
|
+
private readonly options;
|
|
144
|
+
constructor(options: OpenAIProviderOptions);
|
|
145
|
+
getName(): 'openai';
|
|
146
|
+
translateBatch(request: TranslationBatchRequest): Promise<TranslationBatchResponse>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export { AIProvider, BaseAIProvider, CacheOptions, ConsoleLogger, FileCache, GlossaryConfig, GlossaryManager, GlossaryTerm, Logger, OpenAIProvider, OpenAIProviderOptions, PlaceholderInfo, PlaceholderToken, PropertiesDocument, PropertiesSerializationOptions, SilentLogger, SupportedProvider, TranslationBatchRequest, TranslationBatchResponse, TranslationFileOptions, TranslationMode, TranslationProjectOptions, TranslationResult, Translator, TranslatorConfig, TranslatorConfigOverrides, cloneDocument, createDefaultConfig, createDocumentFromEntries, createEmptyPropertiesDocument, createLogger, extractPlaceholders, getLanguageFilePath, hasPlaceholders, listPropertiesEntries, loadTranslatorConfig, maskPlaceholders, parsePropertiesDocument, readPropertiesDocument, resolveTranslationMode, restorePlaceholders, serializePropertiesDocument, translateFile, translateProject, upsertPropertiesValue, validateConfig, validatePlaceholderIntegrity, writePropertiesDocument };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { a as TranslatorConfig, b as TranslationProjectOptions, c as TranslationResult, d as TranslationFileOptions, G as GlossaryTerm, e as TranslatorConfigOverrides, T as TranslationMode, L as Logger, C as CacheOptions, f as GlossaryConfig, P as PropertiesDocument, g as PropertiesSerializationOptions, h as PlaceholderInfo, i as PlaceholderToken, A as AIProvider, S as SupportedProvider, j as TranslationBatchRequest, k as TranslationBatchResponse, O as OpenAIProviderOptions } from './types-DF4QMkU1.js';
|
|
2
|
+
export { F as FileConfig, l as LanguageTranslationSummary, m as PropertiesLine, n as TranslationBatchResponseItem } from './types-DF4QMkU1.js';
|
|
3
|
+
|
|
4
|
+
declare class Translator {
|
|
5
|
+
private readonly config;
|
|
6
|
+
private readonly provider;
|
|
7
|
+
private readonly glossaryManager;
|
|
8
|
+
private readonly cache;
|
|
9
|
+
private readonly logger;
|
|
10
|
+
constructor(config: TranslatorConfig);
|
|
11
|
+
static fromConfig(options?: {
|
|
12
|
+
configPath?: string;
|
|
13
|
+
cwd?: string;
|
|
14
|
+
}): Promise<Translator>;
|
|
15
|
+
translateProject(options?: TranslationProjectOptions): Promise<TranslationResult>;
|
|
16
|
+
translateFile(options?: TranslationFileOptions): Promise<TranslationResult>;
|
|
17
|
+
clearCache(): void;
|
|
18
|
+
getCacheStats(): {
|
|
19
|
+
enabled: boolean;
|
|
20
|
+
ttlMs: number;
|
|
21
|
+
cacheDir: string;
|
|
22
|
+
entriesCount: number;
|
|
23
|
+
};
|
|
24
|
+
getGlossaryTerms(language: string): GlossaryTerm[];
|
|
25
|
+
private translateEntries;
|
|
26
|
+
private resolveEncodeUnicode;
|
|
27
|
+
}
|
|
28
|
+
declare function translateProject(options?: TranslationProjectOptions): Promise<TranslationResult>;
|
|
29
|
+
declare function translateFile(config: TranslatorConfig, options?: TranslationFileOptions): Promise<TranslationResult>;
|
|
30
|
+
|
|
31
|
+
declare function loadTranslatorConfig(options?: {
|
|
32
|
+
configPath?: string;
|
|
33
|
+
cwd?: string;
|
|
34
|
+
overrides?: TranslatorConfigOverrides;
|
|
35
|
+
}): Promise<TranslatorConfig>;
|
|
36
|
+
declare function validateConfig(config: TranslatorConfig): void;
|
|
37
|
+
declare function createDefaultConfig(apiKey: string, targetLanguages: string[]): TranslatorConfig;
|
|
38
|
+
declare function resolveTranslationMode(preferred?: TranslationMode, fallback?: TranslationMode): TranslationMode;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Logging utility for debug output
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Default logger implementation
|
|
46
|
+
* Uses console for output, respects DEBUG environment variable
|
|
47
|
+
*/
|
|
48
|
+
declare class ConsoleLogger implements Logger {
|
|
49
|
+
private debug_enabled;
|
|
50
|
+
private prefix;
|
|
51
|
+
constructor(prefix?: string, debug?: boolean);
|
|
52
|
+
debug(message: string, data?: Record<string, unknown>): void;
|
|
53
|
+
info(message: string, data?: Record<string, unknown>): void;
|
|
54
|
+
warn(message: string, data?: Record<string, unknown>): void;
|
|
55
|
+
error(message: string, error?: Error): void;
|
|
56
|
+
setDebug(enabled: boolean): void;
|
|
57
|
+
isDebugEnabled(): boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* No-op logger (for silent mode)
|
|
61
|
+
*/
|
|
62
|
+
declare class SilentLogger implements Logger {
|
|
63
|
+
debug(_message: string, _data?: Record<string, unknown>): void;
|
|
64
|
+
info(_message: string, _data?: Record<string, unknown>): void;
|
|
65
|
+
warn(_message: string, _data?: Record<string, unknown>): void;
|
|
66
|
+
error(_message: string, _error?: Error): void;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Create a logger instance
|
|
70
|
+
*
|
|
71
|
+
* @param debug Enable debug output
|
|
72
|
+
* @param silent Enable silent mode (no output)
|
|
73
|
+
* @returns Logger instance
|
|
74
|
+
*/
|
|
75
|
+
declare function createLogger(debug?: boolean, silent?: boolean): Logger;
|
|
76
|
+
|
|
77
|
+
declare class FileCache {
|
|
78
|
+
private readonly enabled;
|
|
79
|
+
private readonly ttlMs;
|
|
80
|
+
private readonly cacheDir;
|
|
81
|
+
constructor(options?: CacheOptions);
|
|
82
|
+
get(cacheKey: string): string | null;
|
|
83
|
+
set(cacheKey: string, translation: string): void;
|
|
84
|
+
clear(): void;
|
|
85
|
+
getStats(): {
|
|
86
|
+
enabled: boolean;
|
|
87
|
+
ttlMs: number;
|
|
88
|
+
cacheDir: string;
|
|
89
|
+
entriesCount: number;
|
|
90
|
+
};
|
|
91
|
+
static createKey(input: {
|
|
92
|
+
provider: string;
|
|
93
|
+
model: string;
|
|
94
|
+
sourceLanguage: string;
|
|
95
|
+
targetLanguage: string;
|
|
96
|
+
sourceValue: string;
|
|
97
|
+
glossaryTerms: GlossaryTerm[];
|
|
98
|
+
rules: string[];
|
|
99
|
+
}): string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
declare class GlossaryManager {
|
|
103
|
+
private glossary;
|
|
104
|
+
constructor(glossary?: GlossaryConfig);
|
|
105
|
+
static loadGlossary(source?: GlossaryConfig | string): GlossaryConfig;
|
|
106
|
+
static loadFromFile(filePath: string): GlossaryConfig;
|
|
107
|
+
static validateGlossary(glossary: unknown): void;
|
|
108
|
+
private static validateTerms;
|
|
109
|
+
getGlossary(): GlossaryConfig;
|
|
110
|
+
getTermsForLanguage(language: string): GlossaryTerm[];
|
|
111
|
+
hasTerms(language: string): boolean;
|
|
112
|
+
toJSON(): string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
declare function parsePropertiesDocument(content: string): PropertiesDocument;
|
|
116
|
+
declare function readPropertiesDocument(filePath: string): PropertiesDocument;
|
|
117
|
+
declare function createEmptyPropertiesDocument(): PropertiesDocument;
|
|
118
|
+
declare function listPropertiesEntries(document: PropertiesDocument): Record<string, string>;
|
|
119
|
+
declare function cloneDocument(document: PropertiesDocument): PropertiesDocument;
|
|
120
|
+
declare function upsertPropertiesValue(document: PropertiesDocument, key: string, value: string): void;
|
|
121
|
+
declare function serializePropertiesDocument(document: PropertiesDocument, options?: PropertiesSerializationOptions): string;
|
|
122
|
+
declare function writePropertiesDocument(filePath: string, document: PropertiesDocument, options?: PropertiesSerializationOptions): void;
|
|
123
|
+
declare function createDocumentFromEntries(entries: Record<string, string>): PropertiesDocument;
|
|
124
|
+
declare function getLanguageFilePath(sourceFilePath: string, language: string, pattern?: string, outputDir?: string): string;
|
|
125
|
+
|
|
126
|
+
declare function extractPlaceholders(text: string): string[];
|
|
127
|
+
declare function hasPlaceholders(text: string): boolean;
|
|
128
|
+
declare function maskPlaceholders(text: string): PlaceholderInfo;
|
|
129
|
+
declare function restorePlaceholders(text: string, tokens: PlaceholderToken[]): string;
|
|
130
|
+
declare function validatePlaceholderIntegrity(sourceText: string, translatedText: string): {
|
|
131
|
+
valid: boolean;
|
|
132
|
+
expected: string[];
|
|
133
|
+
actual: string[];
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
declare abstract class BaseAIProvider implements AIProvider {
|
|
137
|
+
abstract getName(): SupportedProvider;
|
|
138
|
+
abstract translateBatch(request: TranslationBatchRequest): Promise<TranslationBatchResponse>;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
declare class OpenAIProvider extends BaseAIProvider {
|
|
142
|
+
private readonly client;
|
|
143
|
+
private readonly options;
|
|
144
|
+
constructor(options: OpenAIProviderOptions);
|
|
145
|
+
getName(): 'openai';
|
|
146
|
+
translateBatch(request: TranslationBatchRequest): Promise<TranslationBatchResponse>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export { AIProvider, BaseAIProvider, CacheOptions, ConsoleLogger, FileCache, GlossaryConfig, GlossaryManager, GlossaryTerm, Logger, OpenAIProvider, OpenAIProviderOptions, PlaceholderInfo, PlaceholderToken, PropertiesDocument, PropertiesSerializationOptions, SilentLogger, SupportedProvider, TranslationBatchRequest, TranslationBatchResponse, TranslationFileOptions, TranslationMode, TranslationProjectOptions, TranslationResult, Translator, TranslatorConfig, TranslatorConfigOverrides, cloneDocument, createDefaultConfig, createDocumentFromEntries, createEmptyPropertiesDocument, createLogger, extractPlaceholders, getLanguageFilePath, hasPlaceholders, listPropertiesEntries, loadTranslatorConfig, maskPlaceholders, parsePropertiesDocument, readPropertiesDocument, resolveTranslationMode, restorePlaceholders, serializePropertiesDocument, translateFile, translateProject, upsertPropertiesValue, validateConfig, validatePlaceholderIntegrity, writePropertiesDocument };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import Se from"path";import ee from"crypto";import g from"fs";import R 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??R.join(t,".i18n-ai-translator-cache"),this.enabled&&!g.existsSync(this.cacheDir)&&g.mkdirSync(this.cacheDir,{recursive:!0})}get(e){if(!this.enabled)return null;let t=R.join(this.cacheDir,`${e}.json`);if(!g.existsSync(t))return null;try{let n=JSON.parse(g.readFileSync(t,"utf-8"));return Date.now()-n.createdAt>this.ttlMs?(g.unlinkSync(t),null):n.translation}catch{return null}}set(e,t){if(!this.enabled)return;let n=R.join(this.cacheDir,`${e}.json`),o={translation:t,createdAt:Date.now()};try{g.writeFileSync(n,JSON.stringify(o),"utf-8")}catch{}}clear(){if(!(!this.enabled||!g.existsSync(this.cacheDir)))for(let e of g.readdirSync(this.cacheDir))e.endsWith(".json")&&g.unlinkSync(R.join(this.cacheDir,e))}getStats(){let e=0;return this.enabled&&g.existsSync(this.cacheDir)&&(e=g.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 ee.createHash("sha256").update(t).digest("hex")}};import Y from"fs";var 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(!Y.existsSync(e))throw new Error(`Glossary file not found: ${e}`);let t=Y.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 re=/\{\d+\}|\{[a-zA-Z_][\w.-]*\}|\$\{[a-zA-Z_][\w.-]*\}|%\d+\$[sdif]|%[sdif]/g,te=/\\[nrtf]/g,G=new RegExp(`${re.source}|${te.source}`,"g");function M(r){return Array.from(r.matchAll(G),e=>e[0])}function ne(r){return new RegExp(G).test(r)}function U(r){let e=[],t=0,n=r.replace(G,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=M(r).sort(),n=M(e).sort();return{valid:t.length===n.length&&t.every((o,s)=>o===n[s]),expected:t,actual:n}}import E from"fs";import T from"path";function Z(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(se),eol:t,hasTrailingNewline:n}}function I(r){return E.existsSync(r)?Z(E.readFileSync(r,"utf-8")):z()}function z(){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 _(r){return{eol:r.eol,hasTrailingNewline:r.hasTrailingNewline,lines:r.lines.map(e=>({...e}))}}function B(r,e,t){let n=ae(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 Q(r,e={}){let n=r.lines.map(o=>ie(o,e)).join(r.eol);return n.length===0?"":r.hasTrailingNewline?`${n}${r.eol}`:n}function W(r,e,t={}){let n=T.dirname(r);E.existsSync(n)||E.mkdirSync(n,{recursive:!0}),E.writeFileSync(r,Q(e,t),"utf-8")}function oe(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??T.dirname(r),s=T.extname(r)||".properties",u=T.basename(r,s);if(t){let i=t.replace("{baseName}",u).replace("{language}",e).replace("{ext}",s);return T.join(o,i)}let d=u==="i18n"?`i18n_${e}${s}`:`${u}_${e}${s}`;return T.join(o,d)}function se(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 u=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:H(u.trimEnd()),value:H(d),separator:o,leadingWhitespace:e,modified:!1}}function ie(r,e){return r.type!=="entry"||!r.modified&&r.raw?r.raw:`${r.leadingWhitespace}${le(r.key)}${r.separator}${ce(r.value,e)}`}function ae(r,e){return r.lines.find(t=>t.type==="entry"&&t.key===e)}function H(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 le(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}import ge from"openai";var x=class{};var ue={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 ge({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:ue,strict:!0}},input:[{role:"system",content:de()},{role:"user",content:JSON.stringify(pe(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 de(){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 pe(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 N from"path";import{pathToFileURL as fe}from"url";import X from"dotenv";var me=["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"],he=["sourceLanguage","targetLanguages","translationMode","encodeUnicode","languageOptions","provider","providerOptions","files","glossary","cache","rules","batchSize","verbose"],ye=["apiKey","model","baseURL","organization","temperature","maxOutputTokens"],ve=["input","outputDir","languageFilePattern"],Pe=["enabled","ttlMs","dir"],Te=["encodeUnicode"];async function S(r){let e=r?.cwd??process.cwd();Le(e);let t=r?.configPath?N.resolve(e,r.configPath):Ee(e),n=t?await xe(t):{};t&&Ce(n,t);let o=be(Oe({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 we(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 Oe(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 be(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 Ee(r){for(let e of me){let t=N.join(r,e);if(k.existsSync(t))return t}}async function xe(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(fe(r).href);return e.default??e}throw new Error(`Unsupported config format: ${r}`)}function Ce(r,e){if(w(r,"Config file",e),L(r,he,"Config file",e),r.providerOptions!==void 0&&(w(r.providerOptions,"config.providerOptions",e),L(r.providerOptions,ye,"config.providerOptions",e)),r.files!==void 0&&(w(r.files,"config.files",e),L(r.files,ve,"config.files",e)),r.cache!==void 0&&(w(r.cache,"config.cache",e),L(r.cache,Pe,"config.cache",e)),r.languageOptions!==void 0){w(r.languageOptions,"config.languageOptions",e);for(let[t,n]of Object.entries(r.languageOptions))w(n,`config.languageOptions.${t}`,e),L(n,Te,`config.languageOptions.${t}`,e)}}function L(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 w(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 Le(r){let e=N.join(r,".env"),t=N.join(r,".env.local");k.existsSync(e)&&X.config({path:e}),k.existsSync(t)&&X.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 ke(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=Re(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=Se.resolve(process.cwd(),e?.inputPath??this.config.files?.input??"i18n/i18n.properties"),n=I(t),o=j(n),s=Object.keys(o),u=e?.languages??this.config.targetLanguages,d=J(e?.mode,this.config.translationMode),i={sourceFile:t,translationMode:d,translations:{}};for(let l of u){this.logger.info("Translating language",{language:l});let p=q(t,l,this.config.files?.languageFilePattern,this.config.files?.outputDir),a=I(p),c=j(a),m=s.filter(F=>{if(d==="overwrite")return!0;let D=c[F];return D===void 0||D===""}),f=await this.translateEntries(o,m,l),h=a.lines.length>0?_(a):_(n);for(let[F,D]of Object.entries(f))B(h,F,D);let y=[];e?.dryRun||W(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??[],u=this.config.batchSize??20,d={};for(let i=0;i<t.length;i+=u){let l=t.slice(i,i+u),p=[];for(let c of l){let m=e[c],f=U(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=Ie(a.items,c.key),f=K(m.translatedValue,c.placeholders),h=V(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 Ae(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 Re(r){if(r.provider==="openai")return new C(r.providerOptions??{});throw new Error(`Unsupported provider: ${r.provider}`)}function Ie(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}export{x as BaseAIProvider,O as ConsoleLogger,v as FileCache,P as GlossaryManager,C as OpenAIProvider,b as SilentLogger,A as Translator,_ as cloneDocument,we as createDefaultConfig,oe as createDocumentFromEntries,z as createEmptyPropertiesDocument,ke as createLogger,M as extractPlaceholders,q as getLanguageFilePath,ne as hasPlaceholders,j as listPropertiesEntries,S as loadTranslatorConfig,U as maskPlaceholders,Z as parsePropertiesDocument,I as readPropertiesDocument,J as resolveTranslationMode,K as restorePlaceholders,Q as serializePropertiesDocument,De as translateFile,Ae as translateProject,B as upsertPropertiesValue,$ as validateConfig,V as validatePlaceholderIntegrity,W as writePropertiesDocument};
|
|
6
|
+
//# sourceMappingURL=index.mjs.map
|