@better-i18n/cli 0.1.1 → 0.1.3

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 (36) hide show
  1. package/README.md +363 -85
  2. package/dist/analyzer/index.d.ts.map +1 -1
  3. package/dist/analyzer/index.js +7 -3
  4. package/dist/analyzer/index.js.map +1 -1
  5. package/dist/analyzer/rules/index.d.ts +4 -3
  6. package/dist/analyzer/rules/index.d.ts.map +1 -1
  7. package/dist/analyzer/rules/index.js +4 -3
  8. package/dist/analyzer/rules/index.js.map +1 -1
  9. package/dist/analyzer/rules/ternary-locale.d.ts.map +1 -1
  10. package/dist/analyzer/rules/ternary-locale.js +5 -1
  11. package/dist/analyzer/rules/ternary-locale.js.map +1 -1
  12. package/dist/analyzer/rules/translation-function.d.ts +12 -0
  13. package/dist/analyzer/rules/translation-function.d.ts.map +1 -0
  14. package/dist/analyzer/rules/translation-function.js +65 -0
  15. package/dist/analyzer/rules/translation-function.js.map +1 -0
  16. package/dist/analyzer/types.d.ts +14 -1
  17. package/dist/analyzer/types.d.ts.map +1 -1
  18. package/dist/commands/extract-keys.d.ts +13 -0
  19. package/dist/commands/extract-keys.d.ts.map +1 -0
  20. package/dist/commands/extract-keys.js +347 -0
  21. package/dist/commands/extract-keys.js.map +1 -0
  22. package/dist/commands/scan.d.ts.map +1 -1
  23. package/dist/commands/scan.js +3 -1
  24. package/dist/commands/scan.js.map +1 -1
  25. package/dist/context/detector.js +2 -2
  26. package/dist/context/detector.js.map +1 -1
  27. package/dist/index.js +14 -2
  28. package/dist/index.js.map +1 -1
  29. package/dist/reporters/eslint-style.d.ts +1 -1
  30. package/dist/reporters/eslint-style.d.ts.map +1 -1
  31. package/dist/reporters/eslint-style.js +19 -8
  32. package/dist/reporters/eslint-style.js.map +1 -1
  33. package/dist/reporters/json.d.ts.map +1 -1
  34. package/dist/reporters/json.js +12 -13
  35. package/dist/reporters/json.js.map +1 -1
  36. package/package.json +2 -2
package/README.md CHANGED
@@ -27,7 +27,7 @@ Hardcoded strings slip into codebases easily. Finding them manually is tedious.
27
27
  # Global install
28
28
  npm install -g @better-i18n/cli
29
29
 
30
- # Or use with npx (no install)
30
+ # Or use with npx (no install needed)
31
31
  npx @better-i18n/cli scan
32
32
 
33
33
  # Or add to your project
@@ -43,93 +43,256 @@ better-i18n scan
43
43
  # That's it! The CLI auto-detects your i18n.config.ts
44
44
  ```
45
45
 
46
- ## Usage
46
+ ## Features
47
+
48
+ - ✅ **Auto-config detection** - Reads your existing `i18n.config.ts`
49
+ - ✅ **Smart filtering** - Ignores CSS classes, URLs, constants, HTML entities
50
+ - ✅ **Glob patterns** - Exclude test files, stories, UI components
51
+ - ✅ **Clickable output** - File paths are Cmd+clickable in VS Code terminal
52
+ - ✅ **CI/CD ready** - JSON output, exit codes, staged files support
53
+ - ✅ **Fast** - Scans 100+ files in <100ms
54
+
55
+ ## Example Output
56
+
57
+ ```
58
+ $ better-i18n scan
59
+
60
+ ✓ Project: better-i18n/landing
61
+ ✓ Found 57 files
62
+
63
+ components/sign-up.tsx (11)
64
+ 24:13 missing "Create an account" i18n/jsx-text
65
+ 32:22 missing "Name" i18n/jsx-text
66
+ 40:22 missing "Email" i18n/jsx-text
67
+
68
+ components/contact.tsx (9)
69
+ 24:59 missing "Contact us" i18n/jsx-text
70
+ 31:22 missing "Message" i18n/jsx-text
71
+
72
+ ✖ 87 problems (87 missing translations)
73
+
74
+ Scanned 57 files in 0.07s
75
+ ```
76
+
77
+ **Cmd+Click** on any file path to jump directly to the issue in VS Code!
78
+
79
+ ## Commands & Options
80
+
81
+ ### `better-i18n scan`
82
+
83
+ Scan your codebase for hardcoded strings.
47
84
 
48
85
  ```bash
