i18ntk 3.1.2 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,34 +3,105 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [3.1.2] - 2026-05-07
9
-
10
- ### Fixed
11
- - Auto Translate now resolves locale roots such as `./locales` to the selected source-language folder such as `./locales/en` when JSON files are stored under language folders.
12
- - Public package staging now verifies root `package.json` and `package.public.json` release metadata are synchronized before pack or publish.
13
- - Added a safe `publish:public:dry-run` path for validating the exact staged npm publish flow.
14
-
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [3.3.0] - 2026-05-20
9
+
15
10
  ### Changed
16
- - Updated release docs, npm README metadata, and package manifests for v3.1.2.
17
- - Kept generated backups, temporary benchmark datasets, local setup state, and debug repair files out of future public repo commits through `.gitignore`.
11
+ - Auto Translate now supports `--provider google|deepl|libretranslate`; DeepL uses `DEEPL_API_KEY`, while LibreTranslate supports `LIBRETRANSLATE_URL` and optional `LIBRETRANSLATE_API_KEY`.
12
+ - Auto Translate provider networking now keeps HTTPS, host allowlist, response-size, private-network, and redacted security logging protections in place for additional providers.
18
13
 
19
- ## [3.1.1] - 2026-05-07
14
+ ### Fixed
15
+ - `i18ntk-complete` now fills missing target-language keys from the English source value with a language prefix such as `[DE] Home` instead of writing `NOT_TRANSLATED`; this works for both `locales/en/*.json` and monolith `locales/en.json` layouts.
20
16
 
21
- ### Added
22
- - **Auto Translate protection file workflow**: Added user-editable `i18ntk-auto-translate.json` support for protected terms, key paths, exact values, and regex patterns.
23
- - **Public package README guard**: Public package staging now verifies `README.md` is included and non-empty before publish.
17
+ ### Security
18
+ - Eliminated all 21 dynamic `require()` calls flagged by Socket.dev: 20 `require(path.join(__dirname, ...))` patterns in `i18ntk-js.js`, `i18ntk-py.js`, `i18ntk-java.js`, `i18ntk-php.js`, and `i18ntk-go.js` converted to static string literal requires.
19
+ - Added `SecurityUtils.validatePath()` gate around the remaining dynamic `require()` in `i18ntk-translate.js` `loadCustomTranslateFn`.
20
+ - Created `utils/translate/safe-network.js` — a secure HTTPS wrapper with URL host/path allowlist validation, response size limits (100KB), suspicious query parameter detection, and security event logging. All outbound network access now flows through this validated layer.
21
+ - Replaced direct `https.get` call in `utils/translate/api.js` with `safeHttpGet` from the safe-network wrapper.
22
+
23
+ ### Docs
24
+ - README.md updated for v3.3.0 Auto Translate providers and secure provider operations.
25
+ - SECURITY.md updated with Socket.dev analysis disclaimer and guidance on expected alerts for a CLI/i18n toolkit.
26
+ - CHANGELOG.md and `package.json` versionInfo updated for v3.3.0.
27
+
28
+ ### Socket.dev Analysis Disclaimer
29
+
30
+ This package is a developer CLI and runtime helper that performs file I/O, network access (translation provider APIs on user request), and environment variable access. As such, Socket.dev will flag the following alerts that are **expected and by design**:
31
+
32
+ | Alert | Why it's expected |
33
+ |---|---|
34
+ | Network access | Only contacts configured translation providers via HTTPS when user invokes auto-translate. All outbound calls flow through `safe-network.js` with host/path allowlist validation, response size limits, private-network blocking, and redacted security event logging. No telemetry, no unexpected outbound calls. |
35
+ | Environment variable access | Centralized through `env-manager.js` with a strict allowlist. Blocks `SECRET`, `PASSWORD`, `KEY`, `TOKEN`, `AWS_*`, `NPM_*`, and 15+ other patterns. |
36
+ | Filesystem access | Reads/writes only project locale files and reports within validated paths. All FS operations gated by `SecurityUtils.validatePath`. |
37
+ | URL strings | Hardcoded default provider URLs for Google, DeepL, and LibreTranslate used only for auto-translation. No external resource loading. |
24
38
 
25
- ### Changed
26
- - Updated README and release documentation for the current Auto Translate protection workflow and public package contents.
27
- - Removed project-specific hardcoded validation examples so users configure their own brand and domain terms.
39
+ The v3.3.0 release resolves the actionable dynamic-require alert by eliminating all 21 instances.
28
40
 
