UNPKG

i18n-ai-translate

Version:

AI-powered localization CLI, Node library, and GitHub Action. Translate i18next JSON, Gettext PO, Java .properties, and iOS .strings with ChatGPT, Claude, Gemini, or local Ollama models.

141 lines (129 loc) 4.49 kB
import { printWarn } from "./utils"; import crypto from "crypto"; import fs from "fs"; /** Bump when the on-disk shape changes; older caches are ignored. */ export const CACHE_VERSION = 1; /** Default cache file written into the cwd when `--cache` has no path. */ export const DEFAULT_CACHE_PATH = ".i18n-ai-translate-cache.json"; /** * A persistent translation memory. `entries` maps a content hash (see * {@link cacheKey}) to the previously translated string. The key is * scoped to the source text plus the input/output languages and the * `--context`, but deliberately *not* the engine or model — so a cached * translation is reused across runs even after switching providers. * * The object is mutated in place by `translate()`; the CLI loads it * before a run and writes it back after. */ export type TranslationCache = { version: number; entries: { [hash: string]: string }; }; /** * @returns a fresh, empty cache at the current schema version */ export function createCache(): TranslationCache { return { entries: {}, version: CACHE_VERSION }; } /** * Compute the content-addressed key for one translatable string. * @param inputLanguageCode - the source language code * @param outputLanguageCode - the target language code * @param context - the `--context` string ("" when unset) * @param source - the source text being translated * @returns a hex sha256 digest uniquely identifying the entry */ export function cacheKey( inputLanguageCode: string, outputLanguageCode: string, context: string, source: string, ): string { return crypto .createHash("sha256") .update( `${inputLanguageCode}${outputLanguageCode}${context}${source}`, ) .digest("hex"); } /** * Look up a previously cached translation. * @param cache - the translation memory * @param inputLanguageCode - the source language code * @param outputLanguageCode - the target language code * @param context - the `--context` string ("" when unset) * @param source - the source text being translated * @returns the cached translation, or undefined on a miss */ export function getCachedTranslation( cache: TranslationCache, inputLanguageCode: string, outputLanguageCode: string, context: string, source: string, ): string | undefined { return cache.entries[ cacheKey(inputLanguageCode, outputLanguageCode, context, source) ]; } /** * Record a translation for reuse on later runs. * @param cache - the translation memory * @param inputLanguageCode - the source language code * @param outputLanguageCode - the target language code * @param context - the `--context` string ("" when unset) * @param source - the source text being translated * @param translated - the translated text to store */ export function setCachedTranslation( cache: TranslationCache, inputLanguageCode: string, outputLanguageCode: string, context: string, source: string, translated: string, ): void { cache.entries[ cacheKey(inputLanguageCode, outputLanguageCode, context, source) ] = translated; } /** * Load a cache from disk, tolerating a missing or incompatible file by * returning a fresh cache rather than throwing — a stale cache should * never break a translation run. * @param filePath - path to the cache JSON file * @returns the loaded cache, or a fresh one */ export function loadCache(filePath: string): TranslationCache { let raw: string; try { raw = fs.readFileSync(filePath, "utf-8"); } catch { // Missing file on the first run is expected. return createCache(); } try { const parsed = JSON.parse(raw); if ( parsed && typeof parsed === "object" && parsed.version === CACHE_VERSION && parsed.entries && typeof parsed.entries === "object" ) { return parsed as TranslationCache; } } catch { // Fall through to the warning below. } printWarn(`Ignoring incompatible cache at ${filePath}; starting fresh.`); return createCache(); } /** * Persist a cache to disk as pretty-printed JSON with a trailing newline. * @param filePath - path to the cache JSON file * @param cache - the translation memory to write */ export function saveCache(filePath: string, cache: TranslationCache): void { fs.writeFileSync(filePath, `${JSON.stringify(cache, null, 4)}\n`); }