86
+ # Basic usage
87
+ better-i18n scan
88
+
49
89
  # Scan specific directory
50
90
  better-i18n scan --dir ./src
51
91
 
52
- # JSON output (for CI/tooling)
53
- better-i18n scan --format json
92
+ # Output formats
93
+ better-i18n scan --format json # JSON output for CI/tooling
94
+ better-i18n scan --format eslint # Human-readable (default)
95
+
96
+ # CI/CD integration
97
+ better-i18n scan --ci # Exit with code 1 if issues found
98
+ better-i18n scan --staged # Only scan git staged files
99
+
100
+ # Debug
101
+ better-i18n scan --verbose # Show detailed output
102
+ ```
103
+
104
+ ### `better-i18n extract-keys`
105
+
106
+ Extract all translation keys used in your codebase (t() function calls).
107
+
108
+ ```bash
109
+ # Extract all translation keys
110
+ better-i18n extract-keys
111
+
112
+ # Compare with remote keys from CDN
113
+ better-i18n extract-keys --compare
54
114
 
55
- # CI mode (exit code 1 if issues found)
56
- better-i18n scan --ci
115
+ # Compare with specific locale
116
+ better-i18n extract-keys --compare --locale tr
57
117
 
58
- # Only scan git staged files (for pre-commit hooks)
59
- better-i18n scan --staged
118
+ # JSON output (default)
119
+ better-i18n extract-keys --format json
60
120
 
61
- # Verbose output
62
- better-i18n scan --verbose
121
+ # Show detailed output
122
+ better-i18n extract-keys --verbose
63
123
  ```
64
124
 
65
- ## Example Output
125
+ **Output format (JSON):**
66
126
 
127
+ ```json
128
+ {
129
+ "localKeys": {
130
+ "project": "better-i18n/landing",
131
+ "namespaces": {
132
+ "auth": ["auth.login", "auth.register", "auth.forgot"],
133
+ "nav": ["nav.home", "nav.about"],
134
+ "hero": ["hero.title", "hero.description"]
135
+ },
136
+ "totalCount": 6,
137
+ "filesScanned": 42
138
+ }
139
+ }
67
140
  ```
68
- $ better-i18n scan
69
141
 
70
- Project: acme/web-app
71
- ✓ Found 45 files
142
+ **With `--compare`:**
143
+
144
+ ```json
145
+ {
146
+ "comparison": {
147
+ "localKeys": { ... },
148
+ "remoteKeys": {
149
+ "namespaces": { ... },
150
+ "totalCount": 150
151
+ },
152
+ "missingKeys": {
153
+ "hero": ["hero.cta", "hero.benefits"]
154
+ },
155
+ "unusedKeys": {
156
+ "old": ["old.section"]
157
+ },
158
+ "coverage": {
159
+ "local": 85,
160
+ "remote": 96
161
+ }
162
+ }
163
+ }
164
+ ```
72
165
 
73
- src/components/Header.tsx
74
- 12:8 warning "Welcome back" i18n/jsx-text
75
- 15:18 warning "Profile picture" i18n/jsx-attribute
166
+ **Human-readable output with `--compare`:**
76
167
 
77
- src/pages/login.tsx
78
- 23:15 error locale === 'en' ? ... : ... i18n/ternary-locale
168
+ ```
169
+ Project: better-i18n/landing
170
+ ✓ Found 57 files
171
+ ✓ Fetched 12 namespaces from CDN
172
+
173
+ 📊 Translation Keys Comparison
174
+
175
+ Coverage:
176
+ Local → Remote: 85%
177
+ Remote Used: 96%
178
+
179
+ ❌ Missing in Remote (2 keys):
180
+ hero: 2 keys
181
+ • hero.cta
182
+ • hero.benefits
79
183
 
