@better-i18n/cli 0.1.4 → 0.1.6
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 +39 -54
- package/dist/analyzer/index.d.ts +8 -2
- package/dist/analyzer/index.d.ts.map +1 -1
- package/dist/analyzer/index.js +148 -3
- package/dist/analyzer/index.js.map +1 -1
- package/dist/analyzer/rules/index.d.ts +2 -0
- package/dist/analyzer/rules/index.d.ts.map +1 -1
- package/dist/analyzer/rules/index.js +2 -0
- package/dist/analyzer/rules/index.js.map +1 -1
- package/dist/analyzer/rules/string-variable.d.ts +13 -0
- package/dist/analyzer/rules/string-variable.d.ts.map +1 -0
- package/dist/analyzer/rules/string-variable.js +187 -0
- package/dist/analyzer/rules/string-variable.js.map +1 -0
- package/dist/analyzer/rules/toast-message.d.ts +13 -0
- package/dist/analyzer/rules/toast-message.d.ts.map +1 -0
- package/dist/analyzer/rules/toast-message.js +103 -0
- package/dist/analyzer/rules/toast-message.js.map +1 -0
- package/dist/analyzer/rules/translation-function.d.ts.map +1 -1
- package/dist/analyzer/rules/translation-function.js +36 -21
- package/dist/analyzer/rules/translation-function.js.map +1 -1
- package/dist/analyzer/types.d.ts +17 -1
- package/dist/analyzer/types.d.ts.map +1 -1
- package/dist/commands/scan.d.ts.map +1 -1
- package/dist/commands/scan.js +21 -2
- package/dist/commands/scan.js.map +1 -1
- package/dist/commands/sync.d.ts +12 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +583 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +8 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/commands/extract-keys.d.ts +0 -13
- package/dist/commands/extract-keys.d.ts.map +0 -1
- package/dist/commands/extract-keys.js +0 -418
- package/dist/commands/extract-keys.js.map +0 -1
package/README.md
CHANGED
|
@@ -101,25 +101,34 @@ better-i18n scan --staged # Only scan git staged files
|
|
|
101
101
|
better-i18n scan --verbose # Show detailed output
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
### `better-i18n
|
|
104
|
+
### `better-i18n sync`
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
Compare local translation keys (t() calls) with your Better i18n cloud project.
|
|
107
107
|
|
|
108
108
|
```bash
|
|
109
|
-
#
|
|
110
|
-
better-i18n
|
|
109
|
+
# Basic usage (grouped tree output)
|
|
110
|
+
better-i18n sync
|
|
111
111
|
|
|
112
|
-
#
|
|
113
|
-
better-i18n
|
|
112
|
+
# Minimal metrics only
|
|
113
|
+
better-i18n sync --summary
|
|
114
114
|
|
|
115
|
-
#
|
|
116
|
-
better-i18n
|
|
115
|
+
# Deep audit log & scope trace
|
|
116
|
+
better-i18n sync --verbose
|
|
117
117
|
|
|
118
|
-
# JSON output
|
|
119
|
-
better-i18n
|
|
118
|
+
# JSON output for CI automation
|
|
119
|
+
better-i18n sync --format json
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Hook-based Namespace Detection
|
|
123
|
+
|
|
124
|
+
The CLI automatically detects namespaces from the `useTranslation` hook.
|
|
120
125
|
|
|
121
|
-
|
|
122
|
-
|
|
126
|
+
```tsx
|
|
127
|
+
// This will be extracted as 'auth.login' and 'auth.register'
|
|
128
|
+
const { t } = useTranslation('auth');
|
|
129
|
+
|
|
130
|
+
t('login');
|
|
131
|
+
t('register');
|
|
123
132
|
```
|
|
124
133
|
|
|
125
134
|
**Output format (JSON):**
|
|
@@ -139,53 +148,29 @@ better-i18n extract-keys --verbose
|
|
|
139
148
|
}
|
|
140
149
|
```
|
|
141
150
|
|
|
142
|
-
**With
|
|
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
|
-
```
|
|
165
|
-
|
|
166
|
-
**Human-readable output with `--compare`:**
|
|
167
|
-
|
|
168
|
-
```
|
|
169
|
-
✓ Project: better-i18n/landing
|
|
170
|
-
✓ Found 57 files
|
|
171
|
-
✓ Fetched 12 namespaces from CDN
|
|
151
|
+
**With `sync` output (default):**
|
|
172
152
|
|
|
153
|
+
```text
|
|
173
154
|
📊 Translation Keys Comparison
|
|
155
|
+
Source locale: en
|
|
174
156
|
|
|
175
157
|
Coverage:
|
|
176
|
-
Local → Remote:
|
|
177
|
-
Remote Used:
|
|
158
|
+
Local → Remote: 59%
|
|
159
|
+
Remote Used: 63%
|
|
160
|
+
|
|
161
|
+
⊕ Missing in Remote (473 keys)
|
|
162
|
+
pages (300)
|
|
163
|
+
affordableEnglishLearning (meta.title, meta.description, ...+12)
|
|
164
|
+
bestApps (hero.badge, title_prefix, title_accent)
|
|
178
165
|
|
|
179
|
-
|
|
180
|
-
hero
|
|
181
|
-
• hero.cta
|
|
182
|
-
• hero.benefits
|
|
166
|
+
hero (5)
|
|
167
|
+
hero (ariaLabel, imageAlt, ...)
|
|
183
168
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
169
|
+
⊖ Unused in Code (386 keys)
|
|
170
|
+
features (25)
|
|
171
|
+
practiceSpeaking (title, subtitle, icon)
|
|
187
172
|
|
|
188
|
-
Scanned
|
|
173
|
+
Scanned 246 files in 0.85s
|
|
189
174
|
✓ Comparison complete
|
|
190
175
|
```
|
|
191
176
|
|
|
@@ -478,10 +463,10 @@ This CLI is one component of the **Better i18n translation management platform**
|
|
|
478
463
|
Developer Workflow:
|
|
479
464
|
├─ Write code with hardcoded strings
|
|
480
465
|
├─ Run: better-i18n scan → Detect hardcoded strings ⚠️
|
|
481
|
-
├─ Run: better-i18n
|
|
466
|
+
├─ Run: better-i18n sync → Compare local vs cloud keys
|
|
482
467
|
├─ Review in Better i18n Dashboard
|
|
483
468
|
├─ GitHub Hook: better-i18n scan --staged → Pre-commit check
|
|
484
|
-
├─ CI/CD: better-i18n
|
|
469
|
+
├─ CI/CD: better-i18n sync --format json → Audit translations in pipeline
|
|
485
470
|
└─ Dashboard: Manage translations, sync with GitHub
|
|
486
471
|
```
|
|
487
472
|
|
package/dist/analyzer/index.d.ts
CHANGED
|
@@ -7,9 +7,15 @@ import type { Issue, LintConfig } from "./types.js";
|
|
|
7
7
|
/**
|
|
8
8
|
* Analyze a single file for hardcoded strings
|
|
9
9
|
*/
|
|
10
|
-
export declare function analyzeFile(filePath: string, config?: LintConfig): Promise<
|
|
10
|
+
export declare function analyzeFile(filePath: string, config?: LintConfig): Promise<{
|
|
11
|
+
issues: Issue[];
|
|
12
|
+
stats: any;
|
|
13
|
+
}>;
|
|
11
14
|
/**
|
|
12
15
|
* Analyze source text (useful for testing)
|
|
13
16
|
*/
|
|
14
|
-
export declare function analyzeSourceText(sourceText: string, filePath: string, config?: LintConfig):
|
|
17
|
+
export declare function analyzeSourceText(sourceText: string, filePath: string, config?: LintConfig): {
|
|
18
|
+
issues: Issue[];
|
|
19
|
+
stats: any;
|
|
20
|
+
};
|
|
15
21
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -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;AAYH,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;IAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAAC,KAAK,EAAE,GAAG,CAAA;CAAE,CAAC,CAG1C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,UAAU,GAClB;IAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAAC,KAAK,EAAE,GAAG,CAAA;CAAE,CA4MjC"}
|
package/dist/analyzer/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { readFileSync } from "node:fs";
|
|
7
7
|
import ts from "typescript";
|
|
8
|
-
import { checkJsxAttribute, checkJsxText, checkTernaryLocale, checkTranslationFunction, } from "./rules/index.js";
|
|
8
|
+
import { checkJsxAttribute, checkJsxText, checkStringVariable, checkTernaryLocale, checkToastMessage, checkTranslationFunction, } from "./rules/index.js";
|
|
9
9
|
/**
|
|
10
10
|
* Analyze a single file for hardcoded strings
|
|
11
11
|
*/
|
|
@@ -19,13 +19,134 @@ export async function analyzeFile(filePath, config) {
|
|
|
19
19
|
export function analyzeSourceText(sourceText, filePath, config) {
|
|
20
20
|
const sourceFile = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(filePath));
|
|
21
21
|
const issues = [];
|
|
22
|
-
const
|
|
22
|
+
const stats = {
|
|
23
|
+
dynamicKeys: 0,
|
|
24
|
+
dynamicNamespaces: 0,
|
|
25
|
+
unboundTranslators: 0,
|
|
26
|
+
rootScopedTranslators: 0,
|
|
27
|
+
};
|
|
28
|
+
// Lexical scope stack: each element is a map of identifier -> NamespaceBinding
|
|
29
|
+
// NamespaceBinding type is imported from types.js
|
|
30
|
+
const scopeStack = [{}];
|
|
31
|
+
function pushScope() {
|
|
32
|
+
scopeStack.push({});
|
|
33
|
+
}
|
|
34
|
+
function popScope() {
|
|
35
|
+
scopeStack.pop();
|
|
36
|
+
}
|
|
37
|
+
function getCurrentBindings() {
|
|
38
|
+
const combined = {};
|
|
39
|
+
for (const scope of scopeStack) {
|
|
40
|
+
Object.assign(combined, scope);
|
|
41
|
+
}
|
|
42
|
+
return combined;
|
|
43
|
+
}
|
|
44
|
+
function registerBinding(name, binding) {
|
|
45
|
+
scopeStack[scopeStack.length - 1][name] = binding;
|
|
46
|
+
}
|
|
47
|
+
function detectBinding(initializer) {
|
|
48
|
+
// Unwrap await
|
|
49
|
+
const call = ts.isAwaitExpression(initializer)
|
|
50
|
+
? initializer.expression
|
|
51
|
+
: initializer;
|
|
52
|
+
if (!ts.isCallExpression(call))
|
|
53
|
+
return null;
|
|
54
|
+
const funcName = getIdentifierName(call.expression);
|
|
55
|
+
if (funcName === "useTranslations" || funcName === "useTranslation") {
|
|
56
|
+
if (call.arguments.length === 0) {
|
|
57
|
+
stats.rootScopedTranslators++;
|
|
58
|
+
return { type: "root-scoped" };
|
|
59
|
+
}
|
|
60
|
+
const arg = call.arguments[0];
|
|
61
|
+
if (ts.isStringLiteral(arg)) {
|
|
62
|
+
return { type: "bound-scoped", namespace: arg.text };
|
|
63
|
+
}
|
|
64
|
+
stats.dynamicNamespaces++;
|
|
65
|
+
return { type: "unknown-scoped", dynamic: true };
|
|
66
|
+
}
|
|
67
|
+
if (funcName === "getTranslations") {
|
|
68
|
+
if (call.arguments.length === 0) {
|
|
69
|
+
stats.rootScopedTranslators++;
|
|
70
|
+
return { type: "root-scoped" };
|
|
71
|
+
}
|
|
72
|
+
const arg = call.arguments[0];
|
|
73
|
+
if (ts.isObjectLiteralExpression(arg)) {
|
|
74
|
+
const nsProp = arg.properties.find((p) => (ts.isPropertyAssignment(p) ||
|
|
75
|
+
ts.isShorthandPropertyAssignment(p)) &&
|
|
76
|
+
ts.isIdentifier(p.name) &&
|
|
77
|
+
p.name.text === "namespace");
|
|
78
|
+
if (!nsProp) {
|
|
79
|
+
stats.rootScopedTranslators++;
|
|
80
|
+
return { type: "root-scoped" };
|
|
81
|
+
}
|
|
82
|
+
if (ts.isPropertyAssignment(nsProp)) {
|
|
83
|
+
if (ts.isStringLiteral(nsProp.initializer)) {
|
|
84
|
+
return {
|
|
85
|
+
type: "bound-scoped",
|
|
86
|
+
namespace: nsProp.initializer.text,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
stats.dynamicNamespaces++;
|
|
90
|
+
return { type: "unknown-scoped", dynamic: true };
|
|
91
|
+
}
|
|
92
|
+
// Shorthand implies identifier, which is dynamic in this context
|
|
93
|
+
stats.dynamicNamespaces++;
|
|
94
|
+
return { type: "unknown-scoped", dynamic: true };
|
|
95
|
+
}
|
|
96
|
+
stats.dynamicNamespaces++;
|
|
97
|
+
return { type: "unknown-scoped", dynamic: true };
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
23
101
|
// Check if rules are enabled
|
|
24
102
|
const rules = config?.rules || {};
|
|
25
103
|
const jsxTextEnabled = rules["jsx-text"] !== "off";
|
|
26
104
|
const jsxAttrEnabled = rules["jsx-attribute"] !== "off";
|
|
27
105
|
const ternaryEnabled = rules["ternary-locale"] !== "off";
|
|
106
|
+
const toastEnabled = rules["toast-message"] !== "off";
|
|
107
|
+
const stringVarEnabled = rules["string-variable"] !== "off";
|
|
28
108
|
function visit(node) {
|
|
109
|
+
let pushed = false;
|
|
110
|
+
// Scoping nodes: functions, blocks
|
|
111
|
+
if (ts.isFunctionLike(node) || ts.isBlock(node)) {
|
|
112
|
+
pushScope();
|
|
113
|
+
pushed = true;
|
|
114
|
+
}
|
|
115
|
+
// Detect bindings in VariableDeclarations
|
|
116
|
+
if (ts.isVariableDeclaration(node) && node.initializer) {
|
|
117
|
+
const binding = detectBinding(node.initializer);
|
|
118
|
+
if (binding) {
|
|
119
|
+
if (ts.isIdentifier(node.name)) {
|
|
120
|
+
registerBinding(node.name.text, binding);
|
|
121
|
+
}
|
|
122
|
+
else if (ts.isObjectBindingPattern(node.name)) {
|
|
123
|
+
// const { t } = useTranslations()
|
|
124
|
+
for (const element of node.name.elements) {
|
|
125
|
+
const id = ts.isIdentifier(element.name) ? element.name.text : null;
|
|
126
|
+
const prop = element.propertyName && ts.isIdentifier(element.propertyName)
|
|
127
|
+
? element.propertyName.text
|
|
128
|
+
: id;
|
|
129
|
+
if (id && prop === "t") {
|
|
130
|
+
registerBinding(id, binding);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Detect bindings in Assignments (t = useTranslations())
|
|
137
|
+
if (ts.isBinaryExpression(node) &&
|
|
138
|
+
node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
|
|
139
|
+
const binding = detectBinding(node.right);
|
|
140
|
+
if (binding && ts.isIdentifier(node.left)) {
|
|
141
|
+
registerBinding(node.left.text, binding);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const ctx = {
|
|
145
|
+
filePath,
|
|
146
|
+
sourceFile,
|
|
147
|
+
namespaceMap: getCurrentBindings(),
|
|
148
|
+
stats,
|
|
149
|
+
};
|
|
29
150
|
// JSX Text
|
|
30
151
|
if (jsxTextEnabled && ts.isJsxText(node)) {
|
|
31
152
|
const issue = checkJsxText(node, ctx);
|
|
@@ -49,11 +170,35 @@ export function analyzeSourceText(sourceText, filePath, config) {
|
|
|
49
170
|
const issue = checkTranslationFunction(node, ctx);
|
|
50
171
|
if (issue)
|
|
51
172
|
issues.push(issue);
|
|
173
|
+
// Toast message calls
|
|
174
|
+
if (toastEnabled) {
|
|
175
|
+
const toastIssue = checkToastMessage(node, ctx);
|
|
176
|
+
if (toastIssue)
|
|
177
|
+
issues.push(toastIssue);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// String variable assignments
|
|
181
|
+
if (stringVarEnabled && ts.isVariableDeclaration(node)) {
|
|
182
|
+
const issue = checkStringVariable(node, ctx);
|
|
183
|
+
if (issue)
|
|
184
|
+
issues.push(issue);
|
|
52
185
|
}
|
|
53
186
|
ts.forEachChild(node, visit);
|
|
187
|
+
if (pushed)
|
|
188
|
+
popScope();
|
|
54
189
|
}
|
|
55
190
|
visit(sourceFile);
|
|
56
|
-
return issues;
|
|
191
|
+
return { issues, stats };
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Helper to get identifier name
|
|
195
|
+
*/
|
|
196
|
+
function getIdentifierName(expr) {
|
|
197
|
+
if (ts.isIdentifier(expr))
|
|
198
|
+
return expr.text;
|
|
199
|
+
if (ts.isPropertyAccessExpression(expr))
|
|
200
|
+
return expr.name.text;
|
|
201
|
+
return null;
|
|
57
202
|
}
|
|
58
203
|
/**
|
|
59
204
|
* Get TypeScript script kind based on file extension
|
|
@@ -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,EACL,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,EAClB,wBAAwB,GACzB,MAAM,kBAAkB,CAAC;AAG1B;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,QAAgB,EAChB,MAAmB;IAEnB,MAAM,UAAU,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACnD,OAAO,iBAAiB,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AACzD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAC/B,UAAkB,EAClB,QAAgB,EAChB,MAAmB;IAEnB,MAAM,UAAU,GAAG,EAAE,CAAC,gBAAgB,CACpC,QAAQ,EACR,UAAU,EACV,EAAE,CAAC,YAAY,CAAC,MAAM,EACtB,IAAI,EACJ,aAAa,CAAC,QAAQ,CAAC,CACxB,CAAC;IAEF,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,MAAM,GAAG,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/analyzer/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export * from "./jsx-attribute.js";
|
|
5
5
|
export * from "./jsx-text.js";
|
|
6
|
+
export * from "./string-variable.js";
|
|
6
7
|
export * from "./ternary-locale.js";
|
|
8
|
+
export * from "./toast-message.js";
|
|
7
9
|
export * from "./translation-function.js";
|
|
8
10
|
//# 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,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,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,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC"}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export * from "./jsx-attribute.js";
|
|
5
5
|
export * from "./jsx-text.js";
|
|
6
|
+
export * from "./string-variable.js";
|
|
6
7
|
export * from "./ternary-locale.js";
|
|
8
|
+
export * from "./toast-message.js";
|
|
7
9
|
export * from "./translation-function.js";
|
|
8
10
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
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
|
+
{"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,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String variable detection rule
|
|
3
|
+
*
|
|
4
|
+
* Detects hardcoded user-facing strings assigned to variables
|
|
5
|
+
* Uses strict filtering to avoid false positives
|
|
6
|
+
*/
|
|
7
|
+
import ts from "typescript";
|
|
8
|
+
import type { Issue, RuleContext } from "../types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Check for string variable assignments with hardcoded user-facing text
|
|
11
|
+
*/
|
|
12
|
+
export declare function checkStringVariable(node: ts.VariableDeclaration, ctx: RuleContext): Issue | null;
|
|
13
|
+
//# sourceMappingURL=string-variable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"string-variable.d.ts","sourceRoot":"","sources":["../../../src/analyzer/rules/string-variable.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AA0EtD;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,EAAE,CAAC,mBAAmB,EAC5B,GAAG,EAAE,WAAW,GACf,KAAK,GAAG,IAAI,CA6Cd"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String variable detection rule
|
|
3
|
+
*
|
|
4
|
+
* Detects hardcoded user-facing strings assigned to variables
|
|
5
|
+
* Uses strict filtering to avoid false positives
|
|
6
|
+
*/
|
|
7
|
+
import ts from "typescript";
|
|
8
|
+
import { generateKeyFromContext, truncate } from "../../utils/text.js";
|
|
9
|
+
/**
|
|
10
|
+
* Patterns to IGNORE (not user-facing text)
|
|
11
|
+
*/
|
|
12
|
+
const IGNORE_PATTERNS = [
|
|
13
|
+
/^[A-Z_][A-Z0-9_]*$/, // SCREAMING_CASE constants
|
|
14
|
+
/^[a-z][a-z0-9]*$/, // Single lowercase word (identifiers)
|
|
15
|
+
/^[a-z-]+$/, // CSS-like (lowercase with hyphens)
|
|
16
|
+
/^[a-z_]+$/, // snake_case identifiers
|
|
17
|
+
/^https?:\/\//, // URLs
|
|
18
|
+
/^\/[\w/.-]+$/, // Paths
|
|
19
|
+
/^\d+(\.\d+)?(px|rem|em|%|vh|vw|ms|s)?$/, // Numbers with units
|
|
20
|
+
/^#[0-9a-fA-F]{3,8}$/, // Hex colors
|
|
21
|
+
/^rgba?\(/, // RGB colors
|
|
22
|
+
/^hsla?\(/, // HSL colors
|
|
23
|
+
/^[\w-]+:[\w-]+$/, // CSS-like property:value
|
|
24
|
+
/^\s*$/, // Whitespace only
|
|
25
|
+
/^[{}[\]()]+$/, // Brackets only
|
|
26
|
+
];
|
|
27
|
+
/**
|
|
28
|
+
* Variable names that typically hold non-translatable values
|
|
29
|
+
*/
|
|
30
|
+
const IGNORE_VARIABLE_NAMES = new Set([
|
|
31
|
+
"id",
|
|
32
|
+
"key",
|
|
33
|
+
"type",
|
|
34
|
+
"name",
|
|
35
|
+
"className",
|
|
36
|
+
"class",
|
|
37
|
+
"style",
|
|
38
|
+
"styles",
|
|
39
|
+
"href",
|
|
40
|
+
"src",
|
|
41
|
+
"url",
|
|
42
|
+
"path",
|
|
43
|
+
"route",
|
|
44
|
+
"endpoint",
|
|
45
|
+
"query",
|
|
46
|
+
"params",
|
|
47
|
+
"config",
|
|
48
|
+
"options",
|
|
49
|
+
"settings",
|
|
50
|
+
"env",
|
|
51
|
+
"mode",
|
|
52
|
+
"format",
|
|
53
|
+
"variant",
|
|
54
|
+
"size",
|
|
55
|
+
"color",
|
|
56
|
+
"status",
|
|
57
|
+
"state",
|
|
58
|
+
"icon",
|
|
59
|
+
"target",
|
|
60
|
+
"rel",
|
|
61
|
+
"method",
|
|
62
|
+
"headers",
|
|
63
|
+
"contentType",
|
|
64
|
+
"mimeType",
|
|
65
|
+
"encoding",
|
|
66
|
+
"charset",
|
|
67
|
+
"locale",
|
|
68
|
+
"lang",
|
|
69
|
+
"language",
|
|
70
|
+
"namespace",
|
|
71
|
+
"ns",
|
|
72
|
+
"prefix",
|
|
73
|
+
"suffix",
|
|
74
|
+
"extension",
|
|
75
|
+
"ext",
|
|
76
|
+
"version",
|
|
77
|
+
"v",
|
|
78
|
+
]);
|
|
79
|
+
/**
|
|
80
|
+
* Check for string variable assignments with hardcoded user-facing text
|
|
81
|
+
*/
|
|
82
|
+
export function checkStringVariable(node, ctx) {
|
|
83
|
+
// Must have an initializer
|
|
84
|
+
if (!node.initializer)
|
|
85
|
+
return null;
|
|
86
|
+
// Must be a string literal
|
|
87
|
+
if (!ts.isStringLiteral(node.initializer))
|
|
88
|
+
return null;
|
|
89
|
+
const text = node.initializer.text;
|
|
90
|
+
// Skip if it doesn't look like user-facing text
|
|
91
|
+
if (!isUserFacingText(text))
|
|
92
|
+
return null;
|
|
93
|
+
// Skip if variable name suggests non-translatable content
|
|
94
|
+
if (ts.isIdentifier(node.name)) {
|
|
95
|
+
const varName = node.name.text.toLowerCase();
|
|
96
|
+
if (IGNORE_VARIABLE_NAMES.has(varName))
|
|
97
|
+
return null;
|
|
98
|
+
// Skip if variable name ends with common technical suffixes
|
|
99
|
+
if (varName.endsWith("id") ||
|
|
100
|
+
varName.endsWith("key") ||
|
|
101
|
+
varName.endsWith("class") ||
|
|
102
|
+
varName.endsWith("style") ||
|
|
103
|
+
varName.endsWith("url") ||
|
|
104
|
+
varName.endsWith("path") ||
|
|
105
|
+
varName.endsWith("type")) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const pos = ctx.sourceFile.getLineAndCharacterOfPosition(node.initializer.getStart());
|
|
110
|
+
return {
|
|
111
|
+
file: ctx.filePath,
|
|
112
|
+
line: pos.line + 1,
|
|
113
|
+
column: pos.character + 1,
|
|
114
|
+
text,
|
|
115
|
+
type: "string-variable",
|
|
116
|
+
severity: "warning",
|
|
117
|
+
message: `Hardcoded string: "${truncate(text, 40)}"`,
|
|
118
|
+
suggestedKey: generateKeyFromContext(text, ctx.filePath),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Determine if a string looks like user-facing text
|
|
123
|
+
* - Must be at least 5 characters
|
|
124
|
+
* - Must start with a capital letter OR contain a space
|
|
125
|
+
* - Must not match any ignore patterns
|
|
126
|
+
*/
|
|
127
|
+
function isUserFacingText(text) {
|
|
128
|
+
// Too short
|
|
129
|
+
if (text.length < 5)
|
|
130
|
+
return false;
|
|
131
|
+
// Check ignore patterns
|
|
132
|
+
for (const pattern of IGNORE_PATTERNS) {
|
|
133
|
+
if (pattern.test(text))
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
// Must either:
|
|
137
|
+
// 1. Start with capital letter and have length >= 5, OR
|
|
138
|
+
// 2. Contain at least one space (indicates a phrase)
|
|
139
|
+
const startsWithCapital = /^[A-Z]/.test(text);
|
|
140
|
+
const hasSpace = /\s/.test(text);
|
|
141
|
+
if (!startsWithCapital && !hasSpace)
|
|
142
|
+
return false;
|
|
143
|
+
// Additional check: if no space, must look like a proper word/title
|
|
144
|
+
// (not a technical identifier like "Material", "Primary", "Default")
|
|
145
|
+
if (!hasSpace) {
|
|
146
|
+
// Common technical single-word values to skip
|
|
147
|
+
const technicalWords = [
|
|
148
|
+
"Primary",
|
|
149
|
+
"Secondary",
|
|
150
|
+
"Default",
|
|
151
|
+
"None",
|
|
152
|
+
"True",
|
|
153
|
+
"False",
|
|
154
|
+
"Null",
|
|
155
|
+
"Undefined",
|
|
156
|
+
"Material",
|
|
157
|
+
"Outlined",
|
|
158
|
+
"Contained",
|
|
159
|
+
"Text",
|
|
160
|
+
"Small",
|
|
161
|
+
"Medium",
|
|
162
|
+
"Large",
|
|
163
|
+
"Left",
|
|
164
|
+
"Right",
|
|
165
|
+
"Center",
|
|
166
|
+
"Top",
|
|
167
|
+
"Bottom",
|
|
168
|
+
"Start",
|
|
169
|
+
"End",
|
|
170
|
+
"Vertical",
|
|
171
|
+
"Horizontal",
|
|
172
|
+
"Enabled",
|
|
173
|
+
"Disabled",
|
|
174
|
+
"Active",
|
|
175
|
+
"Inactive",
|
|
176
|
+
"Loading",
|
|
177
|
+
"Success",
|
|
178
|
+
"Error",
|
|
179
|
+
"Warning",
|
|
180
|
+
"Info",
|
|
181
|
+
];
|
|
182
|
+
if (technicalWords.includes(text))
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=string-variable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"string-variable.js","sourceRoot":"","sources":["../../../src/analyzer/rules/string-variable.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toast message detection rule
|
|
3
|
+
*
|
|
4
|
+
* Detects hardcoded strings in toast function calls
|
|
5
|
+
* Works with: react-hot-toast, sonner, react-toastify
|
|
6
|
+
*/
|
|
7
|
+
import ts from "typescript";
|
|
8
|
+
import type { Issue, RuleContext } from "../types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Check for toast function calls with hardcoded strings
|
|
11
|
+
*/
|
|
12
|
+
export declare function checkToastMessage(node: ts.CallExpression, ctx: RuleContext): Issue | null;
|
|
13
|
+
//# sourceMappingURL=toast-message.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toast-message.d.ts","sourceRoot":"","sources":["../../../src/analyzer/rules/toast-message.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AA2BtD;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,EAAE,CAAC,cAAc,EACvB,GAAG,EAAE,WAAW,GACf,KAAK,GAAG,IAAI,CAuBd"}
|