i18ntk 4.4.5 → 4.5.1

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
@@ -5,6 +5,40 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [4.5.1] - 2026-06-19
9
+
10
+ ### Fixed
11
+ - **Validate:** `getAllKeys()` no longer reports parent namespace objects (e.g., `footer`) as missing keys alongside their leaf children (`footer.copyright`). Only leaf (string) keys are now compared during structural validation.
12
+ - **Validate:** Completion percentage now compares against source locale total keys, not target locale self-count. A locale with 14 of 42 source keys now correctly shows 33% instead of 100%.
13
+ - **Doctor:** No longer flags unconfigured locales (`de`, `ru`) as "missing". Now auto-detects available languages from the i18n directory structure, only checking against actually-configured languages.
14
+ - **Scanner:** Now detects when `sourceDir` was set to the locale directory (common after setup) and falls back to `./src` for source code scanning instead of scanning locale JSON files.
15
+ - **Runtime:** `initRuntime()` now accepts alias parameter names for better developer experience: `localeDir` → `baseDir`, `targetLocale` → `language`, `sourceLocale` → `fallbackLanguage`, `projectRoot` + `localeDir` → `baseDir` resolution.
16
+
17
+ ## [4.5.0] - 2026-06-19
18
+
19
+ ### Security — Prototype Pollution Hardened
20
+ - **safe-json.js:** Added `stripPrototypePollution()` function that recursively filters `__proto__`, `constructor`, and `prototype` keys from parsed JSON locale files. Applied to all `readJsonSafe()` calls.
21
+ - **runtime/index.js:** `deepMerge()` now blocks `__proto__`, `constructor`, and `prototype` keys during locale data merging. `readJsonSafe()` now applies `stripPrototypeKeys()` to all parsed JSON, ensuring prototype pollution protection at runtime data ingestion point.
22
+ - **settings-manager.js:** `mergeWithDefaults()` now filters prototype pollution keys from user-supplied settings before spreading into defaults.
23
+ - **safe-json.js:** Exported `stripPrototypePollution` for use by other modules.
24
+
25
+ ### Fixed
26
+ - **Backup:** Removed duplicate `const sourceDir` declaration that caused SyntaxError at module load (was unrecoverable crash for all backup operations).
27
+ - **Backup:** Added `try/catch` around `JSON.parse()` in restore path to handle corrupt backup files gracefully with a descriptive error message.
28
+ - **Complete:** Added missing `getUnifiedConfig` import from `utils/config-helper` (was ReferenceError at runtime).
29
+ - **Report:** Replaced 3 direct `fs.writeFileSync` calls with `SecurityUtils.safeWriteFileSync` for path containment on report output.
30
+ - **Report Model:** Replaced direct `fs.existsSync` and `fs.readFileSync` with `SecurityUtils` wrappers. Malformed JSON locale files now skipped gracefully with a warning instead of aborting report generation.
31
+ - **Runtime:** Lazy-load failures now log to console when `I18NTK_DEBUG` is set (was silently swallowed).
32
+ - **Settings Manager:** `_saveImmediately()` now re-throws errors instead of silently logging them, so callers can react to save failures.
33
+ - **Config Manager:** Fire-and-forget legacy config migration now has proper `.catch()` error handling instead of an unobserved promise.
34
+ - **i18n-helper:** `stripBOMAndComments()` now safely handles null/undefined inputs.
35
+
36
+ ### Changed
37
+ - **Version:** Bumped to 4.5.0 (minor version due to scope and severity of security fixes).
38
+ - **i18n-helper deepMerge:** Synchronized with runtime `deepMerge` — now uses `Object.keys` (safe) instead of `for...in`, handles null target/fallback, and filters `__proto__`/`constructor`/`prototype` keys for consistent prototype pollution protection across all code paths.
39
+ - **Testing:** Added `tests/edge-case-hardening.test.js` with 33 new tests covering prototype pollution protection, SecurityUtils edge cases, backup corrupt handling, report malformed JSON resilience, validation risk detection null-safety, config manager robustness, version consistency, and deepMerge edge cases.
40
+ - **Audit Complete:** Comprehensive security audit (24 findings) and error-handling audit (42 findings) completed with critical fixes applied.
41
+
8
42
  ## [4.4.5] - 2026-06-08
9
43
 
10
44
  ### Fixed
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # i18ntk v4.4.5
1
+ # i18ntk v4.5.1
2
2
 
3
3
  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
 
