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 +94 -23
- package/README.md +29 -23
- package/SECURITY.md +19 -5
- package/main/i18ntk-complete.js +120 -49
- package/main/i18ntk-translate.js +25 -1
- package/main/manage/index.js +0 -2
- package/main/manage/managers/InteractiveMenu.js +4 -0
- package/main/manage/services/FileManagementService.js +14 -11
- package/package.json +5 -3
- package/runtime/enhanced.d.ts +1 -1
- package/runtime/i18ntk.d.ts +2 -11
- package/ui-locales/de.json +1389 -1359
- package/ui-locales/es.json +1503 -1473
- package/ui-locales/fr.json +1626 -1596
- package/ui-locales/ja.json +1595 -1565
- package/ui-locales/ru.json +1638 -1608
- package/ui-locales/zh.json +1613 -1583
- package/utils/admin-auth.js +6 -6
- package/utils/config-manager.js +6 -3
- package/utils/translate/api.js +172 -41
- package/utils/translate/safe-network.js +280 -0
- package/utils/translate/traverse.js +14 -25
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.
|
|
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
|
-
-
|
|
17
|
-
-
|
|
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
|
-
|
|
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
|
-
###
|
|
22
|
-
-
|
|
23
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
+
# i18ntk v3.3.0
|
|
2
2
|
|
|
3
|
-
|
|
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
|

|
|
6
6
|
|
|
@@ -9,7 +9,7 @@ Zero-dependency internationalization toolkit for setup, scanning, analysis, vali
|
|
|
9
9
|
[](https://nodejs.org)
|
|
10
10
|
[](https://www.npmjs.com/package/i18ntk)
|
|
11
11
|
[](LICENSE)
|
|
12
|
-
[](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.
|
|
33
|
+
## What's New in 3.3.0
|
|
34
34
|
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
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)
|
|
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.
|
|
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.
|
|
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 `
|
|
5
|
+
The supported production line is `3.x`.
|
|
6
6
|
|
|
7
|
-
Versions earlier than `
|
|
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,
|
|
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 `
|
|
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.
|
package/main/i18ntk-complete.js
CHANGED
|
@@ -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
|
|
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 } =
|
|
250
|
-
|
|
251
|
-
|
|
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 =
|
|
259
|
-
|
|
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
|
-
|
|
309
|
-
const baseValue =
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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 (
|
|
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 =
|
|
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 =
|
|
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)) {
|
package/main/i18ntk-translate.js
CHANGED
|
@@ -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
|
|
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,
|
package/main/manage/index.js
CHANGED
|
@@ -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');
|