80
- 3 problems (1 error, 2 warnings)
184
+ ⚠️ Unused in Code (1 key):
185
+ old: 1 key
186
+ • old.section
81
187
 
82
- Scanned 45 files in 0.23s
188
+ Scanned 57 files in 0.12s
189
+ ✓ Comparison complete
83
190
  ```
84
191
 
85
192
  ## Detection Rules
86
193
 
87
- | Rule | Severity | What it catches |
88
- |------|----------|-----------------|
89
- | `jsx-text` | warning | Hardcoded text inside JSX elements |
90
- | `jsx-attribute` | warning | Hardcoded `title`, `alt`, `placeholder`, `aria-label` |
91
- | `ternary-locale` | error | `locale === 'en' ? 'Hello' : 'Hola'` anti-pattern |
194
+ | Rule | Severity | What it catches | Example |
195
+ | ---------------- | -------- | ---------------------- | --------------------------------- |
196
+ | `jsx-text` | missing | Hardcoded text in JSX | `<h1>Hello</h1>` |
197
+ | `jsx-attribute` | missing | Hardcoded attributes | `<img alt="Logo" />` |
198
+ | `ternary-locale` | error | Locale-based ternaries | `locale === 'en' ? 'Hi' : 'Hola'` |
199
+
200
+ ### Automatically Ignored
201
+
202
+ - HTML entities: `&quot;`, `&amp;`, `&#39;`
203
+ - CSS classes: `className="flex items-center"`
204
+ - URLs: `href="https://example.com"`
205
+ - Paths: `/api/users`
206
+ - Numbers: `42`, `3.14`, `100%`
207
+ - Constants: `SCREAMING_CASE`
208
+ - Symbols: `→`, `•`, `...`
92
209
 
93
210
  ## Configuration
94
211
 
95
- The CLI automatically reads your `i18n.config.ts` file. No extra config needed!
212
+ Create or update your `i18n.config.ts`:
96
213
 
97
214
  ```ts
98
- // i18n.config.ts
99
- import { createI18n } from "@better-i18n/next";
100
-
101
- export const { useTranslation, I18nProvider } = createI18n({
102
- project: "your-org/your-project",
103
- defaultLocale: "en",
104
-
105
- // Optional: customize lint behavior
215
+ export const project = "your-org/your-project";
216
+ export const defaultLocale = "en";
217
+
218
+ export const i18nWorkspaceConfig = {
219
+ project,
220
+ defaultLocale,
106
221
  lint: {
222
+ // Files to scan (defaults: ["src", "app", "components", "pages"])
107
223
  include: ["src/**/*.tsx", "app/**/*.tsx"],
108
- exclude: ["**/*.test.tsx", "**/*.stories.tsx"],
224
+
225
+ // Files to ignore (automatically merges with defaults)
226
+ exclude: [
227
+ "**/skeletons.tsx", // Mock/demo components
228
+ "**/*.stories.tsx", // Storybook files
229
+ "**/*.test.tsx", // Test files
230
+ "**/components/ui/**", // UI library components
231
+ ],
232
+
233
+ // Rule configuration (optional)
109
234
  rules: {
110
235
  "jsx-text": "warning",
111
236
  "jsx-attribute": "warning",
112
237
  "ternary-locale": "error",
113
238
  },
114
239
  },
115
- });
240
+ };
116
241
  ```
117
242
 
