ai-localize-framework-detectors 2.0.3 → 2.0.4
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/package.json +22 -2
- package/src/detector.ts +0 -212
- package/src/i18n-detector.ts +0 -140
- package/src/index.ts +0 -2
- package/tsconfig.json +0 -9
package/package.json
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-localize-framework-detectors",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "Auto-detect frontend frameworks from project structure",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"CHANGELOG.md"
|
|
12
|
+
],
|
|
8
13
|
"exports": {
|
|
9
14
|
".": {
|
|
10
15
|
"types": "./dist/index.d.ts",
|
|
@@ -12,8 +17,23 @@
|
|
|
12
17
|
"require": "./dist/index.js"
|
|
13
18
|
}
|
|
14
19
|
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"i18n",
|
|
22
|
+
"localization",
|
|
23
|
+
"l10n",
|
|
24
|
+
"internationalization",
|
|
25
|
+
"ai-localize",
|
|
26
|
+
"framework-detection",
|
|
27
|
+
"react",
|
|
28
|
+
"vue",
|
|
29
|
+
"angular",
|
|
30
|
+
"nextjs"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18.0.0"
|
|
34
|
+
},
|
|
15
35
|
"dependencies": {
|
|
16
|
-
"ai-localize-shared": "2.0.
|
|
36
|
+
"ai-localize-shared": "2.0.4"
|
|
17
37
|
},
|
|
18
38
|
"devDependencies": {
|
|
19
39
|
"tsup": "^8.0.1",
|
package/src/detector.ts
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
|
|
4
|
-
import type { Framework } from 'ai-localize-shared';
|
|
5
|
-
import { readJsonSafe } from 'ai-localize-shared';
|
|
6
|
-
|
|
7
|
-
export interface DetectionResult {
|
|
8
|
-
framework: Framework;
|
|
9
|
-
confidence: number;
|
|
10
|
-
evidence: string[];
|
|
11
|
-
variant?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface PackageJson {
|
|
15
|
-
dependencies?: Record<string, string>;
|
|
16
|
-
devDependencies?: Record<string, string>;
|
|
17
|
-
peerDependencies?: Record<string, string>;
|
|
18
|
-
scripts?: Record<string, string>;
|
|
19
|
-
name?: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Detects the frontend framework used in a project directory.
|
|
24
|
-
* Uses package.json dependencies, config files, and folder structure.
|
|
25
|
-
*/
|
|
26
|
-
export class FrameworkDetector {
|
|
27
|
-
private projectRoot: string;
|
|
28
|
-
private pkg: PackageJson | null;
|
|
29
|
-
|
|
30
|
-
constructor(projectRoot: string) {
|
|
31
|
-
this.projectRoot = projectRoot;
|
|
32
|
-
this.pkg = readJsonSafe<PackageJson>(path.join(projectRoot, 'package.json'));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
detect(): DetectionResult {
|
|
36
|
-
const results: DetectionResult[] = [
|
|
37
|
-
this.detectNextJs(),
|
|
38
|
-
this.detectReact(),
|
|
39
|
-
this.detectAngular(),
|
|
40
|
-
this.detectVue(),
|
|
41
|
-
this.detectJQuery(),
|
|
42
|
-
this.detectVanillaJs(),
|
|
43
|
-
this.detectJsp(),
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
// Sort by confidence descending
|
|
47
|
-
results.sort((a, b) => b.confidence - a.confidence);
|
|
48
|
-
const best = results[0];
|
|
49
|
-
|
|
50
|
-
if (best.confidence === 0) {
|
|
51
|
-
return { framework: 'unknown', confidence: 0, evidence: ['No framework detected'] };
|
|
52
|
-
}
|
|
53
|
-
return best;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
private allDeps(): Record<string, string> {
|
|
57
|
-
if (!this.pkg) return {};
|
|
58
|
-
return {
|
|
59
|
-
...(this.pkg.dependencies || {}),
|
|
60
|
-
...(this.pkg.devDependencies || {}),
|
|
61
|
-
...(this.pkg.peerDependencies || {}),
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private hasDep(name: string): boolean {
|
|
66
|
-
return name in this.allDeps();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
private fileExists(relPath: string): boolean {
|
|
70
|
-
return fs.existsSync(path.join(this.projectRoot, relPath));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
private detectNextJs(): DetectionResult {
|
|
74
|
-
const evidence: string[] = [];
|
|
75
|
-
let confidence = 0;
|
|
76
|
-
|
|
77
|
-
if (this.hasDep('next')) { evidence.push('dep: next'); confidence += 50; }
|
|
78
|
-
if (this.fileExists('next.config.js') || this.fileExists('next.config.ts') || this.fileExists('next.config.mjs')) {
|
|
79
|
-
evidence.push('next.config.js found'); confidence += 40;
|
|
80
|
-
}
|
|
81
|
-
if (this.fileExists('pages') || this.fileExists('app')) {
|
|
82
|
-
evidence.push('pages/app directory found'); confidence += 10;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return { framework: 'react-nextjs', confidence, evidence };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
private detectReact(): DetectionResult {
|
|
89
|
-
const evidence: string[] = [];
|
|
90
|
-
let confidence = 0;
|
|
91
|
-
let variant: Framework = 'react';
|
|
92
|
-
|
|
93
|
-
if (this.hasDep('react')) { evidence.push('dep: react'); confidence += 40; }
|
|
94
|
-
if (this.hasDep('react-dom')) { evidence.push('dep: react-dom'); confidence += 10; }
|
|
95
|
-
|
|
96
|
-
// Vite
|
|
97
|
-
if (this.hasDep('vite') || this.fileExists('vite.config.ts') || this.fileExists('vite.config.js')) {
|
|
98
|
-
evidence.push('vite config found'); confidence += 30; variant = 'react-vite';
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// CRA
|
|
102
|
-
if (this.hasDep('react-scripts')) {
|
|
103
|
-
evidence.push('dep: react-scripts (CRA)'); confidence += 30; variant = 'react-cra';
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// React without specific build tool
|
|
107
|
-
if (confidence >= 50 && variant === 'react') {
|
|
108
|
-
evidence.push('generic react project');
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return { framework: variant, confidence, evidence };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
private detectAngular(): DetectionResult {
|
|
115
|
-
const evidence: string[] = [];
|
|
116
|
-
let confidence = 0;
|
|
117
|
-
let variant: Framework = 'angular';
|
|
118
|
-
|
|
119
|
-
if (this.hasDep('@angular/core')) { evidence.push('dep: @angular/core'); confidence += 50; }
|
|
120
|
-
if (this.fileExists('angular.json')) { evidence.push('angular.json found'); confidence += 30; }
|
|
121
|
-
if (this.fileExists('tsconfig.app.json')) { evidence.push('tsconfig.app.json found'); confidence += 10; }
|
|
122
|
-
|
|
123
|
-
if (this.hasDep('@ngx-translate/core')) {
|
|
124
|
-
evidence.push('dep: @ngx-translate/core'); confidence += 10; variant = 'angular-ngx';
|
|
125
|
-
} else if (confidence > 0) {
|
|
126
|
-
variant = 'angular-i18n';
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return { framework: variant, confidence, evidence };
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
private detectVue(): DetectionResult {
|
|
133
|
-
const evidence: string[] = [];
|
|
134
|
-
let confidence = 0;
|
|
135
|
-
let variant: Framework = 'vue';
|
|
136
|
-
|
|
137
|
-
if (this.hasDep('vue')) { evidence.push('dep: vue'); confidence += 50; }
|
|
138
|
-
if (this.hasDep('@vue/cli-service') || this.fileExists('vue.config.js')) {
|
|
139
|
-
evidence.push('vue-cli config found'); confidence += 20;
|
|
140
|
-
}
|
|
141
|
-
if (this.hasDep('vite') && confidence > 0) {
|
|
142
|
-
evidence.push('vite (vue)'); confidence += 20;
|
|
143
|
-
}
|
|
144
|
-
if (this.hasDep('vue-i18n')) {
|
|
145
|
-
evidence.push('dep: vue-i18n'); confidence += 10; variant = 'vue-i18n';
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return { framework: variant, confidence, evidence };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
private detectJQuery(): DetectionResult {
|
|
152
|
-
const evidence: string[] = [];
|
|
153
|
-
let confidence = 0;
|
|
154
|
-
|
|
155
|
-
if (this.hasDep('jquery')) { evidence.push('dep: jquery'); confidence += 60; }
|
|
156
|
-
// Check for jQuery usage in common files
|
|
157
|
-
const htmlFiles = ['index.html', 'public/index.html'];
|
|
158
|
-
for (const f of htmlFiles) {
|
|
159
|
-
if (!this.fileExists(f)) continue;
|
|
160
|
-
try {
|
|
161
|
-
const content = fs.readFileSync(path.join(this.projectRoot, f), 'utf-8');
|
|
162
|
-
if (content.includes('jquery') || content.includes('jQuery')) {
|
|
163
|
-
evidence.push(`jQuery reference in ${f}`); confidence += 20;
|
|
164
|
-
}
|
|
165
|
-
} catch { /* ignore */ }
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return { framework: 'jquery', confidence, evidence };
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
private detectVanillaJs(): DetectionResult {
|
|
172
|
-
const evidence: string[] = [];
|
|
173
|
-
let confidence = 0;
|
|
174
|
-
|
|
175
|
-
if (!this.pkg) {
|
|
176
|
-
evidence.push('No package.json'); confidence = 20;
|
|
177
|
-
} else {
|
|
178
|
-
const deps = Object.keys(this.allDeps());
|
|
179
|
-
if (deps.length === 0) { evidence.push('No dependencies'); confidence = 30; }
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Check for plain HTML entry
|
|
183
|
-
if (this.fileExists('index.html')) {
|
|
184
|
-
evidence.push('index.html found'); confidence += 10;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return { framework: 'vanilla-js', confidence, evidence };
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
private detectJsp(): DetectionResult {
|
|
191
|
-
const evidence: string[] = [];
|
|
192
|
-
let confidence = 0;
|
|
193
|
-
|
|
194
|
-
// JSP projects typically have WEB-INF and .jsp files
|
|
195
|
-
if (this.fileExists('WEB-INF') || this.fileExists('src/main/webapp')) {
|
|
196
|
-
evidence.push('WEB-INF/webapp found'); confidence += 50;
|
|
197
|
-
}
|
|
198
|
-
if (this.fileExists('pom.xml') || this.fileExists('build.gradle')) {
|
|
199
|
-
evidence.push('Java build file found'); confidence += 30;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return { framework: 'jsp', confidence, evidence };
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Convenience function to detect framework in a directory
|
|
208
|
-
*/
|
|
209
|
-
export function detectFramework(projectRoot: string): DetectionResult {
|
|
210
|
-
const detector = new FrameworkDetector(projectRoot);
|
|
211
|
-
return detector.detect();
|
|
212
|
-
}
|
package/src/i18n-detector.ts
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
|
|
4
|
-
import { readJsonSafe } from 'ai-localize-shared';
|
|
5
|
-
import type { Framework } from 'ai-localize-shared';
|
|
6
|
-
|
|
7
|
-
export interface I18nSetupResult {
|
|
8
|
-
hasI18n: boolean;
|
|
9
|
-
library?: string;
|
|
10
|
-
configFile?: string;
|
|
11
|
-
localesDir?: string;
|
|
12
|
-
namespaces?: string[];
|
|
13
|
-
defaultLanguage?: string;
|
|
14
|
-
targetLanguages?: string[];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface PackageJson {
|
|
18
|
-
dependencies?: Record<string, string>;
|
|
19
|
-
devDependencies?: Record<string, string>;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** Detects existing i18n setup in a project */
|
|
23
|
-
export class I18nDetector {
|
|
24
|
-
private root: string;
|
|
25
|
-
private pkg: PackageJson | null;
|
|
26
|
-
|
|
27
|
-
constructor(projectRoot: string) {
|
|
28
|
-
this.root = projectRoot;
|
|
29
|
-
this.pkg = readJsonSafe<PackageJson>(path.join(projectRoot, 'package.json'));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
detect(framework: Framework): I18nSetupResult {
|
|
33
|
-
switch (framework) {
|
|
34
|
-
case 'react':
|
|
35
|
-
case 'react-cra':
|
|
36
|
-
case 'react-vite':
|
|
37
|
-
case 'react-nextjs':
|
|
38
|
-
return this.detectReactI18n();
|
|
39
|
-
case 'angular':
|
|
40
|
-
case 'angular-ngx':
|
|
41
|
-
case 'angular-i18n':
|
|
42
|
-
return this.detectAngularI18n();
|
|
43
|
-
case 'vue':
|
|
44
|
-
case 'vue-i18n':
|
|
45
|
-
return this.detectVueI18n();
|
|
46
|
-
default:
|
|
47
|
-
return this.detectGenericI18n();
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
private allDeps(): Record<string, string> {
|
|
52
|
-
return {
|
|
53
|
-
...(this.pkg?.dependencies || {}),
|
|
54
|
-
...(this.pkg?.devDependencies || {}),
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
private fileExists(rel: string): boolean {
|
|
59
|
-
return fs.existsSync(path.join(this.root, rel));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
private findLocalesDir(): string | undefined {
|
|
63
|
-
const candidates = ['locales', 'src/locales', 'i18n', 'src/i18n', 'public/locales'];
|
|
64
|
-
return candidates.find((c) => this.fileExists(c));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
private detectLanguagesFromLocalesDir(localesDir?: string): string[] {
|
|
68
|
-
if (!localesDir) return [];
|
|
69
|
-
const full = path.join(this.root, localesDir);
|
|
70
|
-
try {
|
|
71
|
-
return fs
|
|
72
|
-
.readdirSync(full, { withFileTypes: true })
|
|
73
|
-
.filter((e) => e.isDirectory())
|
|
74
|
-
.map((e) => e.name);
|
|
75
|
-
} catch {
|
|
76
|
-
return [];
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
private detectReactI18n(): I18nSetupResult {
|
|
81
|
-
const deps = this.allDeps();
|
|
82
|
-
if (!deps['i18next'] && !deps['react-i18next']) {
|
|
83
|
-
return { hasI18n: false };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const localesDir = this.findLocalesDir();
|
|
87
|
-
const languages = this.detectLanguagesFromLocalesDir(localesDir);
|
|
88
|
-
const configFile = ['i18n.ts', 'i18n.js', 'src/i18n.ts', 'src/i18n.js'].find((f) =>
|
|
89
|
-
this.fileExists(f)
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
hasI18n: true,
|
|
94
|
-
library: 'react-i18next',
|
|
95
|
-
configFile,
|
|
96
|
-
localesDir,
|
|
97
|
-
defaultLanguage: languages[0] || 'en',
|
|
98
|
-
targetLanguages: languages.slice(1),
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
private detectAngularI18n(): I18nSetupResult {
|
|
103
|
-
const deps = this.allDeps();
|
|
104
|
-
if (!deps['@ngx-translate/core'] && !deps['@angular/localize']) {
|
|
105
|
-
return { hasI18n: false };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const localesDir = this.findLocalesDir();
|
|
109
|
-
const library = deps['@ngx-translate/core'] ? '@ngx-translate/core' : '@angular/localize';
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
hasI18n: true,
|
|
113
|
-
library,
|
|
114
|
-
localesDir,
|
|
115
|
-
defaultLanguage: 'en',
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
private detectVueI18n(): I18nSetupResult {
|
|
120
|
-
const deps = this.allDeps();
|
|
121
|
-
if (!deps['vue-i18n']) return { hasI18n: false };
|
|
122
|
-
|
|
123
|
-
const localesDir = this.findLocalesDir();
|
|
124
|
-
const languages = this.detectLanguagesFromLocalesDir(localesDir);
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
hasI18n: true,
|
|
128
|
-
library: 'vue-i18n',
|
|
129
|
-
localesDir,
|
|
130
|
-
defaultLanguage: languages[0] || 'en',
|
|
131
|
-
targetLanguages: languages.slice(1),
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
private detectGenericI18n(): I18nSetupResult {
|
|
136
|
-
const localesDir = this.findLocalesDir();
|
|
137
|
-
if (!localesDir) return { hasI18n: false };
|
|
138
|
-
return { hasI18n: true, localesDir };
|
|
139
|
-
}
|
|
140
|
-
}
|
package/src/index.ts
DELETED