@@ -9,7 +9,7 @@ A zero-dependency internationalization toolkit for setup, scanning, analysis, va
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/4.4.5)](https://socket.dev/npm/package/i18ntk/overview/4.4.5)
12
+ [![socket](https://socket.dev/api/badge/npm/package/i18ntk/4.5.1)](https://socket.dev/npm/package/i18ntk/overview/4.5.1)
13
13
 
14
14
  [![i18ntk Workbench](https://img.shields.io/badge/VS_Code-i18ntk_Workbench-007ACC?logo=visualstudiocode&logoColor=white)](https://marketplace.visualstudio.com/items?itemName=VladNoskov.i18ntk-workbench)
15
15
  [![i18ntk Lens](https://img.shields.io/badge/VS_Code-i18ntk_Lens-007ACC?logo=visualstudiocode&logoColor=white)](https://marketplace.visualstudio.com/items?itemName=VladNoskov.i18ntk-lens)
@@ -55,69 +55,33 @@ npx i18ntk --help
55
55
 
56
56
  i18next is mainly a runtime internationalization library. i18ntk is mainly workflow tooling around translation files. They can work together: i18next handles runtime translation, while i18ntk handles setup, scanning, validation, reporting, and maintenance.
57
57
 
58
- | Need | i18ntk | i18next |
59
- | --- | --- | --- |
60
- | Runtime translation | Basic toolkit | Mature runtime |
61
- | Locale file scanning | Yes | No |
62
- | Missing key detection | Yes | No |
63
- | Unused key detection | Yes | No |
64
- | Validation reports | Yes | Limited |
65
- | Auto-translation workflow | Yes | External tooling |
66
-
67
- ## What's New in 4.4.5
68
-
69
- - **FRAMEWORK DETECTION FIXED**: `detectFramework()` no longer returns unresolved Promises. `react-i18next` (the most popular React i18n framework) is now correctly detected. Added support for Angular (ngx-translate), Next.js (next-intl), Nuxt (nuxt-i18n), Svelte (svelte-i18n), and Solid (solid-i18n).
70
- - **RUNTIME NULL SAFETY**: `t(null)` and `t(undefined)` now return strings instead of crashing rendering engines.
71
- - **JSON DEPTH PROTECTION**: Malicious deeply-nested JSON locale files are rejected before parsing, preventing stack overflow DoS.
72
- - **CONFIG VALIDATION HARDENED**: Absolute configuration paths now undergo `isSafePath` validation instead of being silently skipped.
73
- - **SCANNER CLI FIX**: Removed orphaned duplicate code block in `i18ntk-scanner.js` that caused a SyntaxError.
74
- - **SAFE JSON FIX**: Fixed duplicate `readJsonSafe` that overwrote the secure implementation with a broken version.
75
- - **MEMORY LEAK FIX**: Added periodic cache eviction to `missingKeyCache` to prevent unbounded memory growth.
76
- - **WATCHER ERROR LOGGING**: File watcher errors are now logged to stderr instead of silently swallowed.
77
- - **REPORT MODEL SECURITY**: Report generation now routes through `SecurityUtils` for path containment and file-size validation.
78
-
79
- ## What's New in 4.4.4
80
-
81
- - **SHARED CONFIG FOR EXTENSIONS**: `.i18ntk-config` can now carry editor-owned settings under `extensions.workbench` and `extensions.lens`. The CLI preserves the top-level `extensions` object and ignores unknown nested extension keys.
82
- - **DYNAMIC VALUE REPORTING**: Likely-untranslated reports now ignore placeholder-only and symbol/dynamic values such as `{file}`, `{path}`, and icon-prefixed labels.
83
- - **PLACEHOLDER-AWARE TRANSLATION CHECKS**: Values with translated surrounding copy and English placeholder tokens, such as `"command": "指示: {command}"`, are no longer reported as untranslated.
84
-
85
- ## What's New in 4.4.2
86
-
87
- - **AUTO TRANSLATE RELATIVE PATHS**: Programmatic `processFile()` calls now accept project-relative source paths, matching CLI source resolution.
88
- - **PROTECTED TERMS IN ONLY-MISSING MODE**: Existing translations that intentionally keep protected product terms such as `i18ntk` are no longer treated as English leftovers.
89
- - **BROKEN TARGET RETRIES**: Auto Translate now detects and retries more visibly broken target values, including mojibake, replacement characters, repeated question marks, and target-language prefix leftovers.
90
- - **REGRESSION COVERAGE**: Added focused tests for relative paths, protected product terms, broken values, placeholder handling, and residual checks.
91
-
92
- ## What's New in 4.4.1
93
-
94
- - **DEAD-KEY DETECTION**: Dead-key confidence now uses resolved dynamic key data from usage insights instead of crude text-overlap heuristics. Keys expanded from template literals or const arrays are properly tracked.
95
- - **LOCALE JSON IMPORT DETECTION**: `import en from '../../locales/en/foo.json'` is now detected and property accesses are tracked as key usages, closing the gap between CLI and VSCode scanners.
96
- - **CONFIDENCE-SPLIT REPORTS**: Unused keys are now reported by confidence tier — confirmed (≥80%), likely (40-80%), possibly used (<40%) — instead of a flat list.
97
- - **NEW CLI FLAGS**: `--strict-unused` (only high-confidence keys), `--json` (structured JSON output for CI), `--prune` / `--prune-keep` (stale report cleanup).
98
- - **MOJIBAKE DETECTION**: Replacement-character artifacts like `Abwicklungspr?fung` and `L?ser` are detected during translation analysis.
99
- - **CLIENT-BOUNDARY WARNINGS**: `"use client"` files importing locale JSON are flagged — this bypasses the runtime and increases bundle size.
100
- - **COPY-FORMATTER DETECTION**: Local `const tx = ...` functions that don't call a translation runtime are identified as likely copy formatters, reducing false positive key noise.
101
- - **CONFIG FIX**: `--source-dir` and `--i18n-dir` are no longer forced equal when both are explicitly passed via CLI.
102
- - **Next.js DETECTION**: App Router files with `"use server"` / `"use client"` directives are now detected and reported by component type.
103
- - **VSCode DIAGNOSTICS**: New `i18ntk.clearDiagnostics` command. Stale diagnostics are now cleared at scan start. New diagnostic codes: `i18ntk.clientBoundary`, `i18ntk.copyFormatter`.
104
- - **AUTO TRANSLATE RESUME REPORTS**: If a provider still returns untranslated values after the final targeted retry, Auto Translate writes `i18ntk-reports/auto-translate/latest.json` so tooling can identify and retry only unresolved keys.
105
- - **VS CODE RESIDUAL PICKUP**: i18ntk Workbench and Lens read Auto Translate residual reports, show the affected locale JSON key in the editor, and can add intentionally unchanged keys to Auto Translate protection.
106
- - **WRAPPER CONFIG**: `.i18ntk-config` now supports `usage.translationFunctions`, `usage.serverWrappers`, and `usage.copyFormatters` for fine-grained control.
107
- - **TELEMETRY/EVENT STRING FILTERING**: String literals inside `trackEvent()`, `emitDomainEvent()`, `analytics.track()` and similar calls are classified as telemetry literals and no longer falsely counted as translation usage.
108
- - **OBJECT-METHOD KEY DETECTION**: input.tx("key"), helper.tx("key"), and .tx(\`dynamic.${var}\`) patterns are now recognized as translation calls alongside standalone `tx()` calls.
109
-
110
- - **LOCAL WRAPPER RESOLUTION**: Functions like `const text = (key, fallback) => tx(key)` that internally call the translation runtime are detected, and their string-literal invocations are automatically resolved to actual keys.
58
+ | Need | i18ntk | i18next |
59
+ | ------------------------- | ------------- | ---------------- |
60
+ | Runtime translation | Basic toolkit | Mature runtime |
61
+ | Locale file scanning | Yes | No |
62
+ | Missing key detection | Yes | No |
63
+ | Unused key detection | Yes | No |
64
+ | Validation reports | Yes | Limited |
65
+ | Auto-translation workflow | Yes | External tooling |
111
66
 
112
- See [CHANGELOG.md](./CHANGELOG.md) for more release details.
67
+ ## What's New in 4.5.1
68
+
69
+ - **CORRECT COMPLETENESS**: Validation now shows accurate completion percentages vs source locale (e.g., 33% instead of misleading 100%).
70
+ - **NO MORE PARENT KEYS**: `getAllKeys()` no longer reports parent namespace objects (`footer`) as missing keys alongside their leaf children (`footer.copyright`).
71
+ - **DOCTOR SMARTER**: No longer flags unconfigured languages (`de`, `ru`) as issues. Auto-detects available languages from the i18n directory structure.
72
+ - **SCANNER FIXED**: Scanner now correctly scans `src/` directory for hardcoded text, not `locales/`.
73
+ - **RUNTIME ALIASES**: `initRuntime()` now supports `localeDir`/`targetLocale`/`sourceLocale` as aliases for `baseDir`/`language`/`fallbackLanguage`.
113
74
 
114
- ## Security hardening in 4.4.1
75
+ ## What's New in 4.5.0
115
76
 
116
- - **PATH TRAVERSAL HARDENED**: Backup, complete, and config-helper commands now validate all user-supplied paths through `SecurityUtils.validatePath()`, blocking writes outside project boundaries.
117
- - **JSON DoS PREVENTED**: `safeParseJSON` enforces maximum depth (1000) and maximum size (50 MB) before parsing, preventing denial-of-service via deeply nested or oversized JSON.
118
- - **INPUT SANITIZATION TIGHTENED**: `sanitizeInput` default whitelist no longer allows backslashes or curly braces that could enable path traversal or template injection.
77
+ - **PROTOTYPE POLLUTION HARDENED**: Three layers of defense added — `readJsonSafe()` now recursively strips `__proto__`, `constructor`, and `prototype` keys from all parsed JSON; `deepMerge()` in the runtime blocks these keys during locale merging; `mergeWithDefaults()` in settings-manager filters them from user settings.
78
+ - **BACKUP FIXED**: All backup operations (create, restore, list, verify, cleanup) now work. A duplicate `sourceDir` declaration that caused a SyntaxError at module load has been removed. Corrupt backup files are now handled gracefully with descriptive error messages.
79
+ - **COMPLETE COMMAND FIXED**: `i18ntk-complete` no longer crashes with `getUnifiedConfig is not defined`. The missing config-helper import has been added.
80
+ - **MALFORMED JSON HANDLING**: Report generation now gracefully skips malformed JSON files with a warning instead of aborting the entire report.
81
+ - **NULL SAFETY**: `stripBOMAndComments()` in i18n-helper now handles null/undefined inputs without throwing.
82
+ - **ERROR HANDLING HARDENED**: Lazy-load failures in runtime now log to console when `I18NTK_DEBUG` is set. Settings save errors are now re-thrown instead of silently swallowed. Legacy config migration has proper error handling.
119
83
 
120
- - **LIBRETRANSLATE URL GATED**: Custom LibreTranslate host now requires `I18NTK_ALLOW_CUSTOM_LIBRETRANSLATE_HOST=1` env flag (parity with DeepL).
84
+ See [CHANGELOG.md](./CHANGELOG.md) for more release details.
121
85
 
122
86
  ## Quick Start
123
87
 
@@ -194,25 +158,25 @@ i18ntk-backup
194
158
  i18ntk-translate
195
159
  ```
196
160
 
197
- Note: manager route `i18ntk --command=backup` is disabled in current builds. Use `i18ntk-backup` directly for backup operations.
161
+ Note: manager route `i18ntk --command=backup` is available via the interactive menu. Use `i18ntk-backup` directly for scripted backup operations.
198
162
 
199
163
  ## Command Reference
200
164
 
201
- | Command | What it does | Looks for | Writes or changes |
202
- | --- | --- | --- | --- |
203
- | `i18ntk` | Opens the interactive management menu. | Project config, setup state, available commands. | Only changes files after you choose a command that writes. |
204
- | `i18ntk --command=init` / `i18ntk-init` | Sets up locale folders and missing target-language files. | Source language files and selected target languages. | Locale JSON files, `.i18ntk-config`, optional reports/backups. |
205
- | `i18ntk --command=analyze` / `i18ntk-analyze` | Compares source and target translation coverage. | Missing keys, extra keys, untranslated markers, completion by language. | Markdown/JSON/text reports when report output is enabled. |
206
- | `i18ntk --command=validate` / `i18ntk-validate` | Validates structure and translation quality risks. | Placeholder mismatches, missing keys, risky URLs/emails/secrets, likely English target text. | Validation summary report. Does not edit locale files. |
207
- | `i18ntk --command=usage` / `i18ntk-usage` | Maps translation keys to source files and finds unused/missing keys. | Direct i18n calls, literal known-key references, bounded dynamic templates/object maps, unresolved dynamic expressions, hardcoded text candidates, namespace/file naming mismatches. | Usage report with key locations, namespace recommendations, unresolved dynamic expressions, hardcoded text suggestions, and optional dead-key report. Does not delete unless cleanup deletion is explicitly enabled. |
208
- | `i18ntk report` / `i18ntk-report` | Generates the stable schemaVersion 1 report used by CLI automation and i18ntk Workbench. | Locale completeness, missing keys, unused keys with confidence, placeholders, likely untranslated values, expansion risk, and hardcoded text candidates. | JSON to stdout by default, plus JSON/Markdown/HTML files when `--out` is used. Does not edit locale files. |
209
- | `i18ntk --command=scanner` / `i18ntk-scanner` | Scans source for i18n issues and hardcoded user-facing text. | JSX/template text, common text attributes, i18n usage patterns, source-language text profiles. | Scanner report. Does not edit files. |
210
- | `i18ntk --command=complete` / `i18ntk-complete` | Adds missing keys to target language files for 100% key coverage. | Source-language keys missing from targets. | Target locale JSON files, using missing translation markers/prefixes. |
211
- | `i18ntk --command=translate` / `i18ntk-translate` | Auto-translates locale JSON using configured provider behavior. | Missing, empty, untranslated-marker, source-copy, likely-English, or visibly corrupt target values by default. | Target locale JSON files and translation reports. Existing translated values are kept unless `--translate-all` is used. If unresolved values remain after retry, writes `i18ntk-reports/auto-translate/latest.json` for targeted follow-up. |
212
- | `i18ntk --command=sizing` / `i18ntk-sizing` | Estimates translated string length expansion and layout risk. | Text length, expansion ratios, placeholder-bearing strings. | Sizing report. Does not edit locale files. |
213
- | `i18ntk --command=summary` / `i18ntk-summary` | Shows project translation status. | Configured locales, reports, completeness status. | Console/report output only. |
214
- | `i18ntk-fixer` | Fixes placeholder and missing-marker issues, and can audit English source files with `--check-placeholders`. | Placeholder corruption, missing translation markers, configured language files, `[LANG] ...` leftovers in English locales. | Locale JSON files when fixes are applied. Use dry-run options where available before bulk edits. |
215
- | `i18ntk-backup` | Creates, verifies, restores, and cleans locale backups. | Locale JSON files and backup manifests. | Backup archives/manifests, or restored locale files when using restore. |
165
+ | Command | What it does | Looks for | Writes or changes |
166
+ | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
167
+ | `i18ntk` | Opens the interactive management menu. | Project config, setup state, available commands. | Only changes files after you choose a command that writes. |
168
+ | `i18ntk --command=init` / `i18ntk-init` | Sets up locale folders and missing target-language files. | Source language files and selected target languages. | Locale JSON files, `.i18ntk-config`, optional reports/backups. |
169
+ | `i18ntk --command=analyze` / `i18ntk-analyze` | Compares source and target translation coverage. | Missing keys, extra keys, untranslated markers, completion by language. | Markdown/JSON/text reports when report output is enabled. |
170
+ | `i18ntk --command=validate` / `i18ntk-validate` | Validates structure and translation quality risks. | Placeholder mismatches, missing keys, risky URLs/emails/secrets, likely English target text. | Validation summary report. Does not edit locale files. |
171
+ | `i18ntk --command=usage` / `i18ntk-usage` | Maps translation keys to source files and finds unused/missing keys. | Direct i18n calls, literal known-key references, bounded dynamic templates/object maps, unresolved dynamic expressions, hardcoded text candidates, namespace/file naming mismatches. | Usage report with key locations, namespace recommendations, unresolved dynamic expressions, hardcoded text suggestions, and optional dead-key report. Does not delete unless cleanup deletion is explicitly enabled. |
172
+ | `i18ntk report` / `i18ntk-report` | Generates the stable schemaVersion 1 report used by CLI automation and i18ntk Workbench. | Locale completeness, missing keys, unused keys with confidence, placeholders, likely untranslated values, expansion risk, and hardcoded text candidates. | JSON to stdout by default, plus JSON/Markdown/HTML files when `--out` is used. Does not edit locale files. |
173
+ | `i18ntk --command=scanner` / `i18ntk-scanner` | Scans source for i18n issues and hardcoded user-facing text. | JSX/template text, common text attributes, i18n usage patterns, source-language text profiles. | Scanner report. Does not edit files. |
174
+ | `i18ntk --command=complete` / `i18ntk-complete` | Adds missing keys to target language files for 100% key coverage. | Source-language keys missing from targets. | Target locale JSON files, using missing translation markers/prefixes. |
175
+ | `i18ntk --command=translate` / `i18ntk-translate` | Auto-translates locale JSON using configured provider behavior. | Missing, empty, untranslated-marker, source-copy, likely-English, or visibly corrupt target values by default. | Target locale JSON files and translation reports. Existing translated values are kept unless `--translate-all` is used. If unresolved values remain after retry, writes `i18ntk-reports/auto-translate/latest.json` for targeted follow-up. |
176
+ | `i18ntk --command=sizing` / `i18ntk-sizing` | Estimates translated string length expansion and layout risk. | Text length, expansion ratios, placeholder-bearing strings. | Sizing report. Does not edit locale files. |
177
+ | `i18ntk --command=summary` / `i18ntk-summary` | Shows project translation status. | Configured locales, reports, completeness status. | Console/report output only. |
178
+ | `i18ntk-fixer` | Fixes placeholder and missing-marker issues, and can audit English source files with `--check-placeholders`. | Placeholder corruption, missing translation markers, configured language files, `[LANG] ...` leftovers in English locales. | Locale JSON files when fixes are applied. Use dry-run options where available before bulk edits. |
179
+ | `i18ntk-backup` | Creates, verifies, restores, and cleans locale backups. | Locale JSON files and backup manifests. | Backup archives/manifests, or restored locale files when using restore. |
216
180
 
217
181
  ## Common Options
218
182
 
@@ -426,7 +390,8 @@ i18ntk-usage --source-dir ./src --i18n-dir ./locales --cleanup --dry-run-delete
426
390
  ```
427
391
 
428
392
  Each dead key receives a confidence score (0.0–1.0) factoring:
429
- - Unresolved dynamic key patterns (e.g., `` t(`prefix.${dynamic}`) ``) — lower score and listed in the usage report; simple consts, bounded arrays, object maps, and ternaries are expanded to exact keys where possible
393
+
394
+ - Unresolved dynamic key patterns (e.g., ``t(`prefix.${dynamic}`)``) — lower score and listed in the usage report; simple consts, bounded arrays, object maps, and ternaries are expanded to exact keys where possible
430
395
  - Key appears in source code comments or JSDoc — medium score
431
396
  - Parent file recently modified (<30 days) — medium score
432
397
  - No references found anywhere — high score (>0.8)
@@ -511,7 +476,7 @@ const runtime = require('i18ntk/runtime');
511
476
  const i18n = runtime.initRuntime({
512
477
  baseDir: './locales',
513
478
  language: 'en',
514
- lazy: true
479
+ lazy: true,
515
480
  });
516
481
 
517
482
  console.log(i18n.t('common.hello')); // loads common.json on first access
@@ -541,7 +506,7 @@ const i18n = runtime.initRuntime({
541
506
  language: 'en',
542
507
  fallbackLanguage: 'en',
543
508
  keySeparator: '.',
544
- preload: true
509
+ preload: true,
545
510
  });
546
511
 
547
512
  console.log(i18n.t('common.hello'));
@@ -570,7 +535,7 @@ Example:
570
535
 
571
536
  ```json
572
537
  {
573
- "version": "4.4.5",
538
+ "version": "4.5.1",
574
539
  "sourceDir": "./locales",
575
540
  "i18nDir": "./locales",
576
541
  "outputDir": "./i18ntk-reports",
@@ -660,13 +625,13 @@ The public package manifest includes `readmeFilename: "README.md"`, and the rele
660
625
 
661
626
  ## Related Tools
662
627
 
663
- | Tool | Purpose |
664
- |---|---|
665
- | **i18ntk** | Zero-dependency i18n toolkit for scanning, validation, translation, reports, and runtime loading. |
666
- | **i18ntk Workbench** | Full VS Code localization health dashboard powered by i18ntk. |
667
- | **i18ntk Lens** | Lightweight inline translation hovers, diagnostics, and key navigation. |
668
- | **PublishGuard** | Pre-publish safety scanner for npm packages and VS Code extensions. |
669
- | **ContextKit** | AI coding context manager for AGENTS.md, Claude, Cursor, Copilot, Roo, and Codex files. |
628
+ | Tool | Purpose |
629
+ | -------------------- | ------------------------------------------------------------------------------------------------- |
630
+ | **i18ntk** | Zero-dependency i18n toolkit for scanning, validation, translation, reports, and runtime loading. |
631
+ | **i18ntk Workbench** | Full VS Code localization health dashboard powered by i18ntk. |
632
+ | **i18ntk Lens** | Lightweight inline translation hovers, diagnostics, and key navigation. |
633
+ | **PublishGuard** | Pre-publish safety scanner for npm packages and VS Code extensions. |
634
+ | **ContextKit** | AI coding context manager for AGENTS.md, Claude, Cursor, Copilot, Roo, and Codex files. |
670
635
 
671
636
  ## License
672
637
 
@@ -390,20 +390,6 @@ async function handleCreate(args) {
390
390
  logger.debug(`Using existing backup directory: ${validatedOutputDir}`);
391
391
  }
392
392
 
393
- const sourceDir = path.resolve(dir);
394
- try {
395
- const stats = await fsp.stat(sourceDir);
396
- if (!stats.isDirectory()) {
397
- throw new Error(`Path exists but is not a directory: ${sourceDir}`);
398
- }
399
- logger.debug(`Source directory exists: ${sourceDir}`);
400
- } catch (err) {
401
- if (err.code === 'ENOENT') {
402
- throw new Error(`Directory not found: ${sourceDir}. Please specify a valid directory.`);
403
- }
404
- throw new Error(`Error accessing directory ${sourceDir}: ${err.message}`);
405
- }
406
-
407
393
  logger.info('\nCreating backup...');
408
394
 
409
395
  const files = await collectJsonFiles(sourceDir);
@@ -511,7 +497,15 @@ async function handleRestore(args) {
511
497
  try {
512
498
  const rawContent = SecurityUtils.safeReadFileSync(backupPath, process.cwd(), 'utf8');
513
499
  if (!rawContent) throw new Error(`Could not read backup file: ${backupPath}`);
514
- const backupData = JSON.parse(rawContent);
500
+ let backupData;
501
+ try {
502
+ backupData = JSON.parse(rawContent);
503
+ } catch (parseErr) {
504
+ throw new Error(`Corrupt backup file (invalid JSON): ${backupPath} — ${parseErr.message}`);
505
+ }
506
+ if (!backupData || typeof backupData !== 'object') {
507
+ throw new Error(`Invalid backup file format: ${backupPath}`);
508
+ }
515
509
  const isIncremental = backupData._meta && backupData._meta.type === 'incremental';
516
510
 
517
511
  if (isIncremental) {
@@ -18,6 +18,7 @@ const SecurityUtils = require('../utils/security');
18
18
  const { loadTranslations, t } = require('../utils/i18n-helper');
19
19
  const { getGlobalReadline, closeGlobalReadline } = require('../utils/cli');
20
20
  const SetupEnforcer = require('../utils/setup-enforcer');
21
+ const { getUnifiedConfig } = require('../utils/config-helper');
21
22
 
22
23
  // Ensure setup is complete before running, except for help output.
23
24
  (async () => {
@@ -105,9 +105,22 @@ class I18nDoctor {
105
105
  }
106
106
 
107
107
  const sourceLang = config.sourceLanguage || 'en';
108
- const languages = config.defaultLanguages || [];
108
+ // Only check languages that actually have locale directories in the project
109
109
  const srcDir = path.join(config.i18nDir, sourceLang);
110
110
  const srcFiles = SecurityUtils.safeExistsSync(srcDir) ? fs.readdirSync(srcDir).filter(f => f.endsWith('.json')) : [];
111
+ // Auto-detect available languages from the i18n dir instead of relying on defaultLanguages config
112
+ const availableLangs = new Set();
113
+ if (SecurityUtils.safeExistsSync(config.i18nDir)) {
114
+ const i18nEntries = fs.readdirSync(config.i18nDir, { withFileTypes: true });
115
+ for (const entry of i18nEntries) {
116
+ if (entry.isDirectory() && entry.name !== sourceLang && !entry.name.startsWith('.')) {
117
+ availableLangs.add(entry.name);
118
+ }
119
+ }
120
+ }
121
+ // Only check languages auto-detected from the i18n directory structure.
122
+ // defaultLanguages config is a hint for setup/translate, not a doctor requirement.
123
+ const languages = [...availableLangs];
111
124
 
112
125
  for (const lang of languages) {
113
126
  const langDir = path.join(config.i18nDir, lang);
@@ -3,6 +3,7 @@
3
3
 
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
+ const SecurityUtils = require('../utils/security');
6
7
  const {
7
8
  generateI18ntkReport,
8
9
  renderReportAsMarkdown,
@@ -68,15 +69,15 @@ async function run(argv = process.argv.slice(2), env = process.env) {
68
69
  fs.mkdirSync(outDir, { recursive: true });
69
70
  if (args.json) {
70
71
  exports.json = path.join(outDir, 'i18ntk-report.json');
71
- fs.writeFileSync(exports.json, `${JSON.stringify({ ...report, exports: undefined }, null, 2)}\n`, 'utf8');
72
+ SecurityUtils.safeWriteFileSync(exports.json, `${JSON.stringify({ ...report, exports: undefined }, null, 2)}\n`, projectRoot, 'utf8');
72
73
  }
73
74
  if (args.markdown) {
74
75
  exports.markdown = path.join(outDir, 'i18ntk-report.md');
75
- fs.writeFileSync(exports.markdown, renderReportAsMarkdown(report), 'utf8');
76
+ SecurityUtils.safeWriteFileSync(exports.markdown, renderReportAsMarkdown(report), projectRoot, 'utf8');
76
77
  }
77
78
  if (args.html) {
78
79
  exports.html = path.join(outDir, 'i18ntk-report.html');
79
- fs.writeFileSync(exports.html, renderReportAsHtml(report), 'utf8');
80
+ SecurityUtils.safeWriteFileSync(exports.html, renderReportAsHtml(report), projectRoot, 'utf8');
80
81
  }
81
82
  }
82
83
 
@@ -696,6 +696,11 @@ class I18nTextScanner {
696
696
  this.config = { ...baseConfig, ...(this.config || {}) };
697
697
 
698
698
  this.sourceDir = this.config.sourceDir || './src';
699
+ // If sourceDir equals the locale/i18n directory, fall back to ./src for source code scanning
700
+ const i18nDir = this.config.i18nDir || this.config.localeDir || './locales';
701
+ if (path.resolve(this.sourceDir) === path.resolve(i18nDir)) {
702
+ this.sourceDir = './src';
703
+ }
699
704
 
700
705
  // Source language for multi-language detection
701
706
  this.sourceLanguage = args.sourceLanguage || this.config.sourceLanguage || 'en';
@@ -262,16 +262,21 @@ class I18nValidator {
262
262
  }
263
263
 
264
264
  // Get all keys recursively from an object
265
- getAllKeys(obj, prefix = '') {
265
+ getAllKeys(obj, prefix = '', onlyLeaves = true) {
266
266
  const keys = new Set();
267
267
 
268
268
  for (const [key, value] of Object.entries(obj)) {
269
269
  const fullKey = prefix ? `${prefix}.${key}` : key;
270
- keys.add(fullKey);
271
270
 
272
271
  if (value && typeof value === 'object' && !Array.isArray(value)) {
273
- const nestedKeys = this.getAllKeys(value, fullKey);
272
+ const nestedKeys = this.getAllKeys(value, fullKey, onlyLeaves);
274
273
  nestedKeys.forEach(k => keys.add(k));
274
+ // Only add parent key if we want all keys (not leaves-only)
275
+ if (!onlyLeaves) {
276
+ keys.add(fullKey);
277
+ }
278
+ } else {
279
+ keys.add(fullKey);
275
280
  }
276
281
  }
277
282
 
@@ -517,6 +522,7 @@ class I18nValidator {
517
522
  totalFiles: sourceFiles.length,
518
523
  validFiles: 0,
519
524
  totalKeys: 0,
525
+ sourceTotalKeys: 0,
520
526
  translatedKeys: 0,
521
527
  missingFiles: [],
522
528
  syntaxErrors: [],
@@ -624,6 +630,10 @@ class I18nValidator {
624
630
  validation.summary.validFiles++;
625
631
  validation.summary.totalKeys += translations.totalKeys;
626
632
  validation.summary.translatedKeys += translations.translatedKeys;
633
+ // Count source locale leaf keys as reference total
634
+ if (sourceContent) {
635
+ validation.summary.sourceTotalKeys += this.getAllKeys(sourceContent).size;
636
+ }
627
637
 
628
638
  if (!structural.isConsistent) {
629
639
  validation.summary.structuralIssues.push({
@@ -636,9 +646,10 @@ class I18nValidator {
636
646
  validation.summary.translationIssues.push(...translations.issues);
637
647
  }
638
648
 
639
- // Calculate completion percentage
640
- validation.summary.percentage = validation.summary.totalKeys > 0
641
- ? Math.round((validation.summary.translatedKeys / validation.summary.totalKeys) * 100)
649
+ // Calculate completion percentage against source locale total
650
+ const refTotal = validation.summary.sourceTotalKeys || validation.summary.totalKeys;
651
+ validation.summary.percentage = refTotal > 0
652
+ ? Math.round((validation.summary.translatedKeys / refTotal) * 100)
642
653
  : 0;
643
654
 
644
655
  return validation;
@@ -774,7 +785,7 @@ class I18nValidator {
774
785
 
775
786
  Object.entries(results).forEach(([language, validation]) => {
776
787
  const summary = validation?.summary || {};
777
- lines.push(`${language}: ${summary.percentage || 0}% (${summary.translatedKeys || 0}/${summary.totalKeys || 0})`);
788
+ lines.push(`${language}: ${summary.percentage || 0}% (${summary.translatedKeys || 0}/${summary.sourceTotalKeys || summary.totalKeys || 0} keys vs source)`);
778
789
  });
779
790
 
780
791
  SecurityUtils.safeWriteFileSync(reportPath, lines.join('\n') + '\n', process.cwd(), 'utf8');
@@ -908,7 +919,7 @@ class I18nValidator {
908
919
  const { summary } = validation;
909
920
  const status = summary.syntaxErrors.length > 0 ? '❌' :
910
921
  summary.missingFiles.length > 0 ? '⚠️' : '✅';
911
- console.log(` ${status} ${language}: ${summary.percentage}% (${summary.translatedKeys}/${summary.totalKeys} keys)`);
922
+ console.log(` ${status} ${language}: ${summary.percentage}% (${summary.translatedKeys}/${summary.sourceTotalKeys || summary.totalKeys} keys vs source)`);
912
923
  }
913
924
 
914
925
  // Aggregate issues for JSON output
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18ntk",
3
- "version": "4.4.5",
3
+ "version": "4.5.1",
4
4
  "description": "i18n Tool Kit - Zero-dependency internationalization toolkit for setup, scanning, analysis, validation, auto translation, fixing, reporting, and runtime translation loading.",
5
5
  "readmeFilename": "README.md",
6
6
  "keywords": [
@@ -179,9 +179,9 @@
179
179
  },
180
180
  "preferGlobal": true,
181
181
  "versionInfo": {
182
- "version": "4.4.5",
182
+ "version": "4.5.1",
183
183
  "releaseDate": "05/06/2026",
184
- "lastUpdated": "06/08/2026",
184
+ "lastUpdated": "06/18/2026",
185
185
  "maintainer": "Vlad Noskov",
186
186
  "changelog": "./CHANGELOG.md",
187
187
  "documentation": "./README.md",
@@ -215,7 +215,7 @@
215
215
  "i18ntk/runtime module-level helpers keep the first initialized runtime configuration for compatibility instead of being overwritten by later initRuntime() calls.",
216
216
  "utils/watch-locales.js returns a callable watcher object with EventEmitter methods and stop(); existing bare stop-function usage remains supported."
217
217
  ],
218
- "nextVersion": "4.4.6",
218
+ "nextVersion": "4.5.2",
219
219
  "supportedNodeVersions": ">=16.0.0",
220
220
  "supportedFrameworks": {
221
221
  "react-i18next": ">=11.0.0",
@@ -237,18 +237,18 @@
237
237
  "spring-boot": ">=2.5.0",
238
238
  "laravel": ">=8.0.0"
239
239
  },
240
- "supportPolicy": "Versions earlier than 4.4.1 may be unstable or insecure. Upgrade to 4.4.5 or newer.",
240
+ "supportPolicy": "Versions earlier than 4.4.1 may be unstable or insecure. Upgrade to 4.5.1 or newer.",
241
241
  "deprecations": [
242
242
  "4.3.0",
243
243
  "4.3.1",
244
244
  "4.3.2",
245
245
  "4.3.3"
246
246
  ],
247
- "deprecationMessage": "i18ntk 4.3.x and earlier have known security vulnerabilities (path traversal, JSON DoS). Upgrade to i18ntk@4.4.5 or newer: npm install -g i18ntk@latest",
247
+ "deprecationMessage": "i18ntk 4.3.x and earlier have known security vulnerabilities (path traversal, JSON DoS). Upgrade to i18ntk@4.5.1 or newer: npm install -g i18ntk@latest",
248
248
  "securityAdvisories": [
249
249
  "GHSA-i18ntk-4.3.x-path-traversal: Backup command accepted arbitrary paths without validation (fixed in 4.4.1)",
250
250
  "GHSA-i18ntk-4.3.x-json-dos: Deeply nested JSON files could cause denial of service (fixed in 4.4.1)"
251
251
  ]
252
252
  },
253
- "readme": "# i18ntk v4.4.5\n\nA zero-dependency internationalization toolkit for setup, scanning, analysis, validation, usage tracking, translation completion, automatic JSON locale translation, reporting, and runtime translation loading.\n\n![i18ntk Logo](https://raw.githubusercontent.com/vladnoskv/i18ntk/main/docs/screenshots/i18ntk-logo-public.PNG)\n\n[![npm version](https://img.shields.io/npm/v/i18ntk.svg?color=brightgreen)](https://www.npmjs.com/package/i18ntk)\n[![npm downloads](https://img.shields.io/npm/dt/i18ntk.svg)](https://www.npmjs.com/package/i18ntk)\n[![node](https://img.shields.io/badge/node-%3E%3D16-339933)](https://nodejs.org)\n[![dependencies](https://img.shields.io/badge/dependencies-0-success)](https://www.npmjs.com/package/i18ntk)\n[![license](https://img.shields.io/badge/license-MIT-yellow.svg)](LICENSE)\n[![socket](https://socket.dev/api/badge/npm/package/i18ntk/4.4.5)](https://socket.dev/npm/package/i18ntk/overview/4.4.5)\n\n[![i18ntk Workbench](https://img.shields.io/badge/VS_Code-i18ntk_Workbench-007ACC?logo=visualstudiocode&logoColor=white)](https://marketplace.visualstudio.com/items?itemName=VladNoskov.i18ntk-workbench)\n[![i18ntk Lens](https://img.shields.io/badge/VS_Code-i18ntk_Lens-007ACC?logo=visualstudiocode&logoColor=white)](https://marketplace.visualstudio.com/items?itemName=VladNoskov.i18ntk-lens)\n\n## The i18ntk ecosystem\n\n- i18ntk — CLI and runtime toolkit\n- i18ntk Workbench — full VS Code dashboard and reports\n- i18ntk Lens — inline hovers, CodeLens, and diagnostics\n\nUse the CLI in CI, Workbench for project-level management, and Lens for day-to-day editor feedback.\n\n## Install\n\n```bash\n# global CLI use\nnpm install -g i18ntk\n\n# local project use\nnpm install --save-dev i18ntk\n\n# one-off execution\nnpx i18ntk --help\n```\n\n## i18ntk Summary\n\n**What it does**\n\n- Manages locale files from the command line.\n- Finds missing, unused, risky, and inconsistent translation keys.\n- Produces validation and summary reports.\n- Supports framework-aware i18n workflows.\n- Provides a lightweight runtime translation toolkit.\n\n**What it does not do**\n\n- It is not a translation management SaaS.\n- It does not replace human translation review.\n- It does not force you to replace i18next, react-i18next, vue-i18n, or another runtime.\n\n**Why not i18next?**\n\ni18next is mainly a runtime internationalization library. i18ntk is mainly workflow tooling around translation files. They can work together: i18next handles runtime translation, while i18ntk handles setup, scanning, validation, reporting, and maintenance.\n\n| Need | i18ntk | i18next |\n| --- | --- | --- |\n| Runtime translation | Basic toolkit | Mature runtime |\n| Locale file scanning | Yes | No |\n| Missing key detection | Yes | No |\n| Unused key detection | Yes | No |\n| Validation reports | Yes | Limited |\n| Auto-translation workflow | Yes | External tooling |\n\n## What's New in 4.4.5\n\n- **FRAMEWORK DETECTION FIXED**: `detectFramework()` no longer returns unresolved Promises. `react-i18next` (the most popular React i18n framework) is now correctly detected. Added support for Angular (ngx-translate), Next.js (next-intl), Nuxt (nuxt-i18n), Svelte (svelte-i18n), and Solid (solid-i18n).\n- **RUNTIME NULL SAFETY**: `t(null)` and `t(undefined)` now return strings instead of crashing rendering engines.\n- **JSON DEPTH PROTECTION**: Malicious deeply-nested JSON locale files are rejected before parsing, preventing stack overflow DoS.\n- **CONFIG VALIDATION HARDENED**: Absolute configuration paths now undergo `isSafePath` validation instead of being silently skipped.\n- **SCANNER CLI FIX**: Removed orphaned duplicate code block in `i18ntk-scanner.js` that caused a SyntaxError.\n- **SAFE JSON FIX**: Fixed duplicate `readJsonSafe` that overwrote the secure implementation with a broken version.\n- **MEMORY LEAK FIX**: Added periodic cache eviction to `missingKeyCache` to prevent unbounded memory growth.\n- **WATCHER ERROR LOGGING**: File watcher errors are now logged to stderr instead of silently swallowed.\n- **REPORT MODEL SECURITY**: Report generation now routes through `SecurityUtils` for path containment and file-size validation.\n\n## What's New in 4.4.4\n\n- **SHARED CONFIG FOR EXTENSIONS**: `.i18ntk-config` can now carry editor-owned settings under `extensions.workbench` and `extensions.lens`. The CLI preserves the top-level `extensions` object and ignores unknown nested extension keys.\n- **DYNAMIC VALUE REPORTING**: Likely-untranslated reports now ignore placeholder-only and symbol/dynamic values such as `{file}`, `{path}`, and icon-prefixed labels.\n- **PLACEHOLDER-AWARE TRANSLATION CHECKS**: Values with translated surrounding copy and English placeholder tokens, such as `\"command\": \"指示: {command}\"`, are no longer reported as untranslated.\n\n## What's New in 4.4.2\n\n- **AUTO TRANSLATE RELATIVE PATHS**: Programmatic `processFile()` calls now accept project-relative source paths, matching CLI source resolution.\n- **PROTECTED TERMS IN ONLY-MISSING MODE**: Existing translations that intentionally keep protected product terms such as `i18ntk` are no longer treated as English leftovers.\n- **BROKEN TARGET RETRIES**: Auto Translate now detects and retries more visibly broken target values, including mojibake, replacement characters, repeated question marks, and target-language prefix leftovers.\n- **REGRESSION COVERAGE**: Added focused tests for relative paths, protected product terms, broken values, placeholder handling, and residual checks.\n\n## What's New in 4.4.1\n\n- **DEAD-KEY DETECTION**: Dead-key confidence now uses resolved dynamic key data from usage insights instead of crude text-overlap heuristics. Keys expanded from template literals or const arrays are properly tracked.\n- **LOCALE JSON IMPORT DETECTION**: `import en from '../../locales/en/foo.json'` is now detected and property accesses are tracked as key usages, closing the gap between CLI and VSCode scanners.\n- **CONFIDENCE-SPLIT REPORTS**: Unused keys are now reported by confidence tier — confirmed (≥80%), likely (40-80%), possibly used (<40%) — instead of a flat list.\n- **NEW CLI FLAGS**: `--strict-unused` (only high-confidence keys), `--json` (structured JSON output for CI), `--prune` / `--prune-keep` (stale report cleanup).\n- **MOJIBAKE DETECTION**: Replacement-character artifacts like `Abwicklungspr?fung` and `L?ser` are detected during translation analysis.\n- **CLIENT-BOUNDARY WARNINGS**: `\"use client\"` files importing locale JSON are flagged — this bypasses the runtime and increases bundle size.\n- **COPY-FORMATTER DETECTION**: Local `const tx = ...` functions that don't call a translation runtime are identified as likely copy formatters, reducing false positive key noise.\n- **CONFIG FIX**: `--source-dir` and `--i18n-dir` are no longer forced equal when both are explicitly passed via CLI.\n- **Next.js DETECTION**: App Router files with `\"use server\"` / `\"use client\"` directives are now detected and reported by component type.\n- **VSCode DIAGNOSTICS**: New `i18ntk.clearDiagnostics` command. Stale diagnostics are now cleared at scan start. New diagnostic codes: `i18ntk.clientBoundary`, `i18ntk.copyFormatter`.\n- **AUTO TRANSLATE RESUME REPORTS**: If a provider still returns untranslated values after the final targeted retry, Auto Translate writes `i18ntk-reports/auto-translate/latest.json` so tooling can identify and retry only unresolved keys.\n- **VS CODE RESIDUAL PICKUP**: i18ntk Workbench and Lens read Auto Translate residual reports, show the affected locale JSON key in the editor, and can add intentionally unchanged keys to Auto Translate protection.\n- **WRAPPER CONFIG**: `.i18ntk-config` now supports `usage.translationFunctions`, `usage.serverWrappers`, and `usage.copyFormatters` for fine-grained control.\n- **TELEMETRY/EVENT STRING FILTERING**: String literals inside `trackEvent()`, `emitDomainEvent()`, `analytics.track()` and similar calls are classified as telemetry literals and no longer falsely counted as translation usage.\n- **OBJECT-METHOD KEY DETECTION**: input.tx(\"key\"), helper.tx(\"key\"), and .tx(\\`dynamic.${var}\\`) patterns are now recognized as translation calls alongside standalone `tx()` calls.\n\n- **LOCAL WRAPPER RESOLUTION**: Functions like `const text = (key, fallback) => tx(key)` that internally call the translation runtime are detected, and their string-literal invocations are automatically resolved to actual keys.\n\nSee [CHANGELOG.md](./CHANGELOG.md) for more release details.\n\n## Security hardening in 4.4.1\n\n- **PATH TRAVERSAL HARDENED**: Backup, complete, and config-helper commands now validate all user-supplied paths through `SecurityUtils.validatePath()`, blocking writes outside project boundaries.\n- **JSON DoS PREVENTED**: `safeParseJSON` enforces maximum depth (1000) and maximum size (50 MB) before parsing, preventing denial-of-service via deeply nested or oversized JSON.\n- **INPUT SANITIZATION TIGHTENED**: `sanitizeInput` default whitelist no longer allows backslashes or curly braces that could enable path traversal or template injection.\n\n- **LIBRETRANSLATE URL GATED**: Custom LibreTranslate host now requires `I18NTK_ALLOW_CUSTOM_LIBRETRANSLATE_HOST=1` env flag (parity with DeepL).\n\n## Quick Start\n\nInitialize a project:\n\n```bash\ni18ntk\n# or with explicit command\ni18ntk --command=init\n```\n\nRun common checks:\n\n```bash\ni18ntk --command=analyze\ni18ntk --command=validate\ni18ntk --command=usage\ni18ntk report --json\ni18ntk --command=sizing\ni18ntk --command=summary\n```\n\nComplete or fix translation files:\n\n```bash\ni18ntk --command=complete\ni18ntk-fixer --help\n```\n\nAuto-translate locale JSON:\n\n```bash\ni18ntk --command=translate\n# or\ni18ntk-translate locales/en/common.json de --report-stdout\n```\n\nThe full onboarding guide is in [docs/getting-started.md](./docs/getting-started.md).\n\n## Main Commands\n\nPrimary CLI:\n\n```bash\ni18ntk\ni18ntk --help\ni18ntk --command=init\ni18ntk --command=analyze\ni18ntk --command=validate\ni18ntk --command=usage\ni18ntk report --json --markdown --html --out ./i18ntk-reports\ni18ntk --command=scanner\ni18ntk --command=sizing\ni18ntk --command=complete\ni18ntk --command=translate\ni18ntk --command=summary\n```\n\nStandalone executables:\n\n```bash\ni18ntk-init\ni18ntk-analyze\ni18ntk-validate\ni18ntk-usage\ni18ntk-report\ni18ntk-scanner\ni18ntk-sizing\ni18ntk-complete\ni18ntk-summary\ni18ntk-doctor\ni18ntk-fixer\ni18ntk-backup\ni18ntk-translate\n```\n\nNote: manager route `i18ntk --command=backup` is disabled in current builds. Use `i18ntk-backup` directly for backup operations.\n\n## Command Reference\n\n| Command | What it does | Looks for | Writes or changes |\n| --- | --- | --- | --- |\n| `i18ntk` | Opens the interactive management menu. | Project config, setup state, available commands. | Only changes files after you choose a command that writes. |\n| `i18ntk --command=init` / `i18ntk-init` | Sets up locale folders and missing target-language files. | Source language files and selected target languages. | Locale JSON files, `.i18ntk-config`, optional reports/backups. |\n| `i18ntk --command=analyze` / `i18ntk-analyze` | Compares source and target translation coverage. | Missing keys, extra keys, untranslated markers, completion by language. | Markdown/JSON/text reports when report output is enabled. |\n| `i18ntk --command=validate` / `i18ntk-validate` | Validates structure and translation quality risks. | Placeholder mismatches, missing keys, risky URLs/emails/secrets, likely English target text. | Validation summary report. Does not edit locale files. |\n| `i18ntk --command=usage` / `i18ntk-usage` | Maps translation keys to source files and finds unused/missing keys. | Direct i18n calls, literal known-key references, bounded dynamic templates/object maps, unresolved dynamic expressions, hardcoded text candidates, namespace/file naming mismatches. | Usage report with key locations, namespace recommendations, unresolved dynamic expressions, hardcoded text suggestions, and optional dead-key report. Does not delete unless cleanup deletion is explicitly enabled. |\n| `i18ntk report` / `i18ntk-report` | Generates the stable schemaVersion 1 report used by CLI automation and i18ntk Workbench. | Locale completeness, missing keys, unused keys with confidence, placeholders, likely untranslated values, expansion risk, and hardcoded text candidates. | JSON to stdout by default, plus JSON/Markdown/HTML files when `--out` is used. Does not edit locale files. |\n| `i18ntk --command=scanner` / `i18ntk-scanner` | Scans source for i18n issues and hardcoded user-facing text. | JSX/template text, common text attributes, i18n usage patterns, source-language text profiles. | Scanner report. Does not edit files. |\n| `i18ntk --command=complete` / `i18ntk-complete` | Adds missing keys to target language files for 100% key coverage. | Source-language keys missing from targets. | Target locale JSON files, using missing translation markers/prefixes. |\n| `i18ntk --command=translate` / `i18ntk-translate` | Auto-translates locale JSON using configured provider behavior. | Missing, empty, untranslated-marker, source-copy, likely-English, or visibly corrupt target values by default. | Target locale JSON files and translation reports. Existing translated values are kept unless `--translate-all` is used. If unresolved values remain after retry, writes `i18ntk-reports/auto-translate/latest.json` for targeted follow-up. |\n| `i18ntk --command=sizing` / `i18ntk-sizing` | Estimates translated string length expansion and layout risk. | Text length, expansion ratios, placeholder-bearing strings. | Sizing report. Does not edit locale files. |\n| `i18ntk --command=summary` / `i18ntk-summary` | Shows project translation status. | Configured locales, reports, completeness status. | Console/report output only. |\n| `i18ntk-fixer` | Fixes placeholder and missing-marker issues, and can audit English source files with `--check-placeholders`. | Placeholder corruption, missing translation markers, configured language files, `[LANG] ...` leftovers in English locales. | Locale JSON files when fixes are applied. Use dry-run options where available before bulk edits. |\n| `i18ntk-backup` | Creates, verifies, restores, and cleans locale backups. | Locale JSON files and backup manifests. | Backup archives/manifests, or restored locale files when using restore. |\n\n## Common Options\n\nMany commands support:\n\n- `--source-dir <path>`\n- `--i18n-dir <path>`\n- `--output-dir <path>`\n- `--source-language <code>`\n- `--ui-language <code>`\n- `--no-prompt`\n- `--help`\n\nCommand-specific tools add their own flags such as `--dry-run`, `--output-report`, `--cleanup`, `--predict-expansion`, or Auto Translate provider options.\n\nExample:\n\n```bash\ni18ntk --command=analyze --source-dir=./src --i18n-dir=./locales --output-dir=./i18ntk-reports\n```\n\n## Auto Translate\n\nInteractive manager flow:\n\n```bash\ni18ntk\n# choose \"Auto Translate\"\n```\n\nDirect CLI examples:\n\n```bash\ni18ntk-translate locales/en/common.json de\ni18ntk-translate locales/en/common.json fr --dry-run --report-stdout\ni18ntk-translate locales/en es --source-dir locales/en --files \"*.json\" --no-confirm --preserve-placeholders\n```\n\nProvider examples:\n\n```bash\nexport DEEPL_API_KEY=\"your-deepl-api-key\"\ni18ntk-translate locales/en/common.json de --provider deepl --no-confirm --preserve-placeholders\n\nexport LIBRETRANSLATE_URL=\"https://libretranslate.com/translate\"\nexport LIBRETRANSLATE_API_KEY=\"optional-api-key\"\ni18ntk-translate locales/en/common.json es --provider libretranslate --no-confirm --preserve-placeholders\n```\n\n`google` remains the default provider. You can also set `I18NTK_TRANSLATE_PROVIDER=deepl` or `I18NTK_TRANSLATE_PROVIDER=libretranslate`.\n\nProvider 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.\n\nThe manager flow asks for:\n\n- source locale directory, either the folder with JSON files or a locale root such as `./locales`\n- source language code\n- one or more target languages, or `all`\n- one JSON file or all JSON files in the source directory\n\nIf you select a locale root such as `./locales` and choose source language `en`, the manager automatically uses `./locales/en` when that folder contains the source JSON files.\n\nBefore writing files, the manager can run a dry-run preview. After confirmation it writes translated files under sibling target-language folders, for example:\n\n```text\nlocales/en/common.json\nlocales/de/common.json\nlocales/fr/common.json\n```\n\nAuto Translate is target-aware by default. When a target file already exists, it keeps translated target values and only sends values that are missing, empty, marked as untranslated, still identical to the source, likely still English, or visibly corrupt from encoding damage such as `?????`, replacement characters, or common mojibake. Use `--translate-all` when you intentionally want to re-translate every source string.\n\n### Placeholder Handling\n\nAuto Translate detects common placeholders such as:\n\n- `{name}`\n- `{{count}}`\n- `%s`\n- `%d`\n- `:id`\n- `%{name}`\n- `${value}`\n- `{count, plural, one {# item} other {# items}}`\n- `$t(common.save)`\n- `%(total).2f`\n\nUseful flags:\n\n- `--preserve-placeholders`: translate text around placeholders and reinsert original tokens\n- `--skip-placeholders`: copy placeholder-bearing strings unchanged\n- `--send-placeholders`: send placeholder-bearing strings through translation after masking\n- `--custom-regex <regex>`: add project-specific placeholder detection\n- `--only-missing`: keep existing translated target values and translate only missing/source-copy/likely English values (default)\n- `--translate-all`: re-translate every source string\n\nProgress output is stage-aware for large files. Normal keys are reported as `Translating strings`, while preserve-mode placeholder work is reported as `Translating placeholder-safe text segments`; each progress update includes the current key path when available.\n\n### Protected Terms and Keys\n\nAuto Translate can create and use a project-local protection file:\n\n```bash\ni18ntk-translate locales/en/common.json de --create-protection-file --protection-file ./i18ntk-auto-translate.json\n```\n\nExample `i18ntk-auto-translate.json`:\n\n```json\n{\n \"version\": 1,\n \"terms\": [\n \"BrandName\",\n \"PRODUCT_CODE\",\n { \"value\": \"OK\", \"context\": \"after:Click|Press|Tap\" },\n { \"value\": \"API\", \"context\": \"standalone\" }\n ],\n \"keys\": [\"app.brandName\", \"legal.companyName\", \"product.*.symbol\"],\n \"values\": [\"BrandName Ltd\", \"support@example.com\"],\n \"patterns\": [\"[A-Z]{2,}-\\\\d+\"]\n}\n```\n\n- `terms` are masked before translation and restored exactly afterward.\n - **Plain strings**: masked everywhere (backward compatible).\n - **Context objects**: masked only in specific contexts (`after:word`, `before:word`, `standalone`, `surrounded:left,right`).\n- `keys` are exact key paths or `*` wildcard paths copied unchanged.\n- `values` are exact source values copied unchanged.\n- `patterns` are JavaScript regex strings for advanced protected substrings.\n\nUseful flags:\n\n- `--protection-file <path>`\n- `--create-protection-file`\n- `--no-protection`\n\nOpen Settings and choose `Auto Translate` to edit defaults for placeholder mode, translate-only-needed mode, concurrency, batch size, retry settings, report output, BOM output, protection file path, first-run setup prompt, and update prompt.\n\nSee [docs/auto-translate.md](./docs/auto-translate.md) for the full Auto Translate guide.\n\n## Validation\n\nValidation checks locale structure, completeness, placeholders, and content risks.\n\nValidation warning types are specific:\n\n- `Potential risky content`: URL, email address, or secret-like value\n- `Possible untranslated English content`: target-language value appears to contain too much English\n\nEnglish-content warnings include:\n\n- detected English percentage\n- configured threshold\n- matched word count\n- sample matched words\n\nTune warnings in `.i18ntk-config`:\n\n```json\n{\n \"englishContentThresholdPercent\": 10,\n \"allowedEnglishTerms\": [\"BrandName\", \"PRODUCT_CODE\"]\n}\n```\n\n## Sizing Analysis\n\n`i18ntk-sizing` reports translation file sizes, key counts, average value length, and file-set mismatches across language folders.\n\n```bash\ni18ntk-sizing --source-dir ./locales --format table\ni18ntk-sizing --source-dir ./locales --detailed --output-dir ./i18ntk-reports\n```\n\nUse `--detailed` to print per-file rows in the terminal.\n\n### Expansion Prediction (New in 4.0.0)\n\nPredict UI layout overflow risk by analyzing per-key character-count expansion across languages:\n\n```bash\ni18ntk-sizing --source-dir ./locales --predict-expansion --output-report\n```\n\nExpansion ratios are classified into risk tiers:\n\n- **Safe** (<30% expansion): no UI impact expected\n- **Warning** (30–50%): may overflow in tight layouts — test on target languages\n- **Critical** (>50%): high risk of truncation — review UI element sizing\n\nThe report includes a built-in language-pair expansion reference table (EN→DE +35%, EN→RU +50%, EN→JA −40%, etc.) and lists the top-30 most-expanded keys.\n\n## Scanner: Multi-Language Detection (New in 4.0.0)\n\n`i18ntk-scanner` now supports detecting hardcoded text in multiple source languages beyond English:\n\n```bash\ni18ntk-scanner --source-dir ./src --source-language de\ni18ntk-scanner --source-dir ./src --source-language ja --output-report\n```\n\nSupported language profiles (12+): English, German, French, Spanish, Japanese, Chinese, Russian, Korean, Arabic, Hindi, and more. Each profile includes language-specific character ranges, stopword lists for false-positive filtering, and transliteration rules for key generation.\n\n## Usage: Dead Key Detection (New in 4.0.0)\n\n`i18ntk-usage` can identify translation keys that are defined but never referenced in source code:\n\n```bash\ni18ntk-usage --source-dir ./src --i18n-dir ./locales --cleanup\ni18ntk-usage --source-dir ./src --i18n-dir ./locales --cleanup --dry-run-delete\n```\n\nEach dead key receives a confidence score (0.0–1.0) factoring:\n- Unresolved dynamic key patterns (e.g., `` t(`prefix.${dynamic}`) ``) — lower score and listed in the usage report; simple consts, bounded arrays, object maps, and ternaries are expanded to exact keys where possible\n- Key appears in source code comments or JSDoc — medium score\n- Parent file recently modified (<30 days) — medium score\n- No references found anywhere — high score (>0.8)\n\nThe `--dry-run-delete` flag writes a `.dead-keys.json` report for review before any destructive action.\n\n## Validator: Key Naming Conventions (New in 4.0.0)\n\nEnforce consistent translation key naming across your project:\n\n```bash\ni18ntk-validate --enforce-key-style\n```\n\nConfigure the expected style in `.i18ntk-config`:\n\n```json\n{\n \"keyStyle\": \"dot.notation\"\n}\n```\n\nSupported styles: `dot.notation`, `snake_case`, `camelCase`, `kebab-case`, `flat`. Violations are reported as warnings with suggested canonical forms.\n\n## Watch: Hot Reload (New in 4.0.0)\n\n`utils/watch-locales.js` now provides debounced file watching with EventEmitter support:\n\n```js\nconst watchLocales = require('i18ntk/utils/watch-locales');\nconst watcher = watchLocales('./locales');\n\nwatcher.on('change', (filePath) => {\n console.log('Locale changed:', filePath);\n});\n\nwatcher.on('add', (filePath) => {\n console.log('Locale added:', filePath);\n});\n\n// Later:\nwatcher.stop();\n```\n\nFeatures: 300ms debounce (configurable), SHA-256 hash tracking to skip no-change saves, and a maximum of 50 watched directories.\n\n### Migration\n\nThe `watchLocales` return value gained EventEmitter methods in v4.0.0. Existing stop-function usage still works:\n\n```js\nconst stop = watchLocales('./locales', onChange);\n```\n\nCan be updated to:\n\n```js\nconst watcher = watchLocales('./locales');\nwatcher.on('change', onChange);\nwatcher.stop();\n```\n\nPassing a callback as the second argument is still supported — it auto-subscribes to `change` and `add` events.\n\n## Backup: Incremental Mode (New in 4.0.0)\n\nCreate differential backups that only include changed files:\n\n```bash\ni18ntk-backup create ./locales --incremental\n```\n\nIncremental backups store SHA-256 hashes per file and a parent-chain reference. Restoring an incremental backup automatically chains from the oldest full backup through each incremental diff in order. Chain depth is capped at 10 increments. Use `verify` to validate the hash chain.\n\n## Runtime: Lazy Loading (New in 4.0.0)\n\nReduce memory usage by deferring locale file loads until first key access:\n\n```js\nconst runtime = require('i18ntk/runtime');\n\nconst i18n = runtime.initRuntime({\n baseDir: './locales',\n language: 'en',\n lazy: true\n});\n\nconsole.log(i18n.t('common.hello')); // loads common.json on first access\n```\n\nWhen `lazy: true`, the runtime builds a key-to-file manifest on first access and loads individual files on demand. Files are loaded once and cached. If the manifest is missing or incomplete, the runtime falls back to full eager loading for that language. Manifest size is capped at 100KB with path containment validation.\n\nProduction guidance:\n\n- Prefer the object returned from `initRuntime()` instead of module-level `runtime.t()` in apps with multiple tenants, projects, or locale roots.\n- Use `lazy: true` for large modular locale folders where lower steady-state memory matters more than a small first-key lookup cost.\n- Use `preload: true` without `lazy` for small locale sets or latency-sensitive startup paths.\n- Call `refresh(language)` after deploying or writing changed locale files so cached data and lazy manifests are rebuilt.\n- Use per-call language overrides when rendering one-off alternate-language strings: `i18n.t('common.hello', {}, { language: 'de' })`.\n- Use `translateBatch()` for small groups of labels and `clearCache()` / `getCacheInfo()` for cache maintenance and diagnostics.\n- `i18ntk/runtime/enhanced` remains available for compatibility with existing async/encryption users, but new production integrations should start with `i18ntk/runtime`.\n\n## Runtime API\n\nUse `i18ntk/runtime` when an application needs to read locale JSON files at runtime.\n\n```js\nconst runtime = require('i18ntk/runtime');\n\nconst i18n = runtime.initRuntime({\n baseDir: './locales',\n language: 'en',\n fallbackLanguage: 'en',\n keySeparator: '.',\n preload: true\n});\n\nconsole.log(i18n.t('common.hello'));\ni18n.setLanguage('fr');\nconsole.log(i18n.getLanguage());\nconsole.log(i18n.getAvailableLanguages());\ni18n.refresh('fr');\n```\n\nUseful production helpers:\n\n```js\ni18n.t('common.hello', {}, { language: 'de' }); // per-call language override\ni18n.translateBatch(['menu.home', 'menu.settings']);\ni18n.clearCache('fr');\nconsole.log(i18n.getCacheInfo());\n```\n\nSee [docs/runtime.md](./docs/runtime.md) for runtime details.\n\n## Configuration\n\ni18ntk uses a project-local `.i18ntk-config` file.\n\nExample:\n\n```json\n{\n \"version\": \"4.4.5\",\n \"sourceDir\": \"./locales\",\n \"i18nDir\": \"./locales\",\n \"outputDir\": \"./i18ntk-reports\",\n \"sourceLanguage\": \"en\",\n \"defaultLanguages\": [\"en\", \"de\", \"es\", \"fr\", \"ru\"],\n \"reports\": {\n \"format\": \"markdown\"\n },\n \"englishContentThresholdPercent\": 10,\n \"allowedEnglishTerms\": [\"BrandName\", \"PRODUCT_CODE\"],\n \"autoTranslate\": {\n \"placeholderMode\": \"preserve\",\n \"concurrency\": 12,\n \"batchSize\": 100,\n \"progressInterval\": 25,\n \"retryCount\": 3,\n \"retryDelay\": 1000,\n \"timeout\": 15000,\n \"dryRunFirst\": true,\n \"onlyMissingOrEnglish\": true,\n \"reportStdout\": true,\n \"bom\": false,\n \"protectionEnabled\": true,\n \"protectionFile\": \"./i18ntk-auto-translate.json\",\n \"promptProtectionSetup\": true,\n \"promptProtectionUpdate\": true\n },\n \"setup\": {\n \"completed\": true\n },\n \"extensions\": {\n \"workbench\": {\n \"localeDirectory\": \"./locales\",\n \"sourceLocale\": \"en\"\n },\n \"lens\": {\n \"localeDirectory\": \"./locales\",\n \"sourceLocale\": \"en\",\n \"keyFormats\": [\"dot\", \"snake\"]\n }\n }\n}\n```\n\nSee [docs/api/CONFIGURATION.md](./docs/api/CONFIGURATION.md) for the full configuration model.\n\n## Public Package Contents\n\nThe public package intentionally ships runtime and CLI files only.\n\nThe package includes:\n\n- CLI entry points under `main/`\n- manager commands and services\n- runtime API files under `runtime/`\n- settings UI files required at runtime\n- bundled internal UI locales\n- shared utilities required by the shipped commands\n- `README.md`, `CHANGELOG.md`, `LICENSE`, and policy files\n\nThe public package manifest includes `readmeFilename: \"README.md\"`, and the release staging script fails if `README.md` is missing or empty.\n\n## Documentation\n\n- [Documentation Index](./docs/README.md)\n- [Getting Started](./docs/getting-started.md)\n- [API Reference](./docs/api/API_REFERENCE.md)\n- [Configuration Guide](./docs/api/CONFIGURATION.md)\n- [Runtime API Guide](./docs/runtime.md)\n- [Auto Translate Guide](./docs/auto-translate.md)\n- [Scanner Guide](./docs/scanner-guide.md)\n- [Environment Variables](./docs/environment-variables.md)\n- [Migration Guide v4.3.3](./docs/migration-guide-v4.3.3.md)\n\n## Security\n\n- No API key is required for the default Auto Translate flow.\n- Do not store secrets in locale files, `.i18ntk-config`, or protection files.\n- Project-specific brand/product terms should be configured by the user, not hardcoded into the package.\n- Report security issues using [SECURITY.md](./SECURITY.md).\n\n## Community\n\n- [Contributing](./CONTRIBUTING.md)\n- [Code of Conduct](./CODE_OF_CONDUCT.md)\n- [Funding](./FUNDING.md)\n\n## Related Tools\n\n| Tool | Purpose |\n|---|---|\n| **i18ntk** | Zero-dependency i18n toolkit for scanning, validation, translation, reports, and runtime loading. |\n| **i18ntk Workbench** | Full VS Code localization health dashboard powered by i18ntk. |\n| **i18ntk Lens** | Lightweight inline translation hovers, diagnostics, and key navigation. |\n| **PublishGuard** | Pre-publish safety scanner for npm packages and VS Code extensions. |\n| **ContextKit** | AI coding context manager for AGENTS.md, Claude, Cursor, Copilot, Roo, and Codex files. |\n\n## License\n\nMIT. See [LICENSE](./LICENSE).\n"
253
+ "readme": "# i18ntk v4.5.1\n\nA zero-dependency internationalization toolkit for setup, scanning, analysis, validation, usage tracking, translation completion, automatic JSON locale translation, reporting, and runtime translation loading.\n\n![i18ntk Logo](https://raw.githubusercontent.com/vladnoskv/i18ntk/main/docs/screenshots/i18ntk-logo-public.PNG)\n\n[![npm version](https://img.shields.io/npm/v/i18ntk.svg?color=brightgreen)](https://www.npmjs.com/package/i18ntk)\n[![npm downloads](https://img.shields.io/npm/dt/i18ntk.svg)](https://www.npmjs.com/package/i18ntk)\n[![node](https://img.shields.io/badge/node-%3E%3D16-339933)](https://nodejs.org)\n[![dependencies](https://img.shields.io/badge/dependencies-0-success)](https://www.npmjs.com/package/i18ntk)\n[![license](https://img.shields.io/badge/license-MIT-yellow.svg)](LICENSE)\n[![socket](https://socket.dev/api/badge/npm/package/i18ntk/4.5.1)](https://socket.dev/npm/package/i18ntk/overview/4.5.1)\n\n[![i18ntk Workbench](https://img.shields.io/badge/VS_Code-i18ntk_Workbench-007ACC?logo=visualstudiocode&logoColor=white)](https://marketplace.visualstudio.com/items?itemName=VladNoskov.i18ntk-workbench)\n[![i18ntk Lens](https://img.shields.io/badge/VS_Code-i18ntk_Lens-007ACC?logo=visualstudiocode&logoColor=white)](https://marketplace.visualstudio.com/items?itemName=VladNoskov.i18ntk-lens)\n\n## The i18ntk ecosystem\n\n- i18ntk — CLI and runtime toolkit\n- i18ntk Workbench — full VS Code dashboard and reports\n- i18ntk Lens — inline hovers, CodeLens, and diagnostics\n\nUse the CLI in CI, Workbench for project-level management, and Lens for day-to-day editor feedback.\n\n## Install\n\n```bash\n# global CLI use\nnpm install -g i18ntk\n\n# local project use\nnpm install --save-dev i18ntk\n\n# one-off execution\nnpx i18ntk --help\n```\n\n## i18ntk Summary\n\n**What it does**\n\n- Manages locale files from the command line.\n- Finds missing, unused, risky, and inconsistent translation keys.\n- Produces validation and summary reports.\n- Supports framework-aware i18n workflows.\n- Provides a lightweight runtime translation toolkit.\n\n**What it does not do**\n\n- It is not a translation management SaaS.\n- It does not replace human translation review.\n- It does not force you to replace i18next, react-i18next, vue-i18n, or another runtime.\n\n**Why not i18next?**\n\ni18next is mainly a runtime internationalization library. i18ntk is mainly workflow tooling around translation files. They can work together: i18next handles runtime translation, while i18ntk handles setup, scanning, validation, reporting, and maintenance.\n\n| Need | i18ntk | i18next |\n| ------------------------- | ------------- | ---------------- |\n| Runtime translation | Basic toolkit | Mature runtime |\n| Locale file scanning | Yes | No |\n| Missing key detection | Yes | No |\n| Unused key detection | Yes | No |\n| Validation reports | Yes | Limited |\n| Auto-translation workflow | Yes | External tooling |\n\n## What's New in 4.5.1\n\n- **CORRECT COMPLETENESS**: Validation now shows accurate completion percentages vs source locale (e.g., 33% instead of misleading 100%).\n- **NO MORE PARENT KEYS**: `getAllKeys()` no longer reports parent namespace objects (`footer`) as missing keys alongside their leaf children (`footer.copyright`).\n- **DOCTOR SMARTER**: No longer flags unconfigured languages (`de`, `ru`) as issues. Auto-detects available languages from the i18n directory structure.\n- **SCANNER FIXED**: Scanner now correctly scans `src/` directory for hardcoded text, not `locales/`.\n- **RUNTIME ALIASES**: `initRuntime()` now supports `localeDir`/`targetLocale`/`sourceLocale` as aliases for `baseDir`/`language`/`fallbackLanguage`.\n\n## What's New in 4.5.0\n\n- **PROTOTYPE POLLUTION HARDENED**: Three layers of defense added — `readJsonSafe()` now recursively strips `__proto__`, `constructor`, and `prototype` keys from all parsed JSON; `deepMerge()` in the runtime blocks these keys during locale merging; `mergeWithDefaults()` in settings-manager filters them from user settings.\n- **BACKUP FIXED**: All backup operations (create, restore, list, verify, cleanup) now work. A duplicate `sourceDir` declaration that caused a SyntaxError at module load has been removed. Corrupt backup files are now handled gracefully with descriptive error messages.\n- **COMPLETE COMMAND FIXED**: `i18ntk-complete` no longer crashes with `getUnifiedConfig is not defined`. The missing config-helper import has been added.\n- **MALFORMED JSON HANDLING**: Report generation now gracefully skips malformed JSON files with a warning instead of aborting the entire report.\n- **NULL SAFETY**: `stripBOMAndComments()` in i18n-helper now handles null/undefined inputs without throwing.\n- **ERROR HANDLING HARDENED**: Lazy-load failures in runtime now log to console when `I18NTK_DEBUG` is set. Settings save errors are now re-thrown instead of silently swallowed. Legacy config migration has proper error handling.\n\nSee [CHANGELOG.md](./CHANGELOG.md) for more release details.\n\n## Quick Start\n\nInitialize a project:\n\n```bash\ni18ntk\n# or with explicit command\ni18ntk --command=init\n```\n\nRun common checks:\n\n```bash\ni18ntk --command=analyze\ni18ntk --command=validate\ni18ntk --command=usage\ni18ntk report --json\ni18ntk --command=sizing\ni18ntk --command=summary\n```\n\nComplete or fix translation files:\n\n```bash\ni18ntk --command=complete\ni18ntk-fixer --help\n```\n\nAuto-translate locale JSON:\n\n```bash\ni18ntk --command=translate\n# or\ni18ntk-translate locales/en/common.json de --report-stdout\n```\n\nThe full onboarding guide is in [docs/getting-started.md](./docs/getting-started.md).\n\n## Main Commands\n\nPrimary CLI:\n\n```bash\ni18ntk\ni18ntk --help\ni18ntk --command=init\ni18ntk --command=analyze\ni18ntk --command=validate\ni18ntk --command=usage\ni18ntk report --json --markdown --html --out ./i18ntk-reports\ni18ntk --command=scanner\ni18ntk --command=sizing\ni18ntk --command=complete\ni18ntk --command=translate\ni18ntk --command=summary\n```\n\nStandalone executables:\n\n```bash\ni18ntk-init\ni18ntk-analyze\ni18ntk-validate\ni18ntk-usage\ni18ntk-report\ni18ntk-scanner\ni18ntk-sizing\ni18ntk-complete\ni18ntk-summary\ni18ntk-doctor\ni18ntk-fixer\ni18ntk-backup\ni18ntk-translate\n```\n\nNote: manager route `i18ntk --command=backup` is available via the interactive menu. Use `i18ntk-backup` directly for scripted backup operations.\n\n## Command Reference\n\n| Command | What it does | Looks for | Writes or changes |\n| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `i18ntk` | Opens the interactive management menu. | Project config, setup state, available commands. | Only changes files after you choose a command that writes. |\n| `i18ntk --command=init` / `i18ntk-init` | Sets up locale folders and missing target-language files. | Source language files and selected target languages. | Locale JSON files, `.i18ntk-config`, optional reports/backups. |\n| `i18ntk --command=analyze` / `i18ntk-analyze` | Compares source and target translation coverage. | Missing keys, extra keys, untranslated markers, completion by language. | Markdown/JSON/text reports when report output is enabled. |\n| `i18ntk --command=validate` / `i18ntk-validate` | Validates structure and translation quality risks. | Placeholder mismatches, missing keys, risky URLs/emails/secrets, likely English target text. | Validation summary report. Does not edit locale files. |\n| `i18ntk --command=usage` / `i18ntk-usage` | Maps translation keys to source files and finds unused/missing keys. | Direct i18n calls, literal known-key references, bounded dynamic templates/object maps, unresolved dynamic expressions, hardcoded text candidates, namespace/file naming mismatches. | Usage report with key locations, namespace recommendations, unresolved dynamic expressions, hardcoded text suggestions, and optional dead-key report. Does not delete unless cleanup deletion is explicitly enabled. |\n| `i18ntk report` / `i18ntk-report` | Generates the stable schemaVersion 1 report used by CLI automation and i18ntk Workbench. | Locale completeness, missing keys, unused keys with confidence, placeholders, likely untranslated values, expansion risk, and hardcoded text candidates. | JSON to stdout by default, plus JSON/Markdown/HTML files when `--out` is used. Does not edit locale files. |\n| `i18ntk --command=scanner` / `i18ntk-scanner` | Scans source for i18n issues and hardcoded user-facing text. | JSX/template text, common text attributes, i18n usage patterns, source-language text profiles. | Scanner report. Does not edit files. |\n| `i18ntk --command=complete` / `i18ntk-complete` | Adds missing keys to target language files for 100% key coverage. | Source-language keys missing from targets. | Target locale JSON files, using missing translation markers/prefixes. |\n| `i18ntk --command=translate` / `i18ntk-translate` | Auto-translates locale JSON using configured provider behavior. | Missing, empty, untranslated-marker, source-copy, likely-English, or visibly corrupt target values by default. | Target locale JSON files and translation reports. Existing translated values are kept unless `--translate-all` is used. If unresolved values remain after retry, writes `i18ntk-reports/auto-translate/latest.json` for targeted follow-up. |\n| `i18ntk --command=sizing` / `i18ntk-sizing` | Estimates translated string length expansion and layout risk. | Text length, expansion ratios, placeholder-bearing strings. | Sizing report. Does not edit locale files. |\n| `i18ntk --command=summary` / `i18ntk-summary` | Shows project translation status. | Configured locales, reports, completeness status. | Console/report output only. |\n| `i18ntk-fixer` | Fixes placeholder and missing-marker issues, and can audit English source files with `--check-placeholders`. | Placeholder corruption, missing translation markers, configured language files, `[LANG] ...` leftovers in English locales. | Locale JSON files when fixes are applied. Use dry-run options where available before bulk edits. |\n| `i18ntk-backup` | Creates, verifies, restores, and cleans locale backups. | Locale JSON files and backup manifests. | Backup archives/manifests, or restored locale files when using restore. |\n\n## Common Options\n\nMany commands support:\n\n- `--source-dir <path>`\n- `--i18n-dir <path>`\n- `--output-dir <path>`\n- `--source-language <code>`\n- `--ui-language <code>`\n- `--no-prompt`\n- `--help`\n\nCommand-specific tools add their own flags such as `--dry-run`, `--output-report`, `--cleanup`, `--predict-expansion`, or Auto Translate provider options.\n\nExample:\n\n```bash\ni18ntk --command=analyze --source-dir=./src --i18n-dir=./locales --output-dir=./i18ntk-reports\n```\n\n## Auto Translate\n\nInteractive manager flow:\n\n```bash\ni18ntk\n# choose \"Auto Translate\"\n```\n\nDirect CLI examples:\n\n```bash\ni18ntk-translate locales/en/common.json de\ni18ntk-translate locales/en/common.json fr --dry-run --report-stdout\ni18ntk-translate locales/en es --source-dir locales/en --files \"*.json\" --no-confirm --preserve-placeholders\n```\n\nProvider examples:\n\n```bash\nexport DEEPL_API_KEY=\"your-deepl-api-key\"\ni18ntk-translate locales/en/common.json de --provider deepl --no-confirm --preserve-placeholders\n\nexport LIBRETRANSLATE_URL=\"https://libretranslate.com/translate\"\nexport LIBRETRANSLATE_API_KEY=\"optional-api-key\"\ni18ntk-translate locales/en/common.json es --provider libretranslate --no-confirm --preserve-placeholders\n```\n\n`google` remains the default provider. You can also set `I18NTK_TRANSLATE_PROVIDER=deepl` or `I18NTK_TRANSLATE_PROVIDER=libretranslate`.\n\nProvider 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.\n\nThe manager flow asks for:\n\n- source locale directory, either the folder with JSON files or a locale root such as `./locales`\n- source language code\n- one or more target languages, or `all`\n- one JSON file or all JSON files in the source directory\n\nIf you select a locale root such as `./locales` and choose source language `en`, the manager automatically uses `./locales/en` when that folder contains the source JSON files.\n\nBefore writing files, the manager can run a dry-run preview. After confirmation it writes translated files under sibling target-language folders, for example:\n\n```text\nlocales/en/common.json\nlocales/de/common.json\nlocales/fr/common.json\n```\n\nAuto Translate is target-aware by default. When a target file already exists, it keeps translated target values and only sends values that are missing, empty, marked as untranslated, still identical to the source, likely still English, or visibly corrupt from encoding damage such as `?????`, replacement characters, or common mojibake. Use `--translate-all` when you intentionally want to re-translate every source string.\n\n### Placeholder Handling\n\nAuto Translate detects common placeholders such as:\n\n- `{name}`\n- `{{count}}`\n- `%s`\n- `%d`\n- `:id`\n- `%{name}`\n- `${value}`\n- `{count, plural, one {# item} other {# items}}`\n- `$t(common.save)`\n- `%(total).2f`\n\nUseful flags:\n\n- `--preserve-placeholders`: translate text around placeholders and reinsert original tokens\n- `--skip-placeholders`: copy placeholder-bearing strings unchanged\n- `--send-placeholders`: send placeholder-bearing strings through translation after masking\n- `--custom-regex <regex>`: add project-specific placeholder detection\n- `--only-missing`: keep existing translated target values and translate only missing/source-copy/likely English values (default)\n- `--translate-all`: re-translate every source string\n\nProgress output is stage-aware for large files. Normal keys are reported as `Translating strings`, while preserve-mode placeholder work is reported as `Translating placeholder-safe text segments`; each progress update includes the current key path when available.\n\n### Protected Terms and Keys\n\nAuto Translate can create and use a project-local protection file:\n\n```bash\ni18ntk-translate locales/en/common.json de --create-protection-file --protection-file ./i18ntk-auto-translate.json\n```\n\nExample `i18ntk-auto-translate.json`:\n\n```json\n{\n \"version\": 1,\n \"terms\": [\n \"BrandName\",\n \"PRODUCT_CODE\",\n { \"value\": \"OK\", \"context\": \"after:Click|Press|Tap\" },\n { \"value\": \"API\", \"context\": \"standalone\" }\n ],\n \"keys\": [\"app.brandName\", \"legal.companyName\", \"product.*.symbol\"],\n \"values\": [\"BrandName Ltd\", \"support@example.com\"],\n \"patterns\": [\"[A-Z]{2,}-\\\\d+\"]\n}\n```\n\n- `terms` are masked before translation and restored exactly afterward.\n - **Plain strings**: masked everywhere (backward compatible).\n - **Context objects**: masked only in specific contexts (`after:word`, `before:word`, `standalone`, `surrounded:left,right`).\n- `keys` are exact key paths or `*` wildcard paths copied unchanged.\n- `values` are exact source values copied unchanged.\n- `patterns` are JavaScript regex strings for advanced protected substrings.\n\nUseful flags:\n\n- `--protection-file <path>`\n- `--create-protection-file`\n- `--no-protection`\n\nOpen Settings and choose `Auto Translate` to edit defaults for placeholder mode, translate-only-needed mode, concurrency, batch size, retry settings, report output, BOM output, protection file path, first-run setup prompt, and update prompt.\n\nSee [docs/auto-translate.md](./docs/auto-translate.md) for the full Auto Translate guide.\n\n## Validation\n\nValidation checks locale structure, completeness, placeholders, and content risks.\n\nValidation warning types are specific:\n\n- `Potential risky content`: URL, email address, or secret-like value\n- `Possible untranslated English content`: target-language value appears to contain too much English\n\nEnglish-content warnings include:\n\n- detected English percentage\n- configured threshold\n- matched word count\n- sample matched words\n\nTune warnings in `.i18ntk-config`:\n\n```json\n{\n \"englishContentThresholdPercent\": 10,\n \"allowedEnglishTerms\": [\"BrandName\", \"PRODUCT_CODE\"]\n}\n```\n\n## Sizing Analysis\n\n`i18ntk-sizing` reports translation file sizes, key counts, average value length, and file-set mismatches across language folders.\n\n```bash\ni18ntk-sizing --source-dir ./locales --format table\ni18ntk-sizing --source-dir ./locales --detailed --output-dir ./i18ntk-reports\n```\n\nUse `--detailed` to print per-file rows in the terminal.\n\n### Expansion Prediction (New in 4.0.0)\n\nPredict UI layout overflow risk by analyzing per-key character-count expansion across languages:\n\n```bash\ni18ntk-sizing --source-dir ./locales --predict-expansion --output-report\n```\n\nExpansion ratios are classified into risk tiers:\n\n- **Safe** (<30% expansion): no UI impact expected\n- **Warning** (30–50%): may overflow in tight layouts — test on target languages\n- **Critical** (>50%): high risk of truncation — review UI element sizing\n\nThe report includes a built-in language-pair expansion reference table (EN→DE +35%, EN→RU +50%, EN→JA −40%, etc.) and lists the top-30 most-expanded keys.\n\n## Scanner: Multi-Language Detection (New in 4.0.0)\n\n`i18ntk-scanner` now supports detecting hardcoded text in multiple source languages beyond English:\n\n```bash\ni18ntk-scanner --source-dir ./src --source-language de\ni18ntk-scanner --source-dir ./src --source-language ja --output-report\n```\n\nSupported language profiles (12+): English, German, French, Spanish, Japanese, Chinese, Russian, Korean, Arabic, Hindi, and more. Each profile includes language-specific character ranges, stopword lists for false-positive filtering, and transliteration rules for key generation.\n\n## Usage: Dead Key Detection (New in 4.0.0)\n\n`i18ntk-usage` can identify translation keys that are defined but never referenced in source code:\n\n```bash\ni18ntk-usage --source-dir ./src --i18n-dir ./locales --cleanup\ni18ntk-usage --source-dir ./src --i18n-dir ./locales --cleanup --dry-run-delete\n```\n\nEach dead key receives a confidence score (0.0–1.0) factoring:\n\n- Unresolved dynamic key patterns (e.g., ``t(`prefix.${dynamic}`)``) — lower score and listed in the usage report; simple consts, bounded arrays, object maps, and ternaries are expanded to exact keys where possible\n- Key appears in source code comments or JSDoc — medium score\n- Parent file recently modified (<30 days) — medium score\n- No references found anywhere — high score (>0.8)\n\nThe `--dry-run-delete` flag writes a `.dead-keys.json` report for review before any destructive action.\n\n## Validator: Key Naming Conventions (New in 4.0.0)\n\nEnforce consistent translation key naming across your project:\n\n```bash\ni18ntk-validate --enforce-key-style\n```\n\nConfigure the expected style in `.i18ntk-config`:\n\n```json\n{\n \"keyStyle\": \"dot.notation\"\n}\n```\n\nSupported styles: `dot.notation`, `snake_case`, `camelCase`, `kebab-case`, `flat`. Violations are reported as warnings with suggested canonical forms.\n\n## Watch: Hot Reload (New in 4.0.0)\n\n`utils/watch-locales.js` now provides debounced file watching with EventEmitter support:\n\n```js\nconst watchLocales = require('i18ntk/utils/watch-locales');\nconst watcher = watchLocales('./locales');\n\nwatcher.on('change', (filePath) => {\n console.log('Locale changed:', filePath);\n});\n\nwatcher.on('add', (filePath) => {\n console.log('Locale added:', filePath);\n});\n\n// Later:\nwatcher.stop();\n```\n\nFeatures: 300ms debounce (configurable), SHA-256 hash tracking to skip no-change saves, and a maximum of 50 watched directories.\n\n### Migration\n\nThe `watchLocales` return value gained EventEmitter methods in v4.0.0. Existing stop-function usage still works:\n\n```js\nconst stop = watchLocales('./locales', onChange);\n```\n\nCan be updated to:\n\n```js\nconst watcher = watchLocales('./locales');\nwatcher.on('change', onChange);\nwatcher.stop();\n```\n\nPassing a callback as the second argument is still supported — it auto-subscribes to `change` and `add` events.\n\n## Backup: Incremental Mode (New in 4.0.0)\n\nCreate differential backups that only include changed files:\n\n```bash\ni18ntk-backup create ./locales --incremental\n```\n\nIncremental backups store SHA-256 hashes per file and a parent-chain reference. Restoring an incremental backup automatically chains from the oldest full backup through each incremental diff in order. Chain depth is capped at 10 increments. Use `verify` to validate the hash chain.\n\n## Runtime: Lazy Loading (New in 4.0.0)\n\nReduce memory usage by deferring locale file loads until first key access:\n\n```js\nconst runtime = require('i18ntk/runtime');\n\nconst i18n = runtime.initRuntime({\n baseDir: './locales',\n language: 'en',\n lazy: true,\n});\n\nconsole.log(i18n.t('common.hello')); // loads common.json on first access\n```\n\nWhen `lazy: true`, the runtime builds a key-to-file manifest on first access and loads individual files on demand. Files are loaded once and cached. If the manifest is missing or incomplete, the runtime falls back to full eager loading for that language. Manifest size is capped at 100KB with path containment validation.\n\nProduction guidance:\n\n- Prefer the object returned from `initRuntime()` instead of module-level `runtime.t()` in apps with multiple tenants, projects, or locale roots.\n- Use `lazy: true` for large modular locale folders where lower steady-state memory matters more than a small first-key lookup cost.\n- Use `preload: true` without `lazy` for small locale sets or latency-sensitive startup paths.\n- Call `refresh(language)` after deploying or writing changed locale files so cached data and lazy manifests are rebuilt.\n- Use per-call language overrides when rendering one-off alternate-language strings: `i18n.t('common.hello', {}, { language: 'de' })`.\n- Use `translateBatch()` for small groups of labels and `clearCache()` / `getCacheInfo()` for cache maintenance and diagnostics.\n- `i18ntk/runtime/enhanced` remains available for compatibility with existing async/encryption users, but new production integrations should start with `i18ntk/runtime`.\n\n## Runtime API\n\nUse `i18ntk/runtime` when an application needs to read locale JSON files at runtime.\n\n```js\nconst runtime = require('i18ntk/runtime');\n\nconst i18n = runtime.initRuntime({\n baseDir: './locales',\n language: 'en',\n fallbackLanguage: 'en',\n keySeparator: '.',\n preload: true,\n});\n\nconsole.log(i18n.t('common.hello'));\ni18n.setLanguage('fr');\nconsole.log(i18n.getLanguage());\nconsole.log(i18n.getAvailableLanguages());\ni18n.refresh('fr');\n```\n\nUseful production helpers:\n\n```js\ni18n.t('common.hello', {}, { language: 'de' }); // per-call language override\ni18n.translateBatch(['menu.home', 'menu.settings']);\ni18n.clearCache('fr');\nconsole.log(i18n.getCacheInfo());\n```\n\nSee [docs/runtime.md](./docs/runtime.md) for runtime details.\n\n## Configuration\n\ni18ntk uses a project-local `.i18ntk-config` file.\n\nExample:\n\n```json\n{\n \"version\": \"4.5.1\",\n \"sourceDir\": \"./locales\",\n \"i18nDir\": \"./locales\",\n \"outputDir\": \"./i18ntk-reports\",\n \"sourceLanguage\": \"en\",\n \"defaultLanguages\": [\"en\", \"de\", \"es\", \"fr\", \"ru\"],\n \"reports\": {\n \"format\": \"markdown\"\n },\n \"englishContentThresholdPercent\": 10,\n \"allowedEnglishTerms\": [\"BrandName\", \"PRODUCT_CODE\"],\n \"autoTranslate\": {\n \"placeholderMode\": \"preserve\",\n \"concurrency\": 12,\n \"batchSize\": 100,\n \"progressInterval\": 25,\n \"retryCount\": 3,\n \"retryDelay\": 1000,\n \"timeout\": 15000,\n \"dryRunFirst\": true,\n \"onlyMissingOrEnglish\": true,\n \"reportStdout\": true,\n \"bom\": false,\n \"protectionEnabled\": true,\n \"protectionFile\": \"./i18ntk-auto-translate.json\",\n \"promptProtectionSetup\": true,\n \"promptProtectionUpdate\": true\n },\n \"setup\": {\n \"completed\": true\n },\n \"extensions\": {\n \"workbench\": {\n \"localeDirectory\": \"./locales\",\n \"sourceLocale\": \"en\"\n },\n \"lens\": {\n \"localeDirectory\": \"./locales\",\n \"sourceLocale\": \"en\",\n \"keyFormats\": [\"dot\", \"snake\"]\n }\n }\n}\n```\n\nSee [docs/api/CONFIGURATION.md](./docs/api/CONFIGURATION.md) for the full configuration model.\n\n## Public Package Contents\n\nThe public package intentionally ships runtime and CLI files only.\n\nThe package includes:\n\n- CLI entry points under `main/`\n- manager commands and services\n- runtime API files under `runtime/`\n- settings UI files required at runtime\n- bundled internal UI locales\n- shared utilities required by the shipped commands\n- `README.md`, `CHANGELOG.md`, `LICENSE`, and policy files\n\nThe public package manifest includes `readmeFilename: \"README.md\"`, and the release staging script fails if `README.md` is missing or empty.\n\n## Documentation\n\n- [Documentation Index](./docs/README.md)\n- [Getting Started](./docs/getting-started.md)\n- [API Reference](./docs/api/API_REFERENCE.md)\n- [Configuration Guide](./docs/api/CONFIGURATION.md)\n- [Runtime API Guide](./docs/runtime.md)\n- [Auto Translate Guide](./docs/auto-translate.md)\n- [Scanner Guide](./docs/scanner-guide.md)\n- [Environment Variables](./docs/environment-variables.md)\n- [Migration Guide v4.3.3](./docs/migration-guide-v4.3.3.md)\n\n## Security\n\n- No API key is required for the default Auto Translate flow.\n- Do not store secrets in locale files, `.i18ntk-config`, or protection files.\n- Project-specific brand/product terms should be configured by the user, not hardcoded into the package.\n- Report security issues using [SECURITY.md](./SECURITY.md).\n\n## Community\n\n- [Contributing](./CONTRIBUTING.md)\n- [Code of Conduct](./CODE_OF_CONDUCT.md)\n- [Funding](./FUNDING.md)\n\n## Related Tools\n\n| Tool | Purpose |\n| -------------------- | ------------------------------------------------------------------------------------------------- |\n| **i18ntk** | Zero-dependency i18n toolkit for scanning, validation, translation, reports, and runtime loading. |\n| **i18ntk Workbench** | Full VS Code localization health dashboard powered by i18ntk. |\n| **i18ntk Lens** | Lightweight inline translation hovers, diagnostics, and key navigation. |\n| **PublishGuard** | Pre-publish safety scanner for npm packages and VS Code extensions. |\n| **ContextKit** | AI coding context manager for AGENTS.md, Claude, Cursor, Copilot, Roo, and Codex files. |\n\n## License\n\nMIT. See [LICENSE](./LICENSE).\n"
254
254
  }
package/runtime/index.js CHANGED
@@ -76,24 +76,38 @@ function readJsonSafe(file) {
76
76
  }
77
77
 
78
78
  try {
79
- return JSON.parse(withoutBOM);
79
+ return stripPrototypeKeys(JSON.parse(withoutBOM));
80
80
  } catch (parseError) {
81
81
  const cleaned = stripBOMAndComments(raw);
82
82
  if (!cleaned) {
83
83
  throw new Error(`Empty JSON file: ${file}`);
84
84
  }
85
85
  try {
86
- return JSON.parse(cleaned);
86
+ return stripPrototypeKeys(JSON.parse(cleaned));
87
87
  } catch (_) {
88
- throw new Error(`Invalid JSON in file ${file}: ${parseError.message}`);
89
- }
90
- }
88
+ throw new Error(`Invalid JSON in file ${file}: ${parseError.message}`);
89
+ }
90
+ }
91
+ }
92
+
93
+ function stripPrototypeKeys(obj) {
94
+ if (!obj || typeof obj !== 'object') return obj;
95
+ if (Array.isArray(obj)) return obj.map(item => stripPrototypeKeys(item));
96
+ const clean = {};
97
+ for (const key of Object.keys(obj)) {
98
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;
99
+ const value = obj[key];
100
+ clean[key] = (value && typeof value === 'object') ? stripPrototypeKeys(value) : value;
101
+ }
102
+ return clean;
91
103
  }
92
104
 
93
105
  function deepMerge(target, source) {
94
106
  if (!target || typeof target !== 'object') target = {};
95
107
  if (!source || typeof source !== 'object') return target;
96
108
  for (const key of Object.keys(source)) {
109
+ // Block prototype pollution via __proto__, constructor, prototype keys
110
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;
97
111
  const sv = source[key];
98
112
  const tv = target[key];
99
113
  if (
@@ -326,8 +340,11 @@ function resolveKey(obj, key, sep = '.', runtimeState = null, lang = null) {
326
340
  runtimeState.loadedFiles.add(loadedFileKey);
327
341
  try {
328
342
  loadFileLazy(runtimeState, filePath, lang);
329
- } catch (_) {
330
- // stale manifest entry — already marked as loaded to prevent retry
343
+ } catch (lazyErr) {
344
+ // stale manifest entry — log and mark as loaded to prevent retry
345
+ if (process.env.I18NTK_DEBUG) {
346
+ console.warn(`[i18ntk/runtime] Lazy load failed for ${filePath}: ${lazyErr.message}`);
347
+ }
331
348
  }
332
349
  const langData = runtimeState.cache.get(lang);
333
350
  return resolveKey(langData, key, sep, runtimeState, lang);
@@ -351,7 +368,14 @@ function resolveKey(obj, key, sep = '.', runtimeState = null, lang = null) {
351
368
 
352
369
  // --- Public API ---
353
370
  function initRuntime(options = {}) {
354
- const runtimeState = createState(options);
371
+ // Support alias parameter names for better DX
372
+ const opts = { ...options };
373
+ if (opts.localeDir && !opts.baseDir) opts.baseDir = opts.localeDir;
374
+ if (opts.targetLocale && !opts.language) opts.language = opts.targetLocale;
375
+ if (opts.sourceLocale && !opts.fallbackLanguage) opts.fallbackLanguage = opts.sourceLocale;
376
+ if (opts.projectRoot && !opts.baseDir && opts.localeDir) opts.baseDir = path.resolve(opts.projectRoot, opts.localeDir);
377
+
378
+ const runtimeState = createState(opts);
355
379
  preload(runtimeState, options.preload);
356
380
 
357
381
  if (!singletonInitialized) {
@@ -368,7 +368,13 @@ class SettingsManager {
368
368
  * @returns {object} Merged settings
369
369
  */
370
370
  mergeWithDefaults(loadedSettings) {
371
- const merged = { ...this.defaultConfig, ...loadedSettings };
371
+ // Filter prototype pollution keys before merging
372
+ const safe = {};
373
+ for (const key of Object.keys(loadedSettings || {})) {
374
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;
375
+ safe[key] = loadedSettings[key];
376
+ }
377
+ const merged = { ...this.defaultConfig, ...safe };
372
378
 
373
379
  // Ensure nested objects are properly merged
374
380
  if (loadedSettings.notifications) {
@@ -486,7 +492,8 @@ class SettingsManager {
486
492
 
487
493
  console.log('Settings saved successfully');
488
494
  } catch (error) {
489
- console.error('Error saving settings:', error.message);
495
+ console.error('[i18ntk] Error saving settings:', error.message);
496
+ throw error;
490
497
  }
491
498
 
492
499
  this.saveTimeout = null;
@@ -550,8 +550,11 @@ function loadConfig() {
550
550
  // Attempt to migrate to project settings
551
551
  // Ignore migration errors; we still return merged cfg in memory
552
552
  // eslint-disable-next-line no-unused-vars
553
- logWarn('[i18ntk] Detected legacy config at ~/.i18ntk. Migrating to project settings directory...');
554
- const _ = (async () => { await migrateLegacyIfNeeded(DEFAULT_CONFIG); })();
553
+ logWarn('[i18ntk] Detected legacy config at ~/.i18ntk. Migrating to project settings directory...');
554
+ // Fire migration asynchronously with error handling
555
+ migrateLegacyIfNeeded(DEFAULT_CONFIG).catch((err) => {
556
+ logWarn('[i18ntk] Legacy config migration failed', { error: err.message });
557
+ });
555
558
  }
556
559
  }
557
560
  }
@@ -80,6 +80,7 @@ function safeRequireConfig() {
80
80
  }
81
81
 
82
82
  function stripBOMAndComments(s) {
83
+ if (!s || typeof s !== 'string') return s || '';
83
84
  if (s.charCodeAt(0) === 0xFEFF) s = s.slice(1);
84
85
  s = s.replace(/\/\*[\s\S]*?\*\//g, '');
85
86
  s = s.replace(/^\s*\/\/.*$/mg, '');
@@ -474,7 +475,11 @@ function getAvailableLanguages() {
474
475
  * @returns {object} - The merged object.
475
476
  */
476
477
  function deepMerge(target, source) {
477
- for (const key in source) {
478
+ if (!target || typeof target !== 'object') target = {};
479
+ if (!source || typeof source !== 'object') return target;
480
+ for (const key of Object.keys(source)) {
481
+ // Block prototype pollution
482
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;
478
483
  if (source.hasOwnProperty(key)) {
479
484
  if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key]) &&
480
485
  typeof target[key] === 'object' && target[key] !== null && !Array.isArray(target[key])) {
@@ -44,7 +44,8 @@ function generateI18ntkReport(options = {}) {
44
44
  parsed = JSON.parse(raw);
45
45
  } catch (error) {
46
46
  const rel = path.relative(projectRoot, item.filePath);
47
- throw new Error(`Invalid JSON in ${rel}: ${error.message}`);
47
+ console.warn(`Skipping malformed JSON file: ${rel} (${error.message})`);
48
+ continue;
48
49
  }
49
50
  const flat = flattenObject(parsed);
50
51
  localeValues[item.locale] = { ...(localeValues[item.locale] || {}), ...flat };
@@ -228,7 +229,7 @@ function discoverLocaleFiles(localesDir) {
228
229
  for (const entry of entries) {
229
230
  const entryPath = path.join(localesDir, entry.name);
230
231
  if (entry.isDirectory()) {
231
- for (const filePath of walk(entryPath, ['.json'])) {
232
+ for (const filePath of walk(entryPath, ['.json'], [], entryPath)) {
232
233
  files.push({
233
234
  locale: entry.name,
234
235
  namespace: path.basename(filePath, '.json'),
@@ -246,12 +247,12 @@ function discoverLocaleFiles(localesDir) {
246
247
  return files;
247
248
  }
248
249
 
249
- function walk(dir, extensions, acc = []) {
250
- if (!fs.existsSync(dir)) return acc;
250
+ function walk(dir, extensions, acc = [], basePath = process.cwd()) {
251
+ if (!SecurityUtils.safeExistsSync(dir, basePath)) return acc;
251
252
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
252
253
  if (['node_modules', '.git', 'dist', 'build', 'coverage', 'out', 'tmp', 'temp', 'test', 'tests', 'test_env', 'i18ntk-reports'].includes(entry.name)) continue;
253
254
  const entryPath = path.join(dir, entry.name);
254
- if (entry.isDirectory()) walk(entryPath, extensions, acc);
255
+ if (entry.isDirectory()) walk(entryPath, extensions, acc, basePath);
255
256
  else if (entry.isFile() && extensions.includes(path.extname(entry.name))) acc.push(entryPath);
256
257
  }
257
258
  return acc;
@@ -273,10 +274,11 @@ function scanSourceFiles(sourceDir, availableKeys) {
273
274
  const usageLocations = new Map();
274
275
  const hardcodedTexts = [];
275
276
  const translationValueIndex = new Map();
276
- const files = walk(sourceDir, ['.js', '.jsx', '.ts', '.tsx', '.vue', '.svelte']);
277
+ const basePath = path.resolve(sourceDir);
278
+ const files = walk(sourceDir, ['.js', '.jsx', '.ts', '.tsx', '.vue', '.svelte'], [], basePath);
277
279
 
278
280
  for (const file of files) {
279
- const content = fs.readFileSync(file, 'utf8');
281
+ const content = SecurityUtils.safeReadFileSync(file, basePath, 'utf8');
280
282
  const lines = content.split(/\r?\n/);
281
283
  const keyPatterns = [
282
284
  /\b(?:t|tx|__|_t)\s*\(\s*['"`]([^'"`]+)['"`]/g,