@intl-party/eslint-plugin 1.0.0 → 1.0.2
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 +450 -0
- package/dist/index.d.ts +19 -3
- package/dist/index.js +12 -11
- package/package.json +3 -3
package/README.md
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
# @intl-party/eslint-plugin
|
|
2
|
+
|
|
3
|
+
ESLint plugin for IntlParty - enforce best practices and catch common i18n issues in your code.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🚫 **No hardcoded strings** - Detect untranslated user-facing text
|
|
8
|
+
- 🔍 **Missing translation keys** - Catch references to non-existent translation keys
|
|
9
|
+
- ⚛️ **React hooks enforcement** - Prefer translation hooks over direct i18n usage
|
|
10
|
+
- 📝 **Consistent patterns** - Enforce consistent translation patterns across your codebase
|
|
11
|
+
- ⚙️ **Configurable rules** - Customize rules to fit your project needs
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install --save-dev @intl-party/eslint-plugin
|
|
17
|
+
# or
|
|
18
|
+
pnpm add -D @intl-party/eslint-plugin
|
|
19
|
+
# or
|
|
20
|
+
yarn add --dev @intl-party/eslint-plugin
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Configuration
|
|
24
|
+
|
|
25
|
+
### Basic Setup
|
|
26
|
+
|
|
27
|
+
Add the plugin to your ESLint configuration:
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
// .eslintrc.js
|
|
31
|
+
module.exports = {
|
|
32
|
+
plugins: ["@intl-party"],
|
|
33
|
+
extends: ["@intl-party/recommended"],
|
|
34
|
+
};
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Manual Configuration
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
// .eslintrc.js
|
|
41
|
+
module.exports = {
|
|
42
|
+
plugins: ["@intl-party"],
|
|
43
|
+
rules: {
|
|
44
|
+
"@intl-party/no-hardcoded-strings": "error",
|
|
45
|
+
"@intl-party/no-missing-keys": "error",
|
|
46
|
+
"@intl-party/prefer-translation-hooks": "warn",
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### TypeScript Configuration
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
// .eslintrc.js
|
|
55
|
+
module.exports = {
|
|
56
|
+
extends: ["@intl-party/recommended", "@intl-party/typescript"],
|
|
57
|
+
parserOptions: {
|
|
58
|
+
project: "./tsconfig.json",
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Rules
|
|
64
|
+
|
|
65
|
+
### `@intl-party/no-hardcoded-strings`
|
|
66
|
+
|
|
67
|
+
Prevents hardcoded user-facing strings that should be translated.
|
|
68
|
+
|
|
69
|
+
#### ❌ Incorrect
|
|
70
|
+
|
|
71
|
+
```jsx
|
|
72
|
+
function Welcome() {
|
|
73
|
+
return <h1>Welcome to our app!</h1>; // Hardcoded string
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function Button() {
|
|
77
|
+
return <button>Click here</button>; // Hardcoded string
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const message = "Hello world"; // Hardcoded string
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### ✅ Correct
|
|
84
|
+
|
|
85
|
+
```jsx
|
|
86
|
+
function Welcome() {
|
|
87
|
+
const t = useTranslations("common");
|
|
88
|
+
return <h1>{t("welcome")}</h1>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function Button() {
|
|
92
|
+
const t = useTranslations("common");
|
|
93
|
+
return <button>{t("clickHere")}</button>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const message = t("hello");
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### Configuration
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
{
|
|
103
|
+
"@intl-party/no-hardcoded-strings": ["error", {
|
|
104
|
+
"ignorePatterns": [
|
|
105
|
+
"^\\d+$", // Numbers
|
|
106
|
+
"^[A-Z_]+$", // Constants
|
|
107
|
+
"^https?://", // URLs
|
|
108
|
+
"className", // CSS classes
|
|
109
|
+
"data-*" // Data attributes
|
|
110
|
+
],
|
|
111
|
+
"ignoreElements": ["script", "style"],
|
|
112
|
+
"ignoreAttributes": ["className", "id", "data-testid"]
|
|
113
|
+
}]
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### `@intl-party/no-missing-keys`
|
|
118
|
+
|
|
119
|
+
Catches references to translation keys that don't exist in your translation files.
|
|
120
|
+
|
|
121
|
+
#### ❌ Incorrect
|
|
122
|
+
|
|
123
|
+
```jsx
|
|
124
|
+
function Component() {
|
|
125
|
+
const t = useTranslations("common");
|
|
126
|
+
return <h1>{t("nonExistentKey")}</h1>; // Key doesn't exist
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### ✅ Correct
|
|
131
|
+
|
|
132
|
+
```jsx
|
|
133
|
+
function Component() {
|
|
134
|
+
const t = useTranslations("common");
|
|
135
|
+
return <h1>{t("welcome")}</h1>; // Key exists in common namespace
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### Configuration
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
{
|
|
143
|
+
"@intl-party/no-missing-keys": ["error", {
|
|
144
|
+
"translationsPath": "./messages",
|
|
145
|
+
"defaultLocale": "en",
|
|
146
|
+
"namespaces": ["common", "navigation"],
|
|
147
|
+
"checkDynamicKeys": false
|
|
148
|
+
}]
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### `@intl-party/prefer-translation-hooks`
|
|
153
|
+
|
|
154
|
+
Encourages using translation hooks instead of direct i18n instance usage in React components.
|
|
155
|
+
|
|
156
|
+
#### ❌ Incorrect
|
|
157
|
+
|
|
158
|
+
```jsx
|
|
159
|
+
function Component() {
|
|
160
|
+
const { i18n } = useI18nContext();
|
|
161
|
+
return <h1>{i18n.t("welcome")}</h1>; // Direct i18n usage
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### ✅ Correct
|
|
166
|
+
|
|
167
|
+
```jsx
|
|
168
|
+
function Component() {
|
|
169
|
+
const t = useTranslations("common");
|
|
170
|
+
return <h1>{t("welcome")}</h1>; // Using translation hook
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
#### Configuration
|
|
175
|
+
|
|
176
|
+
```javascript
|
|
177
|
+
{
|
|
178
|
+
"@intl-party/prefer-translation-hooks": ["warn", {
|
|
179
|
+
"allowedMethods": ["formatDate", "formatNumber"],
|
|
180
|
+
"ignoreServerComponents": true
|
|
181
|
+
}]
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Configuration Presets
|
|
186
|
+
|
|
187
|
+
### Recommended Preset
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
// Balanced rules for most projects
|
|
191
|
+
{
|
|
192
|
+
"extends": ["@intl-party/recommended"]
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Includes:
|
|
197
|
+
|
|
198
|
+
- `@intl-party/no-hardcoded-strings`: `error`
|
|
199
|
+
- `@intl-party/no-missing-keys`: `error`
|
|
200
|
+
- `@intl-party/prefer-translation-hooks`: `warn`
|
|
201
|
+
|
|
202
|
+
### Strict Preset
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
// Stricter rules for high-quality i18n
|
|
206
|
+
{
|
|
207
|
+
"extends": ["@intl-party/strict"]
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Includes all recommended rules plus:
|
|
212
|
+
|
|
213
|
+
- Stricter hardcoded string detection
|
|
214
|
+
- Enforcement of namespace consistency
|
|
215
|
+
- Required translation comments
|
|
216
|
+
|
|
217
|
+
### TypeScript Preset
|
|
218
|
+
|
|
219
|
+
```javascript
|
|
220
|
+
// Additional rules for TypeScript projects
|
|
221
|
+
{
|
|
222
|
+
"extends": [
|
|
223
|
+
"@intl-party/recommended",
|
|
224
|
+
"@intl-party/typescript"
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Includes type-aware rules and TypeScript-specific checks.
|
|
230
|
+
|
|
231
|
+
## Advanced Configuration
|
|
232
|
+
|
|
233
|
+
### Project-Specific Settings
|
|
234
|
+
|
|
235
|
+
```javascript
|
|
236
|
+
// .eslintrc.js
|
|
237
|
+
module.exports = {
|
|
238
|
+
plugins: ["@intl-party"],
|
|
239
|
+
settings: {
|
|
240
|
+
"intl-party": {
|
|
241
|
+
// Path to translation files
|
|
242
|
+
translationsPath: "./src/locales",
|
|
243
|
+
|
|
244
|
+
// Default locale for key validation
|
|
245
|
+
defaultLocale: "en",
|
|
246
|
+
|
|
247
|
+
// Available namespaces
|
|
248
|
+
namespaces: ["common", "navigation", "forms"],
|
|
249
|
+
|
|
250
|
+
// Translation file pattern
|
|
251
|
+
filePattern: "{locale}/{namespace}.json",
|
|
252
|
+
|
|
253
|
+
// Ignore patterns for hardcoded strings
|
|
254
|
+
ignorePatterns: [
|
|
255
|
+
"^[A-Z_]+$", // Constants
|
|
256
|
+
"^\\d+$", // Numbers
|
|
257
|
+
"^https?://", // URLs
|
|
258
|
+
"^mailto:", // Email links
|
|
259
|
+
"^tel:", // Phone links
|
|
260
|
+
],
|
|
261
|
+
|
|
262
|
+
// Elements to ignore for hardcoded strings
|
|
263
|
+
ignoreElements: ["script", "style", "code", "pre"],
|
|
264
|
+
|
|
265
|
+
// Attributes to ignore
|
|
266
|
+
ignoreAttributes: ["className", "id", "data-*", "aria-*"],
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
rules: {
|
|
270
|
+
"@intl-party/no-hardcoded-strings": "error",
|
|
271
|
+
"@intl-party/no-missing-keys": "error",
|
|
272
|
+
"@intl-party/prefer-translation-hooks": "warn",
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Framework-Specific Configuration
|
|
278
|
+
|
|
279
|
+
#### Next.js
|
|
280
|
+
|
|
281
|
+
```javascript
|
|
282
|
+
// .eslintrc.js
|
|
283
|
+
module.exports = {
|
|
284
|
+
extends: ["next/core-web-vitals", "@intl-party/recommended"],
|
|
285
|
+
settings: {
|
|
286
|
+
"intl-party": {
|
|
287
|
+
translationsPath: "./messages",
|
|
288
|
+
framework: "nextjs",
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
#### React
|
|
295
|
+
|
|
296
|
+
```javascript
|
|
297
|
+
// .eslintrc.js
|
|
298
|
+
module.exports = {
|
|
299
|
+
extends: ["react-app", "@intl-party/recommended"],
|
|
300
|
+
settings: {
|
|
301
|
+
"intl-party": {
|
|
302
|
+
translationsPath: "./src/translations",
|
|
303
|
+
framework: "react",
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Integration with Build Tools
|
|
310
|
+
|
|
311
|
+
### CI/CD Integration
|
|
312
|
+
|
|
313
|
+
```yaml
|
|
314
|
+
# .github/workflows/lint.yml
|
|
315
|
+
name: Lint
|
|
316
|
+
on: [push, pull_request]
|
|
317
|
+
|
|
318
|
+
jobs:
|
|
319
|
+
lint:
|
|
320
|
+
runs-on: ubuntu-latest
|
|
321
|
+
steps:
|
|
322
|
+
- uses: actions/checkout@v3
|
|
323
|
+
- uses: actions/setup-node@v3
|
|
324
|
+
- run: npm ci
|
|
325
|
+
- run: npm run lint
|
|
326
|
+
- run: npm run lint:i18n # Custom script for i18n-specific linting
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Pre-commit Hooks
|
|
330
|
+
|
|
331
|
+
```json
|
|
332
|
+
// package.json
|
|
333
|
+
{
|
|
334
|
+
"husky": {
|
|
335
|
+
"hooks": {
|
|
336
|
+
"pre-commit": "lint-staged"
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
"lint-staged": {
|
|
340
|
+
"**/*.{js,jsx,ts,tsx}": [
|
|
341
|
+
"eslint --fix",
|
|
342
|
+
"eslint --ext .js,.jsx,.ts,.tsx --config .eslintrc.i18n.js"
|
|
343
|
+
]
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Custom Scripts
|
|
349
|
+
|
|
350
|
+
```json
|
|
351
|
+
// package.json
|
|
352
|
+
{
|
|
353
|
+
"scripts": {
|
|
354
|
+
"lint": "eslint src/",
|
|
355
|
+
"lint:i18n": "eslint src/ --config .eslintrc.i18n.js",
|
|
356
|
+
"lint:fix": "eslint src/ --fix"
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## Troubleshooting
|
|
362
|
+
|
|
363
|
+
### Common Issues
|
|
364
|
+
|
|
365
|
+
1. **False positives for hardcoded strings**
|
|
366
|
+
- Add patterns to `ignorePatterns` in rule configuration
|
|
367
|
+
- Use `eslint-disable-next-line` comments for specific cases
|
|
368
|
+
|
|
369
|
+
2. **Missing key errors for dynamic keys**
|
|
370
|
+
- Set `checkDynamicKeys: false` in rule configuration
|
|
371
|
+
- Use template strings for predictable patterns
|
|
372
|
+
|
|
373
|
+
3. **Performance issues with large translation files**
|
|
374
|
+
- Use `translationsPath` setting to optimize file loading
|
|
375
|
+
- Consider splitting large translation files
|
|
376
|
+
|
|
377
|
+
### Debug Mode
|
|
378
|
+
|
|
379
|
+
Enable debug logging to troubleshoot rule issues:
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
DEBUG=eslint-plugin-intl-party eslint src/
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Custom Rule Configuration
|
|
386
|
+
|
|
387
|
+
```javascript
|
|
388
|
+
// For specific files or patterns
|
|
389
|
+
{
|
|
390
|
+
"overrides": [
|
|
391
|
+
{
|
|
392
|
+
"files": ["**/*.test.{js,jsx,ts,tsx}"],
|
|
393
|
+
"rules": {
|
|
394
|
+
"@intl-party/no-hardcoded-strings": "off"
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
"files": ["**/admin/**"],
|
|
399
|
+
"rules": {
|
|
400
|
+
"@intl-party/no-missing-keys": "warn"
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
]
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## Examples
|
|
408
|
+
|
|
409
|
+
### Real-world Configuration
|
|
410
|
+
|
|
411
|
+
```javascript
|
|
412
|
+
// .eslintrc.js for a Next.js project
|
|
413
|
+
module.exports = {
|
|
414
|
+
extends: ["next/core-web-vitals", "@intl-party/recommended"],
|
|
415
|
+
settings: {
|
|
416
|
+
"intl-party": {
|
|
417
|
+
translationsPath: "./messages",
|
|
418
|
+
defaultLocale: "en",
|
|
419
|
+
namespaces: ["common", "navigation", "forms", "errors"],
|
|
420
|
+
ignorePatterns: [
|
|
421
|
+
"^[A-Z_]+$",
|
|
422
|
+
"^\\d+(\\.\\d+)?$",
|
|
423
|
+
"^#[0-9a-fA-F]{3,6}$",
|
|
424
|
+
"^rgb\\(",
|
|
425
|
+
"^https?://",
|
|
426
|
+
"^mailto:",
|
|
427
|
+
"^\\+\\d",
|
|
428
|
+
],
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
overrides: [
|
|
432
|
+
{
|
|
433
|
+
files: ["**/*.test.{js,jsx,ts,tsx}", "**/*.stories.{js,jsx,ts,tsx}"],
|
|
434
|
+
rules: {
|
|
435
|
+
"@intl-party/no-hardcoded-strings": "off",
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
files: ["**/admin/**", "**/cms/**"],
|
|
440
|
+
rules: {
|
|
441
|
+
"@intl-party/no-hardcoded-strings": "warn",
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
],
|
|
445
|
+
};
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
## License
|
|
449
|
+
|
|
450
|
+
MIT © IntlParty
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
import * as _typescript_eslint_utils_ts_eslint from '@typescript-eslint/utils/ts-eslint';
|
|
2
2
|
|
|
3
|
+
interface PreferTranslationHooksOptions {
|
|
4
|
+
allowDirectUsage?: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface NoMissingKeysOptions {
|
|
8
|
+
translationFiles?: string[];
|
|
9
|
+
defaultLocale?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface NoHardcodedStringsOptions {
|
|
13
|
+
attributes?: string[];
|
|
14
|
+
ignorePattern?: string;
|
|
15
|
+
minLength?: number;
|
|
16
|
+
allowedStrings?: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
3
19
|
declare const plugin: {
|
|
4
20
|
meta: {
|
|
5
21
|
name: string;
|
|
6
22
|
version: string;
|
|
7
23
|
};
|
|
8
24
|
rules: {
|
|
9
|
-
"no-hardcoded-strings": _typescript_eslint_utils_ts_eslint.RuleModule<"hardcodedString" | "hardcodedStringInAttribute", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
10
|
-
"no-missing-keys": _typescript_eslint_utils_ts_eslint.RuleModule<"missingTranslationKey" | "invalidTranslationKey", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
11
|
-
"prefer-translation-hooks": _typescript_eslint_utils_ts_eslint.RuleModule<"preferUseTranslations" | "preferScopedTranslations", [], _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
25
|
+
"no-hardcoded-strings": _typescript_eslint_utils_ts_eslint.RuleModule<"hardcodedString" | "hardcodedStringInAttribute", [NoHardcodedStringsOptions], _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
26
|
+
"no-missing-keys": _typescript_eslint_utils_ts_eslint.RuleModule<"missingTranslationKey" | "invalidTranslationKey", [NoMissingKeysOptions], _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
27
|
+
"prefer-translation-hooks": _typescript_eslint_utils_ts_eslint.RuleModule<"preferUseTranslations" | "preferScopedTranslations", [PreferTranslationHooksOptions], _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
12
28
|
};
|
|
13
29
|
configs: {
|
|
14
30
|
recommended: {
|
package/dist/index.js
CHANGED
|
@@ -34,7 +34,7 @@ var noHardcodedStrings = import_utils.ESLintUtils.RuleCreator(
|
|
|
34
34
|
type: "problem",
|
|
35
35
|
docs: {
|
|
36
36
|
description: "Disallow hardcoded strings in JSX elements and specific attributes",
|
|
37
|
-
recommended: "
|
|
37
|
+
recommended: "recommended"
|
|
38
38
|
},
|
|
39
39
|
fixable: "code",
|
|
40
40
|
schema: [
|
|
@@ -77,7 +77,7 @@ var noHardcodedStrings = import_utils.ESLintUtils.RuleCreator(
|
|
|
77
77
|
}
|
|
78
78
|
},
|
|
79
79
|
defaultOptions: [{}],
|
|
80
|
-
create(context, [options
|
|
80
|
+
create(context, [options]) {
|
|
81
81
|
const {
|
|
82
82
|
attributes = [
|
|
83
83
|
"placeholder",
|
|
@@ -89,7 +89,7 @@ var noHardcodedStrings = import_utils.ESLintUtils.RuleCreator(
|
|
|
89
89
|
ignorePattern,
|
|
90
90
|
minLength = 3,
|
|
91
91
|
allowedStrings = []
|
|
92
|
-
} = options;
|
|
92
|
+
} = options || {};
|
|
93
93
|
const ignoreRegex = ignorePattern ? new RegExp(ignorePattern) : null;
|
|
94
94
|
function isHardcodedString(value) {
|
|
95
95
|
if (value.length < minLength) return false;
|
|
@@ -117,17 +117,18 @@ var noHardcodedStrings = import_utils.ESLintUtils.RuleCreator(
|
|
|
117
117
|
}
|
|
118
118
|
function checkJSXAttribute(node) {
|
|
119
119
|
if (node.name.type === "JSXIdentifier" && attributes.includes(node.name.name) && node.value?.type === "Literal" && typeof node.value.value === "string" && isHardcodedString(node.value.value)) {
|
|
120
|
+
const literalValue = node.value.value;
|
|
120
121
|
context.report({
|
|
121
122
|
node: node.value,
|
|
122
123
|
messageId: "hardcodedStringInAttribute",
|
|
123
124
|
data: {
|
|
124
|
-
text:
|
|
125
|
+
text: literalValue,
|
|
125
126
|
attribute: node.name.name
|
|
126
127
|
},
|
|
127
128
|
fix(fixer) {
|
|
128
129
|
return fixer.replaceText(
|
|
129
130
|
node.value,
|
|
130
|
-
`{t('${generateTranslationKey(
|
|
131
|
+
`{t('${generateTranslationKey(literalValue)}')}`
|
|
131
132
|
);
|
|
132
133
|
}
|
|
133
134
|
});
|
|
@@ -169,7 +170,7 @@ var noMissingKeys = import_utils2.ESLintUtils.RuleCreator(
|
|
|
169
170
|
type: "problem",
|
|
170
171
|
docs: {
|
|
171
172
|
description: "Ensure all translation keys exist in translation files",
|
|
172
|
-
recommended: "
|
|
173
|
+
recommended: "recommended"
|
|
173
174
|
},
|
|
174
175
|
schema: [
|
|
175
176
|
{
|
|
@@ -195,8 +196,8 @@ var noMissingKeys = import_utils2.ESLintUtils.RuleCreator(
|
|
|
195
196
|
}
|
|
196
197
|
},
|
|
197
198
|
defaultOptions: [{}],
|
|
198
|
-
create(context, [options
|
|
199
|
-
const { translationFiles = [], defaultLocale = "en" } = options;
|
|
199
|
+
create(context, [options]) {
|
|
200
|
+
const { translationFiles = [], defaultLocale = "en" } = options || {};
|
|
200
201
|
const translationKeys = /* @__PURE__ */ new Set();
|
|
201
202
|
function isValidTranslationKey(key) {
|
|
202
203
|
return /^[a-zA-Z][a-zA-Z0-9._-]*$/.test(key);
|
|
@@ -237,7 +238,7 @@ var preferTranslationHooks = import_utils3.ESLintUtils.RuleCreator(
|
|
|
237
238
|
type: "suggestion",
|
|
238
239
|
docs: {
|
|
239
240
|
description: "Prefer using translation hooks over direct i18n instance usage in React components",
|
|
240
|
-
recommended: "
|
|
241
|
+
recommended: "recommended"
|
|
241
242
|
},
|
|
242
243
|
fixable: "code",
|
|
243
244
|
schema: [
|
|
@@ -259,8 +260,8 @@ var preferTranslationHooks = import_utils3.ESLintUtils.RuleCreator(
|
|
|
259
260
|
}
|
|
260
261
|
},
|
|
261
262
|
defaultOptions: [{}],
|
|
262
|
-
create(context, [options
|
|
263
|
-
const { allowDirectUsage = false } = options;
|
|
263
|
+
create(context, [options]) {
|
|
264
|
+
const { allowDirectUsage = false } = options || {};
|
|
264
265
|
function checkMemberExpression(node) {
|
|
265
266
|
if (node.object.type === "Identifier" && node.object.name === "i18n" && node.property.type === "Identifier" && node.property.name === "t" && !allowDirectUsage) {
|
|
266
267
|
context.report({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intl-party/eslint-plugin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "ESLint plugin for IntlParty - detect hardcoded strings and enforce i18n best practices",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsup src/index.ts --format cjs --dts",
|
|
45
45
|
"dev": "tsup src/index.ts --format cjs --dts --watch",
|
|
46
|
-
"test": "vitest",
|
|
47
|
-
"test:watch": "vitest
|
|
46
|
+
"test": "vitest --run",
|
|
47
|
+
"test:watch": "vitest",
|
|
48
48
|
"lint": "eslint src --ext .ts",
|
|
49
49
|
"typecheck": "tsc --noEmit",
|
|
50
50
|
"clean": "rm -rf dist"
|