@better-i18n/cli 0.1.8 → 0.2.0
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/dist/analyzer/dynamic-matcher.d.ts +53 -0
- package/dist/analyzer/dynamic-matcher.d.ts.map +1 -0
- package/dist/analyzer/dynamic-matcher.js +93 -0
- package/dist/analyzer/dynamic-matcher.js.map +1 -0
- package/dist/analyzer/index.d.ts +13 -4
- package/dist/analyzer/index.d.ts.map +1 -1
- package/dist/analyzer/index.js +165 -22
- package/dist/analyzer/index.js.map +1 -1
- package/dist/analyzer/rules/data-structure.d.ts +13 -0
- package/dist/analyzer/rules/data-structure.d.ts.map +1 -0
- package/dist/analyzer/rules/data-structure.js +264 -0
- package/dist/analyzer/rules/data-structure.js.map +1 -0
- package/dist/analyzer/rules/index.d.ts +10 -7
- package/dist/analyzer/rules/index.d.ts.map +1 -1
- package/dist/analyzer/rules/index.js +10 -7
- package/dist/analyzer/rules/index.js.map +1 -1
- package/dist/analyzer/rules/jsx-text.js +1 -1
- package/dist/analyzer/rules/jsx-text.js.map +1 -1
- package/dist/analyzer/rules/translation-function.d.ts.map +1 -1
- package/dist/analyzer/rules/translation-function.js +94 -6
- package/dist/analyzer/rules/translation-function.js.map +1 -1
- package/dist/analyzer/types.d.ts +28 -3
- package/dist/analyzer/types.d.ts.map +1 -1
- package/dist/commands/check.d.ts +14 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +70 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/doctor.d.ts +20 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +77 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +158 -272
- package/dist/commands/sync.js.map +1 -1
- package/dist/context/detector.js +1 -1
- package/dist/context/detector.js.map +1 -1
- package/dist/context/oidc.d.ts +33 -0
- package/dist/context/oidc.d.ts.map +1 -0
- package/dist/context/oidc.js +63 -0
- package/dist/context/oidc.js.map +1 -0
- package/dist/doctor/index.d.ts +65 -0
- package/dist/doctor/index.d.ts.map +1 -0
- package/dist/doctor/index.js +252 -0
- package/dist/doctor/index.js.map +1 -0
- package/dist/doctor/project-discovery.d.ts +37 -0
- package/dist/doctor/project-discovery.d.ts.map +1 -0
- package/dist/doctor/project-discovery.js +140 -0
- package/dist/doctor/project-discovery.js.map +1 -0
- package/dist/doctor/score.d.ts +41 -0
- package/dist/doctor/score.d.ts.map +1 -0
- package/dist/doctor/score.js +107 -0
- package/dist/doctor/score.js.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -1
- package/dist/reporters/doctor-eslint.d.ts +17 -0
- package/dist/reporters/doctor-eslint.d.ts.map +1 -0
- package/dist/reporters/doctor-eslint.js +139 -0
- package/dist/reporters/doctor-eslint.js.map +1 -0
- package/dist/reporters/doctor-json.d.ts +12 -0
- package/dist/reporters/doctor-json.d.ts.map +1 -0
- package/dist/reporters/doctor-json.js +21 -0
- package/dist/reporters/doctor-json.js.map +1 -0
- package/dist/reporters/doctor-report.d.ts +23 -0
- package/dist/reporters/doctor-report.d.ts.map +1 -0
- package/dist/reporters/doctor-report.js +62 -0
- package/dist/reporters/doctor-report.js.map +1 -0
- package/dist/reporters/eslint-style.js +4 -4
- package/dist/reporters/eslint-style.js.map +1 -1
- package/dist/rules/categories.d.ts +36 -0
- package/dist/rules/categories.d.ts.map +1 -0
- package/dist/rules/categories.js +80 -0
- package/dist/rules/categories.js.map +1 -0
- package/dist/rules/code/index.d.ts +9 -0
- package/dist/rules/code/index.d.ts.map +1 -0
- package/dist/rules/code/index.js +9 -0
- package/dist/rules/code/index.js.map +1 -0
- package/dist/rules/code/jsx-attribute.d.ts +12 -0
- package/dist/rules/code/jsx-attribute.d.ts.map +1 -0
- package/dist/rules/code/jsx-attribute.js +78 -0
- package/dist/rules/code/jsx-attribute.js.map +1 -0
- package/dist/rules/code/jsx-text.d.ts +12 -0
- package/dist/rules/code/jsx-text.d.ts.map +1 -0
- package/dist/rules/code/jsx-text.js +47 -0
- package/dist/rules/code/jsx-text.js.map +1 -0
- package/dist/rules/code/string-variable.d.ts +13 -0
- package/dist/rules/code/string-variable.d.ts.map +1 -0
- package/dist/rules/code/string-variable.js +185 -0
- package/dist/rules/code/string-variable.js.map +1 -0
- package/dist/rules/code/ternary-locale.d.ts +12 -0
- package/dist/rules/code/ternary-locale.d.ts.map +1 -0
- package/dist/rules/code/ternary-locale.js +53 -0
- package/dist/rules/code/ternary-locale.js.map +1 -0
- package/dist/rules/code/toast-message.d.ts +13 -0
- package/dist/rules/code/toast-message.d.ts.map +1 -0
- package/dist/rules/code/toast-message.js +101 -0
- package/dist/rules/code/toast-message.js.map +1 -0
- package/dist/rules/extraction/data-structure.d.ts +13 -0
- package/dist/rules/extraction/data-structure.d.ts.map +1 -0
- package/dist/rules/extraction/data-structure.js +212 -0
- package/dist/rules/extraction/data-structure.js.map +1 -0
- package/dist/rules/extraction/index.d.ts +6 -0
- package/dist/rules/extraction/index.d.ts.map +1 -0
- package/dist/rules/extraction/index.js +6 -0
- package/dist/rules/extraction/index.js.map +1 -0
- package/dist/rules/extraction/translation-function.d.ts +12 -0
- package/dist/rules/extraction/translation-function.d.ts.map +1 -0
- package/dist/rules/extraction/translation-function.js +153 -0
- package/dist/rules/extraction/translation-function.js.map +1 -0
- package/dist/rules/health/index.d.ts +16 -0
- package/dist/rules/health/index.d.ts.map +1 -0
- package/dist/rules/health/index.js +22 -0
- package/dist/rules/health/index.js.map +1 -0
- package/dist/rules/health/missing-translations.d.ts +13 -0
- package/dist/rules/health/missing-translations.d.ts.map +1 -0
- package/dist/rules/health/missing-translations.js +48 -0
- package/dist/rules/health/missing-translations.js.map +1 -0
- package/dist/rules/health/orphan-keys.d.ts +16 -0
- package/dist/rules/health/orphan-keys.d.ts.map +1 -0
- package/dist/rules/health/orphan-keys.js +50 -0
- package/dist/rules/health/orphan-keys.js.map +1 -0
- package/dist/rules/health/placeholder-mismatch.d.ts +32 -0
- package/dist/rules/health/placeholder-mismatch.d.ts.map +1 -0
- package/dist/rules/health/placeholder-mismatch.js +120 -0
- package/dist/rules/health/placeholder-mismatch.js.map +1 -0
- package/dist/rules/registry.d.ts +84 -0
- package/dist/rules/registry.d.ts.map +1 -0
- package/dist/rules/registry.js +11 -0
- package/dist/rules/registry.js.map +1 -0
- package/dist/utils/cdn-client.d.ts +30 -0
- package/dist/utils/cdn-client.d.ts.map +1 -0
- package/dist/utils/cdn-client.js +59 -0
- package/dist/utils/cdn-client.js.map +1 -0
- package/dist/utils/json-keys.d.ts +60 -0
- package/dist/utils/json-keys.d.ts.map +1 -0
- package/dist/utils/json-keys.js +103 -0
- package/dist/utils/json-keys.js.map +1 -0
- package/dist/utils/key-comparison.d.ts +67 -0
- package/dist/utils/key-comparison.d.ts.map +1 -0
- package/dist/utils/key-comparison.js +299 -0
- package/dist/utils/key-comparison.js.map +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,101 @@
|
|
|
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 { generateKeyFromContext, truncate } from "../../utils/text.js";
|
|
9
|
+
/**
|
|
10
|
+
* Toast function names to detect
|
|
11
|
+
*/
|
|
12
|
+
const TOAST_FUNCTIONS = new Set([
|
|
13
|
+
"toast",
|
|
14
|
+
"success",
|
|
15
|
+
"error",
|
|
16
|
+
"warning",
|
|
17
|
+
"info",
|
|
18
|
+
"loading",
|
|
19
|
+
]);
|
|
20
|
+
/**
|
|
21
|
+
* Toast method names (for toast.error(), toast.success(), etc.)
|
|
22
|
+
*/
|
|
23
|
+
const TOAST_METHODS = new Set([
|
|
24
|
+
"success",
|
|
25
|
+
"error",
|
|
26
|
+
"warning",
|
|
27
|
+
"info",
|
|
28
|
+
"loading",
|
|
29
|
+
"promise",
|
|
30
|
+
"custom",
|
|
31
|
+
]);
|
|
32
|
+
/**
|
|
33
|
+
* Check for toast function calls with hardcoded strings
|
|
34
|
+
*/
|
|
35
|
+
export function checkToastMessage(node, ctx) {
|
|
36
|
+
const funcName = getToastFunctionName(node);
|
|
37
|
+
if (!funcName)
|
|
38
|
+
return null;
|
|
39
|
+
// Get the first argument
|
|
40
|
+
const args = node.arguments;
|
|
41
|
+
if (args.length === 0)
|
|
42
|
+
return null;
|
|
43
|
+
const firstArg = args[0];
|
|
44
|
+
// Handle string literal: toast("Hello")
|
|
45
|
+
if (ts.isStringLiteral(firstArg)) {
|
|
46
|
+
return createIssue(firstArg, firstArg.text, ctx);
|
|
47
|
+
}
|
|
48
|
+
// Handle template literal: toast(`Hello`)
|
|
49
|
+
if (ts.isNoSubstitutionTemplateLiteral(firstArg)) {
|
|
50
|
+
return createIssue(firstArg, firstArg.text, ctx);
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get toast function name from call expression
|
|
56
|
+
* Handles: toast(), toast.error(), toast.success(), etc.
|
|
57
|
+
*/
|
|
58
|
+
function getToastFunctionName(node) {
|
|
59
|
+
const expr = node.expression;
|
|
60
|
+
// Simple call: toast("message")
|
|
61
|
+
if (ts.isIdentifier(expr)) {
|
|
62
|
+
const name = expr.text;
|
|
63
|
+
if (name === "toast" || TOAST_FUNCTIONS.has(name)) {
|
|
64
|
+
return name;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
// Method call: toast.error("message"), toast.success("message")
|
|
69
|
+
if (ts.isPropertyAccessExpression(expr) &&
|
|
70
|
+
ts.isIdentifier(expr.expression)) {
|
|
71
|
+
const objName = expr.expression.text;
|
|
72
|
+
const methodName = expr.name.text;
|
|
73
|
+
if (objName === "toast" && TOAST_METHODS.has(methodName)) {
|
|
74
|
+
return `toast.${methodName}`;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Create an issue for a detected hardcoded toast message
|
|
81
|
+
*/
|
|
82
|
+
function createIssue(node, text, ctx) {
|
|
83
|
+
// Skip very short strings
|
|
84
|
+
if (!text || text.length <= 2)
|
|
85
|
+
return null;
|
|
86
|
+
// Skip strings that look like technical identifiers
|
|
87
|
+
if (/^[a-z_]+$/.test(text))
|
|
88
|
+
return null;
|
|
89
|
+
const pos = ctx.sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
90
|
+
return {
|
|
91
|
+
file: ctx.filePath,
|
|
92
|
+
line: pos.line + 1,
|
|
93
|
+
column: pos.character + 1,
|
|
94
|
+
text,
|
|
95
|
+
type: "toast-message",
|
|
96
|
+
severity: "warning",
|
|
97
|
+
message: `Hardcoded toast: "${truncate(text, 40)}"`,
|
|
98
|
+
suggestedKey: generateKeyFromContext(text, ctx.filePath),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=toast-message.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toast-message.js","sourceRoot":"","sources":["../../../src/rules/code/toast-message.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data structure detection rule
|
|
3
|
+
*
|
|
4
|
+
* Detects translation keys stored in data structures (arrays, objects)
|
|
5
|
+
* Uses property name heuristics to identify translation-relevant strings
|
|
6
|
+
*/
|
|
7
|
+
import ts from "typescript";
|
|
8
|
+
import type { DataStructureConfig, Issue, RuleContext } from "../../analyzer/types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Check data structures for translation keys
|
|
11
|
+
*/
|
|
12
|
+
export declare function checkDataStructure(node: ts.ObjectLiteralExpression | ts.ArrayLiteralExpression, ctx: RuleContext, config?: DataStructureConfig): Issue[];
|
|
13
|
+
//# sourceMappingURL=data-structure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-structure.d.ts","sourceRoot":"","sources":["../../../src/rules/extraction/data-structure.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAkLvF;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,EAAE,CAAC,uBAAuB,GAAG,EAAE,CAAC,sBAAsB,EAC5D,GAAG,EAAE,WAAW,EAChB,MAAM,CAAC,EAAE,mBAAmB,GAC3B,KAAK,EAAE,CAgGT"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data structure detection rule
|
|
3
|
+
*
|
|
4
|
+
* Detects translation keys stored in data structures (arrays, objects)
|
|
5
|
+
* Uses property name heuristics to identify translation-relevant strings
|
|
6
|
+
*/
|
|
7
|
+
import ts from "typescript";
|
|
8
|
+
/**
|
|
9
|
+
* Default property names that likely contain translation keys
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_PROPERTY_NAMES = new Set([
|
|
12
|
+
"message",
|
|
13
|
+
"label",
|
|
14
|
+
"title",
|
|
15
|
+
"text",
|
|
16
|
+
"description",
|
|
17
|
+
"placeholder",
|
|
18
|
+
"tooltip",
|
|
19
|
+
"translationKey",
|
|
20
|
+
"i18nKey",
|
|
21
|
+
"successMessage",
|
|
22
|
+
"errorMessage",
|
|
23
|
+
"warningMessage",
|
|
24
|
+
"infoMessage",
|
|
25
|
+
]);
|
|
26
|
+
/**
|
|
27
|
+
* Default configuration for data structure detection
|
|
28
|
+
*/
|
|
29
|
+
const DEFAULT_CONFIG = {
|
|
30
|
+
enabled: true,
|
|
31
|
+
requireTranslationScope: true,
|
|
32
|
+
maxDepth: 3,
|
|
33
|
+
propertyNames: [],
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Check if a string value looks like a translation key
|
|
37
|
+
* Filters out obvious non-translation strings
|
|
38
|
+
*/
|
|
39
|
+
function looksLikeTranslationKey(value) {
|
|
40
|
+
if (value.length < 2)
|
|
41
|
+
return false;
|
|
42
|
+
if (value.startsWith("http://") || value.startsWith("https://"))
|
|
43
|
+
return false;
|
|
44
|
+
if (value.startsWith("/") || value.startsWith("./") || value.startsWith("../"))
|
|
45
|
+
return false;
|
|
46
|
+
if (value.includes(" ") && (value.includes("-") || value.includes("_")))
|
|
47
|
+
return false;
|
|
48
|
+
if (value.startsWith("data:"))
|
|
49
|
+
return false;
|
|
50
|
+
if (/^#[0-9a-fA-F]{3,8}$/.test(value) || /^rgb\(/.test(value))
|
|
51
|
+
return false;
|
|
52
|
+
if (/^\d+$/.test(value))
|
|
53
|
+
return false;
|
|
54
|
+
if (/^[^a-zA-Z0-9]+$/.test(value))
|
|
55
|
+
return false;
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Extract string literals from object literal expression
|
|
60
|
+
*/
|
|
61
|
+
function extractFromObjectLiteral(node, propertyNames, currentDepth, maxDepth) {
|
|
62
|
+
if (currentDepth > maxDepth)
|
|
63
|
+
return [];
|
|
64
|
+
const keys = [];
|
|
65
|
+
for (const prop of node.properties) {
|
|
66
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
67
|
+
const propName = prop.name.getText();
|
|
68
|
+
if (propertyNames.has(propName)) {
|
|
69
|
+
if (ts.isStringLiteral(prop.initializer)) {
|
|
70
|
+
const value = prop.initializer.text;
|
|
71
|
+
if (looksLikeTranslationKey(value)) {
|
|
72
|
+
keys.push(value);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (ts.isObjectLiteralExpression(prop.initializer)) {
|
|
77
|
+
keys.push(...extractFromObjectLiteral(prop.initializer, propertyNames, currentDepth + 1, maxDepth));
|
|
78
|
+
}
|
|
79
|
+
if (ts.isArrayLiteralExpression(prop.initializer)) {
|
|
80
|
+
keys.push(...extractFromArrayLiteral(prop.initializer, propertyNames, currentDepth + 1, maxDepth));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return keys;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Check if an array looks like a language selector pattern
|
|
88
|
+
*/
|
|
89
|
+
function isLanguageSelectorPattern(node) {
|
|
90
|
+
if (node.elements.length < 2)
|
|
91
|
+
return false;
|
|
92
|
+
const allMatchPattern = node.elements.every((element) => {
|
|
93
|
+
if (!ts.isObjectLiteralExpression(element))
|
|
94
|
+
return false;
|
|
95
|
+
let hasValue = false;
|
|
96
|
+
let hasLabel = false;
|
|
97
|
+
let valueIsLanguageCode = false;
|
|
98
|
+
for (const prop of element.properties) {
|
|
99
|
+
if (!ts.isPropertyAssignment(prop))
|
|
100
|
+
continue;
|
|
101
|
+
const propName = prop.name.getText();
|
|
102
|
+
if (propName === "value" && ts.isStringLiteral(prop.initializer)) {
|
|
103
|
+
hasValue = true;
|
|
104
|
+
const value = prop.initializer.text;
|
|
105
|
+
valueIsLanguageCode = /^[a-z]{2}(-[a-z]{2,3})?$/i.test(value);
|
|
106
|
+
}
|
|
107
|
+
if (propName === "label") {
|
|
108
|
+
hasLabel = true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return hasValue && hasLabel && valueIsLanguageCode;
|
|
112
|
+
});
|
|
113
|
+
return allMatchPattern;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Extract string literals from array literal expression
|
|
117
|
+
*/
|
|
118
|
+
function extractFromArrayLiteral(node, propertyNames, currentDepth, maxDepth) {
|
|
119
|
+
if (currentDepth > maxDepth)
|
|
120
|
+
return [];
|
|
121
|
+
if (isLanguageSelectorPattern(node))
|
|
122
|
+
return [];
|
|
123
|
+
const keys = [];
|
|
124
|
+
for (const element of node.elements) {
|
|
125
|
+
if (ts.isObjectLiteralExpression(element)) {
|
|
126
|
+
keys.push(...extractFromObjectLiteral(element, propertyNames, currentDepth + 1, maxDepth));
|
|
127
|
+
}
|
|
128
|
+
if (ts.isArrayLiteralExpression(element)) {
|
|
129
|
+
keys.push(...extractFromArrayLiteral(element, propertyNames, currentDepth + 1, maxDepth));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return keys;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Check data structures for translation keys
|
|
136
|
+
*/
|
|
137
|
+
export function checkDataStructure(node, ctx, config) {
|
|
138
|
+
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
139
|
+
if (!cfg.enabled)
|
|
140
|
+
return [];
|
|
141
|
+
if (cfg.requireTranslationScope && !ctx.translationScope)
|
|
142
|
+
return [];
|
|
143
|
+
const propertyNames = new Set([
|
|
144
|
+
...DEFAULT_PROPERTY_NAMES,
|
|
145
|
+
...(cfg.propertyNames || []),
|
|
146
|
+
]);
|
|
147
|
+
let extractedKeys = [];
|
|
148
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
149
|
+
extractedKeys = extractFromObjectLiteral(node, propertyNames, 0, cfg.maxDepth || 3);
|
|
150
|
+
}
|
|
151
|
+
else if (ts.isArrayLiteralExpression(node)) {
|
|
152
|
+
extractedKeys = extractFromArrayLiteral(node, propertyNames, 0, cfg.maxDepth || 3);
|
|
153
|
+
}
|
|
154
|
+
if (extractedKeys.length === 0)
|
|
155
|
+
return [];
|
|
156
|
+
const issues = [];
|
|
157
|
+
for (const extractedKey of extractedKeys) {
|
|
158
|
+
let key = extractedKey;
|
|
159
|
+
let namespace;
|
|
160
|
+
let bindingType = "unbound";
|
|
161
|
+
// First try: scope-bound namespace
|
|
162
|
+
if (ctx.namespaceMap) {
|
|
163
|
+
for (const binding of Object.values(ctx.namespaceMap)) {
|
|
164
|
+
if (binding.type === "bound-scoped" && binding.namespace) {
|
|
165
|
+
namespace = binding.namespace;
|
|
166
|
+
bindingType = "bound-scoped";
|
|
167
|
+
if (!key.startsWith(`${namespace}.`)) {
|
|
168
|
+
key = `${namespace}.${extractedKey}`;
|
|
169
|
+
}
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Second try: file-level namespace inference
|
|
175
|
+
if (!namespace && ctx.fileNamespaces && ctx.fileNamespaces.length > 0) {
|
|
176
|
+
if (ctx.fileNamespaces.length === 1) {
|
|
177
|
+
namespace = ctx.fileNamespaces[0];
|
|
178
|
+
bindingType = "bound-scoped";
|
|
179
|
+
key = `${namespace}.${extractedKey}`;
|
|
180
|
+
if (ctx.verbose) {
|
|
181
|
+
console.log(`[VERBOSE] Applied file-level namespace "${namespace}" to data structure key: ${extractedKey}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
namespace = ctx.fileNamespaces[0];
|
|
186
|
+
bindingType = "unknown-scoped";
|
|
187
|
+
key = `${namespace}.${extractedKey}`;
|
|
188
|
+
if (ctx.verbose) {
|
|
189
|
+
console.log(`[VERBOSE] Ambiguous namespace (${ctx.fileNamespaces.length} found in file), using "${namespace}" for: ${extractedKey}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const pos = ctx.sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
194
|
+
issues.push({
|
|
195
|
+
file: ctx.filePath,
|
|
196
|
+
line: pos.line + 1,
|
|
197
|
+
column: pos.character + 1,
|
|
198
|
+
text: extractedKey,
|
|
199
|
+
type: "string-variable",
|
|
200
|
+
severity: "info",
|
|
201
|
+
message: `Translation key in data structure: "${key}"`,
|
|
202
|
+
key,
|
|
203
|
+
bindingType,
|
|
204
|
+
namespace,
|
|
205
|
+
});
|
|
206
|
+
if (ctx.stats && ctx.stats.dataStructureKeys !== undefined) {
|
|
207
|
+
ctx.stats.dataStructureKeys++;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return issues;
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=data-structure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-structure.js","sourceRoot":"","sources":["../../../src/rules/extraction/data-structure.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/rules/extraction/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/rules/extraction/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translation function detection rule
|
|
3
|
+
*
|
|
4
|
+
* Detects t() function calls to extract translation keys used in code
|
|
5
|
+
*/
|
|
6
|
+
import ts from "typescript";
|
|
7
|
+
import type { Issue, RuleContext } from "../../analyzer/types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Check for translation function calls
|
|
10
|
+
*/
|
|
11
|
+
export declare function checkTranslationFunction(node: ts.CallExpression, ctx: RuleContext): Issue | null;
|
|
12
|
+
//# sourceMappingURL=translation-function.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translation-function.d.ts","sourceRoot":"","sources":["../../../src/rules/extraction/translation-function.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAgBlE;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,EAAE,CAAC,cAAc,EACvB,GAAG,EAAE,WAAW,GACf,KAAK,GAAG,IAAI,CAsGd"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translation function detection rule
|
|
3
|
+
*
|
|
4
|
+
* Detects t() function calls to extract translation keys used in code
|
|
5
|
+
*/
|
|
6
|
+
import ts from "typescript";
|
|
7
|
+
/**
|
|
8
|
+
* Translation function names to detect
|
|
9
|
+
*/
|
|
10
|
+
const TRANSLATION_FUNCTIONS = new Set(["t", "useTranslation"]);
|
|
11
|
+
/**
|
|
12
|
+
* Check if identifier looks like a translator variable
|
|
13
|
+
* Common patterns: t, tUser, tAuth, tCommon, etc.
|
|
14
|
+
*/
|
|
15
|
+
function looksLikeTranslator(name) {
|
|
16
|
+
// Single 't' or starts with 't' followed by uppercase (tUser, tAuth, etc.)
|
|
17
|
+
return name === "t" || /^t[A-Z]/.test(name);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Check for translation function calls
|
|
21
|
+
*/
|
|
22
|
+
export function checkTranslationFunction(node, ctx) {
|
|
23
|
+
const bindingId = getBindingIdentifier(node);
|
|
24
|
+
if (!bindingId)
|
|
25
|
+
return null;
|
|
26
|
+
// Check if this identifier is bound to a translation namespace
|
|
27
|
+
let binding = ctx.namespaceMap?.[bindingId];
|
|
28
|
+
if (!binding && !TRANSLATION_FUNCTIONS.has(bindingId)) {
|
|
29
|
+
if (looksLikeTranslator(bindingId)) {
|
|
30
|
+
if (ctx.verbose) {
|
|
31
|
+
const pos = ctx.sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
32
|
+
console.log(`[VERBOSE] Accepting translator by pattern: ${bindingId} at ${ctx.filePath}:${pos.line + 1}`);
|
|
33
|
+
}
|
|
34
|
+
binding = { type: "unknown-scoped", dynamic: false };
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
if (ctx.stats)
|
|
38
|
+
ctx.stats.unboundTranslators++;
|
|
39
|
+
if (ctx.verbose) {
|
|
40
|
+
const pos = ctx.sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
41
|
+
console.log(`[VERBOSE] Unbound translator (not a recognized pattern): ${bindingId} at ${ctx.filePath}:${pos.line + 1}`);
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Get the first argument (translation key)
|
|
47
|
+
const args = node.arguments;
|
|
48
|
+
if (args.length === 0)
|
|
49
|
+
return null;
|
|
50
|
+
const firstArg = args[0];
|
|
51
|
+
// Handle string literal: t("auth.login")
|
|
52
|
+
if (ts.isStringLiteral(firstArg)) {
|
|
53
|
+
const pos = ctx.sourceFile.getLineAndCharacterOfPosition(firstArg.getStart());
|
|
54
|
+
let key = firstArg.text;
|
|
55
|
+
// Apply namespace prefix if bound to a specific namespace
|
|
56
|
+
if (binding?.type === "bound-scoped" && binding.namespace) {
|
|
57
|
+
const prefix = binding.namespace;
|
|
58
|
+
if (!key.startsWith(`${prefix}.`)) {
|
|
59
|
+
key = `${prefix}.${key}`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
file: ctx.filePath,
|
|
64
|
+
line: pos.line + 1,
|
|
65
|
+
column: pos.character + 1,
|
|
66
|
+
text: firstArg.text,
|
|
67
|
+
type: "string-variable",
|
|
68
|
+
severity: "info",
|
|
69
|
+
message: `Translation key: "${key}"`,
|
|
70
|
+
key,
|
|
71
|
+
bindingType: binding?.type || "unbound",
|
|
72
|
+
namespace: binding?.namespace,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// Handle template literal: t(`plans.${planKey}.name`)
|
|
76
|
+
if (ts.isTemplateExpression(firstArg) || ts.isNoSubstitutionTemplateLiteral(firstArg)) {
|
|
77
|
+
const pattern = extractTemplatePattern(firstArg);
|
|
78
|
+
const pos = ctx.sourceFile.getLineAndCharacterOfPosition(firstArg.getStart());
|
|
79
|
+
// Apply namespace prefix if bound
|
|
80
|
+
let fullPattern = pattern;
|
|
81
|
+
if (binding?.type === "bound-scoped" && binding.namespace) {
|
|
82
|
+
const prefix = binding.namespace;
|
|
83
|
+
if (!pattern.startsWith(`${prefix}.`)) {
|
|
84
|
+
fullPattern = `${prefix}.${pattern}`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (ctx.stats)
|
|
88
|
+
ctx.stats.dynamicKeys++;
|
|
89
|
+
return {
|
|
90
|
+
file: ctx.filePath,
|
|
91
|
+
line: pos.line + 1,
|
|
92
|
+
column: pos.character + 1,
|
|
93
|
+
text: pattern,
|
|
94
|
+
type: "string-variable",
|
|
95
|
+
severity: "info",
|
|
96
|
+
message: `Dynamic translation key pattern: "${fullPattern}"`,
|
|
97
|
+
key: fullPattern,
|
|
98
|
+
pattern: fullPattern,
|
|
99
|
+
isDynamic: true,
|
|
100
|
+
bindingType: binding?.type || "unbound",
|
|
101
|
+
namespace: binding?.namespace,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
// Other dynamic keys (computed, variables, etc.)
|
|
105
|
+
if (ctx.stats)
|
|
106
|
+
ctx.stats.dynamicKeys++;
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Extract pattern from template literal
|
|
111
|
+
*/
|
|
112
|
+
function extractTemplatePattern(node) {
|
|
113
|
+
if (ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
114
|
+
return node.text;
|
|
115
|
+
}
|
|
116
|
+
if (!ts.isTemplateExpression(node)) {
|
|
117
|
+
return "";
|
|
118
|
+
}
|
|
119
|
+
let pattern = node.head.text;
|
|
120
|
+
for (const span of node.templateSpans) {
|
|
121
|
+
const expr = span.expression;
|
|
122
|
+
let placeholder = "${x}";
|
|
123
|
+
if (ts.isIdentifier(expr)) {
|
|
124
|
+
placeholder = `\${${expr.text}}`;
|
|
125
|
+
}
|
|
126
|
+
else if (ts.isPropertyAccessExpression(expr)) {
|
|
127
|
+
placeholder = `\${${expr.getText()}}`;
|
|
128
|
+
}
|
|
129
|
+
pattern += placeholder + span.literal.text;
|
|
130
|
+
}
|
|
131
|
+
return pattern;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Extract the binding identifier from a call expression.
|
|
135
|
+
* Handles: t(), t.raw(), t.rich(), t.has()
|
|
136
|
+
*/
|
|
137
|
+
function getBindingIdentifier(node) {
|
|
138
|
+
const expr = node.expression;
|
|
139
|
+
// Simple identifier: t()
|
|
140
|
+
if (ts.isIdentifier(expr)) {
|
|
141
|
+
return expr.text;
|
|
142
|
+
}
|
|
143
|
+
// Property access: t.raw(), t.rich(), t.has()
|
|
144
|
+
if (ts.isPropertyAccessExpression(expr) &&
|
|
145
|
+
ts.isIdentifier(expr.expression)) {
|
|
146
|
+
const propName = expr.name.text;
|
|
147
|
+
if (["raw", "rich", "has"].includes(propName)) {
|
|
148
|
+
return expr.expression.text;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=translation-function.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translation-function.js","sourceRoot":"","sources":["../../../src/rules/extraction/translation-function.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health rules index
|
|
3
|
+
*
|
|
4
|
+
* Exports all health rules and a convenience array for bulk registration.
|
|
5
|
+
* Health rules analyze translation files at the project level.
|
|
6
|
+
*/
|
|
7
|
+
import type { HealthRule } from "../registry.js";
|
|
8
|
+
export { missingTranslationsRule } from "./missing-translations.js";
|
|
9
|
+
export { orphanKeysRule } from "./orphan-keys.js";
|
|
10
|
+
export { placeholderMismatchRule, extractPlaceholders, } from "./placeholder-mismatch.js";
|
|
11
|
+
/**
|
|
12
|
+
* All health rules in recommended execution order.
|
|
13
|
+
* Coverage rules first, then quality, then performance.
|
|
14
|
+
*/
|
|
15
|
+
export declare const healthRules: HealthRule[];
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/rules/health/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAKjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EACL,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,2BAA2B,CAAC;AAEnC;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,UAAU,EAInC,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health rules index
|
|
3
|
+
*
|
|
4
|
+
* Exports all health rules and a convenience array for bulk registration.
|
|
5
|
+
* Health rules analyze translation files at the project level.
|
|
6
|
+
*/
|
|
7
|
+
import { missingTranslationsRule } from "./missing-translations.js";
|
|
8
|
+
import { orphanKeysRule } from "./orphan-keys.js";
|
|
9
|
+
import { placeholderMismatchRule } from "./placeholder-mismatch.js";
|
|
10
|
+
export { missingTranslationsRule } from "./missing-translations.js";
|
|
11
|
+
export { orphanKeysRule } from "./orphan-keys.js";
|
|
12
|
+
export { placeholderMismatchRule, extractPlaceholders, } from "./placeholder-mismatch.js";
|
|
13
|
+
/**
|
|
14
|
+
* All health rules in recommended execution order.
|
|
15
|
+
* Coverage rules first, then quality, then performance.
|
|
16
|
+
*/
|
|
17
|
+
export const healthRules = [
|
|
18
|
+
missingTranslationsRule,
|
|
19
|
+
placeholderMismatchRule,
|
|
20
|
+
orphanKeysRule,
|
|
21
|
+
];
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/rules/health/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAEpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EACL,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,2BAA2B,CAAC;AAEnC;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAiB;IACvC,uBAAuB;IACvB,uBAAuB;IACvB,cAAc;CACf,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Missing Translations Rule
|
|
3
|
+
*
|
|
4
|
+
* Detects translation keys that exist in the source locale but are
|
|
5
|
+
* missing in one or more target locales. This is the most fundamental
|
|
6
|
+
* coverage check — if a key is missing, users see fallback or broken UI.
|
|
7
|
+
*
|
|
8
|
+
* Category: Coverage (highest weight in health score)
|
|
9
|
+
* Default Severity: error
|
|
10
|
+
*/
|
|
11
|
+
import type { HealthRule } from "../registry.js";
|
|
12
|
+
export declare const missingTranslationsRule: HealthRule;
|
|
13
|
+
//# sourceMappingURL=missing-translations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"missing-translations.d.ts","sourceRoot":"","sources":["../../../src/rules/health/missing-translations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EACV,UAAU,EAGX,MAAM,gBAAgB,CAAC;AAIxB,eAAO,MAAM,uBAAuB,EAAE,UAyCrC,CAAC"}
|