@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.
- package/README.md +363 -85
- package/dist/analyzer/index.d.ts.map +1 -1
- package/dist/analyzer/index.js +7 -3
- package/dist/analyzer/index.js.map +1 -1
- package/dist/analyzer/rules/index.d.ts +4 -3
- package/dist/analyzer/rules/index.d.ts.map +1 -1
- package/dist/analyzer/rules/index.js +4 -3
- package/dist/analyzer/rules/index.js.map +1 -1
- package/dist/analyzer/rules/ternary-locale.d.ts.map +1 -1
- package/dist/analyzer/rules/ternary-locale.js +5 -1
- package/dist/analyzer/rules/ternary-locale.js.map +1 -1
- package/dist/analyzer/rules/translation-function.d.ts +12 -0
- package/dist/analyzer/rules/translation-function.d.ts.map +1 -0
- package/dist/analyzer/rules/translation-function.js +65 -0
- package/dist/analyzer/rules/translation-function.js.map +1 -0
- package/dist/analyzer/types.d.ts +14 -1
- package/dist/analyzer/types.d.ts.map +1 -1
- package/dist/commands/extract-keys.d.ts +13 -0
- package/dist/commands/extract-keys.d.ts.map +1 -0
- package/dist/commands/extract-keys.js +347 -0
- package/dist/commands/extract-keys.js.map +1 -0
- package/dist/commands/scan.d.ts.map +1 -1
- package/dist/commands/scan.js +3 -1
- package/dist/commands/scan.js.map +1 -1
- package/dist/context/detector.js +2 -2
- package/dist/context/detector.js.map +1 -1
- package/dist/index.js +14 -2
- package/dist/index.js.map +1 -1
- package/dist/reporters/eslint-style.d.ts +1 -1
- package/dist/reporters/eslint-style.d.ts.map +1 -1
- package/dist/reporters/eslint-style.js +19 -8
- package/dist/reporters/eslint-style.js.map +1 -1
- package/dist/reporters/json.d.ts.map +1 -1
- package/dist/reporters/json.js +12 -13
- package/dist/reporters/json.js.map +1 -1
- 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
|
-
##
|
|
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
|
-
#
|
|
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
|
-
#
|
|
56
|
-
better-i18n
|
|
115
|
+
# Compare with specific locale
|
|
116
|
+
better-i18n extract-keys --compare --locale tr
|
|
57
117
|
|
|
58
|
-
#
|
|
59
|
-
better-i18n
|
|
118
|
+
# JSON output (default)
|
|
119
|
+
better-i18n extract-keys --format json
|
|
60
120
|
|
|
61
|
-
#
|
|
62
|
-
better-i18n
|
|
121
|
+
# Show detailed output
|
|
122
|
+
better-i18n extract-keys --verbose
|
|
63
123
|
```
|
|
64
124
|
|
|
65
|
-
|
|
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
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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
|
-
|
|
184
|
+
⚠️ Unused in Code (1 key):
|
|
185
|
+
old: 1 key
|
|
186
|
+
• old.section
|
|
81
187
|
|
|
82
|
-
Scanned
|
|
188
|
+
Scanned 57 files in 0.12s
|
|
189
|
+
✓ Comparison complete
|
|
83
190
|
```
|
|
84
191
|
|
|
85
192
|
## Detection Rules
|
|
86
193
|
|
|
87
|
-
| Rule
|
|
88
|
-
|
|
89
|
-
| `jsx-text`
|
|
90
|
-
| `jsx-attribute` |
|
|
91
|
-
| `ternary-locale` | error | `locale === 'en' ? '
|
|
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: `"`, `&`, `'`
|
|
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
|
-
|
|
212
|
+
Create or update your `i18n.config.ts`:
|
|
96
213
|
|
|
97
214
|
```ts
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
export const
|
|
102
|
-
project
|
|
103
|
-
defaultLocale
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
132
|
-
name: i18n
|
|
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:
|
|
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
|
-
###
|
|
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
|
-
|
|
312
|
+
Add to `.vscode/tasks.json`:
|
|
157
313
|
|
|
158
314
|
```json
|
|
159
315
|
{
|
|
160
|
-
"
|
|
161
|
-
|
|
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
|
|
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": "
|
|
178
|
-
"projectSlug": "
|
|
358
|
+
"workspaceId": "better-i18n",
|
|
359
|
+
"projectSlug": "landing",
|
|
179
360
|
"defaultLocale": "en"
|
|
180
361
|
},
|
|
181
|
-
"files":
|
|
362
|
+
"files": 57,
|
|
182
363
|
"issues": [
|
|
183
364
|
{
|
|
184
|
-
"file": "
|
|
185
|
-
"line":
|
|
186
|
-
"column":
|
|
187
|
-
"text": "
|
|
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
|
|
371
|
+
"message": "Hardcoded text: \"Create an account\"",
|
|
372
|
+
"suggestedKey": "signUp.createAnAccount"
|
|
191
373
|
}
|
|
192
374
|
],
|
|
193
|
-
"duration":
|
|
375
|
+
"duration": 67
|
|
194
376
|
}
|
|
195
377
|
```
|
|
196
378
|
|
|
197
|
-
|
|
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
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
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
|
-
|
|
490
|
+
## Contributing
|
|
211
491
|
|
|
212
|
-
|
|
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;
|
|
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"}
|
package/dist/analyzer/index.js
CHANGED
|
@@ -5,9 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { readFileSync } from "node:fs";
|
|
7
7
|
import ts from "typescript";
|
|
8
|
-
import { checkJsxAttribute } from "./rules/
|
|
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":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,
|
|
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
|
|
5
|
-
export
|
|
6
|
-
export
|
|
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,
|
|
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
|
|
5
|
-
export
|
|
6
|
-
export
|
|
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,
|
|
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,
|
|
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;
|
|
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"}
|