diff --git a/README.md b/README.md
index 89396fc..4f18804 100644
--- a/README.md
+++ b/README.md
@@ -6,10 +6,12 @@
-**Potomatic** is a command-line tool for translating `.pot` (Portable Object Template) files into multiple languages using AI (currently OpenAI). We built it to streamline large-scale localization of WordPress products, a process we detail in [this behind‑the‑scenes article](https://www.gravitykit.com/translating-wordpress-plugins-using-chatgpt/).
+**Potomatic** is a command-line tool for translating `.pot` (Portable Object Template) files into multiple languages using AI. We built it to streamline large-scale localization of WordPress products, a process we detail in [this behind‑the‑scenes article](https://www.gravitykit.com/translating-wordpress-plugins-using-chatgpt/).
While [`gpt-po`](https://github.com/ryanhex53/gpt-po) helped us get started, we needed smarter retry logic, cost controls, and better visibility into large jobs, among other things. **Potomatic** delivers those improvements and more, as well adds fine‑grained prompt tuning through a built‑in [A/B testing utility](#-ab-testing-for-prompt-optimization).
+Supports multiple AI providers: **OpenAI**, **Google Gemini**, and **Google Translate API**.
+
## 📢 Disclaimer
Translation quality varies depending on factors such as model selection, prompt design, and the complexity of the source text. **Potomatic** can generate a baseline translation, but the output should always be reviewed and verified before use.
@@ -40,7 +42,7 @@ For improved results, consider refining your prompt, using a higher-tier model,
## 🚀 Key Features
-* **🤖 AI‑powered translations** – Translate into any language supported by OpenAI models.
+* **🤖 AI‑powered translations** – Translate into any language supported by OpenAI, Google Gemini, or Google Translate API.
* **📦 Smart batch handling** – Tune batch size, concurrency and retries for the right balance of cost and speed.
* **💰 Cost‑conscious execution** – Accurately estimate costs and tokens, and control the maximum cost of a job.
* **🔄 Incremental & resumable workflows** – Resume interrupted jobs, merge with existing `.po` files, or force a re‑translation.
@@ -285,6 +287,73 @@ Using this example, "Block Editor" and other terms will not be translated to tar
---
+## 🤖 AI Providers
+
+Potomatic supports multiple AI providers for translation:
+
+### Google Gemini
+
+Uses Google's Gemini models for translation. Set the provider to `gemini` and configure your API key.
+
+**Available Models:**
+
+| Model | Prompt Cost | Completion Cost | Best For |
+|-------|-------------|-----------------|----------|
+| `gemini-3.1-pro-preview` | $0.00125/1K tokens | $0.005/1K tokens | Complex translations, highest quality |
+| `gemini-2.5-pro` | $0.0005/1K tokens | $0.0015/1K tokens | High-quality general use |
+| `gemini-2.5-flash` | $0.000175/1K tokens | $0.000525/1K tokens | Fast, cost-effective translations |
+| `gemini-flash-latest` | $0.000175/1K tokens | $0.000525/1K tokens | Latest optimized flash model |
+| `gemini-3.1-flash-lite-preview` | $0.0000375/1K tokens | $0.00015/1K tokens | Ultra-budget translations |
+
+**Usage:**
+```bash
+# Using Gemini 2.5 Flash
+./potomatic -l fr_FR -p translations.pot --provider gemini --model gemini-2.5-flash -k $GOOGLE_API_KEY
+
+# Using the latest flash model
+./potomatic -l fr_FR -p translations.pot --provider gemini --model gemini-flash-latest -k $GOOGLE_API_KEY
+
+# Using budget-friendly flash-lite
+./potomatic -l fr_FR -p translations.pot --provider gemini --model gemini-3.1-flash-lite-preview -k $GOOGLE_API_KEY
+```
+
+### Google Translate API
+
+Uses Google Cloud Translate API for fast, cost-effective translations. Ideal for straightforward text without complex nuances.
+
+**Pricing:** Google Translate API charges based on character count:
+- $20 per 1M characters (standard tier)
+- Volume discounts available for higher usage
+
+**Usage:**
+```bash
+./potomatic -l fr_FR -p translations.pot --provider google-translate -k $GOOGLE_CLOUD_API_KEY
+```
+
+### OpenAI
+
+Uses OpenAI's GPT models for translation. This was the original provider and offers the widest model selection.
+
+**Available Models:**
+
+| Model | Prompt Cost | Completion Cost | Best For |
+|-------|-------------|-----------------|----------|
+| `gpt-4o-mini` | $0.00015/1K tokens | $0.0006/1K tokens | Fast, budget-friendly |
+| `gpt-4o` | $0.0025/1K tokens | $0.01/1K tokens | High-quality |
+| `gpt-4o-mini-2024-07-18` | $0.00015/1K tokens | $0.0006/1K tokens | Specific dated version |
+
+**Usage:**
+```bash
+./potomatic -l fr_FR -p translations.pot --provider openai --model gpt-4o-mini -k $OPENAI_API_KEY
+```
+
+**Environment Variables:**
+- `GOOGLE_API_KEY` - For Gemini provider
+- `GOOGLE_CLOUD_API_KEY` - For Google Translate provider
+- `API_KEY` - Falls back to OpenAI
+
+---
+
## ⚙️ Configuration Files
**Potomatic** uses several configuration files in the `config/` directory to customize its behavior:
diff --git a/config/gemini-pricing.json b/config/gemini-pricing.json
new file mode 100644
index 0000000..4715027
--- /dev/null
+++ b/config/gemini-pricing.json
@@ -0,0 +1,28 @@
+{
+ "models": {
+ "gemini-2.5-pro": {
+ "prompt": 0.0005,
+ "completion": 0.0015
+ },
+ "gemini-2.5-flash": {
+ "prompt": 0.000175,
+ "completion": 0.000525
+ },
+ "gemini-3.1-pro-preview": {
+ "prompt": 0.00125,
+ "completion": 0.005
+ },
+ "gemini-flash-latest": {
+ "prompt": 0.000175,
+ "completion": 0.000525
+ },
+ "gemini-3.1-flash-lite-preview": {
+ "prompt": 0.0000375,
+ "completion": 0.00015
+ }
+ },
+ "fallback": {
+ "prompt": 0.0005,
+ "completion": 0.0015
+ }
+}
\ No newline at end of file
diff --git a/config/google-translate-pricing.json b/config/google-translate-pricing.json
new file mode 100644
index 0000000..7a0c5bc
--- /dev/null
+++ b/config/google-translate-pricing.json
@@ -0,0 +1,12 @@
+{
+ "models": {
+ "default": {
+ "prompt": 0,
+ "completion": 0
+ }
+ },
+ "fallback": {
+ "prompt": 0,
+ "completion": 0
+ }
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 1f60de1..98251a1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,8 +7,9 @@
"": {
"name": "potomatic",
"version": "1.0.0",
- "license": "GPL-3.0-or-later",
+ "license": "MIT",
"dependencies": {
+ "@google/generative-ai": "^0.24.1",
"chalk": "^5.3.0",
"commander": "^12.0.0",
"dotenv": "^16.5.0",
@@ -38,7 +39,7 @@
"vitest": "^3.1.4"
},
"engines": {
- "node": ">=18.0.0"
+ "node": ">=18"
}
},
"node_modules/@ampproject/remapping": {
@@ -590,6 +591,15 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
+ "node_modules/@google/generative-ai": {
+ "version": "0.24.1",
+ "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.24.1.tgz",
+ "integrity": "sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
@@ -1270,6 +1280,7 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"dev": true,
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -1577,7 +1588,6 @@
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
"dev": true,
- "peer": true,
"engines": {
"node": ">=6"
},
@@ -1590,7 +1600,6 @@
"resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz",
"integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==",
"dev": true,
- "peer": true,
"dependencies": {
"semver": "^7.0.0"
}
@@ -2199,6 +2208,7 @@
"integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -2254,7 +2264,6 @@
"resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz",
"integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==",
"dev": true,
- "peer": true,
"dependencies": {
"semver": "^7.5.4"
},
@@ -2383,7 +2392,6 @@
"https://github.com/sponsors/ota-meshi",
"https://opencollective.com/eslint"
],
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.1.2",
"@eslint-community/regexpp": "^4.11.0",
@@ -2401,6 +2409,7 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz",
"integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
"dev": true,
+ "peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.8",
@@ -2486,7 +2495,6 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz",
"integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==",
"dev": true,
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"builtins": "^5.0.1",
@@ -2515,7 +2523,6 @@
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
- "peer": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -2526,7 +2533,6 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
- "peer": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -2590,6 +2596,7 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz",
"integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==",
"dev": true,
+ "peer": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
@@ -3232,7 +3239,6 @@
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz",
"integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==",
"dev": true,
- "peer": true,
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
@@ -3608,7 +3614,6 @@
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
"integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
"dev": true,
- "peer": true,
"dependencies": {
"builtin-modules": "^3.3.0"
},
@@ -4738,6 +4743,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -4964,7 +4970,6 @@
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true,
- "peer": true,
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
@@ -5865,6 +5870,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
"dev": true,
+ "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@@ -5961,6 +5967,7 @@
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.4.tgz",
"integrity": "sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ==",
"dev": true,
+ "peer": true,
"dependencies": {
"@vitest/expect": "3.1.4",
"@vitest/mocker": "3.1.4",
@@ -6302,6 +6309,7 @@
"version": "3.25.23",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.23.tgz",
"integrity": "sha512-Od2bdMosahjSrSgJtakrwjMDb1zM1A3VIHCPGveZt/3/wlrTWBya2lmEh2OYe4OIu8mPTmmr0gnLHIWQXdtWBg==",
+ "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
diff --git a/package.json b/package.json
index 9979a3b..9ac3835 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
},
"scripts": {
"translate": "./potomatic",
+ "translate:gemini": "./potomatic --provider gemini",
"ab-prompt-test": "node tools/ab-prompt-test",
"test": "vitest run",
"test:watch": "vitest",
@@ -45,6 +46,7 @@
"node": ">=18"
},
"dependencies": {
+ "@google/generative-ai": "^0.24.1",
"chalk": "^5.3.0",
"commander": "^12.0.0",
"dotenv": "^16.5.0",
diff --git a/src/config/index.js b/src/config/index.js
index 4c15947..41886bb 100644
--- a/src/config/index.js
+++ b/src/config/index.js
@@ -216,7 +216,8 @@ export function parseCliArguments() {
.option('--locale-format ', 'Format to use for locale codes in file names: `wp_locale` (ru_RU), `iso_639_1` (ru), `iso_639_2` (rus), or `target_lang` (default)', DEFAULTS.LOCALE_FORMAT)
// === Translation Options ==.=
- .option('-k, --api-key ', 'OpenAI API key (overrides API_KEY env var)')
+ .option('--provider ', 'AI provider to use (e.g., "openai", "gemini", "google-translate")', DEFAULTS.PROVIDER)
+ .option('-k, --api-key ', 'Provider API key (overrides API_KEY env var)')
.option('-m, --model ', 'AI model name (e.g., "gpt-4o-mini")', DEFAULTS.MODEL)
.option('--temperature ', 'Creativity level (0.0-2.0); lower = more deterministic, higher = more creative', (val) => Math.max(0, Math.min(2, parseFloat(val))), DEFAULTS.TEMPERATURE)
.option('-F, --force-translate', 'Re-translate all strings, ignoring any existing translations', DEFAULTS.FORCE_TRANSLATE)
@@ -302,7 +303,15 @@ export function validateConfiguration(options) {
const errors = [];
if (!options.dryRun && !options.apiKey && !process.env.API_KEY) {
- errors.push('🔑 API key required (set API_KEY env var, use --api-key, or try --dry-run)');
+ // Google Translate doesn't require an API key
+ if (options.provider !== 'google-translate') {
+ errors.push('🔑 API key required (set API_KEY env var, use --api-key, or try --dry-run)');
+ }
+ }
+
+ // Google Translate requires source language
+ if (options.provider === 'google-translate' && !options.sourceLanguage && !process.env.SOURCE_LANGUAGE) {
+ errors.push('🌐 Source language required for Google Translate (use -s or --source-language)');
}
if (!options.targetLanguages || options.targetLanguages.length === 0) {
diff --git a/src/providers/ProviderFactory.js b/src/providers/ProviderFactory.js
index 6296920..2e9be95 100644
--- a/src/providers/ProviderFactory.js
+++ b/src/providers/ProviderFactory.js
@@ -1,4 +1,6 @@
import { OpenAIProvider } from './openai/OpenAIProvider.js';
+import { GeminiProvider } from './gemini/GeminiProvider.js';
+import { GoogleTranslateProvider } from './google-translate/GoogleTranslateProvider.js';
/**
* Creates and configures AI translation providers based on configuration.
@@ -26,6 +28,10 @@ export class ProviderFactory {
switch (providerName.toLowerCase()) {
case 'openai':
return new OpenAIProvider(config, logger);
+ case 'gemini':
+ return new GeminiProvider(config, logger);
+ case 'google-translate':
+ return new GoogleTranslateProvider(config, logger);
default:
throw new Error(`Unsupported provider: ${providerName}. ` + `Supported providers: ${ProviderFactory.getSupportedProviders().join(', ')}`);
}
@@ -39,7 +45,7 @@ export class ProviderFactory {
* @return {Array} Array of supported provider names.
*/
static getSupportedProviders() {
- return ['openai'];
+ return ['openai', 'gemini', 'google-translate'];
}
/**
@@ -76,6 +82,29 @@ export class ProviderFactory {
model: 'gpt-3.5-turbo',
},
},
+ {
+ name: 'gemini',
+ displayName: 'Google Gemini',
+ description: 'Google Gemini models',
+ status: 'implemented',
+ models: ['gemini-2.5-pro', 'gemini-2.5-flash'],
+ configExample: {
+ provider: 'gemini',
+ apiKey: 'your-gemini-api-key',
+ model: 'gemini-2.5-flash',
+ },
+ },
+ {
+ name: 'google-translate',
+ displayName: 'Google Translate (Free)',
+ description: 'Free Google Translate API - no API key required',
+ status: 'implemented',
+ models: ['default'],
+ configExample: {
+ provider: 'google-translate',
+ sourceLanguage: 'en',
+ },
+ },
];
}
diff --git a/src/providers/gemini/GeminiProvider.js b/src/providers/gemini/GeminiProvider.js
new file mode 100644
index 0000000..ac9b257
--- /dev/null
+++ b/src/providers/gemini/GeminiProvider.js
@@ -0,0 +1,774 @@
+import { GoogleGenerativeAI } from '@google/generative-ai';
+import { Provider } from '../base/Provider.js';
+import { buildXmlPrompt, parseXmlResponse, buildDictionaryResponse } from '../../utils/xmlTranslation.js';
+import { loadDictionary, findDictionaryMatches } from '../../utils/dictionaryUtils.js';
+
+/**
+ * Gemini Provider Implementation.
+ *
+ * Handles translation using Google's Gemini models.
+ * Implements the Provider interface with Gemini-specific functionality.
+ *
+ * @since 1.0.0
+ */
+export class GeminiProvider extends Provider {
+ /**
+ * Creates a new Gemini Provider instance.
+ *
+ * @since 1.0.0
+ *
+ * @param {Object} config - Gemini provider configuration.
+ * @param {Object} logger - Logger instance.
+ */
+ constructor(config, logger) {
+ super(config, logger);
+
+ this.client = null;
+ }
+
+ /**
+ * Initializes the Gemini provider.
+ * Sets up authentication and loads pricing information.
+ *
+ * @since 1.0.0
+ *
+ * @throws {Error} If API key is missing or initialization fails.
+ *
+ * @return {Promise} Resolves when initialization is complete.
+ */
+ async initialize() {
+ if (!this.config.apiKey && !this.config.dryRun) {
+ throw new Error('API key is required for non-dry-run mode');
+ }
+
+ if (!this.config.dryRun && this.config.apiKey) {
+ const genAI = new GoogleGenerativeAI(this.config.apiKey);
+ this.client = genAI.getGenerativeModel({ model: this.config.model });
+ }
+
+ await this._loadProviderPricing('gemini');
+
+ this.logger.debug(`Gemini provider initialized with model: ${this.config.model}`);
+ }
+
+ /**
+ * Validates Gemini provider configuration.
+ *
+ * @since 1.0.0
+ *
+ * @param {Object} config - Configuration to validate.
+ *
+ * @return {Object} Validation result.
+ */
+ validateConfig(config) {
+ const errors = [];
+
+ if (!config.dryRun && !config.apiKey) {
+ errors.push('API key is required (set API_KEY or use --dry-run)');
+ }
+
+ const supportedModels = this.getSupportedModels();
+
+ if (config.model && !supportedModels.includes(config.model)) {
+ errors.push(`Unsupported model: ${config.model}. Supported: ${supportedModels.join(', ')}`);
+ }
+
+ if (config.temperature !== undefined && (config.temperature < 0 || config.temperature > 2)) {
+ errors.push('Temperature must be between 0.0 and 2.0');
+ }
+
+ return {
+ isValid: errors.length === 0,
+ errors,
+ };
+ }
+
+ /**
+ * Translates a batch of strings using Gemini's API.
+ *
+ * @since 1.0.0
+ *
+ * @param {Array} batch - Array of translation items.
+ * @param {string} targetLang - Target language code.
+ * @param {string} model - Gemini model to use.
+ * @param {string} systemPrompt - System prompt for translation.
+ * @param {number} maxRetries - Maximum retry attempts.
+ * @param {number} retryDelayMs - Delay between retries.
+ * @param {number} timeout - Request timeout.
+ * @param {boolean} isDryRun - Whether this is a dry run.
+ * @param {Function} retryProgressCallback - Optional callback for retry progress updates.
+ * @param {Object} debugConfig - Optional debug configuration object.
+ * @param {number} pluralCount - Number of plural forms for target language.
+ *
+ * @return {Promise