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.

232 lines 9.66 kB
"use strict"; 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