i18ntk 4.6.1 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,25 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
 
7
+ ## [4.7.0] - 2026-07-07
8
+
9
+ ### Added
10
+
11
+ - **13 new FRAMEWORK_PATTERNS:** `i18ntk-runtime` (hooks, `<t>` component), `nuxt` (`$tc()`, `localePath()`), `lingui` (`<Translate>` component), `formatjs` (`<FormattedMessage>`), `ngx-translate` (pipe, service patterns), `next-intl`, `svelte-i18n`, `solid-i18n` (`<Translate>`), `fastapi`, `ruby-on-rails` (`I18n.t/l/translate`), `react-native-localize`, `ionic`. Each has framework-specific scanning regex patterns.
12
+ - **8 missing FRAMEWORK_COMPATIBILITY entries:** `nuxt`, `nuxt-i18n`, `next-intl`, `ngx-translate`, `svelte-i18n`, `solid-i18n`, `expo`, `vanilla` with min versions.
13
+ - **Non-Node project detection:** `detectFramework()` now checks Python (`requirements.txt`, `pyproject.toml`, `setup.py`), Rust (`Cargo.toml`), Go (`go.mod`), Ruby (`Gemfile`) when no `package.json` exists. Detects Django, Flask, FastAPI, Rails, and generic i18n usage.
14
+ - **20+ new WRAPPER_SKIP_PATTERNS:** `I18n.t(`, `I18n.translate(`, `useTranslate(`, `t.get(`, `$tc(`, `translateService.instant(`, `formatMessage(`, `bundle.get_message(`, `i18n.NewMessage(`, `_(`, `fluent!`, `ts!` and more.
15
+ - **Expanded KNOWN_WRAPPERS in Lens scanner:** Added `$_`, `$tc`, `gettext`, `gettext_lazy`, `I18n.t`, `I18n.translate`, `I18n.l`, `I18n.localize`.
16
+ - **Expanded NAMESPACE_HELPERS in both scanners:** Added `useTranslate`, `useSpeak`, `withTranslation`.
17
+ - **Framework-aware report-model.js:** `scanSourceFiles()` accepts optional framework parameter, uses `getFrameworkPatterns()` for framework-specific key extraction.
18
+ - **Expanded CLI init framework list:** `checkI18nDependencies()` now scans 27+ npm package names instead of 7.
19
+ - **Sandbox test suite:** 98 new tests covering framework detection against real package.json, requirements.txt, Cargo.toml, go.mod, Gemfile fixtures. Coverage validation for all FRAMEWORK_PATTERNS, FRAMEWORK_SUGGESTIONS, FRAMEWORK_COMPATIBILITY, WRAPPER_SKIP_PATTERNS, and SCANNER_EXTENSIONS.
20
+
21
+ ### Changed
22
+
23
+ - **Version bumped to 4.7.0** for the comprehensive framework detection expansion.
24
+ - **detectFramework()** no longer returns `null` when only non-Node project files are present. Cascades through 5 detection phases: Node → Python → Rust → Go → Ruby.
25
+
7
26
  ## [4.6.1] - 2026-07-05
8
27
 
