@better-i18n/cli 0.1.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 (58) hide show
  1. package/README.md +218 -0
  2. package/dist/analyzer/file-collector.d.ts +21 -0
  3. package/dist/analyzer/file-collector.d.ts.map +1 -0
  4. package/dist/analyzer/file-collector.js +82 -0
  5. package/dist/analyzer/file-collector.js.map +1 -0
  6. package/dist/analyzer/index.d.ts +15 -0
  7. package/dist/analyzer/index.d.ts.map +1 -0
  8. package/dist/analyzer/index.js +66 -0
  9. package/dist/analyzer/index.js.map +1 -0
  10. package/dist/analyzer/rules/index.d.ts +7 -0
  11. package/dist/analyzer/rules/index.d.ts.map +1 -0
  12. package/dist/analyzer/rules/index.js +7 -0
  13. package/dist/analyzer/rules/index.js.map +1 -0
  14. package/dist/analyzer/rules/jsx-attribute.d.ts +12 -0
  15. package/dist/analyzer/rules/jsx-attribute.d.ts.map +1 -0
  16. package/dist/analyzer/rules/jsx-attribute.js +78 -0
  17. package/dist/analyzer/rules/jsx-attribute.js.map +1 -0
  18. package/dist/analyzer/rules/jsx-text.d.ts +12 -0
  19. package/dist/analyzer/rules/jsx-text.d.ts.map +1 -0
  20. package/dist/analyzer/rules/jsx-text.js +45 -0
  21. package/dist/analyzer/rules/jsx-text.js.map +1 -0
  22. package/dist/analyzer/rules/ternary-locale.d.ts +12 -0
  23. package/dist/analyzer/rules/ternary-locale.d.ts.map +1 -0
  24. package/dist/analyzer/rules/ternary-locale.js +49 -0
  25. package/dist/analyzer/rules/ternary-locale.js.map +1 -0
  26. package/dist/analyzer/types.d.ts +47 -0
  27. package/dist/analyzer/types.d.ts.map +1 -0
  28. package/dist/analyzer/types.js +5 -0
  29. package/dist/analyzer/types.js.map +1 -0
  30. package/dist/commands/scan.d.ts +6 -0
  31. package/dist/commands/scan.d.ts.map +1 -0
  32. package/dist/commands/scan.js +74 -0
  33. package/dist/commands/scan.js.map +1 -0
  34. package/dist/context/detector.d.ts +12 -0
  35. package/dist/context/detector.d.ts.map +1 -0
  36. package/dist/context/detector.js +180 -0
  37. package/dist/context/detector.js.map +1 -0
  38. package/dist/index.d.ts +9 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +25 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/reporters/eslint-style.d.ts +11 -0
  43. package/dist/reporters/eslint-style.d.ts.map +1 -0
  44. package/dist/reporters/eslint-style.js +61 -0
  45. package/dist/reporters/eslint-style.js.map +1 -0
  46. package/dist/reporters/json.d.ts +9 -0
  47. package/dist/reporters/json.d.ts.map +1 -0
  48. package/dist/reporters/json.js +35 -0
  49. package/dist/reporters/json.js.map +1 -0
  50. package/dist/utils/colors.d.ts +25 -0
  51. package/dist/utils/colors.d.ts.map +1 -0
  52. package/dist/utils/colors.js +40 -0
  53. package/dist/utils/colors.js.map +1 -0
  54. package/dist/utils/text.d.ts +12 -0
  55. package/dist/utils/text.d.ts.map +1 -0
  56. package/dist/utils/text.js +49 -0
  57. package/dist/utils/text.js.map +1 -0
  58. package/package.json +59 -0
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Ternary locale detection rule
3
+ *
4
+ * Detects anti-pattern: locale === 'en' ? 'Hello' : 'Merhaba'
5
+ */
6
+ import ts from "typescript";
7
+ import { truncate } from "../../utils/text.js";
8
+ /**
9
+ * Check conditional expression for locale-based ternary
10
+ */
11
+ export function checkTernaryLocale(node, ctx) {
12
+ const condition = node.condition;
13
+ // Check for binary expression
14
+ if (!ts.isBinaryExpression(condition))
15
+ return null;
16
+ // Check if comparing with locale
17
+ const left = condition.left;
18
+ let isLocaleComparison = false;
19
+ // Direct identifier: locale === 'en'
20
+ if (ts.isIdentifier(left) && left.text === "locale") {
21
+ isLocaleComparison = true;
22
+ }
23
+ // Property access: i18n.locale === 'en' or router.locale === 'en'
24
+ if (ts.isPropertyAccessExpression(left) && left.name.text === "locale") {
25
+ isLocaleComparison = true;
26
+ }
27
+ if (!isLocaleComparison)
28
+ return null;
29
+ // Check if both branches have string literals
30
+ const hasStringTrue = ts.isStringLiteral(node.whenTrue);
31
+ const hasStringFalse = ts.isStringLiteral(node.whenFalse);
32
+ if (!hasStringTrue && !hasStringFalse)
33
+ return null;
34
+ // Get text for message
35
+ const text = hasStringTrue
36
+ ? node.whenTrue.text
37
+ : node.whenFalse.text;
38
+ const pos = ctx.sourceFile.getLineAndCharacterOfPosition(node.getStart());
39
+ return {
40
+ file: ctx.filePath,
41
+ line: pos.line + 1,
42
+ column: pos.character + 1,
43
+ text,
44
+ type: "ternary-locale",
45
+ severity: "error", // This is an anti-pattern, so error
46
+ message: `Locale ternary pattern detected: "${truncate(text, 30)}"`,
47
+ };
48
+ }
49
+ //# sourceMappingURL=ternary-locale.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ternary-locale.js","sourceRoot":"","sources":["../../../src/analyzer/rules/ternary-locale.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAG/C;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAChC,IAA8B,EAC9B,GAAgB;IAEhB,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IAEjC,8BAA8B;IAC9B,IAAI,CAAC,EAAE,CAAC,kBAAkB,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IAEnD,iCAAiC;IACjC,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC;IAC5B,IAAI,kBAAkB,GAAG,KAAK,CAAC;IAE/B,qCAAqC;IACrC,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACpD,kBAAkB,GAAG,IAAI,CAAC;IAC5B,CAAC;IAED,kEAAkE;IAClE,IAAI,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACvE,kBAAkB,GAAG,IAAI,CAAC;IAC5B,CAAC;IAED,IAAI,CAAC,kBAAkB;QAAE,OAAO,IAAI,CAAC;IAErC,8CAA8C;IAC9C,MAAM,aAAa,GAAG,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxD,MAAM,cAAc,GAAG,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAE1D,IAAI,CAAC,aAAa,IAAI,CAAC,cAAc;QAAE,OAAO,IAAI,CAAC;IAEnD,uBAAuB;IACvB,MAAM,IAAI,GAAG,aAAa;QACxB,CAAC,CAAE,IAAI,CAAC,QAA6B,CAAC,IAAI;QAC1C,CAAC,CAAE,IAAI,CAAC,SAA8B,CAAC,IAAI,CAAC;IAE9C,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,gBAAgB;QACtB,QAAQ,EAAE,OAAO,EAAE,oCAAoC;QACvD,OAAO,EAAE,qCAAqC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG;KACpE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Shared types for the CLI analyzer
3
+ */
4
+ export interface Issue {
5
+ file: string;
6
+ line: number;
7
+ column: number;
8
+ text: string;
9
+ type: "jsx-text" | "jsx-attribute" | "ternary-locale" | "string-variable";
10
+ severity: "error" | "warning";
11
+ message: string;
12
+ suggestedKey?: string;
13
+ }
14
+ export interface RuleContext {
15
+ filePath: string;
16
+ sourceFile: import("typescript").SourceFile;
17
+ }
18
+ export interface ScanOptions {
19
+ dir?: string;
20
+ format: "eslint" | "json";
21
+ fix?: boolean;
22
+ ci?: boolean;
23
+ staged?: boolean;
24
+ verbose?: boolean;
25
+ }
26
+ export interface ProjectContext {
27
+ workspaceId: string;
28
+ projectSlug: string;
29
+ defaultLocale: string;
30
+ cdnBaseUrl?: string;
31
+ lint?: LintConfig;
32
+ }
33
+ export interface LintConfig {
34
+ include?: string[];
35
+ exclude?: string[];
36
+ minLength?: number;
37
+ rules?: Record<string, "error" | "warning" | "off">;
38
+ ignorePatterns?: RegExp[];
39
+ translationFunctions?: string[];
40
+ }
41
+ export interface ScanResult {
42
+ project?: ProjectContext;
43
+ files: number;
44
+ issues: Issue[];
45
+ duration: number;
46
+ }
47
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/analyzer/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,GAAG,eAAe,GAAG,gBAAgB,GAAG,iBAAiB,CAAC;IAC1E,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,OAAO,YAAY,EAAE,UAAU,CAAC;CAC7C;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC1B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,KAAK,CAAC,CAAC;IACpD,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Shared types for the CLI analyzer
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/analyzer/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Scan command implementation
3
+ */
4
+ import type { ScanOptions } from "../analyzer/types.js";
5
+ export declare function scanCommand(options: ScanOptions): Promise<void>;
6
+ //# sourceMappingURL=scan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAS,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAM/D,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA2ErE"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Scan command implementation
3
+ */
4
+ import ora from "ora";
5
+ import { collectFiles } from "../analyzer/file-collector.js";
6
+ import { analyzeFile } from "../analyzer/index.js";
7
+ import { detectProjectContext } from "../context/detector.js";
8
+ import { reportEslintStyle } from "../reporters/eslint-style.js";
9
+ import { reportJson } from "../reporters/json.js";
10
+ import { bold, dim, green } from "../utils/colors.js";
11
+ export async function scanCommand(options) {
12
+ const startTime = Date.now();
13
+ const spinner = ora({ text: "Detecting project...", color: "cyan" }).start();
14
+ // Step 1: Detect project context
15
+ const rootDir = options.dir || process.cwd();
16
+ const context = await detectProjectContext(rootDir);
17
+ if (context) {
18
+ spinner.succeed(`Project: ${bold(context.workspaceId + "/" + context.projectSlug)}`);
19
+ }
20
+ else {
21
+ spinner.warn("No i18n.config.ts found, using defaults");
22
+ }
23
+ // Step 2: Collect files
24
+ spinner.start("Collecting files...");
25
+ const files = await collectFiles({
26
+ rootDir,
27
+ include: context?.lint?.include,
28
+ exclude: context?.lint?.exclude,
29
+ });
30
+ if (files.length === 0) {
31
+ spinner.fail("No .tsx or .jsx files found");
32
+ return;
33
+ }
34
+ spinner.succeed(`Found ${files.length} files`);
35
+ // Step 3: Analyze files
36
+ spinner.start("Scanning for hardcoded strings...");
37
+ const allIssues = [];
38
+ for (const file of files) {
39
+ try {
40
+ const issues = await analyzeFile(file, context?.lint);
41
+ allIssues.push(...issues);
42
+ }
43
+ catch (error) {
44
+ if (options.verbose) {
45
+ console.error(`Error analyzing ${file}:`, error);
46
+ }
47
+ }
48
+ }
49
+ spinner.stop();
50
+ // Step 4: Report results
51
+ const duration = Date.now() - startTime;
52
+ if (options.format === "json") {
53
+ reportJson({
54
+ project: context || undefined,
55
+ files: files.length,
56
+ issues: allIssues,
57
+ duration,
58
+ });
59
+ }
60
+ else {
61
+ reportEslintStyle(allIssues, rootDir);
62
+ // Summary
63
+ console.log();
64
+ if (allIssues.length === 0) {
65
+ console.log(green(bold("✓ No hardcoded strings found")));
66
+ }
67
+ console.log(dim(`Scanned ${files.length} files in ${(duration / 1000).toFixed(2)}s`));
68
+ }
69
+ // Exit with error code for CI
70
+ if (options.ci && allIssues.length > 0) {
71
+ process.exit(1);
72
+ }
73
+ }
74
+ //# sourceMappingURL=scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAEtD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAoB;IACpD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAG,GAAG,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;IAE7E,iCAAiC;IACjC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAC7C,MAAM,OAAO,GAAG,MAAM,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAEpD,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,CAAC,OAAO,CACb,YAAY,IAAI,CAAC,OAAO,CAAC,WAAW,GAAG,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,EAAE,CACpE,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC;IAC1D,CAAC;IAED,wBAAwB;IACxB,OAAO,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC;QAC/B,OAAO;QACP,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO;QAC/B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO;KAChC,CAAC,CAAC;IAEH,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QAC5C,OAAO;IACT,CAAC;IAED,OAAO,CAAC,OAAO,CAAC,SAAS,KAAK,CAAC,MAAM,QAAQ,CAAC,CAAC;IAE/C,wBAAwB;IACxB,OAAO,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACnD,MAAM,SAAS,GAAY,EAAE,CAAC;IAE9B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;YACtD,SAAS,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC;QAC5B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;gBACpB,OAAO,CAAC,KAAK,CAAC,mBAAmB,IAAI,GAAG,EAAE,KAAK,CAAC,CAAC;YACnD,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,CAAC,IAAI,EAAE,CAAC;IAEf,yBAAyB;IACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IAExC,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QAC9B,UAAU,CAAC;YACT,OAAO,EAAE,OAAO,IAAI,SAAS;YAC7B,KAAK,EAAE,KAAK,CAAC,MAAM;YACnB,MAAM,EAAE,SAAS;YACjB,QAAQ;SACT,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,iBAAiB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAEtC,UAAU;QACV,OAAO,CAAC,GAAG,EAAE,CAAC;QACd,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC,CAAC,CAAC;QAC3D,CAAC;QACD,OAAO,CAAC,GAAG,CACT,GAAG,CAAC,WAAW,KAAK,CAAC,MAAM,aAAa,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CACzE,CAAC;IACJ,CAAC;IAED,8BAA8B;IAC9B,IAAI,OAAO,CAAC,EAAE,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Project context detection
3
+ *
4
+ * Auto-detect Better i18n project configuration from i18n.config.ts
5
+ * or createI18n usage. Shared logic with MCP server.
6
+ */
7
+ import type { ProjectContext } from "../analyzer/types.js";
8
+ /**
9
+ * Find and parse i18n configuration in workspace
10
+ */
11
+ export declare function detectProjectContext(startDir?: string): Promise<ProjectContext | null>;
12
+ //# sourceMappingURL=detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../../src/context/detector.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAc,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEvE;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,GAAE,MAAsB,GAC/B,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAehC"}
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Project context detection
3
+ *
4
+ * Auto-detect Better i18n project configuration from i18n.config.ts
5
+ * or createI18n usage. Shared logic with MCP server.
6
+ */
7
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
8
+ import { dirname, join } from "node:path";
9
+ /**
10
+ * Find and parse i18n configuration in workspace
11
+ */
12
+ export async function detectProjectContext(startDir = process.cwd()) {
13
+ try {
14
+ const workspaceRoot = findWorkspaceRoot(startDir);
15
+ // Search for files with i18n config
16
+ const configFile = findConfigFile(workspaceRoot);
17
+ if (configFile) {
18
+ return parseI18nConfig(configFile);
19
+ }
20
+ }
21
+ catch (error) {
22
+ // Silently fail - will use defaults
23
+ }
24
+ return null;
25
+ }
26
+ /**
27
+ * Find workspace root by looking for .git or package.json with workspaces
28
+ */
29
+ function findWorkspaceRoot(startDir) {
30
+ let currentDir = startDir;
31
+ const maxDepth = 10;
32
+ let depth = 0;
33
+ while (depth < maxDepth) {
34
+ if (existsSync(join(currentDir, ".git"))) {
35
+ return currentDir;
36
+ }
37
+ const pkgPath = join(currentDir, "package.json");
38
+ if (existsSync(pkgPath)) {
39
+ try {
40
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
41
+ if (pkg.workspaces) {
42
+ return currentDir;
43
+ }
44
+ }
45
+ catch {
46
+ // Ignore parse errors
47
+ }
48
+ }
49
+ const parentDir = dirname(currentDir);
50
+ if (parentDir === currentDir)
51
+ break;
52
+ currentDir = parentDir;
53
+ depth++;
54
+ }
55
+ return startDir;
56
+ }
57
+ /**
58
+ * Search for i18n config files
59
+ */
60
+ function findConfigFile(workspaceRoot) {
61
+ const files = [];
62
+ function searchDir(dir, depth = 0) {
63
+ if (depth > 5)
64
+ return;
65
+ try {
66
+ const entries = readdirSync(dir);
67
+ for (const entry of entries) {
68
+ if (["node_modules", ".git", "dist", "build", ".next"].includes(entry)) {
69
+ continue;
70
+ }
71
+ const fullPath = join(dir, entry);
72
+ const stat = statSync(fullPath);
73
+ if (stat.isDirectory()) {
74
+ searchDir(fullPath, depth + 1);
75
+ }
76
+ else if (/i18n\.config\.(ts|js)$/.test(entry)) {
77
+ // Direct i18n.config.ts file - highest priority
78
+ return fullPath;
79
+ }
80
+ else if (/\.(ts|tsx|js|jsx)$/.test(entry)) {
81
+ files.push(fullPath);
82
+ }
83
+ }
84
+ }
85
+ catch {
86
+ // Ignore permission errors
87
+ }
88
+ }
89
+ // First look for i18n.config.ts directly
90
+ const directConfigs = ["i18n.config.ts", "i18n.config.js"];
91
+ for (const configName of directConfigs) {
92
+ const configPath = join(workspaceRoot, configName);
93
+ if (existsSync(configPath)) {
94
+ return configPath;
95
+ }
96
+ }
97
+ // Then search subdirectories
98
+ searchDir(workspaceRoot);
99
+ // Search for createI18n import
100
+ for (const file of files) {
101
+ try {
102
+ const content = readFileSync(file, "utf-8");
103
+ if (/import\s+.*createI18n.*from\s+['"]@better-i18n\/(next|core)['"]/.test(content) &&
104
+ /createI18n\s*\(/.test(content)) {
105
+ return file;
106
+ }
107
+ }
108
+ catch {
109
+ // Ignore read errors
110
+ }
111
+ }
112
+ return null;
113
+ }
114
+ /**
115
+ * Parse file to extract i18n configuration
116
+ */
117
+ function parseI18nConfig(filePath) {
118
+ const content = readFileSync(filePath, "utf-8");
119
+ // Try project: "org/slug" format
120
+ const projectMatch = content.match(/project:\s*['"]([^'"]+\/[^'"]+)['"]/);
121
+ if (projectMatch) {
122
+ const [workspaceId, projectSlug] = projectMatch[1].split("/");
123
+ const localeMatch = content.match(/defaultLocale:\s*['"]([^'"]+)['"]/);
124
+ const cdnMatch = content.match(/cdnBaseUrl:\s*['"]([^'"]+)['"]/);
125
+ // Try to parse lint config
126
+ const lint = parseLintConfig(content);
127
+ return {
128
+ workspaceId,
129
+ projectSlug,
130
+ defaultLocale: localeMatch?.[1] || "en",
131
+ cdnBaseUrl: cdnMatch?.[1],
132
+ lint,
133
+ };
134
+ }
135
+ // Try old format: workspaceId + projectSlug
136
+ const workspaceMatch = content.match(/workspaceId:\s*['"]([^'"]+)['"]/);
137
+ const projectSlugMatch = content.match(/projectSlug:\s*['"]([^'"]+)['"]/);
138
+ const localeMatch = content.match(/defaultLocale:\s*['"]([^'"]+)['"]/);
139
+ const cdnMatch = content.match(/cdnBaseUrl:\s*['"]([^'"]+)['"]/);
140
+ if (workspaceMatch && projectSlugMatch) {
141
+ const lint = parseLintConfig(content);
142
+ return {
143
+ workspaceId: workspaceMatch[1],
144
+ projectSlug: projectSlugMatch[1],
145
+ defaultLocale: localeMatch?.[1] || "en",
146
+ cdnBaseUrl: cdnMatch?.[1],
147
+ lint,
148
+ };
149
+ }
150
+ return null;
151
+ }
152
+ /**
153
+ * Parse optional lint config from file content
154
+ */
155
+ function parseLintConfig(content) {
156
+ // Simple regex-based parsing for lint config
157
+ // In real implementation, could use AST parsing
158
+ if (!content.includes("lint:")) {
159
+ return undefined;
160
+ }
161
+ const config = {};
162
+ // Extract include array
163
+ const includeMatch = content.match(/include:\s*\[([^\]]+)\]/);
164
+ if (includeMatch) {
165
+ config.include = includeMatch[1]
166
+ .split(",")
167
+ .map((s) => s.trim().replace(/['"]/g, ""))
168
+ .filter(Boolean);
169
+ }
170
+ // Extract exclude array
171
+ const excludeMatch = content.match(/exclude:\s*\[([^\]]+)\]/);
172
+ if (excludeMatch) {
173
+ config.exclude = excludeMatch[1]
174
+ .split(",")
175
+ .map((s) => s.trim().replace(/['"]/g, ""))
176
+ .filter(Boolean);
177
+ }
178
+ return Object.keys(config).length > 0 ? config : undefined;
179
+ }
180
+ //# sourceMappingURL=detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detector.js","sourceRoot":"","sources":["../../src/context/detector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Better i18n CLI
4
+ *
5
+ * Detect hardcoded strings in React/Next.js apps.
6
+ * Automatically reads i18n.config.ts for project context.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;GAKG"}
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Better i18n CLI
4
+ *
5
+ * Detect hardcoded strings in React/Next.js apps.
6
+ * Automatically reads i18n.config.ts for project context.
7
+ */
8
+ import { program } from "commander";
9
+ import { scanCommand } from "./commands/scan.js";
10
+ program
11
+ .name("better-i18n")
12
+ .description("Detect hardcoded strings in your React/Next.js app")
13
+ .version("0.1.0");
14
+ program
15
+ .command("scan")
16
+ .description("Scan source files for untranslated strings")
17
+ .option("-d, --dir <path>", "Directory to scan (default: current directory)")
18
+ .option("-f, --format <type>", "Output format: eslint, json", "eslint")
19
+ .option("--fix", "Auto-fix: wrap hardcoded text with t()")
20
+ .option("--ci", "CI mode: exit with error code if issues found")
21
+ .option("--staged", "Only scan git staged files")
22
+ .option("--verbose", "Show detailed output")
23
+ .action(scanCommand);
24
+ program.parse();
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,OAAO;KACJ,IAAI,CAAC,aAAa,CAAC;KACnB,WAAW,CAAC,oDAAoD,CAAC;KACjE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,4CAA4C,CAAC;KACzD,MAAM,CAAC,kBAAkB,EAAE,gDAAgD,CAAC;KAC5E,MAAM,CAAC,qBAAqB,EAAE,6BAA6B,EAAE,QAAQ,CAAC;KACtE,MAAM,CAAC,OAAO,EAAE,wCAAwC,CAAC;KACzD,MAAM,CAAC,MAAM,EAAE,+CAA+C,CAAC;KAC/D,MAAM,CAAC,UAAU,EAAE,4BAA4B,CAAC;KAChD,MAAM,CAAC,WAAW,EAAE,sBAAsB,CAAC;KAC3C,MAAM,CAAC,WAAW,CAAC,CAAC;AAEvB,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * ESLint-style terminal reporter
3
+ *
4
+ * Formats issues in familiar ESLint output format
5
+ */
6
+ import type { Issue } from "../analyzer/types.js";
7
+ /**
8
+ * Report issues in ESLint-style format
9
+ */
10
+ export declare function reportEslintStyle(issues: Issue[], rootDir: string): void;
11
+ //# sourceMappingURL=eslint-style.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eslint-style.d.ts","sourceRoot":"","sources":["../../src/reporters/eslint-style.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAUlD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CA6CxE"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * ESLint-style terminal reporter
3
+ *
4
+ * Formats issues in familiar ESLint output format
5
+ */
6
+ import { relative } from "node:path";
7
+ import { bold, dim, red, underline, yellow, } from "../utils/colors.js";
8
+ /**
9
+ * Report issues in ESLint-style format
10
+ */
11
+ export function reportEslintStyle(issues, rootDir) {
12
+ if (issues.length === 0)
13
+ return;
14
+ // Group by file
15
+ const byFile = new Map();
16
+ for (const issue of issues) {
17
+ const existing = byFile.get(issue.file) || [];
18
+ existing.push(issue);
19
+ byFile.set(issue.file, existing);
20
+ }
21
+ let errorCount = 0;
22
+ let warningCount = 0;
23
+ // Print each file
24
+ for (const [file, fileIssues] of byFile.entries()) {
25
+ const relPath = relative(rootDir, file);
26
+ console.log();
27
+ console.log(underline(relPath));
28
+ for (const issue of fileIssues) {
29
+ const loc = dim(`${issue.line}:${issue.column}`);
30
+ const severity = issue.severity === "error" ? red("error") : yellow("warning");
31
+ const rule = dim(`i18n/${issue.type}`);
32
+ const text = truncateForDisplay(issue.text, 50);
33
+ console.log(` ${loc} ${severity} ${text} ${rule}`);
34
+ if (issue.severity === "error") {
35
+ errorCount++;
36
+ }
37
+ else {
38
+ warningCount++;
39
+ }
40
+ }
41
+ }
42
+ // Summary
43
+ console.log();
44
+ const total = errorCount + warningCount;
45
+ const summary = [];
46
+ if (errorCount > 0)
47
+ summary.push(red(`${errorCount} error${errorCount > 1 ? "s" : ""}`));
48
+ if (warningCount > 0)
49
+ summary.push(yellow(`${warningCount} warning${warningCount > 1 ? "s" : ""}`));
50
+ console.log(bold(`✖ ${total} problem${total > 1 ? "s" : ""} (${summary.join(", ")})`));
51
+ }
52
+ /**
53
+ * Truncate text for display, cleaning up whitespace
54
+ */
55
+ function truncateForDisplay(str, maxLen) {
56
+ const cleaned = str.replace(/\s+/g, " ").trim();
57
+ if (cleaned.length <= maxLen)
58
+ return `"${cleaned}"`;
59
+ return `"${cleaned.slice(0, maxLen - 1)}…"`;
60
+ }
61
+ //# sourceMappingURL=eslint-style.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eslint-style.js","sourceRoot":"","sources":["../../src/reporters/eslint-style.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * JSON reporter for CI/CD integration
3
+ */
4
+ import type { ScanResult } from "../analyzer/types.js";
5
+ /**
6
+ * Report results as JSON to stdout
7
+ */
8
+ export declare function reportJson(result: ScanResult): void;
9
+ //# sourceMappingURL=json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/reporters/json.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CA4BnD"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * JSON reporter for CI/CD integration
3
+ */
4
+ /**
5
+ * Report results as JSON to stdout
6
+ */
7
+ export function reportJson(result) {
8
+ const output = {
9
+ project: result.project
10
+ ? {
11
+ workspace: result.project.workspaceId,
12
+ slug: result.project.projectSlug,
13
+ }
14
+ : null,
15
+ summary: {
16
+ total: result.issues.length,
17
+ errors: result.issues.filter((i) => i.severity === "error").length,
18
+ warnings: result.issues.filter((i) => i.severity === "warning").length,
19
+ filesScanned: result.files,
20
+ durationMs: result.duration,
21
+ },
22
+ issues: result.issues.map((issue) => ({
23
+ file: issue.file,
24
+ line: issue.line,
25
+ column: issue.column,
26
+ type: issue.type,
27
+ severity: issue.severity,
28
+ message: issue.message,
29
+ text: issue.text,
30
+ suggestedKey: issue.suggestedKey,
31
+ })),
32
+ };
33
+ console.log(JSON.stringify(output, null, 2));
34
+ }
35
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/reporters/json.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,MAAkB;IAC3C,MAAM,MAAM,GAAG;QACb,OAAO,EAAE,MAAM,CAAC,OAAO;YACrB,CAAC,CAAC;gBACA,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,WAAW;gBACrC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,WAAW;aACjC;YACD,CAAC,CAAC,IAAI;QACR,OAAO,EAAE;YACP,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM;YAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,MAAM;YAClE,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM;YACtE,YAAY,EAAE,MAAM,CAAC,KAAK;YAC1B,UAAU,EAAE,MAAM,CAAC,QAAQ;SAC5B;QACD,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACpC,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,YAAY,EAAE,KAAK,CAAC,YAAY;SACjC,CAAC,CAAC;KACJ,CAAC;IAEF,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * ANSI color utilities for terminal output
3
+ */
4
+ export declare const colors: {
5
+ readonly reset: "\u001B[0m";
6
+ readonly bold: "\u001B[1m";
7
+ readonly dim: "\u001B[2m";
8
+ readonly underline: "\u001B[4m";
9
+ readonly red: "\u001B[31m";
10
+ readonly green: "\u001B[32m";
11
+ readonly yellow: "\u001B[33m";
12
+ readonly blue: "\u001B[34m";
13
+ readonly magenta: "\u001B[35m";
14
+ readonly cyan: "\u001B[36m";
15
+ readonly white: "\u001B[37m";
16
+ readonly gray: "\u001B[90m";
17
+ };
18
+ export declare function red(text: string): string;
19
+ export declare function yellow(text: string): string;
20
+ export declare function green(text: string): string;
21
+ export declare function cyan(text: string): string;
22
+ export declare function dim(text: string): string;
23
+ export declare function bold(text: string): string;
24
+ export declare function underline(text: string): string;
25
+ //# sourceMappingURL=colors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"colors.d.ts","sourceRoot":"","sources":["../../src/utils/colors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,MAAM;;;;;;;;;;;;;CAeT,CAAC;AAEX,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExC;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1C;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExC;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C"}