29
- ### Fixed
30
- - Removed provider-shaped fake secret fixtures from tests to avoid GitHub push protection false positives.
31
- - Ensured public package metadata includes `readmeFilename: "README.md"` so npm can render the package README.
32
-
33
- ## [3.1.0] - 2026-05-07
41
+ ## [3.2.0] - 2026-05-16
42
+
43
+ ### Security
44
+ - **CRITICAL**: Fixed invalid `crypto.createCipherGCM`/`createDecipherGCM` API calls in `admin-pin.js` — replaced with `crypto.createCipheriv`/`createDecipheriv`.
45
+ - **CRITICAL**: Fixed missing `SecurityUtils` imports in `admin-pin.js`, `security-config.js`, and `scripts/security-check.js` causing `ReferenceError` at runtime.
46
+ - **CRITICAL**: Removed encryption key stored alongside ciphertext in `admin-pin.js`. The AES key was stored in the same JSON file as the encrypted PIN, providing zero cryptographic protection. Encryption key is now derived via HKDF from the scrypt hash.
47
+ - Enforced HTTPS-only for Google Translate API requests in `utils/translate/api.js`; dropped `http` protocol support.
48
+ - Fixed `http.get` timeout for Node.js <16.14 compatibility by using `req.setTimeout()` instead of the options-based `{ timeout }` parameter.
49
+ - Added `SecurityUtils.validatePath` checks to `secure-backup.js` `restoreBackup` and `verifyBackup` methods.
50
+ - Added `backupDir` traversal validation in `secure-backup.js` constructor.
51
+ - Fixed `FileManagementService` `isAuthRequiredForScript`/`verifyPin` stubs — previously returned hardcoded `false`/`true`, disabling PIN protection. Now delegates to proper `AdminAuth` module.
52
+ - Fixed `admin-auth.js` `logSecurityEvent` signature mismatches — 6 calls passed raw strings instead of structured objects.
53
+ - Fixed `admin-pin.js` `getPinDisplay` to use stored `pinLength` instead of decrypting the raw PIN into memory.
54
+
55
+ ### Fixed
56
+ - `admin-pin.js` lockout now uses timestamp-based expiry (`lockedUntil`) instead of `setTimeout`, ensuring lockout state survives process restarts.
57
+ - `translate/traverse.js` `setLeaf` now correctly creates `[]` for numeric array indices (was creating `{}`).
58
+ - `translate/traverse.js` extracted shared `parseKeyPath` function — `setLeaf` and `getLeaf` had duplicate path-parsing logic.
59
+ - `translate/traverse.js` `deepClone` now handles `null`, `undefined`, and circular references gracefully.
60
+ - `translate/api.js` retry logic now retries `TimeoutError` and `NetworkError` with exponential backoff (previously only retried rate-limit errors).
61
+ - `translate/api.js` added `User-Agent` header to Google Translate API requests.
62
+ - `main/manage/index.js` `startupTimeout` no longer cleared before `createPrompt` and other blocking initialization steps.
63
+ - `main/manage/index.js` removed silent no-op `t('init.autoDetectedI18nDirectory', ...)` whose return value was never used.
64
+ - `ultra-performance-optimizer.js` removed dead `preallocateMemory` pools (`stringPool`, `objectPool`, `arrayPool`) — ~1MB wasted allocation.
65
+ - `ultra-performance-optimizer.js` `getCacheKey` now uses async `fs.stat` instead of blocking `fs.statSync`.
66
+ - `ultra-performance-optimizer.js` GC timer now enforces minimum 5-second interval and warns when `--expose-gc` is missing.
67
+ - `ultra-performance-optimizer.js` `readFileUltra` now handles files >64KB with chunked reads.
68
+ - `ultra-performance-optimizer.js` `createUltraCache` replaced per-entry `setTimeout` (timer leak) with unified cleanup interval.
69
+ - `ultra-performance-optimizer.js` benchmark now uses real benchmark datasets instead of non-existent mock files.
70
+ - `config-manager.js` now exports `loadSettings`/`saveSettings` aliases — resolves 20+ phantom API fallback calls across the codebase.
71
+ - `config-manager.js` `updateConfig` now clones before deep-merging to prevent in-place cache corruption.
72
+ - `admin-pin.js` scrypt→pbkdf2 fallback now emits a console warning instead of failing silently.
73
+
74
+ ### Changed
75
+ - Updated all documentation to v3.2.0: README, CHANGELOG, docs/README, getting-started, runtime, auto-translate, environment-variables, scanner-guide, API_REFERENCE, COMPONENTS, and CONFIGURATION.
76
+ - Updated `package.json` version, `versionInfo`, `majorChanges`, and `nextVersion` for v3.2.0.
77
+ - Socket badge URL updated to v3.2.0.
78
+
79
+ ## [3.1.2] - 2026-05-07
80
+
81
+ ### Fixed
82
+ - Auto Translate now resolves locale roots such as `./locales` to the selected source-language folder such as `./locales/en` when JSON files are stored under language folders.
83
+ - Public package staging now verifies root `package.json` and `package.public.json` release metadata are synchronized before pack or publish.
84
+ - Added a safe `publish:public:dry-run` path for validating the exact staged npm publish flow.
85
+
86
+ ### Changed
87
+ - Updated release docs, npm README metadata, and package manifests for v3.1.2.
88
+ - Kept generated backups, temporary benchmark datasets, local setup state, and debug repair files out of future public repo commits through `.gitignore`.
89
+
90
+ ## [3.1.1] - 2026-05-07
91
+
92
+ ### Added
93
+ - **Auto Translate protection file workflow**: Added user-editable `i18ntk-auto-translate.json` support for protected terms, key paths, exact values, and regex patterns.
94
+ - **Public package README guard**: Public package staging now verifies `README.md` is included and non-empty before publish.
95
+
96
+ ### Changed
97
+ - Updated README and release documentation for the current Auto Translate protection workflow and public package contents.
98
+ - Removed project-specific hardcoded validation examples so users configure their own brand and domain terms.
99
+
100
+ ### Fixed
101
+ - Removed provider-shaped fake secret fixtures from tests to avoid GitHub push protection false positives.
102
+ - Ensured public package metadata includes `readmeFilename: "README.md"` so npm can render the package README.
103
+
104
+ ## [3.1.0] - 2026-05-07
34
105
 
