@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.
- package/README.md +218 -0
- package/dist/analyzer/file-collector.d.ts +21 -0
- package/dist/analyzer/file-collector.d.ts.map +1 -0
- package/dist/analyzer/file-collector.js +82 -0
- package/dist/analyzer/file-collector.js.map +1 -0
- package/dist/analyzer/index.d.ts +15 -0
- package/dist/analyzer/index.d.ts.map +1 -0
- package/dist/analyzer/index.js +66 -0
- package/dist/analyzer/index.js.map +1 -0
- package/dist/analyzer/rules/index.d.ts +7 -0
- package/dist/analyzer/rules/index.d.ts.map +1 -0
- package/dist/analyzer/rules/index.js +7 -0
- package/dist/analyzer/rules/index.js.map +1 -0
- package/dist/analyzer/rules/jsx-attribute.d.ts +12 -0
- package/dist/analyzer/rules/jsx-attribute.d.ts.map +1 -0
- package/dist/analyzer/rules/jsx-attribute.js +78 -0
- package/dist/analyzer/rules/jsx-attribute.js.map +1 -0
- package/dist/analyzer/rules/jsx-text.d.ts +12 -0
- package/dist/analyzer/rules/jsx-text.d.ts.map +1 -0
- package/dist/analyzer/rules/jsx-text.js +45 -0
- package/dist/analyzer/rules/jsx-text.js.map +1 -0
- package/dist/analyzer/rules/ternary-locale.d.ts +12 -0
- package/dist/analyzer/rules/ternary-locale.d.ts.map +1 -0
- package/dist/analyzer/rules/ternary-locale.js +49 -0
- package/dist/analyzer/rules/ternary-locale.js.map +1 -0
- package/dist/analyzer/types.d.ts +47 -0
- package/dist/analyzer/types.d.ts.map +1 -0
- package/dist/analyzer/types.js +5 -0
- package/dist/analyzer/types.js.map +1 -0
- package/dist/commands/scan.d.ts +6 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +74 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/context/detector.d.ts +12 -0
- package/dist/context/detector.d.ts.map +1 -0
- package/dist/context/detector.js +180 -0
- package/dist/context/detector.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/reporters/eslint-style.d.ts +11 -0
- package/dist/reporters/eslint-style.d.ts.map +1 -0
- package/dist/reporters/eslint-style.js +61 -0
- package/dist/reporters/eslint-style.js.map +1 -0
- package/dist/reporters/json.d.ts +9 -0
- package/dist/reporters/json.d.ts.map +1 -0
- package/dist/reporters/json.js +35 -0
- package/dist/reporters/json.js.map +1 -0
- package/dist/utils/colors.d.ts +25 -0
- package/dist/utils/colors.d.ts.map +1 -0
- package/dist/utils/colors.js +40 -0
- package/dist/utils/colors.js.map +1 -0
- package/dist/utils/text.d.ts +12 -0
- package/dist/utils/text.d.ts.map +1 -0
- package/dist/utils/text.js +49 -0
- package/dist/utils/text.js.map +1 -0
- 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 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/analyzer/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -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":"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"}
|
|
@@ -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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
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"}
|