@abdess76/i18nkit 1.0.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/CHANGELOG.md +35 -0
- package/LICENSE +21 -0
- package/README.md +309 -0
- package/bin/cli.js +48 -0
- package/bin/commands/apply.js +48 -0
- package/bin/commands/check-sync.js +35 -0
- package/bin/commands/extract-utils.js +216 -0
- package/bin/commands/extract.js +198 -0
- package/bin/commands/find-orphans.js +36 -0
- package/bin/commands/help.js +34 -0
- package/bin/commands/index.js +79 -0
- package/bin/commands/translate.js +51 -0
- package/bin/commands/version.js +17 -0
- package/bin/commands/watch.js +34 -0
- package/bin/core/applier-utils.js +144 -0
- package/bin/core/applier.js +165 -0
- package/bin/core/args.js +147 -0
- package/bin/core/backup.js +74 -0
- package/bin/core/command-interface.js +69 -0
- package/bin/core/config.js +108 -0
- package/bin/core/context.js +86 -0
- package/bin/core/detector.js +152 -0
- package/bin/core/file-walker.js +159 -0
- package/bin/core/fs-adapter.js +56 -0
- package/bin/core/help-generator.js +208 -0
- package/bin/core/index.js +63 -0
- package/bin/core/json-utils.js +213 -0
- package/bin/core/key-generator.js +75 -0
- package/bin/core/log-utils.js +26 -0
- package/bin/core/orphan-finder.js +208 -0
- package/bin/core/parser-utils.js +187 -0
- package/bin/core/paths.js +60 -0
- package/bin/core/plugin-interface.js +83 -0
- package/bin/core/plugin-resolver-utils.js +166 -0
- package/bin/core/plugin-resolver.js +211 -0
- package/bin/core/sync-checker-utils.js +99 -0
- package/bin/core/sync-checker.js +199 -0
- package/bin/core/translator.js +197 -0
- package/bin/core/types.js +297 -0
- package/bin/core/watcher.js +119 -0
- package/bin/plugins/adapter-transloco.js +156 -0
- package/bin/plugins/parser-angular.js +56 -0
- package/bin/plugins/parser-primeng.js +79 -0
- package/bin/plugins/parser-typescript.js +66 -0
- package/bin/plugins/provider-deepl.js +65 -0
- package/bin/plugins/provider-mymemory.js +192 -0
- package/package.json +123 -0
- package/types/index.d.ts +85 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const parserUtils = require('../core/parser-utils');
|
|
4
|
+
|
|
5
|
+
const TS_EXTRACTION_PATTERNS = [
|
|
6
|
+
{ regex: /\blabel\s*:\s*['"]([^'"]+)['"]/gi, context: 'ts_labels' },
|
|
7
|
+
{ regex: /\btitle\s*:\s*['"]([^'"]+)['"]/gi, context: 'ts_titles' },
|
|
8
|
+
{ regex: /\btext\s*:\s*['"]([^'"]+)['"]/gi, context: 'ts_text' },
|
|
9
|
+
{ regex: /\bmessage\s*:\s*['"]([^'"]+)['"]/gi, context: 'ts_messages' },
|
|
10
|
+
{ regex: /\bsummary\s*:\s*['"]([^'"]+)['"]/gi, context: 'ts_messages' },
|
|
11
|
+
{ regex: /\bdetail\s*:\s*['"]([^'"]+)['"]/gi, context: 'ts_messages' },
|
|
12
|
+
{ regex: /\bheader\s*:\s*['"]([^'"]+)['"]/gi, context: 'ts_titles' },
|
|
13
|
+
{ regex: /\bplaceholder\s*:\s*['"]([^'"]+)['"]/gi, context: 'ts_placeholders' },
|
|
14
|
+
{ regex: /\btooltip\s*:\s*['"]([^'"]+)['"]/gi, context: 'ts_tooltips' },
|
|
15
|
+
{ regex: /\bdescription\s*:\s*['"]([^'"]+)['"]/gi, context: 'ts_text' },
|
|
16
|
+
{ regex: /\bhint\s*:\s*['"]([^'"]+)['"]/gi, context: 'ts_text' },
|
|
17
|
+
{ regex: /\bcaption\s*:\s*['"]([^'"]+)['"]/gi, context: 'ts_text' },
|
|
18
|
+
{ regex: /\bcontent\s*:\s*['"]([^'"]+)['"]/gi, context: 'ts_text' },
|
|
19
|
+
{ regex: /\bconfirmationMessage\s*:\s*['"]([^'"]+)['"]/gi, context: 'ts_messages' },
|
|
20
|
+
{ regex: /\berrorMessage\s*:\s*['"]([^'"]+)['"]/gi, context: 'ts_messages' },
|
|
21
|
+
{ regex: /\bsuccessMessage\s*:\s*['"]([^'"]+)['"]/gi, context: 'ts_messages' },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
function shouldIgnoreTs(text) {
|
|
25
|
+
return parserUtils.shouldIgnore(text) || parserUtils.isTranslationKey(text);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
name: 'parser-typescript',
|
|
30
|
+
type: 'parser',
|
|
31
|
+
|
|
32
|
+
meta: {
|
|
33
|
+
description: 'Extract i18n strings from TypeScript object literals',
|
|
34
|
+
version: '1.0.0',
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
extensions: ['.ts', '.component.ts'],
|
|
38
|
+
priority: 30,
|
|
39
|
+
|
|
40
|
+
options: [
|
|
41
|
+
{
|
|
42
|
+
flag: '--extract-ts',
|
|
43
|
+
type: 'boolean',
|
|
44
|
+
description: 'Extract from TypeScript files (disabled by default)',
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
|
|
48
|
+
examples: ['i18nkit --extract --extract-ts'],
|
|
49
|
+
|
|
50
|
+
detect(ctx) {
|
|
51
|
+
return ctx.pkg.dependencies?.typescript || ctx.files.includes('tsconfig.json');
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
TS_EXTRACTION_PATTERNS,
|
|
55
|
+
|
|
56
|
+
extract(content, filePath, options = {}) {
|
|
57
|
+
const { extractTsObjects = false } = options;
|
|
58
|
+
if (!extractTsObjects) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
const cleaned = parserUtils.cleanTranslocoCode(content);
|
|
62
|
+
return parserUtils.extractWithPatterns(cleaned, TS_EXTRACTION_PATTERNS, {
|
|
63
|
+
shouldIgnoreFn: shouldIgnoreTs,
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const DEEPL_FREE_ENDPOINT = 'https://api-free.deepl.com/v2/translate';
|
|
4
|
+
const DEEPL_PRO_ENDPOINT = 'https://api.deepl.com/v2/translate';
|
|
5
|
+
|
|
6
|
+
function getEndpoint(apiKey) {
|
|
7
|
+
return apiKey.endsWith(':fx') ? DEEPL_FREE_ENDPOINT : DEEPL_PRO_ENDPOINT;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function getApiKey(options) {
|
|
11
|
+
const apiKey = options.apiKey || process.env.DEEPL_API_KEY;
|
|
12
|
+
if (!apiKey) {
|
|
13
|
+
throw new Error('DEEPL_API_KEY environment variable required for DeepL provider');
|
|
14
|
+
}
|
|
15
|
+
return apiKey;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function buildDeepLRequest(texts, ctx) {
|
|
19
|
+
return {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: { Authorization: `DeepL-Auth-Key ${ctx.apiKey}`, 'Content-Type': 'application/json' },
|
|
22
|
+
body: JSON.stringify({
|
|
23
|
+
text: texts,
|
|
24
|
+
source_lang: ctx.fromLang.toUpperCase(),
|
|
25
|
+
target_lang: ctx.toLang.toUpperCase(),
|
|
26
|
+
}),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function translate(text, ctx) {
|
|
31
|
+
const result = await translateBatch([text], ctx);
|
|
32
|
+
return result[0];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function translateBatch(texts, ctx) {
|
|
36
|
+
const apiKey = getApiKey(ctx);
|
|
37
|
+
const response = await fetch(getEndpoint(apiKey), buildDeepLRequest(texts, { ...ctx, apiKey }));
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error(`DeepL API error: ${response.status} - ${await response.text()}`);
|
|
40
|
+
}
|
|
41
|
+
const data = await response.json();
|
|
42
|
+
return data.translations.map(t => t.text);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = {
|
|
46
|
+
name: 'provider-deepl',
|
|
47
|
+
type: 'provider',
|
|
48
|
+
|
|
49
|
+
meta: {
|
|
50
|
+
description: 'High-quality translation via DeepL API (requires API key)',
|
|
51
|
+
version: '1.0.0',
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
options: [{ flag: '--deepl', type: 'boolean', description: 'Use DeepL API instead of MyMemory' }],
|
|
55
|
+
|
|
56
|
+
env: [{ name: 'DEEPL_API_KEY', required: true, description: 'DeepL API key (free or pro)' }],
|
|
57
|
+
|
|
58
|
+
examples: [
|
|
59
|
+
'DEEPL_API_KEY=xxx i18nkit translate fr:en --deepl',
|
|
60
|
+
'i18nkit translate fr:en --deepl',
|
|
61
|
+
],
|
|
62
|
+
|
|
63
|
+
translate,
|
|
64
|
+
translateBatch,
|
|
65
|
+
};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const MAX_TEXT_LENGTH = 500;
|
|
4
|
+
const API_BATCH_SIZE = 5;
|
|
5
|
+
const API_DELAY_MS = 100;
|
|
6
|
+
|
|
7
|
+
const translationCache = new Map();
|
|
8
|
+
|
|
9
|
+
const buildLangPair = (src, tgt) => `${src}|${tgt}`;
|
|
10
|
+
const buildCacheKey = (from, to, text) => `${from}:${to}:${text}`;
|
|
11
|
+
|
|
12
|
+
function buildMyMemoryUrl(ctx) {
|
|
13
|
+
const { text, sourceLang, targetLang, email } = ctx;
|
|
14
|
+
const base = `https://api.mymemory.translated.net/get?q=${encodeURIComponent(text)}&langpair=${buildLangPair(sourceLang, targetLang)}`;
|
|
15
|
+
return email ? `${base}&de=${encodeURIComponent(email)}` : base;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function checkResponseStatus(data) {
|
|
19
|
+
if (data.responseStatus !== 200) {
|
|
20
|
+
throw new Error(data.responseDetails || 'Unknown error');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function validateMyMemoryResponse(data) {
|
|
25
|
+
if (!data?.responseData?.translatedText) {
|
|
26
|
+
throw new Error('Invalid API response');
|
|
27
|
+
}
|
|
28
|
+
checkResponseStatus(data);
|
|
29
|
+
return data.responseData.translatedText;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getCachedOrNull(cacheKey) {
|
|
33
|
+
return translationCache.has(cacheKey) ? translationCache.get(cacheKey) : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function fetchTranslation(ctx) {
|
|
37
|
+
const { text, fromLang, toLang, email } = ctx;
|
|
38
|
+
const url = buildMyMemoryUrl({ text, sourceLang: fromLang, targetLang: toLang, email });
|
|
39
|
+
const response = await fetch(url);
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
throw new Error(`API error: ${response.status}`);
|
|
42
|
+
}
|
|
43
|
+
return validateMyMemoryResponse(await response.json());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function handleTextTooLong(text, maxLength, verbose) {
|
|
47
|
+
if (verbose) {
|
|
48
|
+
console.log(` [WARN] Text too long (${text.length} chars), keeping original`);
|
|
49
|
+
}
|
|
50
|
+
return text;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function handleTranslationError(error, text, verbose) {
|
|
54
|
+
if (verbose) {
|
|
55
|
+
console.log(` [WARN] Translation failed: ${error.message}`);
|
|
56
|
+
}
|
|
57
|
+
return text;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseTranslateOptions(options = {}) {
|
|
61
|
+
return {
|
|
62
|
+
email: options.email || null,
|
|
63
|
+
verbose: options.verbose || false,
|
|
64
|
+
maxLength: options.maxLength || MAX_TEXT_LENGTH,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function doTranslate(ctx) {
|
|
69
|
+
const { text, fromLang, toLang, email } = ctx;
|
|
70
|
+
const result = await fetchTranslation({ text, fromLang, toLang, email });
|
|
71
|
+
translationCache.set(buildCacheKey(fromLang, toLang, text), result);
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function translate(ctx) {
|
|
76
|
+
const { text, fromLang, toLang, options = {} } = ctx;
|
|
77
|
+
const opts = parseTranslateOptions(options);
|
|
78
|
+
if (text.length > opts.maxLength) {
|
|
79
|
+
return handleTextTooLong(text, opts.maxLength, opts.verbose);
|
|
80
|
+
}
|
|
81
|
+
const cached = getCachedOrNull(buildCacheKey(fromLang, toLang, text));
|
|
82
|
+
if (cached) {
|
|
83
|
+
return cached;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
return await doTranslate({ text, fromLang, toLang, email: opts.email });
|
|
87
|
+
} catch (error) {
|
|
88
|
+
return handleTranslationError(error, text, opts.verbose);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function processBatchResult(ctx) {
|
|
93
|
+
const { result, original, translationMap, verbose = false } = ctx;
|
|
94
|
+
if (result.status === 'fulfilled') {
|
|
95
|
+
translationMap.set(original, result.value);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
translationMap.set(original, original);
|
|
99
|
+
if (verbose) {
|
|
100
|
+
console.warn(` [WARN] Failed: "${original.substring(0, 30)}..."`);
|
|
101
|
+
}
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function processBatch(batch, ctx) {
|
|
106
|
+
const { fromLang, toLang, translationMap, options = {} } = ctx;
|
|
107
|
+
const results = await Promise.allSettled(
|
|
108
|
+
batch.map(text => translate({ text, fromLang, toLang, options })),
|
|
109
|
+
);
|
|
110
|
+
let failures = 0;
|
|
111
|
+
results.forEach((result, j) => {
|
|
112
|
+
if (
|
|
113
|
+
processBatchResult({ result, original: batch[j], translationMap, verbose: options.verbose })
|
|
114
|
+
) {
|
|
115
|
+
failures++;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
return failures;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function notifyProgress(onProgress, processed, total) {
|
|
122
|
+
if (onProgress) {
|
|
123
|
+
onProgress(processed, total);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function delayIfNeeded(processed, total, delayMs) {
|
|
128
|
+
if (processed < total) {
|
|
129
|
+
await new Promise(resolve => {
|
|
130
|
+
setTimeout(resolve, delayMs);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function parseBatchOptions(options = {}) {
|
|
136
|
+
return {
|
|
137
|
+
batchSize: options.batchSize || API_BATCH_SIZE,
|
|
138
|
+
delayMs: options.delayMs || API_DELAY_MS,
|
|
139
|
+
onProgress: options.onProgress || null,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function processBatchIteration(ctx) {
|
|
144
|
+
const { batch, batchCtx, opts, texts, i } = ctx;
|
|
145
|
+
const failures = await processBatch(batch, batchCtx);
|
|
146
|
+
const processed = Math.min(i + opts.batchSize, texts.length);
|
|
147
|
+
notifyProgress(opts.onProgress, processed, texts.length);
|
|
148
|
+
await delayIfNeeded(processed, texts.length, opts.delayMs);
|
|
149
|
+
return failures;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function translateBatch(ctx) {
|
|
153
|
+
const { texts, fromLang, toLang, options = {} } = ctx;
|
|
154
|
+
const opts = parseBatchOptions(options);
|
|
155
|
+
const translationMap = new Map();
|
|
156
|
+
let failedCount = 0;
|
|
157
|
+
const batchCtx = { fromLang, toLang, translationMap, options };
|
|
158
|
+
for (let i = 0; i < texts.length; i += opts.batchSize) {
|
|
159
|
+
failedCount += await processBatchIteration({
|
|
160
|
+
batch: texts.slice(i, i + opts.batchSize),
|
|
161
|
+
batchCtx,
|
|
162
|
+
opts,
|
|
163
|
+
texts,
|
|
164
|
+
i,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return { translationMap, failedCount };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = {
|
|
171
|
+
name: 'provider-mymemory',
|
|
172
|
+
type: 'provider',
|
|
173
|
+
|
|
174
|
+
meta: {
|
|
175
|
+
description: 'Free translation via MyMemory API (rate limited)',
|
|
176
|
+
version: '1.0.0',
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
options: [
|
|
180
|
+
{ flag: '--mymemory', type: 'boolean', description: 'Use MyMemory API (default provider)' },
|
|
181
|
+
{ flag: '--email <email>', type: 'string', description: 'Email for higher rate limits' },
|
|
182
|
+
],
|
|
183
|
+
|
|
184
|
+
env: [
|
|
185
|
+
{ name: 'MYMEMORY_EMAIL', required: false, description: 'Email for higher API rate limits' },
|
|
186
|
+
],
|
|
187
|
+
|
|
188
|
+
examples: ['i18nkit translate fr:en', 'i18nkit translate fr:en --email user@example.com'],
|
|
189
|
+
|
|
190
|
+
translate: ctx => translate(ctx),
|
|
191
|
+
translateBatch: ctx => translateBatch(ctx),
|
|
192
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@abdess76/i18nkit",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Universal i18n CLI - extract translation keys, sync language files, detect missing translations. Extensible plugin architecture.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Abdessamad DERRAZ",
|
|
7
|
+
"url": "https://github.com/Abdess"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"type": "commonjs",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./types/index.d.ts",
|
|
14
|
+
"default": "./bin/cli.js"
|
|
15
|
+
},
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"i18nkit": "./bin/cli.js",
|
|
20
|
+
"i18n": "./bin/cli.js"
|
|
21
|
+
},
|
|
22
|
+
"main": "./bin/cli.js",
|
|
23
|
+
"types": "./types/index.d.ts",
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=22.0.0",
|
|
27
|
+
"npm": ">=10.0.0"
|
|
28
|
+
},
|
|
29
|
+
"devEngines": {
|
|
30
|
+
"runtime": {
|
|
31
|
+
"name": "node",
|
|
32
|
+
"version": ">=22.0.0",
|
|
33
|
+
"onFail": "error"
|
|
34
|
+
},
|
|
35
|
+
"packageManager": {
|
|
36
|
+
"name": "npm",
|
|
37
|
+
"version": ">=10.0.0",
|
|
38
|
+
"onFail": "warn"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"packageManager": "npm@10.9.2",
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"provenance": true,
|
|
44
|
+
"access": "public",
|
|
45
|
+
"registry": "https://registry.npmjs.org/"
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"bin",
|
|
49
|
+
"types",
|
|
50
|
+
"README.md",
|
|
51
|
+
"LICENSE",
|
|
52
|
+
"CHANGELOG.md"
|
|
53
|
+
],
|
|
54
|
+
"keywords": [
|
|
55
|
+
"i18n",
|
|
56
|
+
"internationalization",
|
|
57
|
+
"localization",
|
|
58
|
+
"l10n",
|
|
59
|
+
"translation",
|
|
60
|
+
"extract",
|
|
61
|
+
"sync",
|
|
62
|
+
"cli",
|
|
63
|
+
"transloco",
|
|
64
|
+
"angular",
|
|
65
|
+
"primeng",
|
|
66
|
+
"zero-dependency",
|
|
67
|
+
"missing-translations",
|
|
68
|
+
"orphan-keys",
|
|
69
|
+
"plugin"
|
|
70
|
+
],
|
|
71
|
+
"repository": {
|
|
72
|
+
"type": "git",
|
|
73
|
+
"url": "git+https://github.com/Abdess/i18nkit.git"
|
|
74
|
+
},
|
|
75
|
+
"bugs": {
|
|
76
|
+
"url": "https://github.com/Abdess/i18nkit/issues"
|
|
77
|
+
},
|
|
78
|
+
"homepage": "https://github.com/Abdess/i18nkit#readme",
|
|
79
|
+
"funding": {
|
|
80
|
+
"type": "github",
|
|
81
|
+
"url": "https://github.com/sponsors/Abdess"
|
|
82
|
+
},
|
|
83
|
+
"scripts": {
|
|
84
|
+
"test": "node --test tests/**/*.test.js",
|
|
85
|
+
"test:coverage": "node --test --experimental-test-coverage tests/**/*.test.js",
|
|
86
|
+
"test:watch": "node --test --watch tests/**/*.test.js",
|
|
87
|
+
"lint": "eslint .",
|
|
88
|
+
"lint:fix": "eslint . --fix",
|
|
89
|
+
"format": "prettier --write .",
|
|
90
|
+
"format:check": "prettier --check .",
|
|
91
|
+
"typecheck": "tsc --noEmit",
|
|
92
|
+
"validate": "npm run lint && npm run format:check && npm run typecheck && npm test",
|
|
93
|
+
"docs": "jsdoc -c jsdoc.json",
|
|
94
|
+
"docs:serve": "npx serve docs/api",
|
|
95
|
+
"prepare": "husky",
|
|
96
|
+
"prepublishOnly": "npm run validate",
|
|
97
|
+
"prepack": "npm run validate",
|
|
98
|
+
"version": "npm run format && git add -A",
|
|
99
|
+
"postversion": "git push && git push --tags",
|
|
100
|
+
"release": "npm version patch && npm publish"
|
|
101
|
+
},
|
|
102
|
+
"devDependencies": {
|
|
103
|
+
"@eslint/js": "^9.17.0",
|
|
104
|
+
"@types/node": "^22.10.2",
|
|
105
|
+
"clean-jsdoc-theme": "^4.3.0",
|
|
106
|
+
"eslint": "^9.17.0",
|
|
107
|
+
"globals": "^15.14.0",
|
|
108
|
+
"husky": "^9.1.7",
|
|
109
|
+
"jsdoc": "^4.0.4",
|
|
110
|
+
"lint-staged": "^15.3.0",
|
|
111
|
+
"prettier": "^3.4.2",
|
|
112
|
+
"typescript": "^5.7.2"
|
|
113
|
+
},
|
|
114
|
+
"lint-staged": {
|
|
115
|
+
"*.js": [
|
|
116
|
+
"eslint --fix",
|
|
117
|
+
"prettier --write"
|
|
118
|
+
],
|
|
119
|
+
"*.{json,md,yml,yaml}": [
|
|
120
|
+
"prettier --write"
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* i18nkit - Universal i18n toolkit
|
|
3
|
+
* @packageDocumentation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface I18nKitConfig {
|
|
7
|
+
/** Source directory to scan (default: 'src/app') */
|
|
8
|
+
src?: string;
|
|
9
|
+
/** Default language code */
|
|
10
|
+
lang?: string;
|
|
11
|
+
/** i18n files directory (default: 'src/assets/i18n') */
|
|
12
|
+
i18nDir?: string;
|
|
13
|
+
/** Output format: 'nested' or 'flat' (default: 'nested') */
|
|
14
|
+
format?: 'nested' | 'flat';
|
|
15
|
+
/** Create backups before modifying files (default: true) */
|
|
16
|
+
backup?: boolean;
|
|
17
|
+
/** Folders to exclude from scanning */
|
|
18
|
+
excludedFolders?: string[];
|
|
19
|
+
/** Extract from TypeScript object literals (default: false) */
|
|
20
|
+
extractTsObjects?: boolean;
|
|
21
|
+
/** Include already translated strings (default: false) */
|
|
22
|
+
includeTranslated?: boolean;
|
|
23
|
+
/** CI mode: enables strict and json (default: false) */
|
|
24
|
+
ci?: boolean;
|
|
25
|
+
/** Verbose output (default: false) */
|
|
26
|
+
verbose?: boolean;
|
|
27
|
+
/** JSON report output (default: false) */
|
|
28
|
+
json?: boolean;
|
|
29
|
+
/** Dry run mode (default: false) */
|
|
30
|
+
dryRun?: boolean;
|
|
31
|
+
/** Strict mode - exit 1 on issues (default: false) */
|
|
32
|
+
strict?: boolean;
|
|
33
|
+
/** Auto-apply translations (default: false) */
|
|
34
|
+
autoApply?: boolean;
|
|
35
|
+
/** Interactive mode for confirmations (default: false) */
|
|
36
|
+
interactive?: boolean;
|
|
37
|
+
/** Watch mode (default: false) */
|
|
38
|
+
watch?: boolean;
|
|
39
|
+
/** Use DeepL API instead of MyMemory (default: false) */
|
|
40
|
+
deepl?: boolean;
|
|
41
|
+
/** Email for MyMemory rate limit */
|
|
42
|
+
email?: string;
|
|
43
|
+
/** Languages to initialize */
|
|
44
|
+
initLangs?: string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ExtractionResult {
|
|
48
|
+
file: string;
|
|
49
|
+
text: string;
|
|
50
|
+
rawText: string;
|
|
51
|
+
displayText: string;
|
|
52
|
+
context: string;
|
|
53
|
+
key: string;
|
|
54
|
+
attr?: string;
|
|
55
|
+
isNew: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ExtractionStats {
|
|
59
|
+
files: number;
|
|
60
|
+
clean: number;
|
|
61
|
+
needsWork: number;
|
|
62
|
+
total: number;
|
|
63
|
+
added: number;
|
|
64
|
+
byContext: Record<string, number>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface SyncResult {
|
|
68
|
+
totalKeys: number;
|
|
69
|
+
languages: number;
|
|
70
|
+
missingKeys: number;
|
|
71
|
+
identicalValues: number;
|
|
72
|
+
icuMessages: number;
|
|
73
|
+
icuMismatches: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface OrphanResult {
|
|
77
|
+
totalKeys: number;
|
|
78
|
+
usedKeys: number;
|
|
79
|
+
orphanKeys: number;
|
|
80
|
+
dynamicPatterns: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const EXIT_SUCCESS: 0;
|
|
84
|
+
export const EXIT_UNTRANSLATED: 1;
|
|
85
|
+
export const EXIT_ERROR: 2;
|