@nkzw/eslint-plugin-fbtee 1.3.0 → 1.3.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 +40 -16
- package/lib/index.cjs +12 -7
- package/lib/index.js +12 -7
- package/package.json +3 -3
- package/src/__tests__/no-untranslated-strings-test.tsx +36 -0
- package/src/rules/no-untranslated-strings.tsx +23 -4
package/README.md
CHANGED
|
@@ -605,7 +605,7 @@ const Root = () => {
|
|
|
605
605
|
|
|
606
606
|
## ESLint Plugin
|
|
607
607
|
|
|
608
|
-
You can install the optional
|
|
608
|
+
You can install the optional ESLint plugin to catch common mistakes and enforce best practices when using **fbtee**:
|
|
609
609
|
|
|
610
610
|
```bash
|
|
611
611
|
npm install -D @nkzw/eslint-plugin-fbtee
|
|
@@ -614,32 +614,56 @@ npm install -D @nkzw/eslint-plugin-fbtee
|
|
|
614
614
|
Add the following configuration to your ESLint configuration:
|
|
615
615
|
|
|
616
616
|
```js
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
617
|
+
import fbtee from '@nkzw/eslint-plugin-fbtee';
|
|
618
|
+
|
|
619
|
+
export default [
|
|
620
|
+
fbtee.configs.recommended,
|
|
621
|
+
{
|
|
622
|
+
plugins: {
|
|
623
|
+
'@nkzw/fbtee': fbtee,
|
|
624
|
+
},
|
|
625
|
+
},
|
|
626
|
+
];
|
|
621
627
|
```
|
|
622
628
|
|
|
623
629
|
If you want stricter enforcement of translation rules, you can use the strict configuration, which enables the `no-untranslated-strings` rule. This ensures that all strings in your codebase are marked for translation.
|
|
624
630
|
|
|
625
631
|
```js
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
632
|
+
import fbtee from '@nkzw/eslint-plugin-fbtee';
|
|
633
|
+
|
|
634
|
+
export default [
|
|
635
|
+
fbtee.configs.strict,
|
|
636
|
+
{
|
|
637
|
+
plugins: {
|
|
638
|
+
'@nkzw/fbtee': fbtee,
|
|
639
|
+
},
|
|
640
|
+
},
|
|
641
|
+
];
|
|
630
642
|
```
|
|
631
643
|
|
|
632
644
|
Alternatively, if you'd like more granular control over the rules:
|
|
633
645
|
|
|
634
646
|
```js
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
647
|
+
import fbtee from '@nkzw/eslint-plugin-fbtee';
|
|
648
|
+
|
|
649
|
+
export default [
|
|
650
|
+
fbtee,
|
|
651
|
+
{
|
|
652
|
+
plugins: {
|
|
653
|
+
'@nkzw/fbtee': fbtee,
|
|
654
|
+
},
|
|
655
|
+
rules: {
|
|
656
|
+
'@nkzw/fbtee/no-empty-strings': [
|
|
657
|
+
'error',
|
|
658
|
+
{
|
|
659
|
+
ignoredWords: ['Banana', 'pnpm install fbtee'],
|
|
660
|
+
},
|
|
661
|
+
],
|
|
662
|
+
'@nkzw/fbtee/no-unhelpful-desc': 'error',
|
|
663
|
+
'@nkzw/fbtee/no-untranslated-strings': 'error',
|
|
664
|
+
},
|
|
641
665
|
},
|
|
642
|
-
|
|
666
|
+
];
|
|
643
667
|
```
|
|
644
668
|
|
|
645
669
|
## What's better about `fbtee` than `fbt`?
|
package/lib/index.cjs
CHANGED
|
@@ -25,7 +25,7 @@ const __typescript_eslint_utils = __toESM(require("@typescript-eslint/utils"));
|
|
|
25
25
|
|
|
26
26
|
//#region package.json
|
|
27
27
|
var name = "@nkzw/eslint-plugin-fbtee";
|
|
28
|
-
var version = "1.3.
|
|
28
|
+
var version = "1.3.2";
|
|
29
29
|
var description = "";
|
|
30
30
|
var keywords = [];
|
|
31
31
|
var repository = {
|
|
@@ -41,8 +41,8 @@ var exports$1 = {
|
|
|
41
41
|
};
|
|
42
42
|
var main = "lib/index.cjs";
|
|
43
43
|
var scripts = { "build": "tsdown src/index.tsx -d lib --target=node22 --format=esm,cjs --clean --dts" };
|
|
44
|
-
var dependencies = { "@typescript-eslint/utils": "^8.
|
|
45
|
-
var devDependencies = { "@typescript-eslint/rule-tester": "^8.
|
|
44
|
+
var dependencies = { "@typescript-eslint/utils": "^8.38.0" };
|
|
45
|
+
var devDependencies = { "@typescript-eslint/rule-tester": "^8.38.0" };
|
|
46
46
|
var package_default = {
|
|
47
47
|
name,
|
|
48
48
|
version,
|
|
@@ -284,12 +284,15 @@ const attributes = new Set([
|
|
|
284
284
|
"aria-label",
|
|
285
285
|
"aria-errormessage"
|
|
286
286
|
]);
|
|
287
|
+
const shouldIgnoreParent = (node) => {
|
|
288
|
+
const { parent } = node;
|
|
289
|
+
const openingElement = parent?.type === "JSXElement" ? parent?.openingElement : null;
|
|
290
|
+
return openingElement?.type === "JSXOpeningElement" && openingElement.name.type === "JSXIdentifier" && (openingElement.name.name === "pre" || openingElement.name.name === "code");
|
|
291
|
+
};
|
|
287
292
|
var no_untranslated_strings_default = createRule({
|
|
288
293
|
create(context, options) {
|
|
289
|
-
const ignoredWords = new Set(options[0].ignoredWords.map((
|
|
290
|
-
|
|
291
|
-
return ignoredWords.has(value.trim().toLowerCase());
|
|
292
|
-
}
|
|
294
|
+
const ignoredWords = new Set(options[0].ignoredWords.map((value) => value.trim().toLowerCase()));
|
|
295
|
+
const isIgnoredWord = (value) => ignoredWords.has(value.replaceAll(/\s+/g, " ").toLowerCase());
|
|
293
296
|
return {
|
|
294
297
|
JSXAttribute(node) {
|
|
295
298
|
const propName = getPropName(node);
|
|
@@ -308,6 +311,7 @@ var no_untranslated_strings_default = createRule({
|
|
|
308
311
|
const value = resolveNodeValue(node.expression);
|
|
309
312
|
if (!value) return;
|
|
310
313
|
if (isIgnoredWord(value)) return;
|
|
314
|
+
if (shouldIgnoreParent(node)) return;
|
|
311
315
|
if (!hasFbtParent(node)) context.report({
|
|
312
316
|
messageId: "unwrappedString",
|
|
313
317
|
node
|
|
@@ -317,6 +321,7 @@ var no_untranslated_strings_default = createRule({
|
|
|
317
321
|
const value = node.value.trim();
|
|
318
322
|
if (!value) return;
|
|
319
323
|
if (isIgnoredWord(value)) return;
|
|
324
|
+
if (shouldIgnoreParent(node)) return;
|
|
320
325
|
if (!hasFbtParent(node)) {
|
|
321
326
|
const startOffset = node.range[0] + node.value.indexOf(value);
|
|
322
327
|
const endOffset = startOffset + value.length;
|
package/lib/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { ESLintUtils } from "@typescript-eslint/utils";
|
|
|
2
2
|
|
|
3
3
|
//#region package.json
|
|
4
4
|
var name = "@nkzw/eslint-plugin-fbtee";
|
|
5
|
-
var version = "1.3.
|
|
5
|
+
var version = "1.3.2";
|
|
6
6
|
var description = "";
|
|
7
7
|
var keywords = [];
|
|
8
8
|
var repository = {
|
|
@@ -18,8 +18,8 @@ var exports = {
|
|
|
18
18
|
};
|
|
19
19
|
var main = "lib/index.cjs";
|
|
20
20
|
var scripts = { "build": "tsdown src/index.tsx -d lib --target=node22 --format=esm,cjs --clean --dts" };
|
|
21
|
-
var dependencies = { "@typescript-eslint/utils": "^8.
|
|
22
|
-
var devDependencies = { "@typescript-eslint/rule-tester": "^8.
|
|
21
|
+
var dependencies = { "@typescript-eslint/utils": "^8.38.0" };
|
|
22
|
+
var devDependencies = { "@typescript-eslint/rule-tester": "^8.38.0" };
|
|
23
23
|
var package_default = {
|
|
24
24
|
name,
|
|
25
25
|
version,
|
|
@@ -261,12 +261,15 @@ const attributes = new Set([
|
|
|
261
261
|
"aria-label",
|
|
262
262
|
"aria-errormessage"
|
|
263
263
|
]);
|
|
264
|
+
const shouldIgnoreParent = (node) => {
|
|
265
|
+
const { parent } = node;
|
|
266
|
+
const openingElement = parent?.type === "JSXElement" ? parent?.openingElement : null;
|
|
267
|
+
return openingElement?.type === "JSXOpeningElement" && openingElement.name.type === "JSXIdentifier" && (openingElement.name.name === "pre" || openingElement.name.name === "code");
|
|
268
|
+
};
|
|
264
269
|
var no_untranslated_strings_default = createRule({
|
|
265
270
|
create(context, options) {
|
|
266
|
-
const ignoredWords = new Set(options[0].ignoredWords.map((
|
|
267
|
-
|
|
268
|
-
return ignoredWords.has(value.trim().toLowerCase());
|
|
269
|
-
}
|
|
271
|
+
const ignoredWords = new Set(options[0].ignoredWords.map((value) => value.trim().toLowerCase()));
|
|
272
|
+
const isIgnoredWord = (value) => ignoredWords.has(value.replaceAll(/\s+/g, " ").toLowerCase());
|
|
270
273
|
return {
|
|
271
274
|
JSXAttribute(node) {
|
|
272
275
|
const propName = getPropName(node);
|
|
@@ -285,6 +288,7 @@ var no_untranslated_strings_default = createRule({
|
|
|
285
288
|
const value = resolveNodeValue(node.expression);
|
|
286
289
|
if (!value) return;
|
|
287
290
|
if (isIgnoredWord(value)) return;
|
|
291
|
+
if (shouldIgnoreParent(node)) return;
|
|
288
292
|
if (!hasFbtParent(node)) context.report({
|
|
289
293
|
messageId: "unwrappedString",
|
|
290
294
|
node
|
|
@@ -294,6 +298,7 @@ var no_untranslated_strings_default = createRule({
|
|
|
294
298
|
const value = node.value.trim();
|
|
295
299
|
if (!value) return;
|
|
296
300
|
if (isIgnoredWord(value)) return;
|
|
301
|
+
if (shouldIgnoreParent(node)) return;
|
|
297
302
|
if (!hasFbtParent(node)) {
|
|
298
303
|
const startOffset = node.range[0] + node.value.indexOf(value);
|
|
299
304
|
const endOffset = startOffset + value.length;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nkzw/eslint-plugin-fbtee",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"repository": {
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
},
|
|
17
17
|
"main": "lib/index.cjs",
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@typescript-eslint/utils": "^8.
|
|
19
|
+
"@typescript-eslint/utils": "^8.38.0"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
|
-
"@typescript-eslint/rule-tester": "^8.
|
|
22
|
+
"@typescript-eslint/rule-tester": "^8.38.0"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "tsdown src/index.tsx -d lib --target=node22 --format=esm,cjs --clean --dts"
|
|
@@ -225,6 +225,28 @@ ruleTester.run('no-untranslated-strings', rule, {
|
|
|
225
225
|
},
|
|
226
226
|
],
|
|
227
227
|
},
|
|
228
|
+
{
|
|
229
|
+
code: `
|
|
230
|
+
<div>
|
|
231
|
+
<p>GitHub is cool</p>
|
|
232
|
+
<p>{\`GitHub
|
|
233
|
+
is cool\`}</p>
|
|
234
|
+
<p>
|
|
235
|
+
GitHub
|
|
236
|
+
is
|
|
237
|
+
cool
|
|
238
|
+
</p>
|
|
239
|
+
<p>
|
|
240
|
+
GitHub is cool
|
|
241
|
+
</p>
|
|
242
|
+
</div>
|
|
243
|
+
`,
|
|
244
|
+
options: [
|
|
245
|
+
{
|
|
246
|
+
ignoredWords: ['GitHub is cool'],
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
},
|
|
228
250
|
{
|
|
229
251
|
code: `
|
|
230
252
|
<textarea rows={2}></textarea>
|
|
@@ -275,5 +297,19 @@ ruleTester.run('no-untranslated-strings', rule, {
|
|
|
275
297
|
const foo = helloGreeting || 'goodbye'
|
|
276
298
|
`,
|
|
277
299
|
},
|
|
300
|
+
{
|
|
301
|
+
code: `
|
|
302
|
+
<code>
|
|
303
|
+
console.log('Banana Banana Banana');
|
|
304
|
+
</code>
|
|
305
|
+
`,
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
code: `
|
|
309
|
+
<pre>{\`
|
|
310
|
+
console.log('Banana Banana Banana');
|
|
311
|
+
\`}</pre>
|
|
312
|
+
`,
|
|
313
|
+
},
|
|
278
314
|
],
|
|
279
315
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TSESTree } from '@typescript-eslint/utils';
|
|
1
2
|
import {
|
|
2
3
|
createRule,
|
|
3
4
|
getPropName,
|
|
@@ -17,15 +18,25 @@ const attributes = new Set([
|
|
|
17
18
|
'aria-errormessage',
|
|
18
19
|
]);
|
|
19
20
|
|
|
21
|
+
const shouldIgnoreParent = (node: TSESTree.Node) => {
|
|
22
|
+
const { parent } = node;
|
|
23
|
+
const openingElement =
|
|
24
|
+
parent?.type === 'JSXElement' ? parent?.openingElement : null;
|
|
25
|
+
return (
|
|
26
|
+
openingElement?.type === 'JSXOpeningElement' &&
|
|
27
|
+
openingElement.name.type === 'JSXIdentifier' &&
|
|
28
|
+
(openingElement.name.name === 'pre' || openingElement.name.name === 'code')
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
20
32
|
export default createRule<Options, 'unwrappedString'>({
|
|
21
33
|
create(context, options) {
|
|
22
34
|
const ignoredWords = new Set(
|
|
23
|
-
options[0].ignoredWords.map((
|
|
35
|
+
options[0].ignoredWords.map((value) => value.trim().toLowerCase()),
|
|
24
36
|
);
|
|
25
37
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
38
|
+
const isIgnoredWord = (value: string) =>
|
|
39
|
+
ignoredWords.has(value.replaceAll(/\s+/g, ' ').toLowerCase());
|
|
29
40
|
|
|
30
41
|
return {
|
|
31
42
|
JSXAttribute(node) {
|
|
@@ -73,6 +84,10 @@ export default createRule<Options, 'unwrappedString'>({
|
|
|
73
84
|
return;
|
|
74
85
|
}
|
|
75
86
|
|
|
87
|
+
if (shouldIgnoreParent(node)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
76
91
|
if (!hasFbtParent(node)) {
|
|
77
92
|
context.report({
|
|
78
93
|
messageId: 'unwrappedString',
|
|
@@ -92,6 +107,10 @@ export default createRule<Options, 'unwrappedString'>({
|
|
|
92
107
|
return;
|
|
93
108
|
}
|
|
94
109
|
|
|
110
|
+
if (shouldIgnoreParent(node)) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
95
114
|
if (!hasFbtParent(node)) {
|
|
96
115
|
const startOffset = node.range[0] + node.value.indexOf(value);
|
|
97
116
|
const endOffset = startOffset + value.length;
|