9
28
  ### Fixed
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # i18ntk v4.6.1
1
+ # i18ntk v4.7.0
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.6.1)](https://socket.dev/npm/package/i18ntk/overview/4.6.1)
12
+ [![socket](https://socket.dev/api/badge/npm/package/i18ntk/4.7.0)](https://socket.dev/npm/package/i18ntk/overview/4.7.0)
13
13
 
14
14
  [![i18ntk Workbench](https://img.shields.io/badge/VS_Code-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-Lens-007ACC?logo=visualstudiocode&logoColor=white)](https://marketplace.visualstudio.com/items?itemName=VladNoskov.i18ntk-lens)
@@ -27,15 +27,15 @@ npm install -g i18ntk
27
27
  npx i18ntk --help
28
28
  ```
29
29
 
30
- ## What's New in 4.6.0
30
+ ## What's New in 4.7.0
31
31
 
32
- - **25+ framework detections** — Rust, Remix, Gatsby, Astro, Qwik, SolidJS, Ember, React Native, Expo, Ionic added. All drawn from a single centralized detector.
33
- - **Rust support** — Cargo.toml detection and `.rs` file scanning with fluent/gettext-rs framework patterns.
34
- - **File extensions** — `.astro`, `.mdx`, `.mjs`, `.mts`, `.cjs`, `.cts`, `.rs` scanned across all tools.
35
- - **ICU/Fluent placeholder support** — Fluent `$variable` and ICU `{var, plural, ...}` patterns detected.
36
- - **JSX component detection** — `<Trans>`, `<FormattedMessage>`, `<Translate>` components recognized.
37
- - **Health score fix** — No more negative or misleadingly low scores.
38
- - **Centralized architecture** Framework data, extensions, excludes, and patterns live in one module consumed by all tools.
32
+ - **30+ framework detection patterns** — 13 new FRAMEWORK_PATTERNS: `i18ntk-runtime`, `nuxt`, `lingui`, `formatjs`, `ngx-translate`, `next-intl`, `svelte-i18n`, `solid-i18n`, `fastapi`, `ruby-on-rails`, `react-native-localize`, `ionic`. Each with framework-specific scan regexes for translation calls, JSX components, template directives, and pipes.
33
+ - **Non-Node project detection** — Python (`requirements.txt`, `pyproject.toml`, `setup.py`), Rust (`Cargo.toml`), Go (`go.mod`), Ruby (`Gemfile`) now detected when no `package.json` exists. Detects Django, Flask, FastAPI, Rails, and generic i18n.
34
+ - **20+ new WRAPPER_SKIP_PATTERNS** — Covers I18n.t(), useTranslate(), translateService.instant(), formatMessage(), bundle.get_message(), fluent!, ts! and more.
35
+ - **Framework-aware report generation** — `report-model.js` accepts optional framework parameter and uses framework-specific patterns for key extraction.
36
+ - **Expanded namespace helpers** — `useTranslate` (Qwik), `useSpeak` (Qwik), `withTranslation` (react-i18next) added.
37
+ - **Attribute key detection** — `i18nKey=`, `t-key=`, `data-i18n=` attributes detected in source scanning.
38
+ - **All frame works now have FRAMEWORK_COMPATIBILITY entries and FRAMEWORK_SUGGESTIONS** for consistent tooling.
39
39
 
40
40
  [Full changelog →](./CHANGELOG.md)
41
41
 
@@ -138,13 +138,15 @@ class I18nInitializer {
138
138
  };
139
139
 
140
140
  const i18nFrameworks = [
141
- 'react-i18next',
142
- 'vue-i18n',
143
- 'angular-i18n',
144
- 'i18next',
145
- 'next-i18next',
146
- 'svelte-i18n',
147
- '@nuxtjs/i18n'
141
+ 'react-i18next', 'vue-i18n', 'i18next', 'next-i18next',
142
+ 'svelte-i18n', '@nuxtjs/i18n', 'next-intl', 'remix-i18next',
143
+ 'i18next-remix', 'gatsby-plugin-react-i18next', 'gatsby-plugin-intl',
144
+ 'sveltekit-i18n', 'astro-i18next', '@astrojs/i18n',
145
+ 'qwik-speak', 'qwik-i18n', '@solid-primitives/i18n',
146
+ 'ember-intl', 'react-native-localize', 'expo-localization',
147
+ '@ngx-translate/core', '@ionic/angular',
148
+ 'formatjs', '@lingui/core', 'i18ntk-runtime', 'i18ntk/runtime',
149
+ 'angular-i18n', '@angular/localize'
148
150
  ];
149
151
 
150
152
  const installedFrameworks = i18nFrameworks.filter(framework => dependencies[framework]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18ntk",
3
- "version": "4.6.1",
3
+ "version": "4.7.0",
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.6.1",
183
- "releaseDate": "07/04/2026",
184
- "lastUpdated": "07/05/2026",
182
+ "version": "4.7.0",
183
+ "releaseDate": "07/07/2026",
184
+ "lastUpdated": "07/07/2026",
185
185
  "maintainer": "Vlad Noskov",
186
186
  "changelog": "./CHANGELOG.md",
187
187
  "documentation": "./README.md",
@@ -196,10 +196,11 @@
196
196
  "FILE EXTENSIONS: Added .astro, .mdx, .mjs, .mts, .cjs, .cts, .rs to all file scan lists via centralized SOURCE_EXTENSIONS and SCANNER_EXTENSIONS.",
197
197
  "ICU/FLUENT PLACEHOLDER SUPPORT: Fluent $variable and ICU MessageFormat patterns added to placeholder detection and comparison.",
198
198
  "JSX COMPONENT DETECTION: <Trans>, <FormattedMessage>, <Translate> JSX components detected in both VS Code extensions.",
199
- "COMPREHENSIVE TESTING: All 384+ tests pass across CLI, Workbench, and Lens packages."
199
+ "COMPREHENSIVE TESTING: All 384+ tests pass across CLI, Workbench, and Lens packages.",
200
+ "FRAMEWORK DETECTION v2: Added 13 additional FRAMEWORK_PATTERNS (i18ntk-runtime, nuxt, formatjs, lingui, ngx-translate, next-intl, svelte-i18n, solid-i18n, fastapi, ruby-on-rails, react-native-localize, ionic). Expanded detectFramework() to check Python/Rust/Go/Ruby project files. Added 20+ WRAPPER_SKIP_PATTERNS. Added 8 missing FRAMEWORK_COMPATIBILITY versions. Now supports 30+ frameworks across 15+ languages."
200
201
  ],
201
202
  "breakingChanges": [],
202
- "nextVersion": "4.6.2",
203
+ "nextVersion": "4.7.1",
203
204
  "supportedNodeVersions": ">=16.0.0",
204
205
  "supportedFrameworks": {
205
206
  "react-i18next": ">=11.0.0",
@@ -228,18 +229,18 @@
228
229
  "fluent-rs": ">=0.16.0",
229
230
  "gettext-rs": ">=0.7.0"
230
231
  },
231
- "supportPolicy": "Versions earlier than 4.6.1 may be unstable or insecure in CI automation. Upgrade to 4.6.1 or newer.",
232
+ "supportPolicy": "Versions earlier than 4.7.0 may be unstable or insecure in CI automation. Upgrade to 4.7.0 or newer.",
232
233
  "deprecations": [
233
234
  "4.3.0",
234
235
  "4.3.1",
235
236
  "4.3.2",
236
237
  "4.3.3"
237
238
  ],
238
- "deprecationMessage": "i18ntk 4.3.x and earlier have known security vulnerabilities (path traversal, JSON DoS). Upgrade to i18ntk@4.6.1 or newer: npm install -g i18ntk@latest",
239
+ "deprecationMessage": "i18ntk 4.3.x and earlier have known security vulnerabilities (path traversal, JSON DoS). Upgrade to i18ntk@4.7.0 or newer: npm install -g i18ntk@latest",
239
240
  "securityAdvisories": [
240
241
  "GHSA-i18ntk-4.3.x-path-traversal: Backup command accepted arbitrary paths without validation (fixed in 4.4.1)",
241
242
  "GHSA-i18ntk-4.3.x-json-dos: Deeply nested JSON files could cause denial of service (fixed in 4.4.1)"
242
243
  ]
243
244
  },
244
- "readme": "# i18ntk v4.6.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.6.1)](https://socket.dev/npm/package/i18ntk/overview/4.6.1)\n\n[![i18ntk Workbench](https://img.shields.io/badge/VS_Code-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-Lens-007ACC?logo=visualstudiocode&logoColor=white)](https://marketplace.visualstudio.com/items?itemName=VladNoskov.i18ntk-lens)\n\n## Ecosystem\n\n- **i18ntk** — CLI toolkit and runtime (this package)\n- **i18ntk Workbench** — VS Code dashboard, reports, and key management\n- **i18ntk Lens** — inline hovers, CodeLens, and diagnostics\n\n## Install\n\n```bash\nnpm install -g i18ntk\nnpx i18ntk --help\n```\n\n## What's New in 4.6.0\n\n- **25+ framework detections** — Rust, Remix, Gatsby, Astro, Qwik, SolidJS, Ember, React Native, Expo, Ionic added. All drawn from a single centralized detector.\n- **Rust support** — Cargo.toml detection and `.rs` file scanning with fluent/gettext-rs framework patterns.\n- **File extensions** — `.astro`, `.mdx`, `.mjs`, `.mts`, `.cjs`, `.cts`, `.rs` scanned across all tools.\n- **ICU/Fluent placeholder support** — Fluent `$variable` and ICU `{var, plural, ...}` patterns detected.\n- **JSX component detection** — `<Trans>`, `<FormattedMessage>`, `<Translate>` components recognized.\n- **Health score fix** — No more negative or misleadingly low scores.\n- **Centralized architecture** — Framework data, extensions, excludes, and patterns live in one module consumed by all tools.\n\n[Full changelog →](./CHANGELOG.md)\n\n## Quick Start\n\n```bash\ni18ntk # interactive menu\ni18ntk --command=analyze # coverage report\ni18ntk --command=validate # quality checks\ni18ntk --command=usage # key usage tracking\ni18ntk report --json --out ./reports # full report\ni18ntk --command=complete # fill missing keys\ni18ntk --command=translate # auto-translate\ni18ntk --command=summary # status overview\n```\n\nSee [docs/getting-started.md](./docs/getting-started.md) for the full onboarding guide.\n\n## Command Reference\n\n| Command | Purpose | Output |\n| ----------- | ------------------------------------------ | ----------------------------- |\n| `i18ntk` | Interactive management menu | — |\n| `init` | Setup locale folders and target files | Locale JSON, `.i18ntk-config` |\n| `analyze` | Translation coverage comparison | Reports |\n| `validate` | Structure, quality, and risk validation | Summary report |\n| `usage` | Map keys to source, find dead/missing keys | Usage report |\n| `report` | Stable schema report (JSON/MD/HTML) | stdout or file output |\n| `scanner` | Detect hardcoded text in source files | Scanner report |\n| `complete` | Fill missing keys in target files | Target locale JSON |\n| `translate` | Auto-translate via provider AI | Target locale JSON |\n| `sizing` | Expansion risk and layout analysis | Sizing report |\n| `summary` | Project translation status overview | Console output |\n| `fixer` | Fix placeholders and markers | Locale JSON |\n| `backup` | Create/verify/restore locale backups | Backup archives |\n\nEach is available as `i18ntk --command=<name>` or standalone `i18ntk-<name>`.\n\n## Common Options\n\n```\n--code-dir <path> Source code directory\n--locales-dir <path> Locale files directory\n--output-dir <path> Report output directory\n--source-locale <code> Source language code (e.g. en)\n--framework <name> Override framework detection\n--no-prompt Skip interactive prompts\n--help Show help\n```\n\n## Auto Translate\n\n```bash\ni18ntk-translate locales/en/common.json de\ni18ntk-translate locales/en/common.json fr --dry-run --preserve-placeholders\n```\n\n**Providers:** Google (default), DeepL, LibreTranslate\n\n```bash\nexport DEEPL_API_KEY=\"your-key\"\ni18ntk-translate locales/en/common.json de --provider deepl --no-confirm\n```\n\n**Placeholder-aware translation** detects `{name}`, `{{count}}`, `%s`, `:id`, `${value}`, `$t(key)`, and ICU pattern syntax. The default mode is `--only-missing` — existing translations are preserved.\n\nProtected terms and keys via `i18ntk-auto-translate.json`:\n\n```json\n{\n \"version\": 1,\n \"terms\": [\"BrandName\", \"PRODUCT_CODE\"],\n \"keys\": [\"app.brandName\", \"product.*.symbol\"],\n \"values\": [\"BrandName Ltd\"],\n \"patterns\": [\"[A-Z]{2,}-\\\\d+\"]\n}\n```\n\n[Auto Translate guide →](./docs/auto-translate.md)\n\n## Configuration\n\nExample `.i18ntk-config`:\n\n```json\n{\n \"version\": \"4.6.1\",\n \"sourceDir\": \"./locales\",\n \"i18nDir\": \"./locales\",\n \"sourceLanguage\": \"en\",\n \"defaultLanguages\": [\"en\", \"de\", \"es\", \"fr\", \"ru\"],\n \"keyStyle\": \"dot.notation\",\n \"englishContentThresholdPercent\": 10,\n \"allowedEnglishTerms\": [\"BrandName\"],\n \"autoTranslate\": {\n \"placeholderMode\": \"preserve\",\n \"concurrency\": 12,\n \"onlyMissingOrEnglish\": true\n },\n \"extensions\": {\n \"workbench\": { \"localeDirectory\": \"./locales\", \"sourceLocale\": \"en\" },\n \"lens\": { \"localeDirectory\": \"./locales\", \"sourceLocale\": \"en\", \"keyFormats\": [\"dot\", \"snake\"] }\n }\n}\n```\n\n[Configuration reference →](./docs/api/CONFIGURATION.md)\n\n## Scanner\n\nDetects hardcoded text in 12+ languages with language-specific character ranges and stopword filtering. Framework-specific patterns for React, Vue, Angular, Svelte, Astro, Django, Flask, Python, Rust, Go, and more.\n\n```bash\ni18ntk-scanner --code-dir ./src --source-locale de\ni18ntk-scanner --code-dir ./src --source-locale ja --output-report\n```\n\n## Usage Analysis\n\nTracks key references, detects dead keys with confidence scores, resolves dynamic patterns (templates, arrays, object maps), and recommends namespace alignment.\n\n```bash\ni18ntk-usage --code-dir ./src --locales-dir ./locales --cleanup --dry-run-delete\n```\n\n## Runtime\n\n```js\nconst runtime = require('i18ntk/runtime');\nconst i18n = runtime.initRuntime({\n baseDir: './locales',\n language: 'en',\n fallbackLanguage: 'en',\n});\n\nconsole.log(i18n.t('common.hello'));\ni18n.setLanguage('fr');\nconsole.log(i18n.getAvailableLanguages());\n```\n\n**Lazy loading** reduces memory on large locale folders:\n\n```js\nconst i18n = runtime.initRuntime({ baseDir: './locales', language: 'en', lazy: true });\n```\n\n**Per-call language overrides:**\n\n```js\ni18n.t('common.hello', {}, { language: 'de' });\n```\n\n**Batch translation:**\n\n```js\ni18n.translateBatch(['menu.home', 'menu.settings']);\n```\n\nProduction guidance:\n\n- Use the instance from `initRuntime()` — not module-level `runtime.t()` — in multi-tenant apps\n- Use `lazy: true` for large folders; `preload: true` for small sets\n- Call `refresh(language)` after deploying changed locale files\n- `i18ntk/runtime/enhanced` remains available for async/encryption compatibility\n\n[Runtime guide →](./docs/runtime.md)\n\n## Watch\n\n```js\nconst watchLocales = require('i18ntk/utils/watch-locales');\nconst watcher = watchLocales('./locales');\n\nwatcher.on('change', (filePath) => console.log('changed:', filePath));\nwatcher.on('add', (filePath) => console.log('added:', filePath));\nwatcher.stop();\n```\n\nFeatures: 300ms debounce, SHA-256 hash tracking, 50-directory cap. The callback form `watchLocales('./locales', onChange)` is still supported.\n\n## Documentation\n\n- [Getting Started](./docs/getting-started.md)\n- [Configuration](./docs/api/CONFIGURATION.md)\n- [API Reference](./docs/api/API_REFERENCE.md)\n- [Runtime API](./docs/runtime.md)\n- [Auto Translate](./docs/auto-translate.md)\n- [Scanner Guide](./docs/scanner-guide.md)\n- [Environment Variables](./docs/environment-variables.md)\n\n## Security\n\n- No API key required for default Auto Translate\n- Do not store secrets in locale files, `.i18ntk-config`, or protection files\n- Report issues via [SECURITY.md](./SECURITY.md)\n\n## Related\n\n| Tool | Purpose |\n| ---------------- | ------------------------------------------- |\n| i18ntk Workbench | VS Code localization health dashboard |\n| i18ntk Lens | Inline hovers, CodeLens, and diagnostics |\n| PublishGuard | Pre-publish safety scanner for npm packages |\n| ContextKit | AI coding context manager |\n\n## License\n\nSee [LICENSE](./LICENSE).\n"
245
+ "readme": "# i18ntk v4.7.0\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.7.0)](https://socket.dev/npm/package/i18ntk/overview/4.7.0)\n\n[![i18ntk Workbench](https://img.shields.io/badge/VS_Code-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-Lens-007ACC?logo=visualstudiocode&logoColor=white)](https://marketplace.visualstudio.com/items?itemName=VladNoskov.i18ntk-lens)\n\n## Ecosystem\n\n- **i18ntk** — CLI toolkit and runtime (this package)\n- **i18ntk Workbench** — VS Code dashboard, reports, and key management\n- **i18ntk Lens** — inline hovers, CodeLens, and diagnostics\n\n## Install\n\n```bash\nnpm install -g i18ntk\nnpx i18ntk --help\n```\n\n## What's New in 4.7.0\n\n- **30+ framework detection patterns** — 13 new FRAMEWORK_PATTERNS: `i18ntk-runtime`, `nuxt`, `lingui`, `formatjs`, `ngx-translate`, `next-intl`, `svelte-i18n`, `solid-i18n`, `fastapi`, `ruby-on-rails`, `react-native-localize`, `ionic`. Each with framework-specific scan regexes for translation calls, JSX components, template directives, and pipes.\n- **Non-Node project detection** — Python (`requirements.txt`, `pyproject.toml`, `setup.py`), Rust (`Cargo.toml`), Go (`go.mod`), Ruby (`Gemfile`) now detected when no `package.json` exists. Detects Django, Flask, FastAPI, Rails, and generic i18n.\n- **20+ new WRAPPER_SKIP_PATTERNS** — Covers I18n.t(), useTranslate(), translateService.instant(), formatMessage(), bundle.get_message(), fluent!, ts! and more.\n- **Framework-aware report generation** — `report-model.js` accepts optional framework parameter and uses framework-specific patterns for key extraction.\n- **Expanded namespace helpers** — `useTranslate` (Qwik), `useSpeak` (Qwik), `withTranslation` (react-i18next) added.\n- **Attribute key detection** — `i18nKey=`, `t-key=`, `data-i18n=` attributes detected in source scanning.\n- **All frame works now have FRAMEWORK_COMPATIBILITY entries and FRAMEWORK_SUGGESTIONS** for consistent tooling.\n\n[Full changelog →](./CHANGELOG.md)\n\n## Quick Start\n\n```bash\ni18ntk # interactive menu\ni18ntk --command=analyze # coverage report\ni18ntk --command=validate # quality checks\ni18ntk --command=usage # key usage tracking\ni18ntk report --json --out ./reports # full report\ni18ntk --command=complete # fill missing keys\ni18ntk --command=translate # auto-translate\ni18ntk --command=summary # status overview\n```\n\nSee [docs/getting-started.md](./docs/getting-started.md) for the full onboarding guide.\n\n## Command Reference\n\n| Command | Purpose | Output |\n| ----------- | ------------------------------------------ | ----------------------------- |\n| `i18ntk` | Interactive management menu | — |\n| `init` | Setup locale folders and target files | Locale JSON, `.i18ntk-config` |\n| `analyze` | Translation coverage comparison | Reports |\n| `validate` | Structure, quality, and risk validation | Summary report |\n| `usage` | Map keys to source, find dead/missing keys | Usage report |\n| `report` | Stable schema report (JSON/MD/HTML) | stdout or file output |\n| `scanner` | Detect hardcoded text in source files | Scanner report |\n| `complete` | Fill missing keys in target files | Target locale JSON |\n| `translate` | Auto-translate via provider AI | Target locale JSON |\n| `sizing` | Expansion risk and layout analysis | Sizing report |\n| `summary` | Project translation status overview | Console output |\n| `fixer` | Fix placeholders and markers | Locale JSON |\n| `backup` | Create/verify/restore locale backups | Backup archives |\n\nEach is available as `i18ntk --command=<name>` or standalone `i18ntk-<name>`.\n\n## Common Options\n\n```\n--code-dir <path> Source code directory\n--locales-dir <path> Locale files directory\n--output-dir <path> Report output directory\n--source-locale <code> Source language code (e.g. en)\n--framework <name> Override framework detection\n--no-prompt Skip interactive prompts\n--help Show help\n```\n\n## Auto Translate\n\n```bash\ni18ntk-translate locales/en/common.json de\ni18ntk-translate locales/en/common.json fr --dry-run --preserve-placeholders\n```\n\n**Providers:** Google (default), DeepL, LibreTranslate\n\n```bash\nexport DEEPL_API_KEY=\"your-key\"\ni18ntk-translate locales/en/common.json de --provider deepl --no-confirm\n```\n\n**Placeholder-aware translation** detects `{name}`, `{{count}}`, `%s`, `:id`, `${value}`, `$t(key)`, and ICU pattern syntax. The default mode is `--only-missing` — existing translations are preserved.\n\nProtected terms and keys via `i18ntk-auto-translate.json`:\n\n```json\n{\n \"version\": 1,\n \"terms\": [\"BrandName\", \"PRODUCT_CODE\"],\n \"keys\": [\"app.brandName\", \"product.*.symbol\"],\n \"values\": [\"BrandName Ltd\"],\n \"patterns\": [\"[A-Z]{2,}-\\\\d+\"]\n}\n```\n\n[Auto Translate guide →](./docs/auto-translate.md)\n\n## Configuration\n\nExample `.i18ntk-config`:\n\n```json\n{\n \"version\": \"4.6.1\",\n \"sourceDir\": \"./locales\",\n \"i18nDir\": \"./locales\",\n \"sourceLanguage\": \"en\",\n \"defaultLanguages\": [\"en\", \"de\", \"es\", \"fr\", \"ru\"],\n \"keyStyle\": \"dot.notation\",\n \"englishContentThresholdPercent\": 10,\n \"allowedEnglishTerms\": [\"BrandName\"],\n \"autoTranslate\": {\n \"placeholderMode\": \"preserve\",\n \"concurrency\": 12,\n \"onlyMissingOrEnglish\": true\n },\n \"extensions\": {\n \"workbench\": { \"localeDirectory\": \"./locales\", \"sourceLocale\": \"en\" },\n \"lens\": { \"localeDirectory\": \"./locales\", \"sourceLocale\": \"en\", \"keyFormats\": [\"dot\", \"snake\"] }\n }\n}\n```\n\n[Configuration reference →](./docs/api/CONFIGURATION.md)\n\n## Scanner\n\nDetects hardcoded text in 12+ languages with language-specific character ranges and stopword filtering. Framework-specific patterns for React, Vue, Angular, Svelte, Astro, Django, Flask, Python, Rust, Go, and more.\n\n```bash\ni18ntk-scanner --code-dir ./src --source-locale de\ni18ntk-scanner --code-dir ./src --source-locale ja --output-report\n```\n\n## Usage Analysis\n\nTracks key references, detects dead keys with confidence scores, resolves dynamic patterns (templates, arrays, object maps), and recommends namespace alignment.\n\n```bash\ni18ntk-usage --code-dir ./src --locales-dir ./locales --cleanup --dry-run-delete\n```\n\n## Runtime\n\n```js\nconst runtime = require('i18ntk/runtime');\nconst i18n = runtime.initRuntime({\n baseDir: './locales',\n language: 'en',\n fallbackLanguage: 'en',\n});\n\nconsole.log(i18n.t('common.hello'));\ni18n.setLanguage('fr');\nconsole.log(i18n.getAvailableLanguages());\n```\n\n**Lazy loading** reduces memory on large locale folders:\n\n```js\nconst i18n = runtime.initRuntime({ baseDir: './locales', language: 'en', lazy: true });\n```\n\n**Per-call language overrides:**\n\n```js\ni18n.t('common.hello', {}, { language: 'de' });\n```\n\n**Batch translation:**\n\n```js\ni18n.translateBatch(['menu.home', 'menu.settings']);\n```\n\nProduction guidance:\n\n- Use the instance from `initRuntime()` — not module-level `runtime.t()` — in multi-tenant apps\n- Use `lazy: true` for large folders; `preload: true` for small sets\n- Call `refresh(language)` after deploying changed locale files\n- `i18ntk/runtime/enhanced` remains available for async/encryption compatibility\n\n[Runtime guide →](./docs/runtime.md)\n\n## Watch\n\n```js\nconst watchLocales = require('i18ntk/utils/watch-locales');\nconst watcher = watchLocales('./locales');\n\nwatcher.on('change', (filePath) => console.log('changed:', filePath));\nwatcher.on('add', (filePath) => console.log('added:', filePath));\nwatcher.stop();\n```\n\nFeatures: 300ms debounce, SHA-256 hash tracking, 50-directory cap. The callback form `watchLocales('./locales', onChange)` is still supported.\n\n## Documentation\n\n- [Getting Started](./docs/getting-started.md)\n- [Configuration](./docs/api/CONFIGURATION.md)\n- [API Reference](./docs/api/API_REFERENCE.md)\n- [Runtime API](./docs/runtime.md)\n- [Auto Translate](./docs/auto-translate.md)\n- [Scanner Guide](./docs/scanner-guide.md)\n- [Environment Variables](./docs/environment-variables.md)\n\n## Security\n\n- No API key required for default Auto Translate\n- Do not store secrets in locale files, `.i18ntk-config`, or protection files\n- Report issues via [SECURITY.md](./SECURITY.md)\n\n## Related\n\n| Tool | Purpose |\n| ---------------- | ------------------------------------------- |\n| i18ntk Workbench | VS Code localization health dashboard |\n| i18ntk Lens | Inline hovers, CodeLens, and diagnostics |\n| PublishGuard | Pre-publish safety scanner for npm packages |\n| ContextKit | AI coding context manager |\n\n## License\n\nSee [LICENSE](./LICENSE).\n"
245
246
  }
@@ -22,9 +22,17 @@ const FRAMEWORK_COMPATIBILITY = {
22
22
  'gatsby': { minVersion: '4.0.0' },
23
23
  'astro': { minVersion: '2.0.0' },
24
24
  'qwik': { minVersion: '1.0.0' },
25
+ 'nuxt': { minVersion: '7.0.0' },
26
+ 'nuxt-i18n': { minVersion: '7.0.0' },
27
+ 'next-intl': { minVersion: '2.0.0' },
28
+ 'ngx-translate': { minVersion: '13.0.0' },
29
+ 'svelte-i18n': { minVersion: '3.0.0' },
30
+ 'solid-i18n': { minVersion: '1.0.0' },
25
31
  'ember-intl': { minVersion: '5.0.0' },
26
32
  'react-native-localize': { minVersion: '2.0.0' },
27
- 'ionic': { minVersion: '6.0.0' }
33
+ 'ionic': { minVersion: '6.0.0' },
34
+ 'expo': { minVersion: '48.0.0' },
35
+ 'vanilla': { minVersion: '1.0.0' }
28
36
  };
29
37
 
30
38
  // Define framework detection in order of specificity
@@ -462,6 +470,13 @@ const SOURCE_DIRS = [
462
470
  ];
463
471
 
464
472
  const FRAMEWORK_PATTERNS = {
473
+ 'i18ntk-runtime': [
474
+ />([^<{][^<>{]*[^}>])</g,
475
+ /<button[^>]*>([^<]{2,99})<\/button>/g,
476
+ /i18n\.t\(["']([^"']{2,99})["']\)/g,
477
+ /useI18n\(\)\.t\(["']([^"']{2,99})["']\)/g,
478
+ /<t\s[\s\S]*?\bmessage\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
479
+ ],
465
480
  react: [
466
481
  /children:\s*["']([^"']{2,99})["']/g,
467
482
  /dangerouslySetInnerHTML={{\s*__html:\s*["']([^"']{2,99})["']/g,
@@ -476,6 +491,14 @@ const FRAMEWORK_PATTERNS = {
476
491
  /<button[^>]*>([^<]{2,99})<\/button>/g,
477
492
  /<(?:FormattedMessage|Trans)[\s\S]*?\b(?:id|defaultMessage|i18nKey)\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
478
493
  ],
494
+ nuxt: [
495
+ />([^<{][^<>{]*[^}>])</g,
496
+ /<button[^>]*>([^<]{2,99})<\/button>/g,
497
+ /\$t\(["']([^"']{2,99})["']\)/g,
498
+ /\$tc\(["']([^"']{2,99})["']\)/g,
499
+ /v-t=["']([^"']{2,99})["']/g,
500
+ /\blocalePath\(\s*\)/g
501
+ ],
479
502
  vue: [
480
503
  /v-text=["']([^"']{2,99})["']/g,
481
504
  /v-html=["']([^"']{2,99})["']/g,
@@ -562,6 +585,68 @@ const FRAMEWORK_PATTERNS = {
562
585
  /i18n\.NewMessage\([^,]+,\s*["']([^"']{2,99})["']\)/g,
563
586
  /t\.Get\([^,]+,\s*["']([^"']{2,99})["']\)/g
564
587
  ],
588
+ lingui: [
589
+ />([^<{][^<>{]*[^}>])</g,
590
+ /(?<![\w$.])t\s*\(\s*['"`]([^'"`]{2,99})['"`]/g,
591
+ /_\(\s*['"`]([^'"`]{2,99})['"`]/g,
592
+ /<Translate\s[\s\S]*?\bid\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
593
+ ],
594
+ formatjs: [
595
+ />([^<{][^<>{]*[^}>])</g,
596
+ /formatMessage\(\s*\{\s*id:\s*['"`]([^'"`]{2,99})['"`]/g,
597
+ /<FormattedMessage\s[\s\S]*?\bid\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
598
+ ],
599
+ 'ngx-translate': [
600
+ />([^<{][^<>{]*[^}>])</g,
601
+ /\|\s*translate\s*[^}]*\}\}/g,
602
+ /translateService\.instant\(\s*['"`]([^'"`]{2,99})['"`]/g,
603
+ /translateService\.get\(\s*['"`]([^'"`]{2,99})['"`]/g,
604
+ /translate\s*=\s*["'`]([^"'`]{2,99})["'`]/g,
605
+ /\[innerHTML\]=["']([^"']{2,99})["']/g
606
+ ],
607
+ 'next-intl': [
608
+ />([^<{][^<>{]*[^}>])</g,
609
+ /t\(\s*['"`]([^'"`]{2,99})['"`]/g,
610
+ /\buseTranslations\(\s*()\)/g
611
+ ],
612
+ 'svelte-i18n': [
613
+ />([^<{][^<>{]*[^}>])</g,
614
+ /\$_\(\s*['"`]([^'"`]{2,99})['"`]/g,
615
+ /\$_\w*\(\s*['"`]([^'"`]{2,99})['"`]/g,
616
+ /\bt\.get\(\s*['"`]([^'"`]{2,99})['"`]/g
617
+ ],
618
+ 'solid-i18n': [
619
+ />([^<{][^<>{]*[^}>])</g,
620
+ /\buseI18n\(\s*\)\s*\.\s*t\(\s*['"`]([^'"`]{2,99})['"`]/g,
621
+ /t\(\s*['"`]([^'"`]{2,99})['"`]/g,
622
+ /<Translate\s[\s\S]*?\bid\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
623
+ ],
624
+ fastapi: [
625
+ /_\(\s*['"`]([^'"`]{2,99})['"`]/g,
626
+ /i18n\.t\(\s*['"`]([^'"`]{2,99})['"`]/g,
627
+ /\{\{\s*_['"]([^'"]{2,99})['"]\s*\}\}/g,
628
+ /\{\{\s*gettext\(['"`]([^'"`]{2,99})['"`]\)\s*\}\}/g
629
+ ],
630
+ 'ruby-on-rails': [
631
+ /I18n\.t\(\s*['"]([^'"]{2,99})['"]/g,
632
+ /I18n\.translate\(\s*['"]([^'"]{2,99})['"]/g,
633
+ /I18n\.l\(\s*['"]([^'"]{2,99})['"]/g,
634
+ /<%= t\(['"]([^'"]{2,99})['"]/g,
635
+ /<%= I18n\.t\(['"]([^'"]{2,99})['"]/g,
636
+ /(?<![\w$.])t\s*\(\s*['"]([^'"]{2,99})['"]/g
637
+ ],
638
+ 'react-native-localize': [
639
+ />([^<{][^<>{]*[^}>])</g,
640
+ /\bi18n\.t\(\s*['"`]([^'"`]{2,99})['"`]/g,
641
+ /\bt\(\s*['"`]([^'"`]{2,99})['"`]/g,
642
+ /\btranslate\(\s*['"`]([^'"`]{2,99})['"`]/g
643
+ ],
644
+ ionic: [
645
+ />([^<{][^<>{]*[^}>])</g,
646
+ /\|\s*translate\s*[^}]*\}\}/g,
647
+ /translateService\.instant\(\s*['"`]([^'"`]{2,99})['"`]/g,
648
+ /translateService\.get\(\s*['"`]([^'"`]{2,99})['"`]/g
649
+ ],
565
650
  vanilla: [
566
651
  /t\(["']([^"']{2,99})["']\)/g,
567
652
  /i18n\.t\(["']([^"']{2,99})["']\)/g,
@@ -581,6 +666,18 @@ const FRAMEWORK_SUGGESTIONS = {
581
666
  solid: { hook: 'const [t] = useI18n();', usage: "{t('ui.KEY')}" },
582
667
  ember: { template: "{{t 'ui.KEY'}}", helper: "this.intl.t('ui.KEY')" },
583
668
  gatsby: { hook: 'const { t } = useTranslation();', usage: "{t('ui.KEY')}", plugin: "'gatsby-plugin-react-i18next'" },
669
+ nuxt: { directive: "{{ $t('ui.KEY') }}", method: "this.$t('ui.KEY')", component: "<NuxtLink :to=\"localePath('ui.KEY')\">" },
670
+ 'i18ntk-runtime': { hook: 'const { t } = useI18n();', usage: "{t('ui.KEY')}", component: "<t message=\"ui.KEY\">text</t>" },
671
+ lingui: { hook: 'import { t } from \"@lingui/macro\";', usage: "{t('ui.KEY')}", component: "<Trans id=\"ui.KEY\">text</Trans>" },
672
+ formatjs: { hook: 'import { FormattedMessage, useIntl } from \"react-intl\";', usage: "intl.formatMessage({ id: 'ui.KEY' })", component: "<FormattedMessage id=\"ui.KEY\" />" },
673
+ 'ngx-translate': { pipe: "{{ 'ui.KEY' | translate }}", service: "this.translateService.instant('ui.KEY')" },
674
+ 'next-intl': { hook: 'const t = useTranslations();', usage: "{t('ui.KEY')}" },
675
+ 'svelte-i18n': { store: "$_('ui.KEY')", method: "t.get('ui.KEY')" },
676
+ 'solid-i18n': { hook: 'const [t] = useI18n();', usage: "{t('ui.KEY')}", component: "<Translate id=\"ui.KEY\" />" },
677
+ fastapi: { python: "from fastapi_i18n import _\n_('text')", template: "{{ _('text') }}" },
678
+ 'ruby-on-rails': { helper: "t('ui.KEY')", method: "I18n.t('ui.KEY')", template: "<%= t('ui.KEY') %>" },
679
+ 'react-native-localize': { hook: 'import { t } from \"i18n-js\";', usage: "{t('ui.KEY')}" },
680
+ ionic: { pipe: "{{ 'ui.KEY' | translate }}", service: "this.translateService.instant('ui.KEY')" },
584
681
  django: { template: "{% trans 'text' %}", python: "from django.utils.translation import gettext as _\n_('text')", model: "from django.utils.translation import gettext_lazy as _\n_('text')" },
585
682
  flask: { template: "{{ _('text') }}", python: "from flask_babel import gettext as _\n_('text')", lazy: "from flask_babel import lazy_gettext as _\n_('text')" },
586
683
  python: { gettext: "import gettext\ngettext.gettext('text')", underscore: "from gettext import gettext as _\n_('text')", lazy: "from gettext import gettext_lazy as _\n_('text')" },
@@ -591,8 +688,13 @@ const FRAMEWORK_SUGGESTIONS = {
591
688
 
592
689
  const WRAPPER_SKIP_PATTERNS = [
593
690
  't(', 'tx(', 'i18n.t(', 'i18n.translate(', 'translate(',
594
- 'useI18n(', 'useTranslation(', '__', '__t', '$_(', '$t(',
595
- 'gettext(', 'gettext_lazy(', 'lazy_gettext(', 'pgettext(', 'ngettext('
691
+ 'useI18n(', 'useTranslation(', '__', '__t', '$_(', '$t(', '_(',
692
+ '$tc(', 'gettext(', 'gettext_lazy(', 'lazy_gettext(', 'pgettext(', 'ngettext(',
693
+ 'I18n.t(', 'I18n.translate(', 'I18n.l(', 'I18n.localize(',
694
+ 't.get(', 'useTranslate(', 'useSpeak(', 'withTranslation(',
695
+ 'formatMessage(', 'bundle.get_message(', 'i18n.NewMessage(',
696
+ 'i18n.Translate(', '_l(', '_n(', 't.set(', 'fluent!', 'ts!',
697
+ 'translateService.instant(', 'translateService.get('
596
698
  ];
597
699
 
598
700
  function _keySnippet(text) {
@@ -643,83 +745,168 @@ function detectFramework(projectRoot) {
643
745
  throw new Error('Invalid project root path');
644
746
  }
645
747
 
646
- const packageJsonPath = path.join(projectRoot, 'package.json');
647
748
  const detectedFrameworks = [];
648
749
 
649
- // Only proceed if package.json exists
650
- if (!SecurityUtils.safeExistsSync(packageJsonPath, projectRoot)) {
651
- return null;
750
+ // Phase 1: Check package.json for Node.js-based i18n frameworks
751
+ const packageJsonPath = path.join(projectRoot, 'package.json');
752
+ if (SecurityUtils.safeExistsSync(packageJsonPath, projectRoot)) {
753
+ try {
754
+ const packageJsonContent = SecurityUtils.safeReadFileSync(packageJsonPath, projectRoot, 'utf8');
755
+ if (packageJsonContent) {
756
+ const packageJson = SecurityUtils.safeParseJSON(packageJsonContent);
757
+ if (packageJson) {
758
+ const deps = {
759
+ ...(packageJson.dependencies || {}),
760
+ ...(packageJson.devDependencies || {}),
761
+ ...(packageJson.peerDependencies || {})
762
+ };
763
+
764
+ const sortedFrameworks = Object.entries(FRAMEWORKS).sort((a, b) =>
765
+ (b[1].priority || 0) - (a[1].priority || 0)
766
+ );
767
+
768
+ for (const [id, framework] of sortedFrameworks) {
769
+ try {
770
+ const frameworkDeps = framework.deps || framework.dependencies || [];
771
+ const hasAnyDep = frameworkDeps.some(dep => dep in deps);
772
+ if (hasAnyDep) {
773
+ const mainDep = frameworkDeps[0];
774
+ const frameworkInfo = {
775
+ id, name: framework.name || id, description: framework.description,
776
+ confidence: id === 'i18ntk-runtime' ? 0.95 : 0.9,
777
+ version: deps[mainDep] || '', priority: framework.priority || 0
778
+ };
779
+ detectedFrameworks.push(frameworkInfo);
780
+ }
781
+ } catch (_) { /* skip */ }
782
+ }
783
+ }
784
+ }
785
+ } catch (_) { /* skip */ }
652
786
  }
653
787
 
654
- try {
655
- // Read and parse package.json
656
- const packageJsonContent = SecurityUtils.safeReadFileSync(packageJsonPath, projectRoot, 'utf8');
657
- if (!packageJsonContent) {
658
- return null;
659
- }
660
- const packageJson = SecurityUtils.safeParseJSON(packageJsonContent);
661
- if (!packageJson) {
662
- return null;
788
+ // Phase 2: Check Python project files (no package.json or no Node matches)
789
+ if (detectedFrameworks.length === 0) {
790
+ const pyProjectFiles = ['requirements.txt', 'setup.py', 'setup.cfg', 'pyproject.toml', 'Pipfile', 'Pipfile.lock'];
791
+ const hasPyProject = pyProjectFiles.some(f => SecurityUtils.safeExistsSync(path.join(projectRoot, f), projectRoot));
792
+ if (hasPyProject) {
793
+ const pyDeps = readPyProjectDeps(projectRoot);
794
+ const pyFrameworks = ['django', 'flask', 'fastapi', 'python'];
795
+ for (const id of pyFrameworks) {
796
+ const framework = FRAMEWORKS[id];
797
+ if (!framework) continue;
798
+ const frameworkDeps = framework.deps || [];
799
+ const hasAnyDep = frameworkDeps.some(dep => pyDeps.includes(dep.toLowerCase().replace(/[_-]/g, '')));
800
+ if (hasAnyDep) {
801
+ detectedFrameworks.push({
802
+ id, name: framework.name || id, description: framework.description,
803
+ confidence: 0.8, version: '', priority: framework.priority || 0
804
+ });
805
+ }
806
+ }
807
+ if (detectedFrameworks.length === 0 && hasPyProject) {
808
+ detectedFrameworks.push({
809
+ id: 'python', name: 'Python', description: 'Python project with gettext/i18n',
810
+ confidence: 0.5, version: '', priority: 0
811
+ });
812
+ }
663
813
  }
814
+ }
664
815
 
665
- const deps = {
666
- ...(packageJson.dependencies || {}),
667
- ...(packageJson.devDependencies || {}),
668
- ...(packageJson.peerDependencies || {})
669
- };
670
-
671
- // Sort frameworks by priority (highest first)
672
- const sortedFrameworks = Object.entries(FRAMEWORKS).sort((a, b) =>
673
- (b[1].priority || 0) - (a[1].priority || 0)
674
- );
675
-
676
- // Check each framework's dependencies
677
- for (const [id, framework] of sortedFrameworks) {
816
+ // Phase 3: Check Rust project (Cargo.toml)
817
+ if (detectedFrameworks.length === 0) {
818
+ const cargoPath = path.join(projectRoot, 'Cargo.toml');
819
+ if (SecurityUtils.safeExistsSync(cargoPath, projectRoot)) {
678
820
  try {
679
- const frameworkDeps = framework.deps || framework.dependencies || [];
680
- const hasAnyDep = frameworkDeps.some(dep => dep in deps);
681
-
682
- if (hasAnyDep) {
683
- const mainDep = frameworkDeps[0];
684
- const frameworkInfo = {
685
- id,
686
- name: framework.name || id,
687
- description: framework.description,
688
- confidence: 0.9, // Base confidence when dependencies are found
689
- version: deps[mainDep] || '',
690
- priority: framework.priority || 0
691
- };
692
-
693
- // Boost confidence for i18ntk-runtime
694
- if (id === 'i18ntk-runtime') {
695
- frameworkInfo.confidence = 0.95;
821
+ const cargoContent = SecurityUtils.safeReadFileSync(cargoPath, projectRoot, 'utf8');
822
+ if (cargoContent) {
823
+ const hasRustI18n = /fluent|rust-i18n|gettext/.test(cargoContent);
824
+ if (hasRustI18n || /^\[package\]/m.test(cargoContent)) {
825
+ detectedFrameworks.push({
826
+ id: 'rust', name: 'Rust', description: 'Rust project with i18n',
827
+ confidence: hasRustI18n ? 0.85 : 0.4, version: '', priority: 0
828
+ });
696
829
  }
697
-
698
- detectedFrameworks.push(frameworkInfo);
699
830
  }
700
- } catch (error) {
701
- console.warn(`Error checking framework ${id}:`, error.message);
702
- continue;
703
- }
831
+ } catch (_) { /* skip */ }
704
832
  }
833
+ }
705
834
 
706
- // Return the framework with highest confidence, if any
707
- if (detectedFrameworks.length > 0) {
708
- return detectedFrameworks.sort((a, b) => {
709
- // First sort by confidence
710
- const confidenceDiff = b.confidence - a.confidence;
711
- if (confidenceDiff !== 0) return confidenceDiff;
835
+ // Phase 4: Check Go project (go.mod)
836
+ if (detectedFrameworks.length === 0) {
837
+ const goModPath = path.join(projectRoot, 'go.mod');
838
+ if (SecurityUtils.safeExistsSync(goModPath, projectRoot)) {
839
+ try {
840
+ const goModContent = SecurityUtils.safeReadFileSync(goModPath, projectRoot, 'utf8');
841
+ if (goModContent) {
842
+ const hasGoI18n = /go-i18n|x-text|i18n/.test(goModContent);
843
+ detectedFrameworks.push({
844
+ id: 'go', name: 'Go', description: 'Go project with i18n',
845
+ confidence: hasGoI18n ? 0.85 : 0.4, version: '', priority: 0
846
+ });
847
+ }
848
+ } catch (_) { /* skip */ }
849
+ }
850
+ }
712
851
 
713
- // If confidence is equal, sort by priority
714
- return (b.priority || 0) - (a.priority || 0);
715
- })[0];
852
+ // Phase 5: Check Ruby project (Gemfile)
853
+ if (detectedFrameworks.length === 0) {
854
+ const gemfilePaths = ['Gemfile', 'gems.rb'];
855
+ for (const gf of gemfilePaths) {
856
+ const gemfilePath = path.join(projectRoot, gf);
857
+ if (SecurityUtils.safeExistsSync(gemfilePath, projectRoot)) {
858
+ try {
859
+ const gemfileContent = SecurityUtils.safeReadFileSync(gemfilePath, projectRoot, 'utf8');
860
+ if (gemfileContent) {
861
+ const hasRails = /rails/.test(gemfileContent);
862
+ const hasI18n = /i18n/.test(gemfileContent);
863
+ if (hasRails) {
864
+ detectedFrameworks.push({
865
+ id: 'ruby-on-rails', name: 'Ruby on Rails', description: 'Rails project with i18n',
866
+ confidence: hasI18n ? 0.9 : 0.7, version: '', priority: 0
867
+ });
868
+ break;
869
+ }
870
+ if (hasI18n) {
871
+ detectedFrameworks.push({
872
+ id: 'ruby-on-rails', name: 'Ruby on Rails', description: 'Ruby project with i18n',
873
+ confidence: 0.6, version: '', priority: 0
874
+ });
875
+ break;
876
+ }
877
+ }
878
+ } catch (_) { /* skip */ }
879
+ }
716
880
  }
881
+ }
717
882
 
718
- return null;
719
- } catch (error) {
720
- console.error('Error detecting framework:', error);
721
- return null;
883
+ // Return the framework with highest confidence, if any
884
+ if (detectedFrameworks.length > 0) {
885
+ return detectedFrameworks.sort((a, b) => {
886
+ const confidenceDiff = b.confidence - a.confidence;
887
+ if (confidenceDiff !== 0) return confidenceDiff;
888
+ return (b.priority || 0) - (a.priority || 0);
889
+ })[0];
722
890
  }
891
+
892
+ return null;
893
+ }
894
+
895
+ function readPyProjectDeps(projectRoot) {
896
+ const deps = [];
897
+ try {
898
+ const reqPath = path.join(projectRoot, 'requirements.txt');
899
+ if (SecurityUtils.safeExistsSync(reqPath, projectRoot)) {
900
+ const content = SecurityUtils.safeReadFileSync(reqPath, projectRoot, 'utf8');
901
+ if (content) {
902
+ for (const line of content.split('\n')) {
903
+ const stripped = line.replace(/[<>=!~].*$/, '').trim();
904
+ if (stripped && !stripped.startsWith('#')) deps.push(stripped.toLowerCase().replace(/[_-]/g, ''));
905
+ }
906
+ }
907
+ }
908
+ } catch (_) { /* skip */ }
909
+ return deps;
723
910
  }
724
911
 
725
912
  module.exports = { detectFramework, FRAMEWORKS, FRAMEWORK_COMPATIBILITY,
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
- const { getSourceExtensions, getExcludeDirs } = require('./framework-detector');
5
+ const { getSourceExtensions, getExcludeDirs, getFrameworkPatterns } = require('./framework-detector');
6
6
  const SecurityUtils = require('./security');
7
7
 
8
8
  const EXCLUDE_DIRS = new Set(getExcludeDirs());
@@ -83,7 +83,8 @@ function generateI18ntkReport(options = {}) {
83
83
  Object.keys(values).forEach(key => allKeys.add(key));
84
84
  }
85
85
 
86
- const sourceScan = scanSourceFiles(sourceDir, new Set(Object.keys(sourceValues)));
86
+ const framework = options.framework || 'vanilla';
87
+ const sourceScan = scanSourceFiles(sourceDir, new Set(Object.keys(sourceValues)), framework);
87
88
  const usedKeys = sourceScan.usedKeys;
88
89
  const issues = [];
89
90
  const addIssue = issue => issues.push(normalizeIssue(issue, issues.length + 1, projectRoot));
@@ -288,7 +289,7 @@ function flattenObject(value, prefix = '', out = {}) {
288
289
  return out;
289
290
  }
290
291
 
291
- function scanSourceFiles(sourceDir, availableKeys) {
292
+ function scanSourceFiles(sourceDir, availableKeys, framework) {
292
293
  const usedKeys = new Set();
293
294
  const usageLocations = new Map();
294
295
  const hardcodedTexts = [];
@@ -299,15 +300,25 @@ function scanSourceFiles(sourceDir, availableKeys) {
299
300
  for (const file of files) {
300
301
  const content = SecurityUtils.safeReadFileSync(file, basePath, 'utf8');
301
302
  const lines = content.split(/\r?\n/);
303
+ const frameworkPatterns = framework ? getFrameworkPatterns(framework) : [];
302
304
  const keyPatterns = [
303
305
  /\b(?:t|tx|__|_t)\s*\(\s*['"`]([^'"`]+)['"`]/g,
304
306
  /\bi18n\.t\s*\(\s*['"`]([^'"`]+)['"`]/g,
305
307
  /\bi18nKey\s*=\s*['"`]([^'"`]+)['"`]/g,
308
+ /\$t\(\s*['"`]([^'"`]+)['"`]/g,
309
+ /\|\s*translate/g,
310
+ /\{\{\s*t\s+['"`]([^'"`]+)['"`]/g,
311
+ /\b(I18n|i18n)\.(t|translate|localize|l)\s*\(\s*['"`]([^'"`]+)['"`]/g,
312
+ /\{\%\s*trans\s+['"`]([^'"`]+)['"`]\s*%\}/g,
313
+ /gettext\(['"`]([^'"`]+)['"`]\)/g,
314
+ /_\(['"`]([^'"`]+)['"`]\)/g,
315
+ /\$_\(\s*['"`]([^'"`]+)['"`]/g
306
316
  ];
307
- for (const pattern of keyPatterns) {
317
+ for (const pattern of [...keyPatterns, ...frameworkPatterns]) {
308
318
  let match;
309
319
  while ((match = pattern.exec(content))) {
310
- const key = match[1];
320
+ const key = match[1] || match[2];
321
+ if (!key) continue;
311
322
  usedKeys.add(key);
312
323
  const location = offsetToLocation(content, match.index);
313
324
  const list = usageLocations.get(key) || [];