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.
Files changed (4) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +537 -0
  3. package/dist/cli.js +57 -28
  4. 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
+ [![npm version](https://img.shields.io/npm/v/ai-localize-cli.svg)](https://www.npmjs.com/package/ai-localize-cli)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
9
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
10
+ [![Version](https://img.shields.io/badge/version-2.0.5-blue)](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("Generated " + import_chalk11.default.green(String(keyCount)) + " locale keys");
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
- const vs = createSpinner("Validating locale files...").start();
564
- const validator = new import_ai_localize_validators4.LocaleValidator({
565
- localesDir: path9.resolve(cwd, config.localesDir),
566
- sourceDir: path9.resolve(cwd, config.sourceDir),
567
- defaultLanguage: config.defaultLanguage,
568
- targetLanguages: config.targetLanguages
569
- });
570
- const validationResult = validator.validate();
571
- vs.succeed(
572
- validationResult.valid ? import_chalk11.default.green("Locale files valid!") : import_chalk11.default.yellow(
573
- validationResult.errors.length + " errors, " + validationResult.warnings.length + " warnings"
574
- )
575
- );
576
- if (opts.report !== false) {
577
- const rs = createSpinner("Generating report...").start();
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("Full migration complete!");
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.4",
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-config": "2.0.4",
39
- "ai-localize-scanner": "2.0.4",
40
- "ai-localize-codemods": "2.0.4",
41
- "ai-localize-framework-detectors": "2.0.4",
42
- "ai-localize-shared": "2.0.4",
43
- "ai-localize-validators": "2.0.4",
44
- "ai-localize-aws-cloudfront": "2.0.4",
45
- "ai-localize-locale-engine": "2.0.4",
46
- "ai-localize-reporting": "2.0.4"
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",