@18ways/eslint-plugin-translate 0.0.0-alpha.185b0ef4fee2
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/LICENSE +21 -0
- package/README.md +43 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/rules/require-translation.d.ts +12 -0
- package/dist/rules/require-translation.d.ts.map +1 -0
- package/dist/rules/require-translation.js +260 -0
- package/dist/rules/require-translation.js.map +1 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 18ways
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# @18ways/eslint-plugin-translate
|
|
4
|
+
|
|
5
|
+
18ways makes i18n easy. SEO-ready, AI-powered translations for modern products.
|
|
6
|
+
|
|
7
|
+
`@18ways/eslint-plugin-translate` is the ESLint plugin for 18ways. It catches hard-coded user-facing strings and nudges code toward `<T>` and `useT()`.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -D @18ways/eslint-plugin-translate
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Basic config
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
module.exports = {
|
|
19
|
+
extends: ['plugin:@18ways/translate/recommended'],
|
|
20
|
+
};
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Good / bad
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
// Bad
|
|
27
|
+
export function Header() {
|
|
28
|
+
return <h1>Hello world</h1>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Good
|
|
32
|
+
import { T } from '@18ways/react';
|
|
33
|
+
|
|
34
|
+
export function Header() {
|
|
35
|
+
return (
|
|
36
|
+
<h1>
|
|
37
|
+
<T>Hello world</T>
|
|
38
|
+
</h1>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Docs: [18ways.com/docs](https://18ways.com/docs)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
declare const plugin: {
|
|
2
|
+
meta: {
|
|
3
|
+
name: string;
|
|
4
|
+
version: string;
|
|
5
|
+
};
|
|
6
|
+
rules: {
|
|
7
|
+
'require-translation': import("@typescript-eslint/utils/ts-eslint").RuleModule<"untranslatedText" | "untranslatedAttribute" | "untranslatedStringLiteral" | "untranslatedTemplateLiteral", [import("./rules/require-translation").Options], import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
8
|
+
};
|
|
9
|
+
configs: {
|
|
10
|
+
recommended: {
|
|
11
|
+
plugins: string[];
|
|
12
|
+
rules: {
|
|
13
|
+
'@18ways/translate/require-translation': string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
strict: {
|
|
17
|
+
plugins: string[];
|
|
18
|
+
rules: {
|
|
19
|
+
'@18ways/translate/require-translation': (string | {
|
|
20
|
+
translateComponent: string;
|
|
21
|
+
translateHook: string;
|
|
22
|
+
userFacingAttributes: string[];
|
|
23
|
+
ignorePatterns: string[];
|
|
24
|
+
ignoreFiles: string[];
|
|
25
|
+
})[];
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
export = plugin;
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0DX,CAAC;AAEF,SAAS,MAAM,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const require_translation_1 = require("./rules/require-translation");
|
|
3
|
+
const plugin = {
|
|
4
|
+
meta: {
|
|
5
|
+
name: '@18ways/eslint-plugin-translate',
|
|
6
|
+
version: '1.0.0',
|
|
7
|
+
},
|
|
8
|
+
rules: {
|
|
9
|
+
'require-translation': require_translation_1.requireTranslation,
|
|
10
|
+
},
|
|
11
|
+
configs: {
|
|
12
|
+
recommended: {
|
|
13
|
+
plugins: ['@18ways/translate'],
|
|
14
|
+
rules: {
|
|
15
|
+
'@18ways/translate/require-translation': 'error',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
strict: {
|
|
19
|
+
plugins: ['@18ways/translate'],
|
|
20
|
+
rules: {
|
|
21
|
+
'@18ways/translate/require-translation': [
|
|
22
|
+
'error',
|
|
23
|
+
{
|
|
24
|
+
translateComponent: 'T',
|
|
25
|
+
translateHook: 'useT',
|
|
26
|
+
userFacingAttributes: [
|
|
27
|
+
'alt',
|
|
28
|
+
'title',
|
|
29
|
+
'aria-label',
|
|
30
|
+
'aria-labelledby',
|
|
31
|
+
'aria-describedby',
|
|
32
|
+
'placeholder',
|
|
33
|
+
'value',
|
|
34
|
+
'defaultValue',
|
|
35
|
+
'label',
|
|
36
|
+
'summary',
|
|
37
|
+
'caption',
|
|
38
|
+
],
|
|
39
|
+
ignorePatterns: [
|
|
40
|
+
'^[a-zA-Z0-9_-]{1,3}$',
|
|
41
|
+
'^[A-Z_]+$',
|
|
42
|
+
'^\\d+$',
|
|
43
|
+
'^[a-z]+://.*',
|
|
44
|
+
'^/.*',
|
|
45
|
+
'^#[a-fA-F0-9]{3,8}$',
|
|
46
|
+
'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$',
|
|
47
|
+
'^\\s*$',
|
|
48
|
+
],
|
|
49
|
+
ignoreFiles: [
|
|
50
|
+
'.*\\.test\\.[jt]sx?$',
|
|
51
|
+
'.*\\.spec\\.[jt]sx?$',
|
|
52
|
+
'.*/test/.*',
|
|
53
|
+
'.*/tests/.*',
|
|
54
|
+
'.*/__tests__/.*',
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
module.exports = plugin;
|
|
63
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,qEAAiE;AAEjE,MAAM,MAAM,GAAG;IACb,IAAI,EAAE;QACJ,IAAI,EAAE,iCAAiC;QACvC,OAAO,EAAE,OAAO;KACjB;IACD,KAAK,EAAE;QACL,qBAAqB,EAAE,wCAAkB;KAC1C;IACD,OAAO,EAAE;QACP,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,mBAAmB,CAAC;YAC9B,KAAK,EAAE;gBACL,uCAAuC,EAAE,OAAO;aACjD;SACF;QACD,MAAM,EAAE;YACN,OAAO,EAAE,CAAC,mBAAmB,CAAC;YAC9B,KAAK,EAAE;gBACL,uCAAuC,EAAE;oBACvC,OAAO;oBACP;wBACE,kBAAkB,EAAE,GAAG;wBACvB,aAAa,EAAE,MAAM;wBACrB,oBAAoB,EAAE;4BACpB,KAAK;4BACL,OAAO;4BACP,YAAY;4BACZ,iBAAiB;4BACjB,kBAAkB;4BAClB,aAAa;4BACb,OAAO;4BACP,cAAc;4BACd,OAAO;4BACP,SAAS;4BACT,SAAS;yBACV;wBACD,cAAc,EAAE;4BACd,sBAAsB;4BACtB,WAAW;4BACX,QAAQ;4BACR,cAAc;4BACd,MAAM;4BACN,qBAAqB;4BACrB,mDAAmD;4BACnD,QAAQ;yBACT;wBACD,WAAW,EAAE;4BACX,sBAAsB;4BACtB,sBAAsB;4BACtB,YAAY;4BACZ,aAAa;4BACb,iBAAiB;yBAClB;qBACF;iBACF;aACF;SACF;KACF;CACF,CAAC;AAEF,iBAAS,MAAM,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
export interface Options {
|
|
3
|
+
translateComponent?: string;
|
|
4
|
+
translateHook?: string;
|
|
5
|
+
userFacingAttributes?: string[];
|
|
6
|
+
ignorePatterns?: string[];
|
|
7
|
+
ignoreFiles?: string[];
|
|
8
|
+
}
|
|
9
|
+
type MessageIds = 'untranslatedText' | 'untranslatedAttribute' | 'untranslatedStringLiteral' | 'untranslatedTemplateLiteral';
|
|
10
|
+
export declare const requireTranslation: ESLintUtils.RuleModule<MessageIds, [Options], ESLintUtils.RuleListener>;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=require-translation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-translation.d.ts","sourceRoot":"","sources":["../../src/rules/require-translation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAY,MAAM,0BAA0B,CAAC;AAMjE,MAAM,WAAW,OAAO;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,KAAK,UAAU,GACX,kBAAkB,GAClB,uBAAuB,GACvB,2BAA2B,GAC3B,6BAA6B,CAAC;AAqBlC,eAAO,MAAM,kBAAkB,yEAgQ7B,CAAC"}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requireTranslation = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://github.com/18ways/eslint-plugin-translate/blob/main/docs/rules/${name}.md`);
|
|
6
|
+
const DEFAULT_USER_FACING_ATTRIBUTES = [
|
|
7
|
+
'alt',
|
|
8
|
+
'title',
|
|
9
|
+
'aria-label',
|
|
10
|
+
'aria-labelledby',
|
|
11
|
+
'placeholder',
|
|
12
|
+
'label',
|
|
13
|
+
];
|
|
14
|
+
const DEFAULT_IGNORE_PATTERNS = [
|
|
15
|
+
'^.{1,3}$', // Very short strings (1-3 chars like IDs, icons, symbols)
|
|
16
|
+
'^[A-Z_]+$', // Constants (ALL_CAPS)
|
|
17
|
+
'^\\d+$', // Numbers only
|
|
18
|
+
'^[a-z]+://.*', // URLs
|
|
19
|
+
'^/.*', // Paths
|
|
20
|
+
'^#[a-fA-F0-9]{3,8}$', // Hex colors
|
|
21
|
+
'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$', // Emails
|
|
22
|
+
];
|
|
23
|
+
exports.requireTranslation = createRule({
|
|
24
|
+
name: 'require-translation',
|
|
25
|
+
meta: {
|
|
26
|
+
type: 'suggestion',
|
|
27
|
+
docs: {
|
|
28
|
+
description: 'Require user-facing strings to be wrapped in translation components',
|
|
29
|
+
},
|
|
30
|
+
messages: {
|
|
31
|
+
untranslatedText: 'User-facing text should be wrapped in a <{{translateComponent}}> component: "{{text}}"',
|
|
32
|
+
untranslatedAttribute: 'User-facing attribute "{{attribute}}" should use translated content: "{{text}}"',
|
|
33
|
+
untranslatedStringLiteral: 'User-facing string literal should be translated: "{{text}}"',
|
|
34
|
+
untranslatedTemplateLiteral: 'User-facing template literal should be translated: "{{text}}"',
|
|
35
|
+
},
|
|
36
|
+
schema: [
|
|
37
|
+
{
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: {
|
|
40
|
+
translateComponent: {
|
|
41
|
+
type: 'string',
|
|
42
|
+
default: 'T',
|
|
43
|
+
},
|
|
44
|
+
translateHook: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
default: 'useT',
|
|
47
|
+
},
|
|
48
|
+
userFacingAttributes: {
|
|
49
|
+
type: 'array',
|
|
50
|
+
items: { type: 'string' },
|
|
51
|
+
default: DEFAULT_USER_FACING_ATTRIBUTES,
|
|
52
|
+
},
|
|
53
|
+
ignorePatterns: {
|
|
54
|
+
type: 'array',
|
|
55
|
+
items: { type: 'string' },
|
|
56
|
+
default: DEFAULT_IGNORE_PATTERNS,
|
|
57
|
+
},
|
|
58
|
+
ignoreFiles: {
|
|
59
|
+
type: 'array',
|
|
60
|
+
items: { type: 'string' },
|
|
61
|
+
default: [],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
additionalProperties: false,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
defaultOptions: [
|
|
69
|
+
{
|
|
70
|
+
translateComponent: 'T',
|
|
71
|
+
translateHook: 'useT',
|
|
72
|
+
userFacingAttributes: DEFAULT_USER_FACING_ATTRIBUTES,
|
|
73
|
+
ignorePatterns: DEFAULT_IGNORE_PATTERNS,
|
|
74
|
+
ignoreFiles: [],
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
create(context, [options]) {
|
|
78
|
+
const translateComponent = options.translateComponent || 'T';
|
|
79
|
+
const userFacingAttributes = options.userFacingAttributes || DEFAULT_USER_FACING_ATTRIBUTES;
|
|
80
|
+
const ignorePatterns = options.ignorePatterns || DEFAULT_IGNORE_PATTERNS;
|
|
81
|
+
const ignoreFiles = options.ignoreFiles || [];
|
|
82
|
+
const filename = context.getFilename();
|
|
83
|
+
// Skip files that match ignore patterns
|
|
84
|
+
if (ignoreFiles.some((pattern) => new RegExp(pattern).test(filename))) {
|
|
85
|
+
return {};
|
|
86
|
+
}
|
|
87
|
+
function shouldIgnoreText(text) {
|
|
88
|
+
if (!text || typeof text !== 'string')
|
|
89
|
+
return true;
|
|
90
|
+
// Trim whitespace for checking
|
|
91
|
+
const trimmed = text.trim();
|
|
92
|
+
if (!trimmed)
|
|
93
|
+
return true;
|
|
94
|
+
// Check against ignore patterns
|
|
95
|
+
return ignorePatterns.some((pattern) => new RegExp(pattern).test(trimmed));
|
|
96
|
+
}
|
|
97
|
+
function isInsideTranslateComponent(node) {
|
|
98
|
+
let parent = node.parent;
|
|
99
|
+
while (parent) {
|
|
100
|
+
if (parent.type === 'JSXElement') {
|
|
101
|
+
const openingElement = parent.openingElement;
|
|
102
|
+
if (openingElement.name.type === 'JSXIdentifier' &&
|
|
103
|
+
openingElement.name.name === translateComponent) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
parent = parent.parent;
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
function isWithinTFunction(node) {
|
|
112
|
+
let parent = node.parent;
|
|
113
|
+
while (parent) {
|
|
114
|
+
if (parent.type === 'CallExpression') {
|
|
115
|
+
const callee = parent.callee;
|
|
116
|
+
if (callee.type === 'Identifier' && callee.name === 't') {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
parent = parent.parent;
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
function containsJSXExpressions(text) {
|
|
125
|
+
return /\{.*\}/.test(text);
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
JSXText(node) {
|
|
129
|
+
const text = node.value;
|
|
130
|
+
if (shouldIgnoreText(text))
|
|
131
|
+
return;
|
|
132
|
+
if (isInsideTranslateComponent(node))
|
|
133
|
+
return;
|
|
134
|
+
if (containsJSXExpressions(text))
|
|
135
|
+
return;
|
|
136
|
+
context.report({
|
|
137
|
+
node,
|
|
138
|
+
messageId: 'untranslatedText',
|
|
139
|
+
data: {
|
|
140
|
+
translateComponent,
|
|
141
|
+
text: text.trim(),
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
},
|
|
145
|
+
JSXAttribute(node) {
|
|
146
|
+
if (!node.name || node.name.type !== 'JSXIdentifier')
|
|
147
|
+
return;
|
|
148
|
+
const attributeName = node.name.name;
|
|
149
|
+
if (!userFacingAttributes.includes(attributeName))
|
|
150
|
+
return;
|
|
151
|
+
const value = node.value;
|
|
152
|
+
if (!value)
|
|
153
|
+
return;
|
|
154
|
+
if (value.type === 'Literal' && typeof value.value === 'string') {
|
|
155
|
+
const text = value.value;
|
|
156
|
+
if (shouldIgnoreText(text))
|
|
157
|
+
return;
|
|
158
|
+
context.report({
|
|
159
|
+
node: value,
|
|
160
|
+
messageId: 'untranslatedAttribute',
|
|
161
|
+
data: {
|
|
162
|
+
attribute: attributeName,
|
|
163
|
+
text,
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
else if (value.type === 'JSXExpressionContainer') {
|
|
168
|
+
// Check if the expression is a t() function call
|
|
169
|
+
if (value.expression.type === 'CallExpression' &&
|
|
170
|
+
value.expression.callee.type === 'Identifier' &&
|
|
171
|
+
value.expression.callee.name === 't') {
|
|
172
|
+
return; // This is already translated with t()
|
|
173
|
+
}
|
|
174
|
+
// Check if it's a literal string that should be translated
|
|
175
|
+
if (value.expression.type === 'Literal' && typeof value.expression.value === 'string') {
|
|
176
|
+
const text = value.expression.value;
|
|
177
|
+
if (shouldIgnoreText(text))
|
|
178
|
+
return;
|
|
179
|
+
context.report({
|
|
180
|
+
node: value.expression,
|
|
181
|
+
messageId: 'untranslatedAttribute',
|
|
182
|
+
data: {
|
|
183
|
+
attribute: attributeName,
|
|
184
|
+
text,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
'Literal[value=/\\w/]'(node) {
|
|
191
|
+
if (typeof node.value !== 'string')
|
|
192
|
+
return;
|
|
193
|
+
if (shouldIgnoreText(node.value))
|
|
194
|
+
return;
|
|
195
|
+
if (isInsideTranslateComponent(node))
|
|
196
|
+
return;
|
|
197
|
+
if (isWithinTFunction(node))
|
|
198
|
+
return;
|
|
199
|
+
// Skip if inside JSX attribute (handled by JSXAttribute rule)
|
|
200
|
+
let parent = node.parent;
|
|
201
|
+
while (parent) {
|
|
202
|
+
if (parent.type === 'JSXAttribute')
|
|
203
|
+
return;
|
|
204
|
+
parent = parent.parent;
|
|
205
|
+
}
|
|
206
|
+
// Only report if this appears to be user-facing content
|
|
207
|
+
if (node.parent?.type === 'JSXExpressionContainer' ||
|
|
208
|
+
(node.parent?.type === 'ReturnStatement' && /^[A-Z]/.test(node.value.charAt(0)))) {
|
|
209
|
+
context.report({
|
|
210
|
+
node,
|
|
211
|
+
messageId: 'untranslatedStringLiteral',
|
|
212
|
+
data: {
|
|
213
|
+
text: node.value,
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
TemplateLiteral(node) {
|
|
219
|
+
// Skip if inside translate component or t() function
|
|
220
|
+
if (isInsideTranslateComponent(node))
|
|
221
|
+
return;
|
|
222
|
+
if (isWithinTFunction(node))
|
|
223
|
+
return;
|
|
224
|
+
// Check if this is inside a JSX attribute
|
|
225
|
+
let parent = node.parent;
|
|
226
|
+
while (parent) {
|
|
227
|
+
if (parent.type === 'JSXAttribute') {
|
|
228
|
+
const attr = parent;
|
|
229
|
+
if (attr.name.type === 'JSXIdentifier') {
|
|
230
|
+
const attrName = attr.name.name;
|
|
231
|
+
// Allow template literals in non-user-facing attributes
|
|
232
|
+
// (className, style, key, id, etc. - anything not explicitly user-facing)
|
|
233
|
+
if (!userFacingAttributes.includes(attrName)) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
parent = parent.parent;
|
|
239
|
+
}
|
|
240
|
+
// Check if template literal has only static text (no expressions)
|
|
241
|
+
if (node.expressions.length === 0 && node.quasis.length === 1) {
|
|
242
|
+
const text = node.quasis[0].value.cooked || '';
|
|
243
|
+
if (shouldIgnoreText(text))
|
|
244
|
+
return;
|
|
245
|
+
// Only report if in JSX content (JSXExpressionContainer)
|
|
246
|
+
if (node.parent?.type === 'JSXExpressionContainer') {
|
|
247
|
+
context.report({
|
|
248
|
+
node,
|
|
249
|
+
messageId: 'untranslatedTemplateLiteral',
|
|
250
|
+
data: {
|
|
251
|
+
text: text.substring(0, 50), // Limit display length
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
//# sourceMappingURL=require-translation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-translation.js","sourceRoot":"","sources":["../../src/rules/require-translation.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@18ways/eslint-plugin-translate",
|
|
3
|
+
"version": "0.0.0-alpha.185b0ef4fee2",
|
|
4
|
+
"description": "ESLint plugin to detect untranslated strings that should use 18ways translate functionality",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"test:watch": "vitest watch",
|
|
15
|
+
"lint": "eslint src --ext .ts",
|
|
16
|
+
"prepare": "bun run build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"eslint",
|
|
20
|
+
"plugin",
|
|
21
|
+
"translation",
|
|
22
|
+
"i18n",
|
|
23
|
+
"18ways",
|
|
24
|
+
"internationalization"
|
|
25
|
+
],
|
|
26
|
+
"author": "18ways",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"eslint": "^7.0.0 || ^8.0.0 || ^9.0.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/eslint": "^8.56.0",
|
|
33
|
+
"@types/estree": "^1.0.5",
|
|
34
|
+
"@types/node": "^20.0.0",
|
|
35
|
+
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
36
|
+
"@typescript-eslint/parser": "^6.0.0",
|
|
37
|
+
"eslint": "^8.56.0",
|
|
38
|
+
"globby": "^11.1.0",
|
|
39
|
+
"typescript": "^5.0.0",
|
|
40
|
+
"vitest": "^3.2.4"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@typescript-eslint/utils": "^6.0.0"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=16.0.0"
|
|
47
|
+
},
|
|
48
|
+
"packageManager": "bun@1.3.8"
|
|
49
|
+
}
|