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.
194 lines • 9.66 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = buildDiffCommand;
const constants_1 = require("./constants");
const commander_1 = require("commander");
const cache_1 = require("./cache");
const glossary_1 = require("./glossary");
const utils_1 = require("./utils");
const cli_helpers_1 = require("./cli_helpers");
const translate_directory_1 = require("./translate_directory");
const translate_file_1 = require("./translate_file");
const chat_pool_1 = __importDefault(require("./chat_pool"));
const rate_limiter_1 = __importDefault(require("./rate_limiter"));
const fs_1 = __importStar(require("fs"));
const path_1 = __importDefault(require("path"));
/**
* Builds the diff command for comparing i18n files or directories.
* @returns the diff command with its options and action.
*/
function buildDiffCommand() {
return new commander_1.Command("diff")
.requiredOption("-b, --before <fileOrDirectoryBefore>", "Source i18n file or directory before changes, in the jsons/ directory if a relative path is given")
.requiredOption("-a, --after <fileOrDirectoryAfter>", "Source i18n file or directory after changes, in the jsons/ directory if a relative path is given")
.requiredOption("-l, --input-language <inputLanguageCode>", "The input language's code, in ISO6391 (e.g. en, fr)")
.requiredOption("-e, --engine <engine>", constants_1.CLI_HELP.Engine)
.option("-m, --model <model>", constants_1.CLI_HELP.Model)
.option("-r, --rate-limit-ms <rateLimitMs>", constants_1.CLI_HELP.RateLimit)
.option("-k, --api-key <API key>", "API key")
.option("-h, --host <hostIP:port>", constants_1.CLI_HELP.OllamaHost)
.option("--ensure-changed-translation", constants_1.CLI_HELP.EnsureChangedTranslation, false)
.option("-p, --templated-string-prefix <prefix>", "Prefix for templated strings", constants_1.DEFAULT_TEMPLATED_STRING_PREFIX)
.option("-s, --templated-string-suffix <suffix>", "Suffix for templated strings", constants_1.DEFAULT_TEMPLATED_STRING_SUFFIX)
.option("-n, --batch-size <batchSize>", constants_1.CLI_HELP.BatchSize)
.option("--skip-translation-verification", constants_1.CLI_HELP.SkipTranslationVerification, false)
.option("--skip-styling-verification", constants_1.CLI_HELP.SkipStylingVerification, false)
.option("--override-prompt <path to JSON file>", constants_1.CLI_HELP.OverridePromptFile)
.option("--verbose", constants_1.CLI_HELP.Verbose, false)
.option("--prompt-mode <prompt-mode>", constants_1.CLI_HELP.PromptMode)
.option("--batch-max-tokens <batch-max-tokens>", constants_1.CLI_HELP.MaxTokens)
.option("--dry-run", constants_1.CLI_HELP.DryRun, false)
.option("--no-continue-on-error", constants_1.CLI_HELP.NoContinueOnError)
.option("--concurrency <concurrency>", constants_1.CLI_HELP.Concurrency)
.option("--context <context>", constants_1.CLI_HELP.Context)
.option("--exclude-languages [language codes...]", constants_1.CLI_HELP.ExcludeLanguages)
.option("--tokens-per-minute <tpm>", constants_1.CLI_HELP.TokensPerMinute)
.option("--file-format <format>", constants_1.CLI_HELP.FileFormat)
.option("--cache [path]", constants_1.CLI_HELP.Cache)
.option("--glossary <path>", constants_1.CLI_HELP.Glossary)
.action(async (options) => {
const modelArgs = (0, cli_helpers_1.processModelArgs)(options);
// Shared pool + limiter mirroring cli_translate.ts. Diff
// currently has a single run per invocation (no language
// fan-out here), but plumbing it through keeps the option
// shape symmetric and prevents surprises if diff grows a
// --language-concurrency later.
const sharedRateLimiter = new rate_limiter_1.default(modelArgs.rateLimitMs, Boolean(options.verbose), modelArgs.tokensPerMinute);
const sharedPool = chat_pool_1.default.create({
apiKey: modelArgs.apiKey,
chatParams: modelArgs.chatParams,
concurrency: Math.max(1, modelArgs.concurrency),
engine: options.engine,
host: modelArgs.host,
model: modelArgs.model,
rateLimiter: sharedRateLimiter,
});
let cachePath;
let cache;
if (options.cache) {
const resolvedPath = typeof options.cache === "string"
? options.cache
: cache_1.DEFAULT_CACHE_PATH;
cachePath = resolvedPath;
cache = (0, cache_1.loadCache)(resolvedPath);
}
let glossary;
if (options.glossary) {
try {
glossary = (0, glossary_1.loadGlossary)(options.glossary);
}
catch (e) {
(0, utils_1.printError)(`${e}`);
process.exit(2);
}
}
const sharedOptions = {
...modelArgs,
cache,
context: options.context,
continueOnError: options.continueOnError,
ensureChangedTranslation: options.ensureChangedTranslation,
excludeLanguages: options.excludeLanguages,
format: options.fileFormat,
glossary,
pool: sharedPool,
rateLimiter: sharedRateLimiter,
skipStylingVerification: options.skipStylingVerification,
skipTranslationVerification: options.skipTranslationVerification,
templatedStringPrefix: options.templatedStringPrefix,
templatedStringSuffix: options.templatedStringSuffix,
verbose: options.verbose,
};
let overridePrompt;
if (options.overridePrompt) {
overridePrompt = (0, cli_helpers_1.processOverridePromptFile)(options.overridePrompt);
}
let dryRun;
if (options.dryRun) {
dryRun = {
basePath: (0, fs_1.mkdtempSync)(`/tmp/i18n-ai-translate-${new Date().toISOString().replace(/[:.]/g, "-")}-`),
};
}
const beforeInputPath = (0, utils_1.resolveInputPath)(options.before);
const afterInputPath = (0, utils_1.resolveInputPath)(options.after);
if (fs_1.default.statSync(beforeInputPath).isFile() !==
fs_1.default.statSync(afterInputPath).isFile()) {
(0, utils_1.printError)("--before and --after arguments must be both files or both directories");
return;
}
if (fs_1.default.statSync(beforeInputPath).isFile()) {
// Ensure they're in the same path
if (path_1.default.dirname(beforeInputPath) !==
path_1.default.dirname(afterInputPath)) {
(0, utils_1.printError)("Input files are not in the same directory");
return;
}
await (0, translate_file_1.translateFileDiff)({
...sharedOptions,
dryRun,
engine: options.engine,
inputAfterFileOrPath: afterInputPath,
inputBeforeFileOrPath: beforeInputPath,
inputLanguageCode: options.inputLanguage,
overridePrompt,
});
}
else {
await (0, translate_directory_1.translateDirectoryDiff)({
...sharedOptions,
baseDirectory: path_1.default.resolve(beforeInputPath, ".."),
dryRun,
engine: options.engine,
inputFolderNameAfter: afterInputPath,
inputFolderNameBefore: beforeInputPath,
inputLanguageCode: options.inputLanguage,
overridePrompt,
});
}
// Persist the translation memory after the diff completes.
// Dry-run is a no-write preview, so leave the cache untouched.
if (cache && cachePath && !options.dryRun) {
(0, cache_1.saveCache)(cachePath, cache);
if (options.verbose) {
(0, utils_1.printInfo)(`Wrote translation cache to ${cachePath}`);
}
}
});
}
//# sourceMappingURL=cli_diff.js.map