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.
232 lines • 9.66 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.processModelArgs = processModelArgs;
exports.processOverridePromptFile = processOverridePromptFile;
const constants_1 = require("./constants");
const override_prompt_1 = require("./interfaces/override_prompt");
const utils_1 = require("./utils");
const engine_1 = __importDefault(require("./enums/engine"));
const prompt_mode_1 = __importDefault(require("./enums/prompt_mode"));
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
/**
* Processes the command line arguments to extract model parameters.
* @param options - The command line options object.
* @returns an object containing the model parameters.
*
*/
function processModelArgs(options) {
let model;
let chatParams;
let rateLimitMs = Number(options.rateLimitMs);
let apiKey;
let host;
let promptMode = options.promptMode;
let batchSize = Number(options.batchSize);
let batchMaxTokens = Number(options.batchMaxTokens);
let concurrency = Number(options.concurrency);
if (!options.concurrency || !Number.isFinite(concurrency)) {
concurrency = constants_1.DEFAULT_CONCURRENCY;
}
if (!Number.isInteger(concurrency) || concurrency < 1) {
throw new Error("--concurrency must be a positive integer");
}
// User-supplied --tokens-per-minute overrides the engine default.
// 0 explicitly disables the cap; undefined defers to the per-engine
// default assigned in the switch below.
let tokensPerMinute;
if (options.tokensPerMinute !== undefined) {
const parsed = Number(options.tokensPerMinute);
if (!Number.isFinite(parsed) || parsed < 0) {
throw new Error("--tokens-per-minute must be a non-negative number");
}
tokensPerMinute = parsed === 0 ? undefined : parsed;
}
switch (options.engine) {
case engine_1.default.Gemini:
model = options.model || constants_1.DEFAULT_MODEL[engine_1.default.Gemini];
chatParams = {};
if (!options.rateLimitMs) {
// gemini-2.0-flash-exp limits us to 10 RPM => 1 call every 6 seconds
rateLimitMs = 6000;
}
if (!process.env.GEMINI_API_KEY && !options.apiKey) {
throw new Error("GEMINI_API_KEY not found in .env file");
}
else {
apiKey = options.apiKey || process.env.GEMINI_API_KEY;
}
if (!options.promptMode) {
promptMode = prompt_mode_1.default.JSON;
}
else if (promptMode === prompt_mode_1.default.CSV) {
(0, utils_1.printWarn)("JSON mode recommended for Gemini");
}
if (!options.batchSize) {
batchSize = 32;
}
if (!options.batchMaxTokens) {
batchMaxTokens = 4096;
}
// No default TPM cap for Gemini — the existing --rate-limit-ms
// default (6s, matching ~10 RPM free tier) already prevents
// bursts. Paid-tier users can opt in via --tokens-per-minute.
// Published Gemini 2.5 Flash paid tier is ~250k TPM; use that
// as a user-facing hint in docs rather than a default.
break;
case engine_1.default.ChatGPT:
model = options.model || constants_1.DEFAULT_MODEL[engine_1.default.ChatGPT];
chatParams = {
messages: [],
model,
seed: 69420,
};
if (!options.rateLimitMs) {
// Free-tier rate limits are 3 RPM => 1 call every 20 seconds
// Tier 1 is a reasonable 500 RPM => 1 call every 120ms.
rateLimitMs = 120;
}
if (!process.env.OPENAI_API_KEY && !options.apiKey) {
throw new Error("OPENAI_API_KEY not found in .env file");
}
else {
apiKey = options.apiKey || process.env.OPENAI_API_KEY;
}
if (!options.promptMode) {
promptMode = prompt_mode_1.default.CSV;
}
if (!options.batchSize) {
batchSize = 32;
}
if (!options.batchMaxTokens) {
batchMaxTokens = 4096;
}
// No default TPM cap. The free tier publishes no TPM and
// Tier-1 is ~200k TPM for GPT-5.x — a silent default risks
// mysteriously throttling paid-tier users whose limits are
// higher, while also risking undercounting on the free tier
// where only the RPM gate matters. Users opt in via
// --tokens-per-minute once they know their tier.
break;
case engine_1.default.Ollama:
model = options.model || constants_1.DEFAULT_MODEL[engine_1.default.Ollama];
chatParams = {
messages: [],
model,
seed: 69420,
};
host = options.host || process.env.OLLAMA_HOSTNAME;
if (!options.promptMode) {
promptMode = prompt_mode_1.default.JSON;
}
else if (promptMode === prompt_mode_1.default.CSV) {
(0, utils_1.printWarn)("JSON mode recommended for Ollama");
}
if (!options.batchSize) {
// Ollama's error rate is high with large batches
batchSize = 16;
}
if (!options.batchMaxTokens) {
// Ollama's default amount of tokens per request
batchMaxTokens = 2048;
}
break;
case engine_1.default.Claude:
model = options.model || constants_1.DEFAULT_MODEL[engine_1.default.Claude];
chatParams = {
messages: [],
model,
};
if (!options.rateLimitMs) {
// Anthropic limits us to 50 RPM on the first tier => 1200ms between calls
rateLimitMs = 1200;
}
if (!process.env.ANTHROPIC_API_KEY && !options.apiKey) {
throw new Error("ANTHROPIC_API_KEY not found in .env file");
}
else {
apiKey = options.apiKey || process.env.ANTHROPIC_API_KEY;
}
if (!options.promptMode) {
promptMode = prompt_mode_1.default.CSV;
}
if (!options.batchSize) {
batchSize = 32;
}
// No default TPM cap. Claude's free tier is 5 RPM / 20k TPM;
// Tier-1 is 50 RPM / 40k TPM; higher tiers go up from there.
// The RPM gate (1200ms default) paces calls enough that most
// users won't blow TPM without concurrency > 1. Opt in via
// --tokens-per-minute 20000 on free tier, 40000 on Tier-1.
break;
default: {
throw new Error("Invalid engine");
}
}
switch (promptMode) {
case prompt_mode_1.default.CSV:
if (options.batchMaxTokens) {
throw new Error("'--batch-max-tokens' is not used in CSV mode");
}
break;
case prompt_mode_1.default.JSON:
if (options.skipStylingVerification) {
throw new Error("'--skip-styling-verification' is not used in JSON mode");
}
if (options.engine === engine_1.default.Claude) {
throw new Error("JSON mode is not compatible with Anthropic");
}
break;
default: {
throw new Error("Invalid prompt mode");
}
}
return {
apiKey,
batchMaxTokens,
batchSize,
chatParams,
concurrency,
host,
model: options.model || constants_1.DEFAULT_MODEL[options.engine],
promptMode,
rateLimitMs,
tokensPerMinute,
};
}
/**
* Processes the override prompt file.
* @param overridePromptFilePath - The path to the override prompt file.
* @returns an object containing the override prompt.
*/
function processOverridePromptFile(overridePromptFilePath) {
const filePath = path_1.default.resolve(process.cwd(), overridePromptFilePath);
if (!fs_1.default.existsSync(filePath)) {
throw new Error(`The override prompt file does not exist at ${filePath}`);
}
let overridePrompt;
try {
overridePrompt = JSON.parse(fs_1.default.readFileSync(filePath, "utf-8"));
}
catch (err) {
throw new Error(`Failed to read the override prompt file. err = ${err}`);
}
if (Object.keys(overridePrompt).length === 0) {
throw new Error(`Received an empty object for the override prompt file. Valid keys are: ${override_prompt_1.OVERRIDE_PROMPT_KEYS.join(", ")}`);
}
for (const key of Object.keys(overridePrompt)) {
if (!override_prompt_1.OVERRIDE_PROMPT_KEYS.includes(key)) {
throw new Error(`Received an unexpected key ${key} in the override prompt file. Valid keys are: ${override_prompt_1.OVERRIDE_PROMPT_KEYS.join(", ")}`);
}
}
for (const value of Object.values(overridePrompt)) {
if (typeof value !== "string") {
throw new Error(`Expected a string as a key for every entry in the override prompt file. Received: ${typeof value}`);
}
}
return overridePrompt;
}
//# sourceMappingURL=cli_helpers.js.map