@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.
Files changed (37) hide show
  1. package/README.md +39 -54
  2. package/dist/analyzer/index.d.ts +8 -2
  3. package/dist/analyzer/index.d.ts.map +1 -1
  4. package/dist/analyzer/index.js +148 -3
  5. package/dist/analyzer/index.js.map +1 -1
  6. package/dist/analyzer/rules/index.d.ts +2 -0
  7. package/dist/analyzer/rules/index.d.ts.map +1 -1
  8. package/dist/analyzer/rules/index.js +2 -0
  9. package/dist/analyzer/rules/index.js.map +1 -1
  10. package/dist/analyzer/rules/string-variable.d.ts +13 -0
  11. package/dist/analyzer/rules/string-variable.d.ts.map +1 -0
  12. package/dist/analyzer/rules/string-variable.js +187 -0
  13. package/dist/analyzer/rules/string-variable.js.map +1 -0
  14. package/dist/analyzer/rules/toast-message.d.ts +13 -0
  15. package/dist/analyzer/rules/toast-message.d.ts.map +1 -0
  16. package/dist/analyzer/rules/toast-message.js +103 -0
  17. package/dist/analyzer/rules/toast-message.js.map +1 -0
  18. package/dist/analyzer/rules/translation-function.d.ts.map +1 -1
  19. package/dist/analyzer/rules/translation-function.js +36 -21
  20. package/dist/analyzer/rules/translation-function.js.map +1 -1
  21. package/dist/analyzer/types.d.ts +17 -1
  22. package/dist/analyzer/types.d.ts.map +1 -1
  23. package/dist/commands/scan.d.ts.map +1 -1
  24. package/dist/commands/scan.js +21 -2
  25. package/dist/commands/scan.js.map +1 -1
  26. package/dist/commands/sync.d.ts +12 -0
  27. package/dist/commands/sync.d.ts.map +1 -0
  28. package/dist/commands/sync.js +583 -0
  29. package/dist/commands/sync.js.map +1 -0
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.js +8 -10
  32. package/dist/index.js.map +1 -1
  33. package/package.json +1 -1
  34. package/dist/commands/extract-keys.d.ts +0 -13
  35. package/dist/commands/extract-keys.d.ts.map +0 -1
  36. package/dist/commands/extract-keys.js +0 -418
  37. 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 extract-keys`
104
+ ### `better-i18n sync`
105
105
 
106
- Extract all translation keys used in your codebase (t() function calls).
106
+ Compare local translation keys (t() calls) with your Better i18n cloud project.
107
107
 
108
108
  ```bash
109
- # Extract all translation keys
110
- better-i18n extract-keys
109
+ # Basic usage (grouped tree output)
110
+ better-i18n sync
111
111
 
112
- # Compare with remote keys from CDN
113
- better-i18n extract-keys --compare
112
+ # Minimal metrics only
113
+ better-i18n sync --summary
114
114
 
115
- # Compare with specific locale
116
- better-i18n extract-keys --compare --locale tr
115
+ # Deep audit log & scope trace
116
+ better-i18n sync --verbose
117
117
 
118
- # JSON output (default)
119
- better-i18n extract-keys --format json
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
- # Show detailed output
122
- better-i18n extract-keys --verbose
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 `--compare`:**
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: 85%
177
- Remote Used: 96%
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
- Missing in Remote (2 keys):
180
- hero: 2 keys
181
- • hero.cta
182
- • hero.benefits
166
+ hero (5)
167
+ hero (ariaLabel, imageAlt, ...)
183
168
 
184
- ⚠️ Unused in Code (1 key):
185
- old: 1 key
186
- old.section
169
+ Unused in Code (386 keys)
170
+ features (25)
171
+ practiceSpeaking (title, subtitle, icon)
187
172
 
188
- Scanned 57 files in 0.12s
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 extract-keysExtract used keys
466
+ ├─ Run: better-i18n syncCompare 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 scan --ciFail build if strings found
469
+ ├─ CI/CD: better-i18n sync --format json Audit translations in pipeline
485
470
  └─ Dashboard: Manage translations, sync with GitHub
486
471
  ```
487
472
 
@@ -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<Issue[]>;
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): Issue[];
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;AAUH,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,KAAK,EAAE,CAAC,CAGlB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,UAAU,GAClB,KAAK,EAAE,CAiDT"}
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"}
@@ -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 ctx = { filePath, sourceFile };
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":"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"}
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"}