@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.
Files changed (142) hide show
  1. package/dist/analyzer/dynamic-matcher.d.ts +53 -0
  2. package/dist/analyzer/dynamic-matcher.d.ts.map +1 -0
  3. package/dist/analyzer/dynamic-matcher.js +93 -0
  4. package/dist/analyzer/dynamic-matcher.js.map +1 -0
  5. package/dist/analyzer/index.d.ts +13 -4
  6. package/dist/analyzer/index.d.ts.map +1 -1
  7. package/dist/analyzer/index.js +165 -22
  8. package/dist/analyzer/index.js.map +1 -1
  9. package/dist/analyzer/rules/data-structure.d.ts +13 -0
  10. package/dist/analyzer/rules/data-structure.d.ts.map +1 -0
  11. package/dist/analyzer/rules/data-structure.js +264 -0
  12. package/dist/analyzer/rules/data-structure.js.map +1 -0
  13. package/dist/analyzer/rules/index.d.ts +10 -7
  14. package/dist/analyzer/rules/index.d.ts.map +1 -1
  15. package/dist/analyzer/rules/index.js +10 -7
  16. package/dist/analyzer/rules/index.js.map +1 -1
  17. package/dist/analyzer/rules/jsx-text.js +1 -1
  18. package/dist/analyzer/rules/jsx-text.js.map +1 -1
  19. package/dist/analyzer/rules/translation-function.d.ts.map +1 -1
  20. package/dist/analyzer/rules/translation-function.js +94 -6
  21. package/dist/analyzer/rules/translation-function.js.map +1 -1
  22. package/dist/analyzer/types.d.ts +28 -3
  23. package/dist/analyzer/types.d.ts.map +1 -1
  24. package/dist/commands/check.d.ts +14 -0
  25. package/dist/commands/check.d.ts.map +1 -0
  26. package/dist/commands/check.js +70 -0
  27. package/dist/commands/check.js.map +1 -0
  28. package/dist/commands/doctor.d.ts +20 -0
  29. package/dist/commands/doctor.d.ts.map +1 -0
  30. package/dist/commands/doctor.js +77 -0
  31. package/dist/commands/doctor.js.map +1 -0
  32. package/dist/commands/sync.d.ts +1 -0
  33. package/dist/commands/sync.d.ts.map +1 -1
  34. package/dist/commands/sync.js +158 -272
  35. package/dist/commands/sync.js.map +1 -1
  36. package/dist/context/detector.js +1 -1
  37. package/dist/context/detector.js.map +1 -1
  38. package/dist/context/oidc.d.ts +33 -0
  39. package/dist/context/oidc.d.ts.map +1 -0
  40. package/dist/context/oidc.js +63 -0
  41. package/dist/context/oidc.js.map +1 -0
  42. package/dist/doctor/index.d.ts +65 -0
  43. package/dist/doctor/index.d.ts.map +1 -0
  44. package/dist/doctor/index.js +252 -0
  45. package/dist/doctor/index.js.map +1 -0
  46. package/dist/doctor/project-discovery.d.ts +37 -0
  47. package/dist/doctor/project-discovery.d.ts.map +1 -0
  48. package/dist/doctor/project-discovery.js +140 -0
  49. package/dist/doctor/project-discovery.js.map +1 -0
  50. package/dist/doctor/score.d.ts +41 -0
  51. package/dist/doctor/score.d.ts.map +1 -0
  52. package/dist/doctor/score.js +107 -0
  53. package/dist/doctor/score.js.map +1 -0
  54. package/dist/index.js +35 -0
  55. package/dist/index.js.map +1 -1
  56. package/dist/reporters/doctor-eslint.d.ts +17 -0
  57. package/dist/reporters/doctor-eslint.d.ts.map +1 -0
  58. package/dist/reporters/doctor-eslint.js +139 -0
  59. package/dist/reporters/doctor-eslint.js.map +1 -0
  60. package/dist/reporters/doctor-json.d.ts +12 -0
  61. package/dist/reporters/doctor-json.d.ts.map +1 -0
  62. package/dist/reporters/doctor-json.js +21 -0
  63. package/dist/reporters/doctor-json.js.map +1 -0
  64. package/dist/reporters/doctor-report.d.ts +23 -0
  65. package/dist/reporters/doctor-report.d.ts.map +1 -0
  66. package/dist/reporters/doctor-report.js +62 -0
  67. package/dist/reporters/doctor-report.js.map +1 -0
  68. package/dist/reporters/eslint-style.js +4 -4
  69. package/dist/reporters/eslint-style.js.map +1 -1
  70. package/dist/rules/categories.d.ts +36 -0
  71. package/dist/rules/categories.d.ts.map +1 -0
  72. package/dist/rules/categories.js +80 -0
  73. package/dist/rules/categories.js.map +1 -0
  74. package/dist/rules/code/index.d.ts +9 -0
  75. package/dist/rules/code/index.d.ts.map +1 -0
  76. package/dist/rules/code/index.js +9 -0
  77. package/dist/rules/code/index.js.map +1 -0
  78. package/dist/rules/code/jsx-attribute.d.ts +12 -0
  79. package/dist/rules/code/jsx-attribute.d.ts.map +1 -0
  80. package/dist/rules/code/jsx-attribute.js +78 -0
  81. package/dist/rules/code/jsx-attribute.js.map +1 -0
  82. package/dist/rules/code/jsx-text.d.ts +12 -0
  83. package/dist/rules/code/jsx-text.d.ts.map +1 -0
  84. package/dist/rules/code/jsx-text.js +47 -0
  85. package/dist/rules/code/jsx-text.js.map +1 -0
  86. package/dist/rules/code/string-variable.d.ts +13 -0
  87. package/dist/rules/code/string-variable.d.ts.map +1 -0
  88. package/dist/rules/code/string-variable.js +185 -0
  89. package/dist/rules/code/string-variable.js.map +1 -0
  90. package/dist/rules/code/ternary-locale.d.ts +12 -0
  91. package/dist/rules/code/ternary-locale.d.ts.map +1 -0
  92. package/dist/rules/code/ternary-locale.js +53 -0
  93. package/dist/rules/code/ternary-locale.js.map +1 -0
  94. package/dist/rules/code/toast-message.d.ts +13 -0
  95. package/dist/rules/code/toast-message.d.ts.map +1 -0
  96. package/dist/rules/code/toast-message.js +101 -0
  97. package/dist/rules/code/toast-message.js.map +1 -0
  98. package/dist/rules/extraction/data-structure.d.ts +13 -0
  99. package/dist/rules/extraction/data-structure.d.ts.map +1 -0
  100. package/dist/rules/extraction/data-structure.js +212 -0
  101. package/dist/rules/extraction/data-structure.js.map +1 -0
  102. package/dist/rules/extraction/index.d.ts +6 -0
  103. package/dist/rules/extraction/index.d.ts.map +1 -0
  104. package/dist/rules/extraction/index.js +6 -0
  105. package/dist/rules/extraction/index.js.map +1 -0
  106. package/dist/rules/extraction/translation-function.d.ts +12 -0
  107. package/dist/rules/extraction/translation-function.d.ts.map +1 -0
  108. package/dist/rules/extraction/translation-function.js +153 -0
  109. package/dist/rules/extraction/translation-function.js.map +1 -0
  110. package/dist/rules/health/index.d.ts +16 -0
  111. package/dist/rules/health/index.d.ts.map +1 -0
  112. package/dist/rules/health/index.js +22 -0
  113. package/dist/rules/health/index.js.map +1 -0
  114. package/dist/rules/health/missing-translations.d.ts +13 -0
  115. package/dist/rules/health/missing-translations.d.ts.map +1 -0
  116. package/dist/rules/health/missing-translations.js +48 -0
  117. package/dist/rules/health/missing-translations.js.map +1 -0
  118. package/dist/rules/health/orphan-keys.d.ts +16 -0
  119. package/dist/rules/health/orphan-keys.d.ts.map +1 -0
  120. package/dist/rules/health/orphan-keys.js +50 -0
  121. package/dist/rules/health/orphan-keys.js.map +1 -0
  122. package/dist/rules/health/placeholder-mismatch.d.ts +32 -0
  123. package/dist/rules/health/placeholder-mismatch.d.ts.map +1 -0
  124. package/dist/rules/health/placeholder-mismatch.js +120 -0
  125. package/dist/rules/health/placeholder-mismatch.js.map +1 -0
  126. package/dist/rules/registry.d.ts +84 -0
  127. package/dist/rules/registry.d.ts.map +1 -0
  128. package/dist/rules/registry.js +11 -0
  129. package/dist/rules/registry.js.map +1 -0
  130. package/dist/utils/cdn-client.d.ts +30 -0
  131. package/dist/utils/cdn-client.d.ts.map +1 -0
  132. package/dist/utils/cdn-client.js +59 -0
  133. package/dist/utils/cdn-client.js.map +1 -0
  134. package/dist/utils/json-keys.d.ts +60 -0
  135. package/dist/utils/json-keys.d.ts.map +1 -0
  136. package/dist/utils/json-keys.js +103 -0
  137. package/dist/utils/json-keys.js.map +1 -0
  138. package/dist/utils/key-comparison.d.ts +67 -0
  139. package/dist/utils/key-comparison.d.ts.map +1 -0
  140. package/dist/utils/key-comparison.js +299 -0
  141. package/dist/utils/key-comparison.js.map +1 -0
  142. 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":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,sBAAsB,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGvE;;GAEG;AACH,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;IAC9B,OAAO;IACP,SAAS;IACT,OAAO;IACP,SAAS;IACT,MAAM;IACN,SAAS;CACV,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC;IAC5B,SAAS;IACT,OAAO;IACP,SAAS;IACT,MAAM;IACN,SAAS;IACT,SAAS;IACT,QAAQ;CACT,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAuB,EACvB,GAAgB;IAEhB,MAAM,QAAQ,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAC5C,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3B,yBAAyB;IACzB,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;IAC5B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAEzB,wCAAwC;IACxC,IAAI,EAAE,CAAC,eAAe,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjC,OAAO,WAAW,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACnD,CAAC;IAED,0CAA0C;IAC1C,IAAI,EAAE,CAAC,+BAA+B,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjD,OAAO,WAAW,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACnD,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,SAAS,oBAAoB,CAAC,IAAuB;IACnD,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC;IAE7B,gCAAgC;IAChC,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACvB,IAAI,IAAI,KAAK,OAAO,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAClD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,gEAAgE;IAChE,IACE,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC;QACnC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,EAChC,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QACrC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QAElC,IAAI,OAAO,KAAK,OAAO,IAAI,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YACzD,OAAO,SAAS,UAAU,EAAE,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,SAAS,WAAW,CAClB,IAAa,EACb,IAAY,EACZ,GAAgB;IAEhB,0BAA0B;IAC1B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAE3C,oDAAoD;IACpD,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAExC,MAAM,GAAG,GAAG,GAAG,CAAC,UAAU,CAAC,6BAA6B,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IAE1E,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,QAAQ;QAClB,IAAI,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC;QAClB,MAAM,EAAE,GAAG,CAAC,SAAS,GAAG,CAAC;QACzB,IAAI;QACJ,IAAI,EAAE,eAAe;QACrB,QAAQ,EAAE,SAAS;QACnB,OAAO,EAAE,qBAAqB,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG;QACnD,YAAY,EAAE,sBAAsB,CAAC,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC;KACzD,CAAC;AACJ,CAAC"}
@@ -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,6 @@
1
+ /**
2
+ * Key extraction rules — extract translation keys from code patterns
3
+ */
4
+ export { checkTranslationFunction } from "./translation-function.js";
5
+ export { checkDataStructure } from "./data-structure.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -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,6 @@
1
+ /**
2
+ * Key extraction rules — extract translation keys from code patterns
3
+ */
4
+ export { checkTranslationFunction } from "./translation-function.js";
5
+ export { checkDataStructure } from "./data-structure.js";
6
+ //# sourceMappingURL=index.js.map
@@ -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"}