35
106
  ### Added
36
107
  - **Placeholder-preserve translation mode**: Translates text segments around dynamic placeholders and reinserts the original tokens exactly.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # i18ntk v3.1.2
1
+ # i18ntk v3.3.0
2
2
 
3
- Zero-dependency internationalization toolkit for setup, scanning, analysis, validation, usage tracking, translation completion, automatic JSON locale translation, reporting, and runtime translation loading.
3
+ A i18n toolkit - A zero-dependency internationalization toolkit for setup, scanning, analysis, validation, usage tracking, translation completion, automatic JSON locale translation, reporting, and runtime translation loading.
4
4
 
5
5
  ![i18ntk Logo](https://raw.githubusercontent.com/vladnoskv/i18ntk/main/docs/screenshots/i18ntk-logo-public.PNG)
6
6
 
@@ -9,7 +9,7 @@ Zero-dependency internationalization toolkit for setup, scanning, analysis, vali
9
9
  [![node](https://img.shields.io/badge/node-%3E%3D16-339933)](https://nodejs.org)
10
10
  [![dependencies](https://img.shields.io/badge/dependencies-0-success)](https://www.npmjs.com/package/i18ntk)
11
11
  [![license](https://img.shields.io/badge/license-MIT-yellow.svg)](LICENSE)
12
- [![socket](https://socket.dev/api/badge/npm/package/i18ntk/3.1.2)](https://socket.dev/npm/package/i18ntk/overview/3.1.2)
12
+ [![socket](https://socket.dev/api/badge/npm/package/i18ntk/3.3.0)](https://socket.dev/npm/package/i18ntk/overview/3.3.0)
13
13
 
14
14
  ## Install
15
15
 
@@ -30,21 +30,14 @@ Requirements:
30
30
  - npm `>=8.0.0`
31
31
  - No runtime dependencies
32
32
 
33
- ## What's New in 3.1.2
33
+ ## What's New in 3.3.0
34
34
 
35
- - Auto Translate can translate strings that contain placeholders by translating text around the placeholders and reinserting the original tokens.
36
- - Auto Translate supports user-editable protection rules in `i18ntk-auto-translate.json` for brand names, product terms, exact values, key paths, and regex patterns.
37
- - The manager Auto Translate flow runs in-process, avoiding production `child_process` usage for that command.
38
- - The target-language prompt supports `all` to translate into every configured target language while excluding the source language.
39
- - Source-directory prompts are clearer and accept absolute paths or project-relative paths.
40
- - Validation warnings now distinguish URLs, email addresses, secret-like values, and likely untranslated English content.
41
- - Sizing reports now include per-language file counts, file-set mismatches, and per-file key/character statistics.
42
- - Internal UI locale coverage is enforced against the English UI locale.
43
- - Public package staging verifies `README.md` is present before publish.
44
- - Auto Translate now resolves locale roots such as `./locales` to the selected source-language folder such as `./locales/en` when needed.
45
- - Public package staging now fails when root `package.json` and `package.public.json` release metadata drift.
35
+ - **SECURITY**: Eliminated all 21 dynamic `require()` calls flagged by Socket.dev; 20 converted to static string literals, 1 gated with `SecurityUtils.validatePath`.
36
+ - **AUTO TRANSLATE**: Added provider selection for Google, DeepL, and LibreTranslate.
37
+ - **FIX**: `i18ntk-complete` now fills missing target-language keys from English values with language prefixes instead of `NOT_TRANSLATED`.
38
+ - **DOCS**: SECURITY.md updated with Socket.dev analysis disclaimer explaining expected alerts for a CLI/i18n toolkit.
46
39
 
47
- See [CHANGELOG.md](./CHANGELOG.md) and [docs/migration-guide-v3.1.2.md](./docs/migration-guide-v3.1.2.md) for release details.
40
+ See [CHANGELOG.md](./CHANGELOG.md) for more release details.
48
41
 
49
42
  ## Quick Start
50
43
 
@@ -52,7 +45,7 @@ Initialize a project:
52
45
 
53
46
  ```bash
54
47
  i18ntk
55
- # or
48
+ # or with explicit command
56
49
  i18ntk --command=init
57
50
  ```
58
51
 
@@ -118,8 +111,8 @@ i18ntk-fixer
118
111
  i18ntk-backup
119
112
  i18ntk-translate
120
113
  ```
121
-
122
- Note: manager route `i18ntk --command=backup` is disabled in current builds. Use `i18ntk-backup` directly for backup operations.
114
+ `n
115
+ Note: manager route `i18ntk --command=backup` is disabled in current builds. Use `i18ntk-backup` (or legacy `i18ntk-backup`) directly for backup operations.
123
116
 
124
117
  ## Common Options
125
118
 
@@ -157,6 +150,21 @@ i18ntk-translate locales/en/common.json fr --dry-run --report-stdout
157
150
  i18ntk-translate locales/en es --source-dir locales/en --files "*.json" --no-confirm --preserve-placeholders
158
151
  ```
159
152
 
153
+ Provider examples:
154
+
155
+ ```bash
156
+ export DEEPL_API_KEY="your-deepl-api-key"
157
+ i18ntk-translate locales/en/common.json de --provider deepl --no-confirm --preserve-placeholders
158
+
159
+ export LIBRETRANSLATE_URL="https://libretranslate.com/translate"
160
+ export LIBRETRANSLATE_API_KEY="optional-api-key"
161
+ i18ntk-translate locales/en/common.json es --provider libretranslate --no-confirm --preserve-placeholders
162
+ ```
163
+
164
+ `google` remains the default provider. You can also set `I18NTK_TRANSLATE_PROVIDER=deepl` or `I18NTK_TRANSLATE_PROVIDER=libretranslate`.
165
+
166
+ Provider requests are HTTPS-only and response-size limited, and security logs redact provider query strings and response bodies. DeepL is pinned to official DeepL hosts by default; set `I18NTK_ALLOW_CUSTOM_TRANSLATE_HOSTS=1` only for a trusted DeepL-compatible proxy. Custom LibreTranslate URLs are blocked for localhost/private IP ranges unless `I18NTK_ALLOW_PRIVATE_TRANSLATE_URLS=1` is set for trusted local testing. Keep provider API keys in environment variables or a secret manager.
167
+
160
168
  The manager flow asks for:
161
169
 
162
170
  - source locale directory, either the folder with JSON files or a locale root such as `./locales`
@@ -296,7 +304,7 @@ Example:
296
304
 
297
305
  ```json
298
306
  {
299
- "version": "3.1.2",
307
+ "version": "3.3.0",
300
308
  "sourceDir": "./locales",
301
309
  "i18nDir": "./locales",
302
310
  "outputDir": "./i18ntk-reports",
@@ -354,11 +362,9 @@ The public package manifest includes `readmeFilename: "README.md"`, and the rele
354
362
  - [Auto Translate Guide](./docs/auto-translate.md)
355
363
  - [Scanner Guide](./docs/scanner-guide.md)
356
364
  - [Environment Variables](./docs/environment-variables.md)
357
- - [Migration Guide v3.1.2](./docs/migration-guide-v3.1.2.md)
365
+ - [Migration Guide v3.2.0](./docs/migration-guide-v3.2.0.md)
358
366
  - [Migration Guide v3.1.1](./docs/migration-guide-v3.1.1.md)
359
367
  - [Migration Guide v3.0.0](./docs/migration-guide-v3.0.0.md)
360
- - [Migration Guide v2.6.0](./docs/migration-guide-v2.6.0.md)
361
- - [Migration Guide v2.5.1](./docs/migration-guide-v2.5.1.md)
362
368
 
363
369
  ## Security
364
370
 
package/SECURITY.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  ## Supported Versions
4
4
 
5
- The supported production line is `2.5.x`.
5
+ The supported production line is `3.x`.
6
6
 
7
- Versions earlier than `2.5.0` are not recommended for production use because later releases include package, filesystem, environment, and admin-auth hardening.
7
+ Versions earlier than `3.3.0` are not recommended for production use because later releases include Auto Translate provider hardening, dynamic-require elimination, and path-validation hardening.
8
8
 
9
9
  ## Security Model
10
10
 
@@ -14,8 +14,8 @@ Security priorities:
14
14
 
15
15
  - zero runtime dependencies
16
16
  - no install-time lifecycle commands in the public package manifest
17
- - no shipped local setup state, admin PINs, backups, reports, logs, credentials, or generated artifacts
18
- - centralized environment-variable access through `utils/env-manager.js`
17
+ - no shipped local setup state, admin PINs, backup files, reports, logs, credentials, or generated artifacts
18
+ - centralized environment-variable access through `utils/env-manager.js` with a strict allowlist
19
19
  - path containment checks based on resolved paths and `path.relative()`
20
20
  - timing-safe comparison for authentication hashes or tokens
21
21
  - silent-by-default logging for production-like contexts
@@ -24,6 +24,20 @@ Security priorities:
24
24
 
25
25
  The npm package uses a stripped public manifest. It must not contain install-time lifecycle commands, dependency fields, local setup state, or development tooling.
26
26
 
27
+ ## Socket.dev Analysis Disclaimer
28
+
29
+ Socket.dev scans the published npm package and may flag the following alerts. These are **expected behaviors** for a developer CLI/i18n toolkit and are mitigated as described:
30
+
31
+ | Alert | Design Rationale | Mitigation |
32
+ |---|---|---|
33
+ | **Network access** | Contacts configured translation providers via HTTPS only when user explicitly invokes auto-translate. No telemetry, no beaconing, no background network activity. | `utils/translate/safe-network.js` enforces HTTPS, host/path allowlists, response-size limits, private-network blocking, and redacted security logging for Google, DeepL, and LibreTranslate provider requests. |
34
+ | **Environment variable access** | Reads env vars for logging level, output directory, UI language, and project paths. Required for CLI configuration. | `utils/env-manager.js` uses a fixed allowlist (28 vars). Blocks `SECRET`, `PASSWORD`, `KEY`, `TOKEN`, `AWS_*`, `GITHUB_*`, `NPM_*`, `NODE_*`, `PATH`, `HOME`, `USER`, `SHELL`, and 8 more patterns. |
35
+ | **Filesystem access** | Reads/writes locale JSON files, config files, and reports within the user's project. Core function of the toolkit. | All FS operations are gated by `SecurityUtils.validatePath()` with path-traversal detection, symlink resolution, and base-path containment checks. |
36
+ | **URL strings** | Contains hardcoded translation provider endpoint URLs for Google, DeepL, and LibreTranslate defaults. | Only used when user invokes `i18ntk-translate`. Custom provider URLs remain HTTPS-only and are validated before use. |
37
+
38
+ The v3.3.0 release **resolved** the previously actionable Socket.dev alert:
39
+ - **Dynamic require** — all 21 instances eliminated (20 converted to static string literals, 1 gated with `SecurityUtils.validatePath`).
40
+
27
41
  ## Reporting Vulnerabilities
28
42
 
29
43
  Do not report security vulnerabilities in public GitHub issues.
@@ -46,7 +60,7 @@ Security reports are reviewed privately first. Confirmed issues should receive:
46
60
 
47
61
  ## User Guidance
48
62
 
49
- - Keep i18ntk updated to `2.5.0` or newer.
63
+ - Keep i18ntk updated to `3.3.0` or newer.
50
64
  - Do not commit `.i18ntk-config`, admin PIN files, backup directories, generated reports, logs, npm credentials, or secret material.
51
65
  - Run i18ntk only in project directories you trust.
52
66
  - Review generated translation changes before committing them.
@@ -161,9 +161,14 @@ class I18nCompletionTool {
161
161
  return lowered.startsWith('backup-') || lowered === 'backup' || lowered === 'reports' || lowered === 'i18ntk-reports';
162
162
  }
163
163
 
164
- // Get all JSON files from a language directory
165
- getLanguageFiles(language) {
166
- const languageDir = path.join(this.sourceDir, language);
164
+ // Get all JSON files from a language directory
165
+ getLanguageFiles(language) {
166
+ const monolithFile = path.join(this.sourceDir, `${language}.json`);
167
+ if (SecurityUtils.safeExistsSync(monolithFile, this.config.projectRoot)) {
168
+ return [`${language}.json`];
169
+ }
170
+
171
+ const languageDir = path.join(this.sourceDir, language);
167
172
 
168
173
  if (!SecurityUtils.safeExistsSync(languageDir, this.config.projectRoot)) {
169
174
  return [];
@@ -173,8 +178,20 @@ class I18nCompletionTool {
173
178
  .filter(file => {
174
179
  return file.endsWith('.json') &&
175
180
  !this.config.excludeFiles.includes(file);
176
- });
177
- }
181
+ });
182
+ }
183
+
184
+ getLanguageFilePath(language, fileName) {
185
+ if (fileName === `${language}.json`) {
186
+ return path.join(this.sourceDir, fileName);
187
+ }
188
+
189
+ return path.join(this.sourceDir, language, fileName);
190
+ }
191
+
192
+ usesMonolithFile(language) {
193
+ return SecurityUtils.safeExistsSync(path.join(this.sourceDir, `${language}.json`), this.config.projectRoot);
194
+ }
178
195
 
179
196
  // Parse key path and determine which file it belongs to
180
197
  parseKeyPath(keyPath) {
@@ -238,25 +255,30 @@ class I18nCompletionTool {
238
255
  }
239
256
 
240
257
  // Add missing keys to a language
241
- addMissingKeysToLanguage(language, missingKeys, dryRun = false) {
242
- const languageDir = path.join(this.sourceDir, language);
243
- const changes = [];
258
+ addMissingKeysToLanguage(language, missingKeys, dryRun = false) {
259
+ const languageDir = path.join(this.sourceDir, language);
260
+ const changes = [];
261
+ const usesMonolith = this.usesMonolithFile(language);
244
262
 
245
263
  // Group keys by file
246
264
  const keysByFile = {};
247
265
 
248
- missingKeys.forEach(keyPath => {
249
- const { file, key } = this.parseKeyPath(keyPath);
250
- if (!keysByFile[file]) {
251
- keysByFile[file] = [];
252
- }
266
+ missingKeys.forEach(keyPath => {
267
+ const { file, key } = usesMonolith
268
+ ? { file: `${language}.json`, key: keyPath }
269
+ : this.parseKeyPath(keyPath);
270
+ if (!keysByFile[file]) {
271
+ keysByFile[file] = [];
272
+ }
253
273
  keysByFile[file].push({ keyPath, key });
254
274
  });
255
275
 
256
- // Process each file
257
- for (const [fileName, keys] of Object.entries(keysByFile)) {
258
- const filePath = path.join(languageDir, fileName);
259
- let fileContent = {};
276
+ // Process each file
277
+ for (const [fileName, keys] of Object.entries(keysByFile)) {
278
+ const filePath = usesMonolith
279
+ ? path.join(this.sourceDir, fileName)
280
+ : path.join(languageDir, fileName);
281
+ let fileContent = {};
260
282
 
261
283
  // Load existing file or create new
262
284
  if (SecurityUtils.safeExistsSync(filePath, this.config.projectRoot)) {
@@ -266,12 +288,12 @@ class I18nCompletionTool {
266
288
  console.warn(t("completeTranslations.warning_could_not_parse_filepa", { filePath })); ;
267
289
  fileContent = {};
268
290
  }
269
- } else {
270
- // Create directory if it doesn't exist
271
- if (!SecurityUtils.safeExistsSync(languageDir, this.config.projectRoot)) {
272
- if (!dryRun) {
273
- SecurityUtils.safeMkdirSync(languageDir, this.config.projectRoot, { recursive: true });
274
- }
291
+ } else {
292
+ // Create directory if it doesn't exist
293
+ if (!usesMonolith && !SecurityUtils.safeExistsSync(languageDir, this.config.projectRoot)) {
294
+ if (!dryRun) {
295
+ SecurityUtils.safeMkdirSync(languageDir, this.config.projectRoot, { recursive: true });
296
+ }
275
297
  }
276
298
  }
277
299
 
@@ -303,19 +325,66 @@ class I18nCompletionTool {
303
325
  return changes;
304
326
  }
305
327
 
306
- // Generate appropriate translation value based on key and language
307
- generateTranslationValue(keyPath, language) {
308
- // Generate value from key path for source language
309
- const baseValue = this.generateValueFromKey(keyPath);
310
-
311
- // For source language, use the generated value
312
- if (language === this.config.sourceLanguage) {
313
- return baseValue;
314
- }
315
-
316
- // For other languages, use the not translated marker
317
- return this.config.notTranslatedMarker || 'NOT_TRANSLATED';
318
- }
328
+ // Generate appropriate translation value based on key and language
329
+ generateTranslationValue(keyPath, language) {
330
+ const sourceValue = this.getSourceValueForKeyPath(keyPath);
331
+ const baseValue = typeof sourceValue === 'string' && sourceValue.trim() !== ''
332
+ ? sourceValue
333
+ : this.generateValueFromKey(keyPath);
334
+
335
+ // For source language, use the generated value
336
+ if (language === this.config.sourceLanguage) {
337
+ return baseValue;
338
+ }
339
+
340
+ return `[${language.toUpperCase()}] ${baseValue}`;
341
+ }
342
+
343
+ getNestedValue(obj, keyPath) {
344
+ const keys = String(keyPath || '').split('.');
345
+ let current = obj;
346
+
347
+ for (const key of keys) {
348
+ if (!current || typeof current !== 'object' || !(key in current)) {
349
+ return undefined;
350
+ }
351
+ current = current[key];
352
+ }
353
+
354
+ return current;
355
+ }
356
+
357
+ getSourceValueForKeyPath(keyPath) {
358
+ if (!this.sourceLanguageDir && !this.usesMonolithFile(this.config.sourceLanguage)) {
359
+ return undefined;
360
+ }
361
+
362
+ const sourceFiles = this.getLanguageFiles(this.config.sourceLanguage);
363
+ const keyPathStr = String(keyPath || '');
364
+ const parsed = this.parseKeyPath(keyPathStr);
365
+
366
+ for (const fileName of sourceFiles) {
367
+ const sourceFilePath = this.getLanguageFilePath(this.config.sourceLanguage, fileName);
368
+ try {
369
+ const sourceContent = SecurityUtils.safeParseJSON(SecurityUtils.safeReadFileSync(sourceFilePath, this.config.projectRoot, 'utf8'));
370
+ if (!sourceContent || typeof sourceContent !== 'object') continue;
371
+
372
+ const candidates = [keyPathStr];
373
+ if (fileName === parsed.file) {
374
+ candidates.push(parsed.key);
375
+ }
376
+
377
+ for (const candidate of candidates) {
378
+ const value = this.getNestedValue(sourceContent, candidate);
379
+ if (value !== undefined) return value;
380
+ }
381
+ } catch (error) {
382
+ console.warn(t("complete.couldNotParseSource", { file: sourceFilePath }));
383
+ }
384
+ }
385
+
386
+ return undefined;
387
+ }
319
388
 
320
389
  // Generate a readable value from a key path
321
390
  generateValueFromKey(keyPath) {
@@ -351,18 +420,18 @@ class I18nCompletionTool {
351
420
  }
352
421
 
353
422
  // Get missing keys by comparing source language with target languages
354
- getMissingKeysFromComparison() {
355
- const sourceFiles = this.getLanguageFiles(this.config.sourceLanguage);
356
- const missingKeys = [];
357
-
358
- if (!SecurityUtils.safeExistsSync(this.sourceLanguageDir, this.config.projectRoot)) {
359
- console.log(t("complete.sourceLanguageNotFound", { sourceLanguage: this.config.sourceLanguage }));
360
- return [];
361
- }
362
-
363
- // Process each file in source language
364
- for (const fileName of sourceFiles) {
365
- const sourceFilePath = path.join(this.sourceLanguageDir, fileName);
423
+ getMissingKeysFromComparison() {
424
+ const sourceFiles = this.getLanguageFiles(this.config.sourceLanguage);
425
+ const missingKeys = [];
426
+
427
+ if (sourceFiles.length === 0) {
428
+ console.log(t("complete.sourceLanguageNotFound", { sourceLanguage: this.config.sourceLanguage }));
429
+ return [];
430
+ }
431
+
432
+ // Process each file in source language
433
+ for (const fileName of sourceFiles) {
434
+ const sourceFilePath = this.getLanguageFilePath(this.config.sourceLanguage, fileName);
366
435
 
367
436
  try {
368
437
  const sourceContent = SecurityUtils.safeParseJSON(SecurityUtils.safeReadFileSync(sourceFilePath, this.config.projectRoot, 'utf8'));
@@ -373,7 +442,9 @@ class I18nCompletionTool {
373
442
  for (const language of languages) {
374
443
  if (language === this.config.sourceLanguage) continue;
375
444
 
376
- const targetFilePath = path.join(this.sourceDir, language, fileName);
445
+ const targetFilePath = fileName === `${this.config.sourceLanguage}.json` || this.usesMonolithFile(language)
446
+ ? path.join(this.sourceDir, `${language}.json`)
447
+ : path.join(this.sourceDir, language, fileName);
377
448
  let targetKeys = [];
378
449
 
379
450
  if (SecurityUtils.safeExistsSync(targetFilePath, this.config.projectRoot)) {
@@ -15,6 +15,7 @@
15
15
  * Options:
16
16
  * --source-dir <dir> Source directory (default: ./locales/en)
17
17
  * --output-dir <dir> Output directory (default: ./locales/<lang>)
18
+ * --provider <name> Translation provider: google, deepl, libretranslate
18
19
  * --custom-regex <regex> Additional placeholder regex pattern
19
20
  * --no-confirm Skip all confirmation dialogs
20
21
  * --preserve-placeholders Translate text around placeholders and reinsert tokens
@@ -90,6 +91,7 @@ function printHelp() {
90
91
  'Options:',
91
92
  ' --source-dir <dir> Source directory containing locale files',
92
93
  ' --output-dir <dir> Output directory for translated files',
94
+ ' --provider <name> Provider: google (default), deepl, libretranslate',
93
95
  ' --source-lang <code> Source language code (default: en)',
94
96
  ' --custom-regex <regex> Additional placeholder regex pattern',
95
97
  ' --no-confirm Automate: skip confirmation dialogs',
@@ -110,6 +112,15 @@ function printHelp() {
110
112
  ' --retry-count <n> Max retries per failed request (default: 3)',
111
113
  ' --retry-delay <ms> Base backoff delay in ms (default: 1000)',
112
114
  ' --timeout <ms> HTTP request timeout in ms (default: 15000)',
115
+ '',
116
+ 'Environment:',
117
+ ' I18NTK_TRANSLATE_PROVIDER Default provider when --provider is omitted',
118
+ ' DEEPL_API_KEY Required for --provider deepl',
119
+ ' DEEPL_API_URL Optional, defaults to https://api-free.deepl.com/v2/translate',
120
+ ' I18NTK_ALLOW_CUSTOM_TRANSLATE_HOSTS=1 Allow custom DeepL-compatible HTTPS hosts',
121
+ ' LIBRETRANSLATE_URL Optional, defaults to https://libretranslate.com/translate',
122
+ ' LIBRETRANSLATE_API_KEY Optional API key for LibreTranslate servers that require one',
123
+ ' I18NTK_ALLOW_PRIVATE_TRANSLATE_URLS=1 Allow localhost/private provider URLs for trusted testing',
113
124
  ' -h, --help Show this help',
114
125
  ].join('\n'));
115
126
  }
@@ -121,6 +132,7 @@ function parseArgs(argv) {
121
132
  sourceDir: null,
122
133
  outputDir: null,
123
134
  sourceLang: 'en',
135
+ provider: process.env.I18NTK_TRANSLATE_PROVIDER || 'google',
124
136
  customRegex: [],
125
137
  noConfirm: false,
126
138
  preservePlaceholders: false,
@@ -161,6 +173,7 @@ function parseArgs(argv) {
161
173
  else if (arg === '--source-dir' && i + 1 < argv.length) { args.sourceDir = argv[++i]; }
162
174
  else if (arg === '--output-dir' && i + 1 < argv.length) { args.outputDir = argv[++i]; }
163
175
  else if (arg === '--source-lang' && i + 1 < argv.length) { args.sourceLang = argv[++i]; }
176
+ else if (arg === '--provider' && i + 1 < argv.length) { args.provider = argv[++i]; }
164
177
  else if (arg === '--custom-regex' && i + 1 < argv.length) { args.customRegex.push(argv[++i]); }
165
178
  else if (arg === '--protection-file' && i + 1 < argv.length) { args.protectionFile = argv[++i]; }
166
179
  else if (arg === '--concurrency' && i + 1 < argv.length) { args.concurrency = parseInt(argv[++i], 10) || 3; }
@@ -206,7 +219,17 @@ function loadCustomTranslateFn(modulePath) {
206
219
  if (!modulePath) return null;
207
220
  try {
208
221
  const resolved = path.isAbsolute(modulePath) ? modulePath : path.resolve(process.cwd(), modulePath);
209
- const mod = require(resolved);
222
+ const validated = SecurityUtils.validatePath(resolved);
223
+ if (!validated) {
224
+ SecurityUtils.logSecurityEvent('Blocked unsafe custom translate module path', 'warn', {
225
+ modulePath,
226
+ resolved,
227
+ source: 'user'
228
+ });
229
+ console.error(`Error: Custom translate module path "${modulePath}" failed security validation.`);
230
+ process.exit(1);
231
+ }
232
+ const mod = require(validated);
210
233
  if (typeof mod === 'function') return mod;
211
234
  if (mod && typeof mod.translate === 'function') return mod.translate;
212
235
  if (mod && typeof mod.default === 'function') return mod.default;
@@ -676,6 +699,7 @@ async function processFile(sourcePath, targetLang, args) {
676
699
 
677
700
  const translateOptions = {
678
701
  sourceLang: args.sourceLang,
702
+ provider: args.provider,
679
703
  concurrency: args.concurrency,
680
704
  batchSize: args.batchSize,
681
705
  retryCount: args.retryCount,
@@ -146,7 +146,6 @@ class I18nManager {
146
146
 
147
147
  if (hasLanguageDirs) {
148
148
  this.config.sourceDir = possiblePath;
149
- t('init.autoDetectedI18nDirectory', { path: possiblePath });
150
149
  break;
151
150
  }
152
151
  } catch (error) {
@@ -347,7 +346,6 @@ class I18nManager {
347
346
  if (!shouldSkipInitCheck) {
348
347
  await SetupEnforcer.checkSetupCompleteAsync();
349
348
  }
350
- clearStartupTimeout();
351
349
 
352
350
  prompt = createPrompt({ noPrompt: args.noPrompt });
353
351
  const interactive = isInteractive({ noPrompt: args.noPrompt });
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * Interactive Menu Manager
3
3
  * @module managers/InteractiveMenu
4
+ * @deprecated This module is superseded by the I18nManager.showInteractiveMenu
5
+ * implementation in main/manage/index.js. This file is retained only for
6
+ * backward compatibility with existing test references and should not be
7
+ * used in new code.
4
8
  */
5
9
 
6
10
  const { t } = require('../../../utils/i18n-helper');