118
- ### Lint Options
243
+ ### Config Options
244
+
245
+ | Option | Type | Description |
246
+ | --------- | ---------- | ---------------------------------------------------------------------------------- |
247
+ | `include` | `string[]` | Glob patterns for files to scan (default: `["src", "app", "components", "pages"]`) |
248
+ | `exclude` | `string[]` | Glob patterns to ignore (merges with defaults: `node_modules`, `.next`, etc.) |
249
+ | `rules` | `object` | Set severity: `"error"` \| `"warning"` \| `"off"` |
250
+
251
+ ## Usage Scenarios
119
252
 
120
- | Option | Type | Description |
121
- |--------|------|-------------|
122
- | `include` | `string[]` | Glob patterns for files to scan |
123
- | `exclude` | `string[]` | Glob patterns for files to ignore |
124
- | `rules` | `object` | Rule severity: `"error"`, `"warning"`, or `"off"` |
253
+ ### 1. Local Development
125
254
 
126
- ## CI/CD Integration
255
+ Add to your `package.json`:
256
+
257
+ ```json
258
+ {
259
+ "scripts": {
260
+ "lint": "next lint && better-i18n scan --ci",
261
+ "lint:i18n": "better-i18n scan"
262
+ }
263
+ }
264
+ ```
127
265
 
