@ollang-dev/sdk 0.3.1
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/bin/tms.js +47 -0
- package/dist/browser/index.d.ts +143 -0
- package/dist/browser/index.js +2336 -0
- package/dist/browser/ollang-browser.min.js +1 -0
- package/dist/browser/script-loader.d.ts +1 -0
- package/dist/browser/script-loader.js +53 -0
- package/dist/client.d.ts +13 -0
- package/dist/client.js +60 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +74 -0
- package/dist/resources/cms.d.ts +29 -0
- package/dist/resources/cms.js +34 -0
- package/dist/resources/customInstructions.d.ts +11 -0
- package/dist/resources/customInstructions.js +24 -0
- package/dist/resources/orders.d.ts +13 -0
- package/dist/resources/orders.js +65 -0
- package/dist/resources/projects.d.ts +8 -0
- package/dist/resources/projects.js +29 -0
- package/dist/resources/revisions.d.ts +9 -0
- package/dist/resources/revisions.js +18 -0
- package/dist/resources/scans.d.ts +38 -0
- package/dist/resources/scans.js +52 -0
- package/dist/resources/uploads.d.ts +8 -0
- package/dist/resources/uploads.js +25 -0
- package/dist/tms/config.d.ts +16 -0
- package/dist/tms/config.js +172 -0
- package/dist/tms/detector/audio-detector.d.ts +27 -0
- package/dist/tms/detector/audio-detector.js +168 -0
- package/dist/tms/detector/auto-detect.d.ts +1 -0
- package/dist/tms/detector/auto-detect.js +152 -0
- package/dist/tms/detector/cms-detector.d.ts +17 -0
- package/dist/tms/detector/cms-detector.js +94 -0
- package/dist/tms/detector/content-type-detector.d.ts +79 -0
- package/dist/tms/detector/content-type-detector.js +2 -0
- package/dist/tms/detector/hardcoded-detector.d.ts +11 -0
- package/dist/tms/detector/hardcoded-detector.js +154 -0
- package/dist/tms/detector/i18n-detector.d.ts +16 -0
- package/dist/tms/detector/i18n-detector.js +311 -0
- package/dist/tms/detector/image-detector.d.ts +11 -0
- package/dist/tms/detector/image-detector.js +170 -0
- package/dist/tms/detector/text-detector.d.ts +9 -0
- package/dist/tms/detector/text-detector.js +35 -0
- package/dist/tms/detector/video-detector.d.ts +12 -0
- package/dist/tms/detector/video-detector.js +188 -0
- package/dist/tms/index.d.ts +12 -0
- package/dist/tms/index.js +38 -0
- package/dist/tms/multi-content-tms.d.ts +42 -0
- package/dist/tms/multi-content-tms.js +230 -0
- package/dist/tms/server/index.d.ts +1 -0
- package/dist/tms/server/index.js +1473 -0
- package/dist/tms/server/strapi-pusher.d.ts +31 -0
- package/dist/tms/server/strapi-pusher.js +296 -0
- package/dist/tms/server/strapi-schema.d.ts +47 -0
- package/dist/tms/server/strapi-schema.js +93 -0
- package/dist/tms/tms.d.ts +48 -0
- package/dist/tms/tms.js +875 -0
- package/dist/tms/types.d.ts +189 -0
- package/dist/tms/types.js +2 -0
- package/dist/tms/ui-dist/assets/index-5U1Hw3uo.css +1 -0
- package/dist/tms/ui-dist/assets/index-HvrqZV5Z.js +149 -0
- package/dist/tms/ui-dist/index.html +14 -0
- package/dist/types/index.d.ts +174 -0
- package/dist/types/index.js +2 -0
- package/package.json +98 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { TextItem } from '../types';
|
|
2
|
+
import { CapturedContent } from '../../browser';
|
|
3
|
+
export declare class CMSDetector {
|
|
4
|
+
static convertToTextItems(contents: CapturedContent[]): TextItem[];
|
|
5
|
+
static convertToTextItem(content: CapturedContent): TextItem;
|
|
6
|
+
private static generateTags;
|
|
7
|
+
static filterByCMSType(items: TextItem[], cmsType: string): TextItem[];
|
|
8
|
+
static filterByCMSField(items: TextItem[], field: string): TextItem[];
|
|
9
|
+
static groupByURL(items: TextItem[]): Map<string, TextItem[]>;
|
|
10
|
+
static groupByCMSField(items: TextItem[]): Map<string, TextItem[]>;
|
|
11
|
+
static getStats(items: TextItem[]): {
|
|
12
|
+
total: number;
|
|
13
|
+
byType: Record<string, number>;
|
|
14
|
+
byField: Record<string, number>;
|
|
15
|
+
byURL: Record<string, number>;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CMSDetector = void 0;
|
|
4
|
+
class CMSDetector {
|
|
5
|
+
static convertToTextItems(contents) {
|
|
6
|
+
return contents.map((content) => this.convertToTextItem(content));
|
|
7
|
+
}
|
|
8
|
+
static convertToTextItem(content) {
|
|
9
|
+
const textItem = {
|
|
10
|
+
id: content.id,
|
|
11
|
+
text: content.text,
|
|
12
|
+
type: 'cms',
|
|
13
|
+
source: {
|
|
14
|
+
file: content.url,
|
|
15
|
+
line: 0,
|
|
16
|
+
column: 0,
|
|
17
|
+
context: content.selector,
|
|
18
|
+
},
|
|
19
|
+
selected: false,
|
|
20
|
+
cmsField: content.cmsField,
|
|
21
|
+
cmsId: content.cmsId,
|
|
22
|
+
category: content.cmsType || 'dynamic',
|
|
23
|
+
tags: this.generateTags(content),
|
|
24
|
+
strapiContentType: content.strapiContentType,
|
|
25
|
+
strapiEntryId: content.strapiEntryId,
|
|
26
|
+
strapiField: content.strapiField,
|
|
27
|
+
};
|
|
28
|
+
return textItem;
|
|
29
|
+
}
|
|
30
|
+
static generateTags(content) {
|
|
31
|
+
const tags = [];
|
|
32
|
+
if (content.cmsType) {
|
|
33
|
+
tags.push(`cms:${content.cmsType}`);
|
|
34
|
+
}
|
|
35
|
+
if (content.tagName) {
|
|
36
|
+
tags.push(`tag:${content.tagName}`);
|
|
37
|
+
}
|
|
38
|
+
if (content.cmsField) {
|
|
39
|
+
tags.push(`field:${content.cmsField}`);
|
|
40
|
+
}
|
|
41
|
+
Object.keys(content.attributes).forEach((key) => {
|
|
42
|
+
if (key.startsWith('data-')) {
|
|
43
|
+
tags.push(`attr:${key.replace('data-', '')}`);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return tags;
|
|
47
|
+
}
|
|
48
|
+
static filterByCMSType(items, cmsType) {
|
|
49
|
+
return items.filter((item) => item.category === cmsType);
|
|
50
|
+
}
|
|
51
|
+
static filterByCMSField(items, field) {
|
|
52
|
+
return items.filter((item) => item.cmsField === field);
|
|
53
|
+
}
|
|
54
|
+
static groupByURL(items) {
|
|
55
|
+
const groups = new Map();
|
|
56
|
+
items.forEach((item) => {
|
|
57
|
+
const url = item.source.file;
|
|
58
|
+
if (!groups.has(url)) {
|
|
59
|
+
groups.set(url, []);
|
|
60
|
+
}
|
|
61
|
+
groups.get(url).push(item);
|
|
62
|
+
});
|
|
63
|
+
return groups;
|
|
64
|
+
}
|
|
65
|
+
static groupByCMSField(items) {
|
|
66
|
+
const groups = new Map();
|
|
67
|
+
items.forEach((item) => {
|
|
68
|
+
const field = item.cmsField || 'unknown';
|
|
69
|
+
if (!groups.has(field)) {
|
|
70
|
+
groups.set(field, []);
|
|
71
|
+
}
|
|
72
|
+
groups.get(field).push(item);
|
|
73
|
+
});
|
|
74
|
+
return groups;
|
|
75
|
+
}
|
|
76
|
+
static getStats(items) {
|
|
77
|
+
const stats = {
|
|
78
|
+
total: items.length,
|
|
79
|
+
byType: {},
|
|
80
|
+
byField: {},
|
|
81
|
+
byURL: {},
|
|
82
|
+
};
|
|
83
|
+
items.forEach((item) => {
|
|
84
|
+
const type = item.category || 'unknown';
|
|
85
|
+
stats.byType[type] = (stats.byType[type] || 0) + 1;
|
|
86
|
+
const field = item.cmsField || 'unknown';
|
|
87
|
+
stats.byField[field] = (stats.byField[field] || 0) + 1;
|
|
88
|
+
const url = item.source.file;
|
|
89
|
+
stats.byURL[url] = (stats.byURL[url] || 0) + 1;
|
|
90
|
+
});
|
|
91
|
+
return stats;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
exports.CMSDetector = CMSDetector;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export type ContentType = 'i18n' | 'hardcoded' | 'video' | 'image' | 'audio' | 'document' | 'cms';
|
|
2
|
+
export interface ContentItem {
|
|
3
|
+
id: string;
|
|
4
|
+
type: ContentType;
|
|
5
|
+
path: string;
|
|
6
|
+
metadata: Record<string, any>;
|
|
7
|
+
}
|
|
8
|
+
export interface I18nContent extends ContentItem {
|
|
9
|
+
type: 'i18n';
|
|
10
|
+
metadata: {
|
|
11
|
+
key: string;
|
|
12
|
+
namespace?: string;
|
|
13
|
+
text: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface VideoContent extends ContentItem {
|
|
17
|
+
type: 'video';
|
|
18
|
+
filename?: string;
|
|
19
|
+
url?: string;
|
|
20
|
+
line?: number;
|
|
21
|
+
column?: number;
|
|
22
|
+
metadata: {
|
|
23
|
+
duration: number;
|
|
24
|
+
format: string;
|
|
25
|
+
hasSubtitles: boolean;
|
|
26
|
+
subtitlePath?: string;
|
|
27
|
+
size?: number;
|
|
28
|
+
isUrl?: boolean;
|
|
29
|
+
sourceFile?: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export interface ImageContent extends ContentItem {
|
|
33
|
+
type: 'image';
|
|
34
|
+
filename?: string;
|
|
35
|
+
url?: string;
|
|
36
|
+
line?: number;
|
|
37
|
+
column?: number;
|
|
38
|
+
metadata: {
|
|
39
|
+
hasText: boolean;
|
|
40
|
+
detectedText?: string[];
|
|
41
|
+
format: string;
|
|
42
|
+
size?: number;
|
|
43
|
+
isUrl?: boolean;
|
|
44
|
+
sourceFile?: string;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export interface AudioContent extends ContentItem {
|
|
48
|
+
type: 'audio';
|
|
49
|
+
filename?: string;
|
|
50
|
+
url?: string;
|
|
51
|
+
line?: number;
|
|
52
|
+
column?: number;
|
|
53
|
+
metadata: {
|
|
54
|
+
duration: number;
|
|
55
|
+
format: string;
|
|
56
|
+
size?: number;
|
|
57
|
+
isUrl?: boolean;
|
|
58
|
+
sourceFile?: string;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export interface HardcodedTextContent extends ContentItem {
|
|
62
|
+
type: 'hardcoded';
|
|
63
|
+
metadata: {
|
|
64
|
+
text: string;
|
|
65
|
+
line: number;
|
|
66
|
+
column: number;
|
|
67
|
+
context: string;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export type AnyContentItem = I18nContent | VideoContent | ImageContent | AudioContent | HardcodedTextContent;
|
|
71
|
+
export interface ContentDetector<T extends ContentItem = ContentItem> {
|
|
72
|
+
detect(projectRoot: string, config: DetectionConfig): Promise<T[]>;
|
|
73
|
+
supports(filePath: string): boolean;
|
|
74
|
+
}
|
|
75
|
+
export interface DetectionConfig {
|
|
76
|
+
includePaths: string[];
|
|
77
|
+
excludePaths: string[];
|
|
78
|
+
includePatterns: string[];
|
|
79
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { TextItem, ScanConfig } from '../types.js';
|
|
2
|
+
export declare class HardcodedDetector {
|
|
3
|
+
private readonly MIN_TEXT_LENGTH;
|
|
4
|
+
private readonly MAX_TEXT_LENGTH;
|
|
5
|
+
detect(config: ScanConfig): Promise<TextItem[]>;
|
|
6
|
+
private scanDirectory;
|
|
7
|
+
private scanFile;
|
|
8
|
+
private isCodeFile;
|
|
9
|
+
private isValidText;
|
|
10
|
+
private shouldExclude;
|
|
11
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.HardcodedDetector = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
class HardcodedDetector {
|
|
40
|
+
constructor() {
|
|
41
|
+
this.MIN_TEXT_LENGTH = 3;
|
|
42
|
+
this.MAX_TEXT_LENGTH = 500;
|
|
43
|
+
}
|
|
44
|
+
async detect(config) {
|
|
45
|
+
const texts = [];
|
|
46
|
+
for (const includePath of config.includePaths) {
|
|
47
|
+
const hardcodedTexts = await this.scanDirectory(includePath, config.excludePaths);
|
|
48
|
+
texts.push(...hardcodedTexts);
|
|
49
|
+
}
|
|
50
|
+
return texts;
|
|
51
|
+
}
|
|
52
|
+
async scanDirectory(dirPath, excludePaths) {
|
|
53
|
+
const texts = [];
|
|
54
|
+
if (!fs.existsSync(dirPath)) {
|
|
55
|
+
return texts;
|
|
56
|
+
}
|
|
57
|
+
const stat = fs.statSync(dirPath);
|
|
58
|
+
if (stat.isFile()) {
|
|
59
|
+
if (this.isCodeFile(dirPath) && !this.shouldExclude(dirPath, excludePaths)) {
|
|
60
|
+
const fileTexts = await this.scanFile(dirPath);
|
|
61
|
+
texts.push(...fileTexts);
|
|
62
|
+
}
|
|
63
|
+
return texts;
|
|
64
|
+
}
|
|
65
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
68
|
+
if (this.shouldExclude(fullPath, excludePaths)) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (entry.isDirectory()) {
|
|
72
|
+
const subTexts = await this.scanDirectory(fullPath, excludePaths);
|
|
73
|
+
texts.push(...subTexts);
|
|
74
|
+
}
|
|
75
|
+
else if (entry.isFile() && this.isCodeFile(entry.name)) {
|
|
76
|
+
const fileTexts = await this.scanFile(fullPath);
|
|
77
|
+
texts.push(...fileTexts);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return texts;
|
|
81
|
+
}
|
|
82
|
+
async scanFile(filePath) {
|
|
83
|
+
const texts = [];
|
|
84
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
85
|
+
const lines = content.split('\n');
|
|
86
|
+
const jsxTextRegex = />([^<>{}\n]+)</g;
|
|
87
|
+
lines.forEach((lineContent, lineIndex) => {
|
|
88
|
+
const trimmedLine = lineContent.trim();
|
|
89
|
+
if (trimmedLine.startsWith('//') ||
|
|
90
|
+
trimmedLine.startsWith('/*') ||
|
|
91
|
+
trimmedLine.startsWith('*') ||
|
|
92
|
+
trimmedLine.startsWith('<!--')) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
let match;
|
|
96
|
+
const regex = new RegExp(jsxTextRegex);
|
|
97
|
+
while ((match = regex.exec(lineContent)) !== null) {
|
|
98
|
+
const text = match[1].trim();
|
|
99
|
+
if (this.isValidText(text)) {
|
|
100
|
+
texts.push({
|
|
101
|
+
id: `hardcoded-${filePath}-${lineIndex}-${match.index}`,
|
|
102
|
+
text,
|
|
103
|
+
type: 'hardcoded',
|
|
104
|
+
source: {
|
|
105
|
+
file: filePath,
|
|
106
|
+
line: lineIndex + 1,
|
|
107
|
+
column: match.index,
|
|
108
|
+
context: lineContent.trim(),
|
|
109
|
+
},
|
|
110
|
+
selected: false,
|
|
111
|
+
status: 'scanned',
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
return texts;
|
|
117
|
+
}
|
|
118
|
+
isCodeFile(fileName) {
|
|
119
|
+
const codeExtensions = ['.js', '.jsx', '.ts', '.tsx', '.vue', '.svelte', '.html'];
|
|
120
|
+
return codeExtensions.some((ext) => fileName.endsWith(ext));
|
|
121
|
+
}
|
|
122
|
+
isValidText(text) {
|
|
123
|
+
if (text.length < this.MIN_TEXT_LENGTH || text.length > this.MAX_TEXT_LENGTH) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
if (!/[a-zA-Z]/.test(text)) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
if (!text.trim()) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
const skipPatterns = [
|
|
133
|
+
/^[0-9]+$/,
|
|
134
|
+
/^[a-z_]+$/i,
|
|
135
|
+
/^[A-Z_]+$/,
|
|
136
|
+
/^\$\{.*\}$/,
|
|
137
|
+
/^https?:\/\//,
|
|
138
|
+
/^\/[a-z0-9\-_/]+$/i,
|
|
139
|
+
/^#[0-9a-f]{3,6}$/i,
|
|
140
|
+
/^rgb\(/,
|
|
141
|
+
/^\{.*\}$/,
|
|
142
|
+
/^\[.*\]$/,
|
|
143
|
+
];
|
|
144
|
+
return !skipPatterns.some((pattern) => pattern.test(text));
|
|
145
|
+
}
|
|
146
|
+
shouldExclude(filePath, excludePaths) {
|
|
147
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
148
|
+
return excludePaths.some((exclude) => {
|
|
149
|
+
const normalizedExclude = exclude.replace(/\\/g, '/');
|
|
150
|
+
return normalizedPath.includes(normalizedExclude);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
exports.HardcodedDetector = HardcodedDetector;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { TextItem, ScanConfig, I18nSetupInfo } from '../types.js';
|
|
2
|
+
export declare class I18nDetector {
|
|
3
|
+
detect(config: ScanConfig): Promise<TextItem[]>;
|
|
4
|
+
private isI18nDirectory;
|
|
5
|
+
private scanI18nDirectory;
|
|
6
|
+
detectFile(filePath: string): Promise<TextItem[]>;
|
|
7
|
+
detectSetup(projectRoot: string): Promise<I18nSetupInfo | null>;
|
|
8
|
+
private detectNextI18next;
|
|
9
|
+
private detectReactI18next;
|
|
10
|
+
private detectVueI18n;
|
|
11
|
+
private detectGenericI18n;
|
|
12
|
+
private buildI18nSetupFromDir;
|
|
13
|
+
private extractKeyValues;
|
|
14
|
+
private parseSimpleYaml;
|
|
15
|
+
private fileExists;
|
|
16
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.I18nDetector = void 0;
|
|
37
|
+
const fs = __importStar(require("fs/promises"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
class I18nDetector {
|
|
40
|
+
async detect(config) {
|
|
41
|
+
const texts = [];
|
|
42
|
+
for (const includePath of config.includePaths) {
|
|
43
|
+
if (await this.isI18nDirectory(includePath)) {
|
|
44
|
+
const dirTexts = await this.scanI18nDirectory(includePath, config.sourceLanguage);
|
|
45
|
+
texts.push(...dirTexts);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const setup = await this.detectSetup(includePath);
|
|
49
|
+
if (!setup || setup.framework === 'none') {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
for (const translationFile of setup.translationFiles) {
|
|
53
|
+
if (config.sourceLanguage && translationFile.language !== config.sourceLanguage) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const fileTexts = await this.detectFile(translationFile.path);
|
|
57
|
+
texts.push(...fileTexts);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return texts;
|
|
62
|
+
}
|
|
63
|
+
async isI18nDirectory(dirPath) {
|
|
64
|
+
try {
|
|
65
|
+
const stat = await fs.stat(dirPath);
|
|
66
|
+
if (!stat.isDirectory())
|
|
67
|
+
return false;
|
|
68
|
+
const dirName = path.basename(dirPath).toLowerCase();
|
|
69
|
+
if (dirName.includes('i18n') ||
|
|
70
|
+
dirName.includes('locale') ||
|
|
71
|
+
dirName.includes('translation')) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
const files = await fs.readdir(dirPath);
|
|
75
|
+
const hasJsonFiles = files.some((f) => f.endsWith('.json') || f.endsWith('.yaml') || f.endsWith('.yml'));
|
|
76
|
+
return hasJsonFiles;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async scanI18nDirectory(dirPath, sourceLanguage) {
|
|
83
|
+
const texts = [];
|
|
84
|
+
const items = await fs.readdir(dirPath, { withFileTypes: true });
|
|
85
|
+
for (const item of items) {
|
|
86
|
+
const itemPath = path.join(dirPath, item.name);
|
|
87
|
+
if (item.isDirectory()) {
|
|
88
|
+
const subTexts = await this.scanI18nDirectory(itemPath, sourceLanguage);
|
|
89
|
+
texts.push(...subTexts);
|
|
90
|
+
}
|
|
91
|
+
else if (item.isFile()) {
|
|
92
|
+
if (item.name.endsWith('.json') ||
|
|
93
|
+
item.name.endsWith('.yaml') ||
|
|
94
|
+
item.name.endsWith('.yml')) {
|
|
95
|
+
const lang = path.basename(item.name, path.extname(item.name));
|
|
96
|
+
if (sourceLanguage && lang !== sourceLanguage) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const fileTexts = await this.detectFile(itemPath);
|
|
100
|
+
texts.push(...fileTexts);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return texts;
|
|
105
|
+
}
|
|
106
|
+
async detectFile(filePath) {
|
|
107
|
+
const texts = [];
|
|
108
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
109
|
+
let data;
|
|
110
|
+
if (filePath.endsWith('.json')) {
|
|
111
|
+
data = JSON.parse(content);
|
|
112
|
+
}
|
|
113
|
+
else if (filePath.endsWith('.yaml') || filePath.endsWith('.yml')) {
|
|
114
|
+
data = this.parseSimpleYaml(content);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
return texts;
|
|
118
|
+
}
|
|
119
|
+
const items = this.extractKeyValues(data, '', filePath);
|
|
120
|
+
texts.push(...items);
|
|
121
|
+
return texts;
|
|
122
|
+
}
|
|
123
|
+
async detectSetup(projectRoot) {
|
|
124
|
+
const nextI18nConfig = path.join(projectRoot, 'next-i18next.config.js');
|
|
125
|
+
if (await this.fileExists(nextI18nConfig)) {
|
|
126
|
+
return this.detectNextI18next(projectRoot);
|
|
127
|
+
}
|
|
128
|
+
const packageJson = path.join(projectRoot, 'package.json');
|
|
129
|
+
if (await this.fileExists(packageJson)) {
|
|
130
|
+
const pkg = JSON.parse(await fs.readFile(packageJson, 'utf-8'));
|
|
131
|
+
if (pkg.dependencies?.['react-i18next'] || pkg.devDependencies?.['react-i18next']) {
|
|
132
|
+
return this.detectReactI18next(projectRoot);
|
|
133
|
+
}
|
|
134
|
+
if (pkg.dependencies?.['vue-i18n'] || pkg.devDependencies?.['vue-i18n']) {
|
|
135
|
+
return this.detectVueI18n(projectRoot);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return this.detectGenericI18n(projectRoot);
|
|
139
|
+
}
|
|
140
|
+
async detectNextI18next(projectRoot) {
|
|
141
|
+
const translationFiles = [];
|
|
142
|
+
const localesDir = path.join(projectRoot, 'public', 'locales');
|
|
143
|
+
if (await this.fileExists(localesDir)) {
|
|
144
|
+
const languages = await fs.readdir(localesDir);
|
|
145
|
+
for (const lang of languages) {
|
|
146
|
+
const langDir = path.join(localesDir, lang);
|
|
147
|
+
const stat = await fs.stat(langDir);
|
|
148
|
+
if (stat.isDirectory()) {
|
|
149
|
+
const files = await fs.readdir(langDir);
|
|
150
|
+
for (const file of files) {
|
|
151
|
+
if (file.endsWith('.json')) {
|
|
152
|
+
translationFiles.push({
|
|
153
|
+
language: lang,
|
|
154
|
+
path: path.join(langDir, file),
|
|
155
|
+
format: 'json',
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
framework: 'next-i18next',
|
|
164
|
+
configFile: path.join(projectRoot, 'next-i18next.config.js'),
|
|
165
|
+
translationFiles,
|
|
166
|
+
defaultNamespace: 'common',
|
|
167
|
+
namespaces: [...new Set(translationFiles.map((f) => path.basename(f.path, '.json')))],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
async detectReactI18next(projectRoot) {
|
|
171
|
+
const possibleDirs = [
|
|
172
|
+
path.join(projectRoot, 'public', 'locales'),
|
|
173
|
+
path.join(projectRoot, 'src', 'locales'),
|
|
174
|
+
path.join(projectRoot, 'locales'),
|
|
175
|
+
];
|
|
176
|
+
for (const dir of possibleDirs) {
|
|
177
|
+
if (await this.fileExists(dir)) {
|
|
178
|
+
return this.buildI18nSetupFromDir(dir, 'react-i18next');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
framework: 'react-i18next',
|
|
183
|
+
translationFiles: [],
|
|
184
|
+
defaultNamespace: 'translation',
|
|
185
|
+
namespaces: [],
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
async detectVueI18n(projectRoot) {
|
|
189
|
+
const possibleDirs = [
|
|
190
|
+
path.join(projectRoot, 'src', 'locales'),
|
|
191
|
+
path.join(projectRoot, 'locales'),
|
|
192
|
+
];
|
|
193
|
+
for (const dir of possibleDirs) {
|
|
194
|
+
if (await this.fileExists(dir)) {
|
|
195
|
+
return this.buildI18nSetupFromDir(dir, 'vue-i18n');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
framework: 'vue-i18n',
|
|
200
|
+
translationFiles: [],
|
|
201
|
+
defaultNamespace: 'messages',
|
|
202
|
+
namespaces: [],
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
async detectGenericI18n(projectRoot) {
|
|
206
|
+
const possibleDirs = [
|
|
207
|
+
path.join(projectRoot, 'public', 'locales'),
|
|
208
|
+
path.join(projectRoot, 'src', 'locales'),
|
|
209
|
+
path.join(projectRoot, 'locales'),
|
|
210
|
+
path.join(projectRoot, 'translations'),
|
|
211
|
+
path.join(projectRoot, 'i18n'),
|
|
212
|
+
];
|
|
213
|
+
for (const dir of possibleDirs) {
|
|
214
|
+
if (await this.fileExists(dir)) {
|
|
215
|
+
return this.buildI18nSetupFromDir(dir, 'custom');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
async buildI18nSetupFromDir(dir, framework) {
|
|
221
|
+
const translationFiles = [];
|
|
222
|
+
const items = await fs.readdir(dir, { withFileTypes: true });
|
|
223
|
+
for (const item of items) {
|
|
224
|
+
if (item.isDirectory()) {
|
|
225
|
+
const langDir = path.join(dir, item.name);
|
|
226
|
+
const files = await fs.readdir(langDir);
|
|
227
|
+
for (const file of files) {
|
|
228
|
+
if (file.endsWith('.json') || file.endsWith('.yaml') || file.endsWith('.yml')) {
|
|
229
|
+
translationFiles.push({
|
|
230
|
+
language: item.name,
|
|
231
|
+
path: path.join(langDir, file),
|
|
232
|
+
format: file.endsWith('.json') ? 'json' : 'yaml',
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else if (item.isFile()) {
|
|
238
|
+
const fileName = item.name;
|
|
239
|
+
if (fileName.endsWith('.json') || fileName.endsWith('.yaml') || fileName.endsWith('.yml')) {
|
|
240
|
+
const lang = path.basename(fileName, path.extname(fileName));
|
|
241
|
+
translationFiles.push({
|
|
242
|
+
language: lang,
|
|
243
|
+
path: path.join(dir, fileName),
|
|
244
|
+
format: fileName.endsWith('.json') ? 'json' : 'yaml',
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
framework,
|
|
251
|
+
translationFiles,
|
|
252
|
+
defaultNamespace: 'common',
|
|
253
|
+
namespaces: [
|
|
254
|
+
...new Set(translationFiles.map((f) => path.basename(f.path, path.extname(f.path)))),
|
|
255
|
+
],
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
extractKeyValues(obj, prefix, filePath, lineOffset = 0) {
|
|
259
|
+
const items = [];
|
|
260
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
261
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
262
|
+
if (typeof value === 'string') {
|
|
263
|
+
items.push({
|
|
264
|
+
id: `i18n-${filePath}-${fullKey}`,
|
|
265
|
+
text: value,
|
|
266
|
+
type: 'i18n',
|
|
267
|
+
source: {
|
|
268
|
+
file: filePath,
|
|
269
|
+
line: lineOffset,
|
|
270
|
+
column: 0,
|
|
271
|
+
},
|
|
272
|
+
i18nKey: fullKey,
|
|
273
|
+
selected: false,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
else if (typeof value === 'object' && value !== null) {
|
|
277
|
+
const nestedItems = this.extractKeyValues(value, fullKey, filePath, lineOffset);
|
|
278
|
+
items.push(...nestedItems);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return items;
|
|
282
|
+
}
|
|
283
|
+
parseSimpleYaml(content) {
|
|
284
|
+
const result = {};
|
|
285
|
+
const lines = content.split('\n');
|
|
286
|
+
for (const line of lines) {
|
|
287
|
+
const trimmed = line.trim();
|
|
288
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
289
|
+
continue;
|
|
290
|
+
const colonIndex = trimmed.indexOf(':');
|
|
291
|
+
if (colonIndex === -1)
|
|
292
|
+
continue;
|
|
293
|
+
const key = trimmed.substring(0, colonIndex).trim();
|
|
294
|
+
const value = trimmed.substring(colonIndex + 1).trim();
|
|
295
|
+
if (value) {
|
|
296
|
+
result[key] = value.replace(/^["']|["']$/g, '');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return result;
|
|
300
|
+
}
|
|
301
|
+
async fileExists(filePath) {
|
|
302
|
+
try {
|
|
303
|
+
await fs.access(filePath);
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
exports.I18nDetector = I18nDetector;
|