ai-localize-cli 2.0.4 → 2.0.6
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 +51 -0
- package/README.md +537 -0
- package/dist/cli.js +57 -28
- package/package.json +10 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# ai-localize-cli
|
|
2
2
|
|
|
3
|
+
## 2.0.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Add per-package README.md files so each package displays documentation on npmjs.com
|
|
8
|
+
- Update README version badge to 2.0.6
|
|
9
|
+
|
|
10
|
+
## 2.0.5
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- **Bug fix — `validate` always reported "valid"**: Downstream fix in `ai-localize-validators`.
|
|
15
|
+
The `MissingKeyValidator` now catches keys where the target language value equals the English
|
|
16
|
+
source placeholder (seeded by the extractor). Validation correctly reports
|
|
17
|
+
`Missing translation for "key" in "fr" (value equals source)` instead of silently passing.
|
|
18
|
+
|
|
19
|
+
- **Bug fix — `full-migrate` pipeline appeared to do nothing**: Three issues fixed:
|
|
20
|
+
1. `staticKeys` from `ai-localize.config.json` are now correctly passed to `LocaleExtractor`
|
|
21
|
+
in the `full-migrate` command (the standalone `extract` command already did this).
|
|
22
|
+
2. `--dry-run` mode now prints a clear yellow banner, lists every locale file that *would*
|
|
23
|
+
be written, and skips validation/report with an explanatory message — previously it
|
|
24
|
+
produced no output for the write step, making the pipeline look inactive.
|
|
25
|
+
3. Validation and report phases now run only in non-dry-run mode; error/warning details
|
|
26
|
+
are printed inline after validation (matching the standalone `validate` command).
|
|
27
|
+
4. `LocaleWriter` write counts (`X new, Y merged`) are now logged after every live write.
|
|
28
|
+
|
|
29
|
+
- **Bug fix — `report` CLI summary crashed with `RangeError`**: Downstream fix in
|
|
30
|
+
`ai-localize-reporting`; coverage percentage is now clamped to `[0, 100]` and all bar
|
|
31
|
+
renderers guard against negative `String.repeat()` calls.
|
|
32
|
+
|
|
33
|
+
### Dependency Updates
|
|
34
|
+
|
|
35
|
+
- ai-localize-validators@2.0.5
|
|
36
|
+
- ai-localize-reporting@2.0.5
|
|
37
|
+
- ai-localize-locale-engine@2.0.5
|
|
38
|
+
|
|
39
|
+
## 2.0.4
|
|
40
|
+
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- Updated dependencies
|
|
44
|
+
- ai-localize-scanner@2.0.4
|
|
45
|
+
- ai-localize-config@2.0.4
|
|
46
|
+
- ai-localize-shared@2.0.4
|
|
47
|
+
- ai-localize-aws-cloudfront@2.0.4
|
|
48
|
+
- ai-localize-codemods@2.0.4
|
|
49
|
+
- ai-localize-framework-detectors@2.0.4
|
|
50
|
+
- ai-localize-locale-engine@2.0.4
|
|
51
|
+
- ai-localize-reporting@2.0.4
|
|
52
|
+
- ai-localize-validators@2.0.4
|
|
53
|
+
|
|
3
54
|
## 2.0.3
|
|
4
55
|
|
|
5
56
|
### Minor Changes
|
package/README.md
ADDED
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
# ai-localize-core
|
|
2
|
+
|
|
3
|
+
> Enterprise-grade deterministic localization + CloudFront CDN migration platform for React, Vue, and Angular.
|
|
4
|
+
|
|
5
|
+
**No AI. No cloud API. Fully offline. Reproducible outputs.**
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/ai-localize-cli)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](https://nodejs.org)
|
|
10
|
+
[](CHANGELOG.md)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## What's New in v2.0.5
|
|
15
|
+
|
|
16
|
+
| Fix | Details |
|
|
17
|
+
|---|---|
|
|
18
|
+
| **`validate` always returned "valid"** | `MissingKeyValidator` now correctly detects keys where the target language value equals the English source placeholder. These are seeded automatically by `extract` and must be replaced with real translations. Error message: `Missing translation for "key" in "fr" (value equals source)` |
|
|
19
|
+
| **`full-migrate` appeared to only generate a report** | `staticKeys` from config is now wired through to the extractor. `--dry-run` now shows a full preview. Validation and report are correctly skipped in dry-run mode. |
|
|
20
|
+
| **`report` crashed with `RangeError: Invalid count value`** | Coverage % went negative when all keys were untranslated (missingKeys counted per-language). Now clamped to `[0, 100]`. All bar renderers guard against negative `String.repeat()`. |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## What it does
|
|
25
|
+
|
|
26
|
+
`ai-localize-core` automates the full localization lifecycle using static AST analysis:
|
|
27
|
+
|
|
28
|
+
1. **Scan** — finds every hardcoded UI string in your source code
|
|
29
|
+
2. **Extract** — generates deterministic locale JSON files (`en.json`, `fr.json`, …)
|
|
30
|
+
3. **Codemod** — injects `useTranslation()` and wraps strings with `t('key')` automatically
|
|
31
|
+
4. **Validate** — catches missing, unused, duplicate, and placeholder-mismatch keys
|
|
32
|
+
5. **Report** — produces a comprehensive HTML report with legends and full details
|
|
33
|
+
6. **CDN Migrate** — uploads assets to S3, replaces legacy CDN URLs with CloudFront paths
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Supported Frameworks
|
|
38
|
+
|
|
39
|
+
| Framework | Detection | Codemods | Default i18n Library |
|
|
40
|
+
|---|---|---|---|
|
|
41
|
+
| React (CRA, Vite, Next.js) | ✅ | ✅ | `react-i18next` |
|
|
42
|
+
| Angular (ngx-translate, i18n) | ✅ | ✅ | `@ngx-translate/core` |
|
|
43
|
+
| Vue 3 (vue-i18n) | ✅ | ✅ | `vue-i18n` |
|
|
44
|
+
| jQuery / Vanilla JS | ✅ | — | — |
|
|
45
|
+
| JSP | ✅ | — | — |
|
|
46
|
+
| Custom hook (any architecture) | ✅ | ✅ | configurable |
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Global install (gives you the `ai-localize` command everywhere)
|
|
54
|
+
npm install -g ai-localize-cli
|
|
55
|
+
|
|
56
|
+
# Or as a project dev dependency
|
|
57
|
+
npm install --save-dev ai-localize-cli
|
|
58
|
+
|
|
59
|
+
# Or use without installing
|
|
60
|
+
npx ai-localize-cli <command>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Quick Start
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# 1. Initialize — creates ai-localize.config.json with all options
|
|
69
|
+
cd your-project
|
|
70
|
+
npx ai-localize init
|
|
71
|
+
|
|
72
|
+
# 2. Scan for hardcoded text
|
|
73
|
+
npx ai-localize scan
|
|
74
|
+
|
|
75
|
+
# 3. Extract locale files
|
|
76
|
+
npx ai-localize extract
|
|
77
|
+
# → locales/en/translation.json, locales/fr/translation.json …
|
|
78
|
+
|
|
79
|
+
# 4. Preview codemods (dry run first!)
|
|
80
|
+
npx ai-localize full-migrate --dry-run
|
|
81
|
+
|
|
82
|
+
# 5. Apply codemods
|
|
83
|
+
npx ai-localize full-migrate
|
|
84
|
+
|
|
85
|
+
# 6. Validate locale files
|
|
86
|
+
npx ai-localize validate
|
|
87
|
+
|
|
88
|
+
# 7. Generate HTML report
|
|
89
|
+
npx ai-localize report
|
|
90
|
+
# → .ai-localize-reports/report-2026-05-26T12-00-00.html
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## CLI Commands
|
|
96
|
+
|
|
97
|
+
| Command | Description |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `ai-localize init` | Initialize config with auto-detected framework + all fields |
|
|
100
|
+
| `ai-localize scan` | Scan source for hardcoded text + asset references |
|
|
101
|
+
| `ai-localize extract` | Generate locale JSON files |
|
|
102
|
+
| `ai-localize validate` | Check for missing / unused / duplicate / placeholder-mismatch keys. Also flags keys whose target value still equals the English source (un-translated placeholders seeded by `extract`). |
|
|
103
|
+
| `ai-localize cleanup` | Remove unused locale keys |
|
|
104
|
+
| `ai-localize report` | Generate a comprehensive HTML report |
|
|
105
|
+
| `ai-localize full-migrate` | Run the full pipeline: scan → extract → codemod → validate → report |
|
|
106
|
+
| `ai-localize migrate-cdn` | Upload assets to S3 + replace legacy CDN URLs + invalidate CloudFront |
|
|
107
|
+
| `ai-localize upload-assets` | Upload static assets to S3 |
|
|
108
|
+
| `ai-localize replace-cdn` | Replace legacy CDN URLs using an existing asset manifest |
|
|
109
|
+
|
|
110
|
+
### Common options (all commands)
|
|
111
|
+
|
|
112
|
+
| Option | Description |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `--cwd <path>` | Working directory (default: `process.cwd()`) |
|
|
115
|
+
| `--dry-run` | Preview changes without writing files |
|
|
116
|
+
|
|
117
|
+
### `scan` options
|
|
118
|
+
|
|
119
|
+
| Option | Description |
|
|
120
|
+
|---|---|
|
|
121
|
+
| `--incremental` | Only scan git-changed files |
|
|
122
|
+
| `--staged` | Only scan git-staged files |
|
|
123
|
+
| `--output <path>` | Save full scan results as JSON |
|
|
124
|
+
| `--extract-cdns <path>` | Save discovered CDN/legacy URLs to JSON |
|
|
125
|
+
|
|
126
|
+
### `report` options
|
|
127
|
+
|
|
128
|
+
| Option | Description |
|
|
129
|
+
|---|---|
|
|
130
|
+
| `--output-dir <path>` | Report directory (default: `.ai-localize-reports`) |
|
|
131
|
+
| `--filename <name>` | Custom filename (default: `report-<timestamp>.html`) |
|
|
132
|
+
|
|
133
|
+
### `full-migrate` options
|
|
134
|
+
|
|
135
|
+
| Option | Description |
|
|
136
|
+
|---|---|
|
|
137
|
+
| `--no-codemods` | Skip codemod injection phase |
|
|
138
|
+
| `--no-report` | Skip HTML report generation |
|
|
139
|
+
| `--report-dir <path>` | Report directory (default: `.ai-localize-reports`) |
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Configuration
|
|
144
|
+
|
|
145
|
+
Running `npx ai-localize init` creates `ai-localize.config.json` with every field included:
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"framework": "react-vite",
|
|
150
|
+
"defaultLanguage": "en",
|
|
151
|
+
"targetLanguages": ["fr", "de"],
|
|
152
|
+
"sourceDir": "src",
|
|
153
|
+
"localesDir": "locales",
|
|
154
|
+
"localeStructure": "nested",
|
|
155
|
+
"ignorePatterns": ["node_modules", "dist", ".git", "coverage"],
|
|
156
|
+
"includePatterns": [],
|
|
157
|
+
"keyPrefix": "",
|
|
158
|
+
"namespaces": [],
|
|
159
|
+
"incrementalCache": true,
|
|
160
|
+
"cacheDir": ".ai-localize-cache",
|
|
161
|
+
"codemods": {
|
|
162
|
+
"importPackage": "react-i18next",
|
|
163
|
+
"hookName": "useTranslation",
|
|
164
|
+
"translationFunction": "t",
|
|
165
|
+
"namespace": "",
|
|
166
|
+
"accessorStyle": "function"
|
|
167
|
+
},
|
|
168
|
+
"aws": {
|
|
169
|
+
"region": "us-east-1",
|
|
170
|
+
"bucket": "",
|
|
171
|
+
"distributionId": "",
|
|
172
|
+
"cdnBaseUrl": "",
|
|
173
|
+
"legacyCdnPattern": "",
|
|
174
|
+
"assetsPrefix": "assets",
|
|
175
|
+
"profile": ""
|
|
176
|
+
},
|
|
177
|
+
"plugins": []
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Config field reference
|
|
182
|
+
|
|
183
|
+
| Field | Type | Default | Description |
|
|
184
|
+
|---|---|---|---|
|
|
185
|
+
| `framework` | string | `"unknown"` | Framework — controls which codemod runs. Values: `"react"` `"react-cra"` `"react-vite"` `"react-nextjs"` `"angular"` `"angular-ngx"` `"angular-i18n"` `"vue"` `"vue-i18n"` `"jquery"` `"vanilla-js"` `"jsp"` `"unknown"` |
|
|
186
|
+
| `defaultLanguage` | string | `"en"` | Source language code (any BCP-47 tag) |
|
|
187
|
+
| `targetLanguages` | string[] | `[]` | Languages to generate locale files for |
|
|
188
|
+
| `sourceDir` | string | `"src"` | Root directory to scan |
|
|
189
|
+
| `localesDir` | string | `"locales"` | Where locale files are written |
|
|
190
|
+
| `localeStructure` | `"nested"` \| `"flat"` | `"nested"` | `"nested"` — one file per language+namespace (`locales/en/translation.json`). `"flat"` — one file per language, all keys merged (`locales/en.json`) |
|
|
191
|
+
| `keyStyle` | `"path"` \| `"screaming_snake"` | `"path"` | `"path"` — hierarchical dot-notation from file path + text: `settings.page.save_changes`. `"screaming_snake"` — UPPER_SNAKE_CASE from text value: `"Save Changes"` → `SAVE_CHANGES` |
|
|
192
|
+
| `staticKeys` | object | `{}` | Key/value pairs injected into every locale file regardless of scanning. Use for enum labels, status codes, or strings not hardcoded in source files. Example: `{"MAX_COUNT":"Max Count","ALLOWED":"Allowed"}` |
|
|
193
|
+
| `ignorePatterns` | string[] | `["node_modules","dist",…]` | Dirs/patterns to skip |
|
|
194
|
+
| `includePatterns` | string[] | — | If set, only scan matching files (supports `*` and `**` globs) |
|
|
195
|
+
| `keyPrefix` | string | — | Prefix added to every generated key (e.g. `"myapp"` → `"myapp.components.button.save"`) |
|
|
196
|
+
| `namespaces` | string[] | — | Explicit namespace list. If omitted, derived from first path segment after `sourceDir` |
|
|
197
|
+
| `incrementalCache` | boolean | `true` | Cache file hashes for faster re-runs |
|
|
198
|
+
| `cacheDir` | string | `".ai-localize-cache"` | Cache directory |
|
|
199
|
+
| `codemods` | object | — | Codemod injection options (see below) |
|
|
200
|
+
| `aws` | object | — | AWS S3 + CloudFront settings |
|
|
201
|
+
| `plugins` | string[] | `[]` | Custom plugin module paths |
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Locale File Layout
|
|
206
|
+
|
|
207
|
+
### `localeStructure: "nested"` (default)
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
locales/
|
|
211
|
+
en/
|
|
212
|
+
common.json
|
|
213
|
+
dashboard.json
|
|
214
|
+
fr/
|
|
215
|
+
common.json
|
|
216
|
+
dashboard.json
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### `localeStructure: "flat"`
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
locales/
|
|
223
|
+
en.json ← all namespaces merged
|
|
224
|
+
fr.json
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Set in config:
|
|
228
|
+
```json
|
|
229
|
+
{ "localeStructure": "flat" }
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Codemod Configuration
|
|
235
|
+
|
|
236
|
+
The `codemods` block controls what code is injected. All fields are optional with framework-specific defaults.
|
|
237
|
+
|
|
238
|
+
| Field | Default | Description |
|
|
239
|
+
|---|---|---|
|
|
240
|
+
| `importPackage` | `"react-i18next"` | npm package **or** project-relative path (e.g. `"src/hooks/useTranslation"`) |
|
|
241
|
+
| `hookName` | `"useTranslation"` | Hook function to import and call |
|
|
242
|
+
| `translationFunction` | `"t"` | Accessor destructured from the hook |
|
|
243
|
+
| `namespace` | — | Argument to the hook: `useTranslation("common")` |
|
|
244
|
+
| `accessorStyle` | `"function"` | `"function"` → `t('key')` · `"bracket"` → `t['key']` |
|
|
245
|
+
|
|
246
|
+
### Standard react-i18next
|
|
247
|
+
|
|
248
|
+
```json
|
|
249
|
+
{
|
|
250
|
+
"codemods": {
|
|
251
|
+
"importPackage": "react-i18next",
|
|
252
|
+
"hookName": "useTranslation",
|
|
253
|
+
"translationFunction": "t"
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Injects:
|
|
259
|
+
```tsx
|
|
260
|
+
import { useTranslation } from 'react-i18next';
|
|
261
|
+
// inside component:
|
|
262
|
+
const { t } = useTranslation();
|
|
263
|
+
return <h1>{t('header.welcome')}</h1>;
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Custom local hook (per-file path resolution)
|
|
267
|
+
|
|
268
|
+
```json
|
|
269
|
+
{
|
|
270
|
+
"codemods": {
|
|
271
|
+
"importPackage": "src/hooks/useTranslation",
|
|
272
|
+
"hookName": "useTranslation",
|
|
273
|
+
"translationFunction": "t"
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
The codemod automatically computes the correct relative path **per file**:
|
|
279
|
+
|
|
280
|
+
| File | Injected import |
|
|
281
|
+
|---|---|
|
|
282
|
+
| `src/App.tsx` | `import { useTranslation } from './hooks/useTranslation'` |
|
|
283
|
+
| `src/components/Button.tsx` | `import { useTranslation } from '../../hooks/useTranslation'` |
|
|
284
|
+
| `src/pages/dashboard/Header.tsx` | `import { useTranslation } from '../../../hooks/useTranslation'` |
|
|
285
|
+
|
|
286
|
+
### Bracket notation
|
|
287
|
+
|
|
288
|
+
```json
|
|
289
|
+
{
|
|
290
|
+
"codemods": {
|
|
291
|
+
"accessorStyle": "bracket"
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Generates `t['some.key']` instead of `t('some.key')`.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Scanner: Recognising Custom Translation Calls
|
|
301
|
+
|
|
302
|
+
Set `codemods.importPackage` and the scanner will automatically skip strings already wrapped by your custom hook — they won't be re-reported as hardcoded text.
|
|
303
|
+
|
|
304
|
+
**Supported import path formats:**
|
|
305
|
+
|
|
306
|
+
| Format | Example |
|
|
307
|
+
|---|---|
|
|
308
|
+
| npm package | `"react-i18next"` |
|
|
309
|
+
| Scoped npm package | `"@angular/core"` |
|
|
310
|
+
| Project-relative path | `"src/hooks/useTranslation"` |
|
|
311
|
+
| Path alias | `"@/hooks/useTranslation"` |
|
|
312
|
+
| Relative path | `"../../hooks/useTranslation"` |
|
|
313
|
+
|
|
314
|
+
All relative/alias forms match by **tail segment comparison**, so `hooks/useTranslation` matches `../../hooks/useTranslation`, `./hooks/useTranslation`, and `@/hooks/useTranslation`.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## HTML Report
|
|
319
|
+
|
|
320
|
+
Running `npx ai-localize report` generates a self-contained HTML file:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
npx ai-localize report
|
|
324
|
+
# → .ai-localize-reports/report-2026-05-26T12-00-00.html
|
|
325
|
+
|
|
326
|
+
npx ai-localize report --output-dir ./audit --filename localization-audit.html
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
The report contains:
|
|
330
|
+
- **8 summary cards** — Files Scanned, Hardcoded Texts, Keys Generated, Missing Translations, Unused Keys, Assets Found, Assets Uploaded, Legacy CDN URLs
|
|
331
|
+
- **Explainer box** — why Hardcoded Texts and Keys Generated may differ
|
|
332
|
+
- **Hardcoded Texts table** — file, line, text, suggested key, context
|
|
333
|
+
- **Missing Translations table** — key, language, file, message
|
|
334
|
+
- **Unused Keys table** — keys to clean up
|
|
335
|
+
- **Assets table** — uploaded asset paths, S3 keys, CloudFront URLs
|
|
336
|
+
- Each section has a **legend** explaining what the data means and what action to take
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## AWS / CDN Migration
|
|
341
|
+
|
|
342
|
+
Fill in the `aws` block in your config (or use environment variables):
|
|
343
|
+
|
|
344
|
+
```json
|
|
345
|
+
{
|
|
346
|
+
"aws": {
|
|
347
|
+
"region": "us-east-1",
|
|
348
|
+
"bucket": "my-assets-bucket",
|
|
349
|
+
"distributionId": "E1ABCDEFGHIJKL",
|
|
350
|
+
"cdnBaseUrl": "https://d123.cloudfront.net",
|
|
351
|
+
"legacyCdnPattern": "https://old-legacy-cdn.company.com",
|
|
352
|
+
"assetsPrefix": "static-assets/v1"
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Or via `.env`:
|
|
358
|
+
```bash
|
|
359
|
+
AWS_S3_BUCKET=my-assets-bucket
|
|
360
|
+
AWS_CF_DISTRIBUTION_ID=E1ABCDEFGHIJKL
|
|
361
|
+
AI_LOCALIZE_CDN_BASE_URL=https://d123.cloudfront.net
|
|
362
|
+
AI_LOCALIZE_LEGACY_CDN_PATTERN=https://old-legacy-cdn.company.com
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
One-step CDN migration:
|
|
366
|
+
```bash
|
|
367
|
+
npx ai-localize migrate-cdn --assets-dir ./public --invalidate
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## Before / After Example
|
|
373
|
+
|
|
374
|
+
**Before:**
|
|
375
|
+
```tsx
|
|
376
|
+
export const Banner = () => (
|
|
377
|
+
<div>
|
|
378
|
+
<h1>Welcome to the Dashboard</h1>
|
|
379
|
+
<p>Manage your campaigns easily.</p>
|
|
380
|
+
<button>Get Started</button>
|
|
381
|
+
</div>
|
|
382
|
+
);
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**After `npx ai-localize full-migrate`:**
|
|
386
|
+
```tsx
|
|
387
|
+
import { useTranslation } from 'react-i18next';
|
|
388
|
+
|
|
389
|
+
export const Banner = () => {
|
|
390
|
+
const { t } = useTranslation();
|
|
391
|
+
return (
|
|
392
|
+
<div>
|
|
393
|
+
<h1>{t('banner.welcome_to_the_dashboard')}</h1>
|
|
394
|
+
<p>{t('banner.manage_your_campaigns_easily')}</p>
|
|
395
|
+
<button>{t('banner.get_started')}</button>
|
|
396
|
+
</div>
|
|
397
|
+
);
|
|
398
|
+
};
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
**Generated `locales/en/common.json`:**
|
|
402
|
+
```json
|
|
403
|
+
{
|
|
404
|
+
"banner.get_started": "Get Started",
|
|
405
|
+
"banner.manage_your_campaigns_easily": "Manage your campaigns easily.",
|
|
406
|
+
"banner.welcome_to_the_dashboard": "Welcome to the Dashboard"
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
**Generated `locales/fr/common.json`** (seeded with English values for translators):
|
|
411
|
+
```json
|
|
412
|
+
{
|
|
413
|
+
"banner.get_started": "Get Started",
|
|
414
|
+
"banner.manage_your_campaigns_easily": "Manage your campaigns easily.",
|
|
415
|
+
"banner.welcome_to_the_dashboard": "Welcome to the Dashboard"
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
> **Tip — run `validate` after seeding:** New target-language entries are seeded with
|
|
420
|
+
> the English source value. `ai-localize validate` flags these as
|
|
421
|
+
> `"Missing translation (value equals source)"` until real translations are provided.
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## Key Generation
|
|
426
|
+
|
|
427
|
+
Keys are generated deterministically from the file path + text:
|
|
428
|
+
|
|
429
|
+
```
|
|
430
|
+
src/pages/dashboard/Banner.tsx + "Welcome to the Dashboard"
|
|
431
|
+
→ pages.dashboard.banner.welcome_to_the_dashboard
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
Format: `<folder>.<folder>.<component>.<slugified_text>`
|
|
435
|
+
|
|
436
|
+
- Relative to `sourceDir`
|
|
437
|
+
- CamelCase segments are snake_cased
|
|
438
|
+
- Text is lowercased, punctuation stripped, spaces replaced with `_`, truncated to 60 chars
|
|
439
|
+
- Duplicate strings across files share the same key
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
## Programmatic API
|
|
444
|
+
|
|
445
|
+
```ts
|
|
446
|
+
import { ProjectScanner } from 'ai-localize-scanner';
|
|
447
|
+
import { LocaleExtractor, LocaleWriter } from 'ai-localize-locale-engine';
|
|
448
|
+
import { CodemodRunner } from 'ai-localize-codemods';
|
|
449
|
+
import { LocaleValidator } from 'ai-localize-validators';
|
|
450
|
+
import { buildReport, generateHtmlReport } from 'ai-localize-reporting';
|
|
451
|
+
|
|
452
|
+
const config = {
|
|
453
|
+
framework: 'react-vite',
|
|
454
|
+
defaultLanguage: 'en',
|
|
455
|
+
targetLanguages: ['fr'],
|
|
456
|
+
sourceDir: 'src',
|
|
457
|
+
localesDir: 'locales',
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
// Scan
|
|
461
|
+
const scanner = new ProjectScanner(config);
|
|
462
|
+
const scanResult = await scanner.scan();
|
|
463
|
+
|
|
464
|
+
// Extract
|
|
465
|
+
const extractor = new LocaleExtractor({ defaultLanguage: 'en', targetLanguages: ['fr'] });
|
|
466
|
+
const { localeFiles } = extractor.extract(scanResult.detectedTexts);
|
|
467
|
+
|
|
468
|
+
// Write
|
|
469
|
+
const writer = new LocaleWriter({ localesDir: './locales' });
|
|
470
|
+
writer.write(localeFiles);
|
|
471
|
+
|
|
472
|
+
// Validate
|
|
473
|
+
const validator = new LocaleValidator({ localesDir: './locales', sourceDir: './src', defaultLanguage: 'en', targetLanguages: ['fr'] });
|
|
474
|
+
const validation = validator.validate();
|
|
475
|
+
|
|
476
|
+
// Report
|
|
477
|
+
const report = buildReport({ scanResult, validationResult: validation });
|
|
478
|
+
generateHtmlReport(report, './report.html');
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
---
|
|
482
|
+
|
|
483
|
+
## Monorepo Packages
|
|
484
|
+
|
|
485
|
+
| Package | npm install | Description |
|
|
486
|
+
|---|---|---|
|
|
487
|
+
| `ai-localize-shared` | `npm i ai-localize-shared` | Shared types, utilities, constants |
|
|
488
|
+
| `ai-localize-config` | `npm i ai-localize-config` | Config loading + Zod validation |
|
|
489
|
+
| `ai-localize-framework-detectors` | `npm i ai-localize-framework-detectors` | Auto-detect React / Angular / Vue |
|
|
490
|
+
| `ai-localize-scanner` | `npm i ai-localize-scanner` | Babel AST scanner + incremental scanning |
|
|
491
|
+
| `ai-localize-codemods` | `npm i ai-localize-codemods` | React / Vue / Angular i18n injection |
|
|
492
|
+
| `ai-localize-locale-engine` | `npm i ai-localize-locale-engine` | Locale file extraction, writing, merging |
|
|
493
|
+
| `ai-localize-validators` | `npm i ai-localize-validators` | Missing / unused / duplicate key validation |
|
|
494
|
+
| `ai-localize-aws-cloudfront` | `npm i ai-localize-aws-cloudfront` | S3 upload + CloudFront invalidation |
|
|
495
|
+
| `ai-localize-reporting` | `npm i ai-localize-reporting` | HTML + CLI report generation |
|
|
496
|
+
| `ai-localize-cli` | `npm i -g ai-localize-cli` | Full CLI (`ai-localize` command) |
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
## CI/CD Integration
|
|
501
|
+
|
|
502
|
+
### GitHub Actions
|
|
503
|
+
|
|
504
|
+
```yaml
|
|
505
|
+
- name: Localization check
|
|
506
|
+
run: |
|
|
507
|
+
npx ai-localize scan
|
|
508
|
+
npx ai-localize validate
|
|
509
|
+
npx ai-localize report --output-dir ./reports
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### Pre-commit hook
|
|
513
|
+
|
|
514
|
+
```bash
|
|
515
|
+
npx ai-localize scan --staged
|
|
516
|
+
npx ai-localize validate
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
---
|
|
520
|
+
|
|
521
|
+
## Full Documentation
|
|
522
|
+
|
|
523
|
+
See [`INSTRUCTION_GUIDE.md`](INSTRUCTION_GUIDE.md) for the complete developer guide including:
|
|
524
|
+
- All CLI options with examples
|
|
525
|
+
- Full config field reference
|
|
526
|
+
- Codemod config deep-dive
|
|
527
|
+
- Custom hook integration
|
|
528
|
+
- Locale layout options
|
|
529
|
+
- CDN migration walkthrough
|
|
530
|
+
- HTML report guide
|
|
531
|
+
- Troubleshooting
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
## License
|
|
536
|
+
|
|
537
|
+
MIT © ai-localize-core contributors
|
package/dist/cli.js
CHANGED
|
@@ -528,6 +528,17 @@ function fullMigrateCommand() {
|
|
|
528
528
|
const { config } = await (0, import_ai_localize_config10.loadConfig)(cwd);
|
|
529
529
|
cs.succeed("Configuration loaded");
|
|
530
530
|
const structure = config.localeStructure ?? "nested";
|
|
531
|
+
const staticKeys = config.staticKeys ?? {};
|
|
532
|
+
const staticKeyCount = Object.keys(staticKeys).length;
|
|
533
|
+
if (dryRun) {
|
|
534
|
+
logger.info(import_chalk11.default.yellow("Dry run mode \u2014 no files will be written or modified"));
|
|
535
|
+
}
|
|
536
|
+
logger.info("Locale structure: " + import_chalk11.default.cyan(structure));
|
|
537
|
+
if (staticKeyCount > 0) {
|
|
538
|
+
logger.info(
|
|
539
|
+
"Static keys: " + import_chalk11.default.cyan(String(staticKeyCount)) + " (" + Object.keys(staticKeys).join(", ") + ")"
|
|
540
|
+
);
|
|
541
|
+
}
|
|
531
542
|
const ss = createSpinner("Scanning for hardcoded text...").start();
|
|
532
543
|
const scanner = new import_ai_localize_scanner6.ProjectScanner(config);
|
|
533
544
|
const scanResult = await scanner.scan();
|
|
@@ -540,17 +551,29 @@ function fullMigrateCommand() {
|
|
|
540
551
|
defaultLanguage: config.defaultLanguage,
|
|
541
552
|
targetLanguages: config.targetLanguages,
|
|
542
553
|
// Flat layout merges all keys into one file per language — no namespace splitting
|
|
543
|
-
namespaceSplitting: structure === "nested"
|
|
554
|
+
namespaceSplitting: structure === "nested",
|
|
555
|
+
staticKeys
|
|
544
556
|
});
|
|
545
557
|
const { localeFiles, keyCount } = extractor.extract(uniqueTexts);
|
|
546
|
-
es.succeed(
|
|
558
|
+
es.succeed(
|
|
559
|
+
"Generated " + import_chalk11.default.green(String(keyCount)) + " locale keys" + (staticKeyCount > 0 ? import_chalk11.default.dim(" (+ " + staticKeyCount + " static)") : "")
|
|
560
|
+
);
|
|
547
561
|
if (!dryRun) {
|
|
548
562
|
const writer = new import_ai_localize_locale_engine2.LocaleWriter({
|
|
549
563
|
localesDir: path9.resolve(cwd, config.localesDir),
|
|
550
564
|
merge: true,
|
|
551
565
|
localeStructure: structure
|
|
552
566
|
});
|
|
553
|
-
writer.write(localeFiles);
|
|
567
|
+
const { written, created, merged } = writer.write(localeFiles);
|
|
568
|
+
logger.success(
|
|
569
|
+
"Wrote " + written.length + " locale files (" + created.length + " new, " + merged.length + " merged)"
|
|
570
|
+
);
|
|
571
|
+
} else {
|
|
572
|
+
logger.info("Dry run \u2014 locale files that would be written:");
|
|
573
|
+
localeFiles.forEach((lf) => {
|
|
574
|
+
const label = structure === "flat" ? `${lf.language}.json` : `${lf.language}/${lf.namespace}.json`;
|
|
575
|
+
logger.info(" " + import_chalk11.default.gray(label) + " \u2014 " + Object.keys(lf.entries).length + " keys");
|
|
576
|
+
});
|
|
554
577
|
}
|
|
555
578
|
if (opts.codemods !== false) {
|
|
556
579
|
const ms = createSpinner("Applying i18n codemods...").start();
|
|
@@ -560,33 +583,39 @@ function fullMigrateCommand() {
|
|
|
560
583
|
"Codemods: " + import_chalk11.default.green(String(codemodResult.totalReplacements)) + " replacements in " + codemodResult.changedFiles + " files"
|
|
561
584
|
);
|
|
562
585
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
const report = (0, import_ai_localize_reporting2.buildReport)({ scanResult, validationResult });
|
|
579
|
-
const reportDir = path9.resolve(cwd, opts.reportDir);
|
|
580
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
581
|
-
const htmlPath = path9.join(reportDir, "report-" + timestamp + ".html");
|
|
582
|
-
(0, import_ai_localize_reporting2.generateHtmlReport)(report, htmlPath);
|
|
583
|
-
rs.succeed("Report saved to " + import_chalk11.default.cyan(htmlPath));
|
|
584
|
-
(0, import_ai_localize_reporting2.printCliSummary)(report);
|
|
585
|
-
logger.info(
|
|
586
|
-
"\n View report:\n " + import_chalk11.default.underline("file://" + htmlPath)
|
|
586
|
+
if (dryRun) {
|
|
587
|
+
logger.info(import_chalk11.default.dim("Skipping validation in dry-run mode (no files were written)"));
|
|
588
|
+
} else {
|
|
589
|
+
const vs = createSpinner("Validating locale files...").start();
|
|
590
|
+
const validator = new import_ai_localize_validators4.LocaleValidator({
|
|
591
|
+
localesDir: path9.resolve(cwd, config.localesDir),
|
|
592
|
+
sourceDir: path9.resolve(cwd, config.sourceDir),
|
|
593
|
+
defaultLanguage: config.defaultLanguage,
|
|
594
|
+
targetLanguages: config.targetLanguages
|
|
595
|
+
});
|
|
596
|
+
const validationResult = validator.validate();
|
|
597
|
+
vs.succeed(
|
|
598
|
+
validationResult.valid ? import_chalk11.default.green("Locale files valid!") : import_chalk11.default.yellow(
|
|
599
|
+
validationResult.errors.length + " errors, " + validationResult.warnings.length + " warnings"
|
|
600
|
+
)
|
|
587
601
|
);
|
|
602
|
+
validationResult.errors.forEach((e) => logger.error(` [${e.type}] ${e.message}`));
|
|
603
|
+
validationResult.warnings.forEach((w) => logger.warn(` [${w.type}] ${w.message}`));
|
|
604
|
+
if (opts.report !== false) {
|
|
605
|
+
const rs = createSpinner("Generating report...").start();
|
|
606
|
+
const report = (0, import_ai_localize_reporting2.buildReport)({ scanResult, validationResult });
|
|
607
|
+
const reportDir = path9.resolve(cwd, opts.reportDir);
|
|
608
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
609
|
+
const htmlPath = path9.join(reportDir, "report-" + timestamp + ".html");
|
|
610
|
+
(0, import_ai_localize_reporting2.generateHtmlReport)(report, htmlPath);
|
|
611
|
+
rs.succeed("Report saved to " + import_chalk11.default.cyan(htmlPath));
|
|
612
|
+
(0, import_ai_localize_reporting2.printCliSummary)(report);
|
|
613
|
+
logger.info("\n View report:\n " + import_chalk11.default.underline("file://" + htmlPath));
|
|
614
|
+
}
|
|
588
615
|
}
|
|
589
|
-
logger.success(
|
|
616
|
+
logger.success(
|
|
617
|
+
dryRun ? "Dry run complete \u2014 no files were modified." : "Full migration complete!"
|
|
618
|
+
);
|
|
590
619
|
} catch (err) {
|
|
591
620
|
logger.error("Migration failed: " + String(err));
|
|
592
621
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-localize-cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"description": "CLI for ai-localize-core: scan, extract, validate, codemod and migrate CDN",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ai-localize": "./dist/cli.js"
|
|
@@ -35,15 +35,15 @@
|
|
|
35
35
|
"chalk": "^5.3.0",
|
|
36
36
|
"ora": "^8.0.1",
|
|
37
37
|
"inquirer": "^9.2.12",
|
|
38
|
-
"ai-localize-
|
|
39
|
-
"ai-localize-
|
|
40
|
-
"ai-localize-codemods": "2.0.
|
|
41
|
-
"ai-localize-
|
|
42
|
-
"ai-localize-
|
|
43
|
-
"ai-localize-
|
|
44
|
-
"ai-localize-
|
|
45
|
-
"ai-localize-
|
|
46
|
-
"ai-localize-
|
|
38
|
+
"ai-localize-shared": "2.0.6",
|
|
39
|
+
"ai-localize-framework-detectors": "2.0.6",
|
|
40
|
+
"ai-localize-codemods": "2.0.6",
|
|
41
|
+
"ai-localize-locale-engine": "2.0.6",
|
|
42
|
+
"ai-localize-scanner": "2.0.6",
|
|
43
|
+
"ai-localize-config": "2.0.6",
|
|
44
|
+
"ai-localize-validators": "2.0.6",
|
|
45
|
+
"ai-localize-reporting": "2.0.6",
|
|
46
|
+
"ai-localize-aws-cloudfront": "2.0.6"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/inquirer": "^9.0.7",
|