128
- ### GitHub Actions
266
+ Run before commits:
267
+
268
+ ```bash
269
+ npm run lint:i18n
270
+ ```
271
+
272
+ ### 2. Pre-commit Hook
273
+
274
+ Install [Husky](https://typicode.github.io/husky/):
275
+
276
+ ```bash
277
+ npx husky init
278
+ echo "npx @better-i18n/cli scan --staged --ci" > .husky/pre-commit
279
+ ```
280
+
281
+ Or with [lint-staged](https://github.com/lint-staged/lint-staged):
282
+
283
+ ```json
284
+ {
285
+ "lint-staged": {
286
+ "*.{tsx,jsx}": ["better-i18n scan --ci"]
287
+ }
288
+ }
289
+ ```
290
+
291
+ ### 3. GitHub Actions CI
129
292
 
130
293
  ```yaml
131
- # .github/workflows/i18n-lint.yml
132
- name: i18n Lint
294
+ # .github/workflows/i18n-check.yml
295
+ name: i18n Check
133
296
 
134
297
  on: [push, pull_request]
135
298
 
@@ -140,78 +303,193 @@ jobs:
140
303
  - uses: actions/checkout@v4
141
304
  - uses: actions/setup-node@v4
142
305
  with:
143
- node-version: '20'
144
- - run: npx @better-i18n/cli scan --ci
306
+ node-version: "20"
307
+ - run: npx @better-i18n/cli scan --ci --format json
145
308
  ```
146
309
 
147
- ### Pre-commit Hook
148
-
149
- With [Husky](https://typicode.github.io/husky/):
150
-
151
- ```bash
152
- # .husky/pre-commit
153
- npx @better-i18n/cli scan --staged --ci
154
- ```
310
+ ### 4. VS Code Integration
155
311
 
156
- Or with [lint-staged](https://github.com/lint-staged/lint-staged):
312
+ Add to `.vscode/tasks.json`:
157
313
 
158
314
  ```json
159
315
  {
160
- "lint-staged": {
161
- "*.{tsx,jsx}": ["better-i18n scan --ci"]
162
- }
316
+ "version": "2.0.0",
317
+ "tasks": [
318
+ {
319
+ "label": "i18n: Check translations",
320
+ "type": "shell",
321
+ "command": "npx @better-i18n/cli scan",
322
+ "problemMatcher": [],
323
+ "presentation": {
324
+ "reveal": "always",
325
+ "panel": "new"
326
+ }
327
+ }
328
+ ]
163
329
  }
164
330
  ```
165
331
 
332
+ Run with: `Cmd+Shift+P` → `Tasks: Run Task` → `i18n: Check translations`
333
+
334
+ ### 5. Monorepo Usage
335
+
336
+ ```bash
337
+ # Scan specific package
338
+ cd packages/web-app
339
+ better-i18n scan
340
+
341
+ # Or from root with --dir
342
+ better-i18n scan --dir packages/web-app
343
+ ```
344
+
345
+ Each package can have its own `i18n.config.ts`.
346
+
166
347
  ## JSON Output
167
348
 
168
- Use `--format json` for programmatic access:
349
+ Use `--format json` for programmatic integration:
169
350
 
170
351
  ```bash
171
- better-i18n scan --format json
352
+ better-i18n scan --format json > i18n-report.json
172
353
  ```
173
354
 
174
355
  ```json
175
356
  {
176
357
  "project": {
177
- "workspaceId": "acme",
178
- "projectSlug": "web-app",
358
+ "workspaceId": "better-i18n",
359
+ "projectSlug": "landing",
179
360
  "defaultLocale": "en"
180
361
  },
181
- "files": 45,
362
+ "files": 57,
182
363
  "issues": [
183
364
  {
184
- "file": "src/components/Header.tsx",
185
- "line": 12,
186
- "column": 8,
187
- "text": "Welcome back",
365
+ "file": "components/sign-up.tsx",
366
+ "line": 24,
367
+ "column": 13,
368
+ "text": "Create an account",
188
369
  "type": "jsx-text",
189
370
  "severity": "warning",
190
- "message": "Hardcoded text in JSX"
371
+ "message": "Hardcoded text: \"Create an account\"",
372
+ "suggestedKey": "signUp.createAnAccount"
191
373
  }
192
374
  ],
193
- "duration": 234
375
+ "duration": 67
194
376
  }
195
377
  ```
196
378
 
197
- ## What Gets Ignored
379
+ ### JSON Schema
380
+
381
+ ```ts
382
+ interface ScanResult {
383
+ project?: {
384
+ workspaceId: string;
385
+ projectSlug: string;
386
+ defaultLocale: string;
387
+ };
388
+ files: number;
389
+ issues: Issue[];
390
+ duration: number;
391
+ }
198
392
 
199
- The CLI is smart about ignoring non-translatable content:
393
+ interface Issue {
394
+ file: string; // Relative path
395
+ line: number; // Line number
396
+ column: number; // Column number
397
+ text: string; // Hardcoded text
398
+ type: "jsx-text" | "jsx-attribute" | "ternary-locale";
399
+ severity: "error" | "warning";
400
+ message: string; // Human-readable message
401
+ suggestedKey?: string; // Auto-generated translation key
402
+ }
403
+ ```
200
404
 
201
- - CSS class names: `className="flex items-center"`
202
- - ✅ URLs: `href="https://example.com"`
203
- - Numbers and constants
204
- - ✅ Import paths
205
- - ✅ Code identifiers
206
- - Single characters and punctuation
405
+ ## Advanced Usage
406
+
407
+ ### Custom Scripts
408
+
409
+ ```bash
410
+ # Count missing translations
411
+ better-i18n scan --format json | jq '.issues | length'
412
+
413
+ # Get unique files with issues
414
+ better-i18n scan --format json | jq -r '.issues[].file' | sort -u
415
+
416
+ # Filter only errors
417
+ better-i18n scan --format json | jq '.issues[] | select(.severity == "error")'
418
+ ```
419
+
420
+ ### Combine with Other Tools
421
+
422
+ ```bash
423
+ # Run with TypeScript checks
424
+ tsc --noEmit && better-i18n scan --ci
425
+
426
+ # Run with ESLint
427
+ eslint . && better-i18n scan --ci
428
+
429
+ # Parallel execution
430
+ npm-run-all --parallel typecheck lint:eslint lint:i18n
431
+ ```
432
+
433
+ ## Troubleshooting
434
+
435
+ ### Config not detected
436
+
437
+ Make sure your `i18n.config.ts` exports either:
438
+
439
+ - `export const project = "org/slug"`
440
+ - `export const i18nWorkspaceConfig = { project: "org/slug" }`
441
+
442
+ ### Too many false positives
443
+
444
+ Add exclusions to your config:
445
+
446
+ ```ts
447
+ exclude: ["**/*.stories.tsx", "**/demo/**", "**/examples/**"];
448
+ ```
449
+
450
+ ### Clickable links not working
451
+
452
+ Make sure you're using VS Code's integrated terminal. External terminals may not support clickable file paths.
453
+
454
+ ## Part of Better i18n Ecosystem
455
+
456
+ This CLI is one component of the **Better i18n translation management platform**:
457
+
458
+ ### Platform Components
459
+
460
+ - **[@better-i18n/cli](https://www.npmjs.com/package/@better-i18n/cli)** - This CLI tool (detect hardcoded strings, extract keys)
461
+ - **[@better-i18n/next](https://www.npmjs.com/package/@better-i18n/next)** - Next.js SDK for runtime translation
462
+ - **[@better-i18n/app](https://dash.better-i18n.com)** - Web dashboard for translation management
463
+ - `@better-i18n/mcp`: Model Context Protocol server for AI assistants.
464
+
465
+ ### Platform Features
466
+
467
+ - **GitHub Integration** - Sync translations with your repositories
468
+ - **Real-time Collaboration** - Team workflows on translations
469
+ - **CDN Delivery** - Serve translations globally from edge locations
470
+ - **Multi-language Editor** - Manage all languages in one interface
471
+ - **REST API** - Programmatic access for CI/CD automation
472
+ - **AI Context Analysis** - Automatically extract terminology from websites
473
+ - **Namespace Organization** - Organize translations by feature/module
474
+
475
+ ### How This CLI Fits In
476
+
477
+ ```
478
+ Developer Workflow:
479
+ ├─ Write code with hardcoded strings
480
+ ├─ Run: better-i18n scan → Detect hardcoded strings ⚠️
481
+ ├─ Run: better-i18n extract-keys → Extract used keys
482
+ ├─ Review in Better i18n Dashboard
483
+ ├─ GitHub Hook: better-i18n scan --staged → Pre-commit check
484
+ ├─ CI/CD: better-i18n scan --ci → Fail build if strings found
485
+ └─ Dashboard: Manage translations, sync with GitHub
486
+ ```
207
487
 
208
- ## Part of Better i18n
488
+ The CLI works **in your local development** to catch issues before they ship, while the platform handles the translation management workflow.
209
489
 
210
- This CLI is part of the [Better i18n](https://better-i18n.com) ecosystem:
490
+ ## Contributing
211
491
 
212
- - **[@better-i18n/next](https://www.npmjs.com/package/@better-i18n/next)** - Next.js i18n SDK
213
- - **[@better-i18n/cli](https://www.npmjs.com/package/@better-i18n/cli)** - This CLI
214
- - **[Dashboard](https://better-i18n.com)** - Visual translation management
492
+ Found a bug or have a feature request? [Open an issue](https://github.com/better-i18n/better-i18n/issues).
215
493
 
216
494
  ## License
217
495
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analyzer/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAe,MAAM,YAAY,CAAC;AAEjE;;GAEG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,KAAK,EAAE,CAAC,CAGlB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,UAAU,GAClB,KAAK,EAAE,CA2CT"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analyzer/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAe,MAAM,YAAY,CAAC;AAEjE;;GAEG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,KAAK,EAAE,CAAC,CAGlB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,UAAU,GAClB,KAAK,EAAE,CAiDT"}
@@ -5,9 +5,7 @@
5
5
  */
6
6
  import { readFileSync } from "node:fs";
7
7
  import ts from "typescript";
8
- import { checkJsxAttribute } from "./rules/jsx-attribute.js";
9
- import { checkJsxText } from "./rules/jsx-text.js";
10
- import { checkTernaryLocale } from "./rules/ternary-locale.js";
8
+ import { checkJsxAttribute, checkJsxText, checkTernaryLocale, checkTranslationFunction, } from "./rules/index.js";
11
9
  /**
12
10
  * Analyze a single file for hardcoded strings
13
11
  */
@@ -46,6 +44,12 @@ export function analyzeSourceText(sourceText, filePath, config) {
46
44
  if (issue)
47
45
  issues.push(issue);
48
46
  }
47
+ // Translation function calls
48
+ if (ts.isCallExpression(node)) {
49
+ const issue = checkTranslationFunction(node, ctx);
50
+ if (issue)
51
+ issues.push(issue);
52
+ }
49
53
  ts.forEachChild(node, visit);
50
54
  }
51
55
  visit(sourceFile);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/analyzer/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/analyzer/index.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Rules index - exports all detection rules
3
3
  */
4
- export { checkJsxAttribute } from "./jsx-attribute.js";
5
- export { checkJsxText } from "./jsx-text.js";
6
- export { checkTernaryLocale } from "./ternary-locale.js";
4
+ export * from "./jsx-attribute.js";
5
+ export * from "./jsx-text.js";
6
+ export * from "./ternary-locale.js";
7
+ export * from "./translation-function.js";
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/analyzer/rules/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/analyzer/rules/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC"}
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Rules index - exports all detection rules
3
3
  */
4
- export { checkJsxAttribute } from "./jsx-attribute.js";
5
- export { checkJsxText } from "./jsx-text.js";
6
- export { checkTernaryLocale } from "./ternary-locale.js";
4
+ export * from "./jsx-attribute.js";
5
+ export * from "./jsx-text.js";
6
+ export * from "./ternary-locale.js";
7
+ export * from "./translation-function.js";
7
8
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/analyzer/rules/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/analyzer/rules/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ternary-locale.d.ts","sourceRoot":"","sources":["../../../src/analyzer/rules/ternary-locale.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEtD;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,EAAE,CAAC,qBAAqB,EAC9B,GAAG,EAAE,WAAW,GACf,KAAK,GAAG,IAAI,CA4Cd"}
1
+ {"version":3,"file":"ternary-locale.d.ts","sourceRoot":"","sources":["../../../src/analyzer/rules/ternary-locale.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEtD;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,EAAE,CAAC,qBAAqB,EAC9B,GAAG,EAAE,WAAW,GACf,KAAK,GAAG,IAAI,CAgDd"}
@@ -4,7 +4,7 @@
4
4
  * Detects anti-pattern: locale === 'en' ? 'Hello' : 'Merhaba'
5
5
  */
6
6
  import ts from "typescript";
7
- import { truncate } from "../../utils/text.js";
7
+ import { generateKeyFromContext, truncate } from "../../utils/text.js";
8
8
  /**
9
9
  * Check conditional expression for locale-based ternary
10
10
  */
@@ -35,6 +35,9 @@ export function checkTernaryLocale(node, ctx) {
35
35
  const text = hasStringTrue
36
36
  ? node.whenTrue.text
37
37
  : node.whenFalse.text;
38
+ // Ignore empty strings (common in URL construction: locale === 'en' ? '' : locale + '/')
39
+ if (text.trim() === "")
40
+ return null;
38
41
  const pos = ctx.sourceFile.getLineAndCharacterOfPosition(node.getStart());
39
42
  return {
40
43
  file: ctx.filePath,
@@ -44,6 +47,7 @@ export function checkTernaryLocale(node, ctx) {
44
47
  type: "ternary-locale",
45
48
  severity: "error", // This is an anti-pattern, so error
46
49
  message: `Locale ternary pattern detected: "${truncate(text, 30)}"`,
50
+ suggestedKey: generateKeyFromContext(text, ctx.filePath),
47
51
  };
48
52
  }
49
53
  //# sourceMappingURL=ternary-locale.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ternary-locale.js","sourceRoot":"","sources":["../../../src/analyzer/rules/ternary-locale.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAG/C;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAChC,IAA8B,EAC9B,GAAgB;IAEhB,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IAEjC,8BAA8B;IAC9B,IAAI,CAAC,EAAE,CAAC,kBAAkB,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IAEnD,iCAAiC;IACjC,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC;IAC5B,IAAI,kBAAkB,GAAG,KAAK,CAAC;IAE/B,qCAAqC;IACrC,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACpD,kBAAkB,GAAG,IAAI,CAAC;IAC5B,CAAC;IAED,kEAAkE;IAClE,IAAI,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACvE,kBAAkB,GAAG,IAAI,CAAC;IAC5B,CAAC;IAED,IAAI,CAAC,kBAAkB;QAAE,OAAO,IAAI,CAAC;IAErC,8CAA8C;IAC9C,MAAM,aAAa,GAAG,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxD,MAAM,cAAc,GAAG,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAE1D,IAAI,CAAC,aAAa,IAAI,CAAC,cAAc;QAAE,OAAO,IAAI,CAAC;IAEnD,uBAAuB;IACvB,MAAM,IAAI,GAAG,aAAa;QACxB,CAAC,CAAE,IAAI,CAAC,QAA6B,CAAC,IAAI;QAC1C,CAAC,CAAE,IAAI,CAAC,SAA8B,CAAC,IAAI,CAAC;IAE9C,MAAM,GAAG,GAAG,GAAG,CAAC,UAAU,CAAC,6BAA6B,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IAE1E,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,QAAQ;QAClB,IAAI,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC;QAClB,MAAM,EAAE,GAAG,CAAC,SAAS,GAAG,CAAC;QACzB,IAAI;QACJ,IAAI,EAAE,gBAAgB;QACtB,QAAQ,EAAE,OAAO,EAAE,oCAAoC;QACvD,OAAO,EAAE,qCAAqC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG;KACpE,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"ternary-locale.js","sourceRoot":"","sources":["../../../src/analyzer/rules/ternary-locale.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,sBAAsB,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGvE;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAChC,IAA8B,EAC9B,GAAgB;IAEhB,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IAEjC,8BAA8B;IAC9B,IAAI,CAAC,EAAE,CAAC,kBAAkB,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IAEnD,iCAAiC;IACjC,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC;IAC5B,IAAI,kBAAkB,GAAG,KAAK,CAAC;IAE/B,qCAAqC;IACrC,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACpD,kBAAkB,GAAG,IAAI,CAAC;IAC5B,CAAC;IAED,kEAAkE;IAClE,IAAI,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACvE,kBAAkB,GAAG,IAAI,CAAC;IAC5B,CAAC;IAED,IAAI,CAAC,kBAAkB;QAAE,OAAO,IAAI,CAAC;IAErC,8CAA8C;IAC9C,MAAM,aAAa,GAAG,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxD,MAAM,cAAc,GAAG,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAE1D,IAAI,CAAC,aAAa,IAAI,CAAC,cAAc;QAAE,OAAO,IAAI,CAAC;IAEnD,uBAAuB;IACvB,MAAM,IAAI,GAAG,aAAa;QACxB,CAAC,CAAE,IAAI,CAAC,QAA6B,CAAC,IAAI;QAC1C,CAAC,CAAE,IAAI,CAAC,SAA8B,CAAC,IAAI,CAAC;IAE9C,yFAAyF;IACzF,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAEpC,MAAM,GAAG,GAAG,GAAG,CAAC,UAAU,CAAC,6BAA6B,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IAE1E,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,QAAQ;QAClB,IAAI,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC;QAClB,MAAM,EAAE,GAAG,CAAC,SAAS,GAAG,CAAC;QACzB,IAAI;QACJ,IAAI,EAAE,gBAAgB;QACtB,QAAQ,EAAE,OAAO,EAAE,oCAAoC;QACvD,OAAO,EAAE,qCAAqC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG;QACnE,YAAY,EAAE,sBAAsB,CAAC,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC;KACzD,CAAC;AACJ,CAAC"}