poly-lexis 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 +280 -0
- package/dist/cli/translations.d.ts +2 -0
- package/dist/cli/translations.js +1364 -0
- package/dist/cli/translations.js.map +1 -0
- package/dist/index.d.ts +244 -0
- package/dist/index.js +1016 -0
- package/dist/index.js.map +1 -0
- package/dist/translations/core/translations-config.schema.json +94 -0
- package/package.json +65 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1016 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
5
|
+
}) : x)(function(x) {
|
|
6
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
+
});
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/translations/core/types.ts
|
|
18
|
+
var DEFAULT_CONFIG, DEFAULT_LANGUAGES;
|
|
19
|
+
var init_types = __esm({
|
|
20
|
+
"src/translations/core/types.ts"() {
|
|
21
|
+
"use strict";
|
|
22
|
+
DEFAULT_CONFIG = {
|
|
23
|
+
translationsPath: "public/static/locales",
|
|
24
|
+
languages: ["en"],
|
|
25
|
+
sourceLanguage: "en",
|
|
26
|
+
typesOutputPath: "src/types/i18nTypes.ts"
|
|
27
|
+
};
|
|
28
|
+
DEFAULT_LANGUAGES = [
|
|
29
|
+
"en",
|
|
30
|
+
"fr",
|
|
31
|
+
"it",
|
|
32
|
+
"pl",
|
|
33
|
+
"es",
|
|
34
|
+
"pt",
|
|
35
|
+
"de",
|
|
36
|
+
"de_at",
|
|
37
|
+
"nl",
|
|
38
|
+
"sv",
|
|
39
|
+
"hu",
|
|
40
|
+
"cs",
|
|
41
|
+
"ja",
|
|
42
|
+
"zh_hk"
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// src/translations/core/schema.ts
|
|
48
|
+
function isValidLanguage(lang) {
|
|
49
|
+
return SUPPORTED_LANGUAGES.includes(lang);
|
|
50
|
+
}
|
|
51
|
+
function validateLanguages(languages) {
|
|
52
|
+
const invalid = languages.filter((lang) => !isValidLanguage(lang));
|
|
53
|
+
return {
|
|
54
|
+
valid: invalid.length === 0,
|
|
55
|
+
invalid
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
var SUPPORTED_LANGUAGES, TRANSLATION_CONFIG_SCHEMA;
|
|
59
|
+
var init_schema = __esm({
|
|
60
|
+
"src/translations/core/schema.ts"() {
|
|
61
|
+
"use strict";
|
|
62
|
+
SUPPORTED_LANGUAGES = [
|
|
63
|
+
"en",
|
|
64
|
+
// English
|
|
65
|
+
"fr",
|
|
66
|
+
// French
|
|
67
|
+
"it",
|
|
68
|
+
// Italian
|
|
69
|
+
"pl",
|
|
70
|
+
// Polish
|
|
71
|
+
"es",
|
|
72
|
+
// Spanish
|
|
73
|
+
"pt",
|
|
74
|
+
// Portuguese
|
|
75
|
+
"de",
|
|
76
|
+
// German
|
|
77
|
+
"de_at",
|
|
78
|
+
// German (Austria)
|
|
79
|
+
"nl",
|
|
80
|
+
// Dutch
|
|
81
|
+
"sv",
|
|
82
|
+
// Swedish
|
|
83
|
+
"hu",
|
|
84
|
+
// Hungarian
|
|
85
|
+
"cs",
|
|
86
|
+
// Czech
|
|
87
|
+
"ja",
|
|
88
|
+
// Japanese
|
|
89
|
+
"zh_hk",
|
|
90
|
+
// Chinese (Hong Kong)
|
|
91
|
+
"zh_cn",
|
|
92
|
+
// Chinese (Simplified)
|
|
93
|
+
"ko",
|
|
94
|
+
// Korean
|
|
95
|
+
"ru",
|
|
96
|
+
// Russian
|
|
97
|
+
"ar",
|
|
98
|
+
// Arabic
|
|
99
|
+
"he",
|
|
100
|
+
// Hebrew
|
|
101
|
+
"tr",
|
|
102
|
+
// Turkish
|
|
103
|
+
"da",
|
|
104
|
+
// Danish
|
|
105
|
+
"fi",
|
|
106
|
+
// Finnish
|
|
107
|
+
"no",
|
|
108
|
+
// Norwegian
|
|
109
|
+
"pt_br"
|
|
110
|
+
// Portuguese (Brazil)
|
|
111
|
+
];
|
|
112
|
+
TRANSLATION_CONFIG_SCHEMA = {
|
|
113
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
114
|
+
title: "Translation Configuration",
|
|
115
|
+
description: "Configuration for the translation management system",
|
|
116
|
+
type: "object",
|
|
117
|
+
properties: {
|
|
118
|
+
translationsPath: {
|
|
119
|
+
type: "string",
|
|
120
|
+
description: "Path to the translations directory relative to project root",
|
|
121
|
+
default: "public/static/locales"
|
|
122
|
+
},
|
|
123
|
+
languages: {
|
|
124
|
+
type: "array",
|
|
125
|
+
description: "List of language codes to support",
|
|
126
|
+
items: {
|
|
127
|
+
type: "string",
|
|
128
|
+
enum: SUPPORTED_LANGUAGES
|
|
129
|
+
},
|
|
130
|
+
minItems: 1,
|
|
131
|
+
uniqueItems: true
|
|
132
|
+
},
|
|
133
|
+
sourceLanguage: {
|
|
134
|
+
type: "string",
|
|
135
|
+
description: 'Source language for translations (usually "en")',
|
|
136
|
+
enum: SUPPORTED_LANGUAGES,
|
|
137
|
+
default: "en"
|
|
138
|
+
},
|
|
139
|
+
typesOutputPath: {
|
|
140
|
+
type: "string",
|
|
141
|
+
description: "Path to output TypeScript types file",
|
|
142
|
+
default: "src/types/i18nTypes.ts"
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
required: ["translationsPath", "languages", "sourceLanguage"],
|
|
146
|
+
additionalProperties: false
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// src/translations/utils/utils.ts
|
|
152
|
+
import * as fs from "fs";
|
|
153
|
+
import * as path from "path";
|
|
154
|
+
function readTranslations(translationsPath, language) {
|
|
155
|
+
const langPath = path.join(translationsPath, language);
|
|
156
|
+
if (!fs.existsSync(langPath)) {
|
|
157
|
+
return {};
|
|
158
|
+
}
|
|
159
|
+
const files = fs.readdirSync(langPath).filter((f) => f.endsWith(".json"));
|
|
160
|
+
const translations = {};
|
|
161
|
+
for (const file of files) {
|
|
162
|
+
const namespace = path.basename(file, ".json");
|
|
163
|
+
const filePath = path.join(langPath, file);
|
|
164
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
165
|
+
translations[namespace] = JSON.parse(content);
|
|
166
|
+
}
|
|
167
|
+
return translations;
|
|
168
|
+
}
|
|
169
|
+
function writeTranslation(translationsPath, language, namespace, translations) {
|
|
170
|
+
const langPath = path.join(translationsPath, language);
|
|
171
|
+
if (!fs.existsSync(langPath)) {
|
|
172
|
+
fs.mkdirSync(langPath, { recursive: true });
|
|
173
|
+
}
|
|
174
|
+
const filePath = path.join(langPath, `${namespace}.json`);
|
|
175
|
+
fs.writeFileSync(filePath, JSON.stringify(translations, null, 2) + "\n", "utf-8");
|
|
176
|
+
}
|
|
177
|
+
function getAvailableLanguages(translationsPath) {
|
|
178
|
+
if (!fs.existsSync(translationsPath)) {
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
return fs.readdirSync(translationsPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
|
|
182
|
+
}
|
|
183
|
+
function getNamespaces(translationsPath, language) {
|
|
184
|
+
const langPath = path.join(translationsPath, language);
|
|
185
|
+
if (!fs.existsSync(langPath)) {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
return fs.readdirSync(langPath).filter((f) => f.endsWith(".json")).map((f) => path.basename(f, ".json"));
|
|
189
|
+
}
|
|
190
|
+
function hasInterpolation(text) {
|
|
191
|
+
return /\{\{[^}]+\}\}/g.test(text);
|
|
192
|
+
}
|
|
193
|
+
function extractVariables(text) {
|
|
194
|
+
const matches = text.match(/\{\{([^}]+)\}\}/g);
|
|
195
|
+
if (!matches) return [];
|
|
196
|
+
return matches.map((match) => match.replace(/\{\{|\}\}/g, "").trim());
|
|
197
|
+
}
|
|
198
|
+
function validateVariables(sourceText, translatedText) {
|
|
199
|
+
const sourceVars = extractVariables(sourceText).sort();
|
|
200
|
+
const translatedVars = extractVariables(translatedText).sort();
|
|
201
|
+
if (sourceVars.length !== translatedVars.length) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
return sourceVars.every((v, i) => v === translatedVars[i]);
|
|
205
|
+
}
|
|
206
|
+
function sortKeys(obj) {
|
|
207
|
+
const sorted = {};
|
|
208
|
+
const keys = Object.keys(obj).sort();
|
|
209
|
+
for (const key of keys) {
|
|
210
|
+
sorted[key] = obj[key];
|
|
211
|
+
}
|
|
212
|
+
return sorted;
|
|
213
|
+
}
|
|
214
|
+
function ensureTranslationsStructure(translationsPath, languages) {
|
|
215
|
+
if (!fs.existsSync(translationsPath)) {
|
|
216
|
+
fs.mkdirSync(translationsPath, { recursive: true });
|
|
217
|
+
}
|
|
218
|
+
for (const lang of languages) {
|
|
219
|
+
const langPath = path.join(translationsPath, lang);
|
|
220
|
+
if (!fs.existsSync(langPath)) {
|
|
221
|
+
fs.mkdirSync(langPath, { recursive: true });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
var init_utils = __esm({
|
|
226
|
+
"src/translations/utils/utils.ts"() {
|
|
227
|
+
"use strict";
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// src/translations/cli/init.ts
|
|
232
|
+
var init_exports = {};
|
|
233
|
+
__export(init_exports, {
|
|
234
|
+
detectExistingTranslations: () => detectExistingTranslations,
|
|
235
|
+
initTranslations: () => initTranslations,
|
|
236
|
+
loadConfig: () => loadConfig
|
|
237
|
+
});
|
|
238
|
+
import * as fs2 from "fs";
|
|
239
|
+
import * as path2 from "path";
|
|
240
|
+
function detectExistingTranslations(projectRoot) {
|
|
241
|
+
const possiblePaths = ["public/static/locales", "public/locales", "src/locales", "locales", "i18n", "translations"];
|
|
242
|
+
for (const possiblePath of possiblePaths) {
|
|
243
|
+
const fullPath = path2.join(projectRoot, possiblePath);
|
|
244
|
+
if (fs2.existsSync(fullPath)) {
|
|
245
|
+
const languages = getAvailableLanguages(fullPath);
|
|
246
|
+
if (languages.length > 0) {
|
|
247
|
+
return { path: possiblePath, languages };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return { path: null, languages: [] };
|
|
252
|
+
}
|
|
253
|
+
function initTranslations(projectRoot, config = {}) {
|
|
254
|
+
console.log("=====");
|
|
255
|
+
console.log("Initializing translation structure");
|
|
256
|
+
console.log("=====");
|
|
257
|
+
const existing = detectExistingTranslations(projectRoot);
|
|
258
|
+
let finalConfig = { ...DEFAULT_CONFIG, ...config };
|
|
259
|
+
if (existing.path && existing.languages.length > 0) {
|
|
260
|
+
console.log(`\u2713 Detected existing translations at: ${existing.path}`);
|
|
261
|
+
console.log(`\u2713 Found languages: ${existing.languages.join(", ")}`);
|
|
262
|
+
const validation2 = validateLanguages(existing.languages);
|
|
263
|
+
if (!validation2.valid) {
|
|
264
|
+
console.log(`\u26A0\uFE0F Warning: Invalid language codes found: ${validation2.invalid.join(", ")}`);
|
|
265
|
+
console.log("These languages will be skipped.");
|
|
266
|
+
}
|
|
267
|
+
const validLanguages = existing.languages.filter((lang) => !validation2.invalid.includes(lang));
|
|
268
|
+
finalConfig = {
|
|
269
|
+
...finalConfig,
|
|
270
|
+
translationsPath: existing.path,
|
|
271
|
+
languages: validLanguages.length > 0 ? validLanguages : finalConfig.languages
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
const translationsPath = path2.join(projectRoot, finalConfig.translationsPath);
|
|
275
|
+
const languages = finalConfig.languages.length > 0 ? finalConfig.languages : [...DEFAULT_LANGUAGES];
|
|
276
|
+
console.log(`Project root: ${projectRoot}`);
|
|
277
|
+
console.log(`Translations path: ${translationsPath}`);
|
|
278
|
+
console.log(`Languages: ${languages.join(", ")}`);
|
|
279
|
+
console.log("=====");
|
|
280
|
+
const validation = validateLanguages(languages);
|
|
281
|
+
if (!validation.valid) {
|
|
282
|
+
throw new Error(`Invalid language codes: ${validation.invalid.join(", ")}`);
|
|
283
|
+
}
|
|
284
|
+
ensureTranslationsStructure(translationsPath, languages);
|
|
285
|
+
const sourceLanguage = finalConfig.sourceLanguage;
|
|
286
|
+
const sourcePath = path2.join(translationsPath, sourceLanguage);
|
|
287
|
+
const commonPath = path2.join(sourcePath, "common.json");
|
|
288
|
+
if (!fs2.existsSync(commonPath)) {
|
|
289
|
+
const sampleTranslations = {
|
|
290
|
+
LOADING: "Loading",
|
|
291
|
+
SAVE: "Save",
|
|
292
|
+
CANCEL: "Cancel",
|
|
293
|
+
SUBMIT: "Submit",
|
|
294
|
+
ERROR: "Error",
|
|
295
|
+
SUCCESS: "Success"
|
|
296
|
+
};
|
|
297
|
+
fs2.writeFileSync(commonPath, JSON.stringify(sampleTranslations, null, 2) + "\n", "utf-8");
|
|
298
|
+
console.log(`Created sample file: ${commonPath}`);
|
|
299
|
+
}
|
|
300
|
+
for (const lang of languages) {
|
|
301
|
+
if (lang === sourceLanguage) continue;
|
|
302
|
+
const langCommonPath = path2.join(translationsPath, lang, "common.json");
|
|
303
|
+
if (!fs2.existsSync(langCommonPath)) {
|
|
304
|
+
fs2.writeFileSync(langCommonPath, "{}\n", "utf-8");
|
|
305
|
+
console.log(`Created empty file: ${langCommonPath}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
const configPath = path2.join(projectRoot, ".translationsrc.json");
|
|
309
|
+
if (!fs2.existsSync(configPath)) {
|
|
310
|
+
const configContent = {
|
|
311
|
+
$schema: "./node_modules/@repo/env-scripts/dist/translations/core/translations-config.schema.json",
|
|
312
|
+
translationsPath: finalConfig.translationsPath,
|
|
313
|
+
languages,
|
|
314
|
+
sourceLanguage,
|
|
315
|
+
typesOutputPath: finalConfig.typesOutputPath
|
|
316
|
+
};
|
|
317
|
+
fs2.writeFileSync(configPath, JSON.stringify(configContent, null, 2) + "\n", "utf-8");
|
|
318
|
+
console.log(`Created config file: ${configPath}`);
|
|
319
|
+
}
|
|
320
|
+
console.log("=====");
|
|
321
|
+
console.log("Translation structure initialized successfully!");
|
|
322
|
+
console.log("=====");
|
|
323
|
+
}
|
|
324
|
+
function loadConfig(projectRoot) {
|
|
325
|
+
const configPath = path2.join(projectRoot, ".translationsrc.json");
|
|
326
|
+
if (!fs2.existsSync(configPath)) {
|
|
327
|
+
const existing = detectExistingTranslations(projectRoot);
|
|
328
|
+
if (existing.path && existing.languages.length > 0) {
|
|
329
|
+
console.log(`\u2139\uFE0F No config found, but detected translations at ${existing.path}`);
|
|
330
|
+
return {
|
|
331
|
+
...DEFAULT_CONFIG,
|
|
332
|
+
translationsPath: existing.path,
|
|
333
|
+
languages: existing.languages
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
return DEFAULT_CONFIG;
|
|
337
|
+
}
|
|
338
|
+
const configContent = fs2.readFileSync(configPath, "utf-8");
|
|
339
|
+
const config = JSON.parse(configContent);
|
|
340
|
+
if (config.languages) {
|
|
341
|
+
const validation = validateLanguages(config.languages);
|
|
342
|
+
if (!validation.valid) {
|
|
343
|
+
console.warn(`\u26A0\uFE0F Warning: Invalid language codes in config: ${validation.invalid.join(", ")}`);
|
|
344
|
+
console.warn("Please update .translationsrc.json with valid language codes.");
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return { ...DEFAULT_CONFIG, ...config };
|
|
348
|
+
}
|
|
349
|
+
var init_init = __esm({
|
|
350
|
+
"src/translations/cli/init.ts"() {
|
|
351
|
+
"use strict";
|
|
352
|
+
init_schema();
|
|
353
|
+
init_types();
|
|
354
|
+
init_utils();
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// src/translations/index.ts
|
|
359
|
+
init_types();
|
|
360
|
+
init_schema();
|
|
361
|
+
init_utils();
|
|
362
|
+
|
|
363
|
+
// src/translations/utils/translator.ts
|
|
364
|
+
async function translateText(text, targetLang, sourceLang = "en", apiKey) {
|
|
365
|
+
if (!apiKey) {
|
|
366
|
+
throw new Error("Google Translate API key is required. Set GOOGLE_TRANSLATE_API_KEY environment variable.");
|
|
367
|
+
}
|
|
368
|
+
const variableMap = /* @__PURE__ */ new Map();
|
|
369
|
+
let placeholderIndex = 0;
|
|
370
|
+
const textWithPlaceholders = text.replace(/\{\{([^}]+)\}\}/g, (match) => {
|
|
371
|
+
const placeholder = `__PLACEHOLDER_${placeholderIndex}__`;
|
|
372
|
+
variableMap.set(placeholder, match);
|
|
373
|
+
placeholderIndex++;
|
|
374
|
+
return placeholder;
|
|
375
|
+
});
|
|
376
|
+
const url = `https://translation.googleapis.com/language/translate/v2?key=${apiKey}`;
|
|
377
|
+
const response = await fetch(url, {
|
|
378
|
+
method: "POST",
|
|
379
|
+
headers: {
|
|
380
|
+
"Content-Type": "application/json"
|
|
381
|
+
},
|
|
382
|
+
body: JSON.stringify({
|
|
383
|
+
q: textWithPlaceholders,
|
|
384
|
+
source: sourceLang,
|
|
385
|
+
target: targetLang.split("_")[0],
|
|
386
|
+
// Convert 'pt_BR' to 'pt'
|
|
387
|
+
format: "text"
|
|
388
|
+
})
|
|
389
|
+
});
|
|
390
|
+
const data = await response.json();
|
|
391
|
+
if (data.error) {
|
|
392
|
+
throw new Error(`Google Translate API error: ${data.error.message}`);
|
|
393
|
+
}
|
|
394
|
+
let translatedText = data.data.translations[0].translatedText;
|
|
395
|
+
for (const [placeholder, original] of variableMap) {
|
|
396
|
+
translatedText = translatedText.replace(new RegExp(placeholder, "g"), original);
|
|
397
|
+
}
|
|
398
|
+
return translatedText;
|
|
399
|
+
}
|
|
400
|
+
async function translateBatch(texts, targetLang, sourceLang = "en", apiKey, delayMs = 100) {
|
|
401
|
+
const results = [];
|
|
402
|
+
for (const text of texts) {
|
|
403
|
+
const translated = await translateText(text, targetLang, sourceLang, apiKey);
|
|
404
|
+
results.push(translated);
|
|
405
|
+
if (delayMs > 0) {
|
|
406
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return results;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// src/translations/index.ts
|
|
413
|
+
init_init();
|
|
414
|
+
|
|
415
|
+
// src/translations/cli/init-interactive.ts
|
|
416
|
+
init_schema();
|
|
417
|
+
init_types();
|
|
418
|
+
init_init();
|
|
419
|
+
import * as path3 from "path";
|
|
420
|
+
import { checkbox, confirm, input } from "@inquirer/prompts";
|
|
421
|
+
async function initTranslationsInteractive(projectRoot = process.cwd()) {
|
|
422
|
+
console.log("\n\u{1F30D} Translation System Setup\n");
|
|
423
|
+
const configPath = path3.join(projectRoot, ".translationsrc.json");
|
|
424
|
+
const alreadyExists = __require("fs").existsSync(configPath);
|
|
425
|
+
if (alreadyExists) {
|
|
426
|
+
console.log("\u26A0\uFE0F Configuration file already exists at .translationsrc.json\n");
|
|
427
|
+
const shouldOverwrite = await confirm({
|
|
428
|
+
message: "Would you like to reconfigure?",
|
|
429
|
+
default: false
|
|
430
|
+
});
|
|
431
|
+
if (!shouldOverwrite) {
|
|
432
|
+
console.log("\u2713 Keeping existing configuration");
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
const { detectExistingTranslations: detectExistingTranslations2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
437
|
+
const existing = detectExistingTranslations2(projectRoot);
|
|
438
|
+
let translationsPath;
|
|
439
|
+
let languages;
|
|
440
|
+
if (existing.path) {
|
|
441
|
+
console.log(`\u2713 Found existing translations at: ${existing.path}`);
|
|
442
|
+
const useExisting = await confirm({
|
|
443
|
+
message: "Use this location?",
|
|
444
|
+
default: true
|
|
445
|
+
});
|
|
446
|
+
if (useExisting) {
|
|
447
|
+
translationsPath = existing.path;
|
|
448
|
+
} else {
|
|
449
|
+
translationsPath = await input({
|
|
450
|
+
message: "Enter translations directory path:",
|
|
451
|
+
default: DEFAULT_CONFIG.translationsPath,
|
|
452
|
+
validate: (value) => {
|
|
453
|
+
if (!value.trim()) return "Path is required";
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
} else {
|
|
459
|
+
translationsPath = await input({
|
|
460
|
+
message: "Where should translations be stored?",
|
|
461
|
+
default: DEFAULT_CONFIG.translationsPath,
|
|
462
|
+
validate: (value) => {
|
|
463
|
+
if (!value.trim()) return "Path is required";
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
if (existing.languages.length > 0) {
|
|
469
|
+
console.log(`\u2713 Found existing languages: ${existing.languages.join(", ")}`);
|
|
470
|
+
const useExistingLangs = await confirm({
|
|
471
|
+
message: "Use these languages?",
|
|
472
|
+
default: true
|
|
473
|
+
});
|
|
474
|
+
if (useExistingLangs) {
|
|
475
|
+
languages = existing.languages;
|
|
476
|
+
} else {
|
|
477
|
+
languages = await selectLanguages();
|
|
478
|
+
}
|
|
479
|
+
} else {
|
|
480
|
+
languages = await selectLanguages();
|
|
481
|
+
}
|
|
482
|
+
const sourceLanguage = await input({
|
|
483
|
+
message: "What is your source language?",
|
|
484
|
+
default: "en",
|
|
485
|
+
validate: (value) => {
|
|
486
|
+
if (!SUPPORTED_LANGUAGES.includes(value)) {
|
|
487
|
+
return `Invalid language code. Must be one of: ${SUPPORTED_LANGUAGES.join(", ")}`;
|
|
488
|
+
}
|
|
489
|
+
if (!languages.includes(value)) {
|
|
490
|
+
return "Source language must be in the list of supported languages";
|
|
491
|
+
}
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
const typesOutputPath = await input({
|
|
496
|
+
message: "Where should TypeScript types be generated?",
|
|
497
|
+
default: DEFAULT_CONFIG.typesOutputPath
|
|
498
|
+
});
|
|
499
|
+
console.log("\n\u{1F4CB} Configuration Summary:");
|
|
500
|
+
console.log(` Translations: ${translationsPath}`);
|
|
501
|
+
console.log(` Languages: ${languages.join(", ")}`);
|
|
502
|
+
console.log(` Source: ${sourceLanguage}`);
|
|
503
|
+
console.log(` Types: ${typesOutputPath}`);
|
|
504
|
+
const confirmInit = await confirm({
|
|
505
|
+
message: "\nProceed with initialization?",
|
|
506
|
+
default: true
|
|
507
|
+
});
|
|
508
|
+
if (!confirmInit) {
|
|
509
|
+
console.log("\u274C Cancelled");
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const config = {
|
|
513
|
+
translationsPath,
|
|
514
|
+
languages,
|
|
515
|
+
sourceLanguage,
|
|
516
|
+
typesOutputPath
|
|
517
|
+
};
|
|
518
|
+
console.log();
|
|
519
|
+
initTranslations(projectRoot, config);
|
|
520
|
+
console.log("\n\u{1F4DD} Configuration saved to: .translationsrc.json");
|
|
521
|
+
console.log("\n\u{1F4A1} Next steps:");
|
|
522
|
+
console.log(' 1. Run "translations add" to add your first translation key');
|
|
523
|
+
console.log(' 2. Run "translations" to validate and generate types');
|
|
524
|
+
console.log(" 3. Check the .translationsrc.json file for your configuration\n");
|
|
525
|
+
}
|
|
526
|
+
async function selectLanguages() {
|
|
527
|
+
const languageChoices = SUPPORTED_LANGUAGES.map((lang) => ({
|
|
528
|
+
name: `${lang} - ${getLanguageName(lang)}`,
|
|
529
|
+
value: lang,
|
|
530
|
+
checked: lang === "en"
|
|
531
|
+
// English selected by default
|
|
532
|
+
}));
|
|
533
|
+
const selected = await checkbox({
|
|
534
|
+
message: "Select languages to support (space to select, enter to confirm):",
|
|
535
|
+
choices: languageChoices,
|
|
536
|
+
required: true,
|
|
537
|
+
pageSize: 15,
|
|
538
|
+
loop: false
|
|
539
|
+
});
|
|
540
|
+
return selected;
|
|
541
|
+
}
|
|
542
|
+
function getLanguageName(code) {
|
|
543
|
+
const names = {
|
|
544
|
+
en: "English",
|
|
545
|
+
fr: "French",
|
|
546
|
+
it: "Italian",
|
|
547
|
+
pl: "Polish",
|
|
548
|
+
es: "Spanish",
|
|
549
|
+
pt: "Portuguese",
|
|
550
|
+
de: "German",
|
|
551
|
+
de_at: "German (Austria)",
|
|
552
|
+
nl: "Dutch",
|
|
553
|
+
sv: "Swedish",
|
|
554
|
+
hu: "Hungarian",
|
|
555
|
+
cs: "Czech",
|
|
556
|
+
ja: "Japanese",
|
|
557
|
+
zh_hk: "Chinese (Hong Kong)",
|
|
558
|
+
zh_cn: "Chinese (Simplified)",
|
|
559
|
+
ko: "Korean",
|
|
560
|
+
ru: "Russian",
|
|
561
|
+
ar: "Arabic",
|
|
562
|
+
he: "Hebrew",
|
|
563
|
+
tr: "Turkish",
|
|
564
|
+
da: "Danish",
|
|
565
|
+
fi: "Finnish",
|
|
566
|
+
no: "Norwegian",
|
|
567
|
+
pt_br: "Portuguese (Brazil)"
|
|
568
|
+
};
|
|
569
|
+
return names[code] || code;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/translations/cli/add-key.ts
|
|
573
|
+
import * as path4 from "path";
|
|
574
|
+
init_utils();
|
|
575
|
+
init_init();
|
|
576
|
+
async function addTranslationKey(projectRoot, options) {
|
|
577
|
+
const config = loadConfig(projectRoot);
|
|
578
|
+
const translationsPath = path4.join(projectRoot, config.translationsPath);
|
|
579
|
+
const { namespace, key, value, autoTranslate = false, apiKey } = options;
|
|
580
|
+
console.log("=====");
|
|
581
|
+
console.log("Adding translation key");
|
|
582
|
+
console.log("=====");
|
|
583
|
+
console.log(`Namespace: ${namespace}`);
|
|
584
|
+
console.log(`Key: ${key}`);
|
|
585
|
+
console.log(`Value (${config.sourceLanguage}): ${value}`);
|
|
586
|
+
console.log("=====");
|
|
587
|
+
const sourceLang = config.sourceLanguage;
|
|
588
|
+
const sourceTranslations = readTranslations(translationsPath, sourceLang);
|
|
589
|
+
if (!sourceTranslations[namespace]) {
|
|
590
|
+
sourceTranslations[namespace] = {};
|
|
591
|
+
}
|
|
592
|
+
if (sourceTranslations[namespace][key]) {
|
|
593
|
+
console.log(`\u26A0 Warning: Key "${key}" already exists in ${namespace}. Updating value.`);
|
|
594
|
+
}
|
|
595
|
+
sourceTranslations[namespace][key] = value;
|
|
596
|
+
const sortedSource = sortKeys(sourceTranslations[namespace]);
|
|
597
|
+
writeTranslation(translationsPath, sourceLang, namespace, sortedSource);
|
|
598
|
+
console.log(`\u2713 Added to ${sourceLang}/${namespace}.json`);
|
|
599
|
+
const otherLanguages = config.languages.filter((lang) => lang !== sourceLang);
|
|
600
|
+
if (autoTranslate && apiKey) {
|
|
601
|
+
console.log("\nAuto-translating to other languages...");
|
|
602
|
+
for (const lang of otherLanguages) {
|
|
603
|
+
try {
|
|
604
|
+
const targetTranslations = readTranslations(translationsPath, lang);
|
|
605
|
+
if (!targetTranslations[namespace]) {
|
|
606
|
+
targetTranslations[namespace] = {};
|
|
607
|
+
}
|
|
608
|
+
if (!targetTranslations[namespace][key] || targetTranslations[namespace][key].trim() === "") {
|
|
609
|
+
const translated = await translateText(value, lang, sourceLang, apiKey);
|
|
610
|
+
targetTranslations[namespace][key] = translated;
|
|
611
|
+
const sorted = sortKeys(targetTranslations[namespace]);
|
|
612
|
+
writeTranslation(translationsPath, lang, namespace, sorted);
|
|
613
|
+
console.log(` \u2713 ${lang}: "${translated}"`);
|
|
614
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
615
|
+
} else {
|
|
616
|
+
console.log(` - ${lang}: Already exists, skipping`);
|
|
617
|
+
}
|
|
618
|
+
} catch (error) {
|
|
619
|
+
console.error(` \u2717 ${lang}: Translation failed - ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
} else {
|
|
623
|
+
console.log("\nAdding empty values to other languages...");
|
|
624
|
+
for (const lang of otherLanguages) {
|
|
625
|
+
const targetTranslations = readTranslations(translationsPath, lang);
|
|
626
|
+
if (!targetTranslations[namespace]) {
|
|
627
|
+
targetTranslations[namespace] = {};
|
|
628
|
+
}
|
|
629
|
+
if (!targetTranslations[namespace][key]) {
|
|
630
|
+
targetTranslations[namespace][key] = "";
|
|
631
|
+
const sorted = sortKeys(targetTranslations[namespace]);
|
|
632
|
+
writeTranslation(translationsPath, lang, namespace, sorted);
|
|
633
|
+
console.log(` \u2713 ${lang}/${namespace}.json`);
|
|
634
|
+
} else {
|
|
635
|
+
console.log(` - ${lang}/${namespace}.json: Already exists`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
if (!autoTranslate) {
|
|
639
|
+
console.log("\n\u2139 Use --auto-translate flag to automatically translate to all languages");
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
console.log("=====");
|
|
643
|
+
console.log("Translation key added successfully!");
|
|
644
|
+
console.log("=====");
|
|
645
|
+
}
|
|
646
|
+
async function addTranslationKeys(projectRoot, entries, autoTranslate = false, apiKey) {
|
|
647
|
+
console.log(`Adding ${entries.length} translation keys...`);
|
|
648
|
+
for (const entry of entries) {
|
|
649
|
+
await addTranslationKey(projectRoot, {
|
|
650
|
+
namespace: entry.namespace,
|
|
651
|
+
key: entry.key,
|
|
652
|
+
value: entry.value,
|
|
653
|
+
autoTranslate,
|
|
654
|
+
apiKey
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// src/translations/cli/validate.ts
|
|
660
|
+
init_utils();
|
|
661
|
+
init_init();
|
|
662
|
+
import * as path5 from "path";
|
|
663
|
+
function validateTranslations(projectRoot = process.cwd()) {
|
|
664
|
+
const config = loadConfig(projectRoot);
|
|
665
|
+
const translationsPath = path5.join(projectRoot, config.translationsPath);
|
|
666
|
+
const sourceLanguage = config.sourceLanguage;
|
|
667
|
+
const missing = [];
|
|
668
|
+
const empty = [];
|
|
669
|
+
const sourceTranslations = readTranslations(translationsPath, sourceLanguage);
|
|
670
|
+
const sourceNamespaces = getNamespaces(translationsPath, sourceLanguage);
|
|
671
|
+
const languages = getAvailableLanguages(translationsPath).filter((lang) => lang !== sourceLanguage);
|
|
672
|
+
console.log("=====");
|
|
673
|
+
console.log("Validating translations");
|
|
674
|
+
console.log("=====");
|
|
675
|
+
console.log(`Source language: ${sourceLanguage}`);
|
|
676
|
+
console.log(`Target languages: ${languages.join(", ")}`);
|
|
677
|
+
console.log(`Namespaces: ${sourceNamespaces.join(", ")}`);
|
|
678
|
+
console.log("=====");
|
|
679
|
+
for (const language of languages) {
|
|
680
|
+
const targetTranslations = readTranslations(translationsPath, language);
|
|
681
|
+
for (const namespace of sourceNamespaces) {
|
|
682
|
+
const sourceKeys = sourceTranslations[namespace] || {};
|
|
683
|
+
const targetKeys = targetTranslations[namespace] || {};
|
|
684
|
+
for (const [key, sourceValue] of Object.entries(sourceKeys)) {
|
|
685
|
+
const targetValue = targetKeys[key];
|
|
686
|
+
if (targetValue === void 0) {
|
|
687
|
+
missing.push({
|
|
688
|
+
namespace,
|
|
689
|
+
key,
|
|
690
|
+
language,
|
|
691
|
+
sourceValue
|
|
692
|
+
});
|
|
693
|
+
} else if (typeof targetValue === "string" && targetValue.trim() === "") {
|
|
694
|
+
empty.push({
|
|
695
|
+
namespace,
|
|
696
|
+
key,
|
|
697
|
+
language,
|
|
698
|
+
sourceValue
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
const valid = missing.length === 0 && empty.length === 0;
|
|
705
|
+
if (valid) {
|
|
706
|
+
console.log("\u2713 All translations are valid!");
|
|
707
|
+
} else {
|
|
708
|
+
if (missing.length > 0) {
|
|
709
|
+
console.log(`
|
|
710
|
+
\u26A0 Found ${missing.length} missing translations:`);
|
|
711
|
+
for (const item of missing.slice(0, 10)) {
|
|
712
|
+
console.log(` ${item.language}/${item.namespace}.json -> ${item.key}`);
|
|
713
|
+
}
|
|
714
|
+
if (missing.length > 10) {
|
|
715
|
+
console.log(` ... and ${missing.length - 10} more`);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (empty.length > 0) {
|
|
719
|
+
console.log(`
|
|
720
|
+
\u26A0 Found ${empty.length} empty translations:`);
|
|
721
|
+
for (const item of empty.slice(0, 10)) {
|
|
722
|
+
console.log(` ${item.language}/${item.namespace}.json -> ${item.key}`);
|
|
723
|
+
}
|
|
724
|
+
if (empty.length > 10) {
|
|
725
|
+
console.log(` ... and ${empty.length - 10} more`);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
console.log("=====");
|
|
730
|
+
return { valid, missing, empty };
|
|
731
|
+
}
|
|
732
|
+
function getMissingForLanguage(projectRoot, language) {
|
|
733
|
+
const result = validateTranslations(projectRoot);
|
|
734
|
+
const items = [
|
|
735
|
+
...result.missing.filter((m) => m.language === language).map((m) => ({ ...m, type: "missing" })),
|
|
736
|
+
...result.empty.filter((e) => e.language === language).map((e) => ({ ...e, type: "empty" }))
|
|
737
|
+
];
|
|
738
|
+
return items;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// src/translations/cli/auto-fill.ts
|
|
742
|
+
import * as path6 from "path";
|
|
743
|
+
init_utils();
|
|
744
|
+
init_init();
|
|
745
|
+
async function autoFillTranslations(projectRoot = process.cwd(), options = {}) {
|
|
746
|
+
const config = loadConfig(projectRoot);
|
|
747
|
+
const translationsPath = path6.join(projectRoot, config.translationsPath);
|
|
748
|
+
const { apiKey, limit = 1e3, delayMs = 100, dryRun = false } = options;
|
|
749
|
+
if (!apiKey) {
|
|
750
|
+
throw new Error("Google Translate API key is required. Set GOOGLE_TRANSLATE_API_KEY or pass --api-key");
|
|
751
|
+
}
|
|
752
|
+
const languagesToProcess = options.language ? [options.language] : config.languages.filter((lang) => lang !== config.sourceLanguage);
|
|
753
|
+
console.log("=====");
|
|
754
|
+
console.log("Auto-filling translations");
|
|
755
|
+
console.log("=====");
|
|
756
|
+
console.log(`Languages: ${languagesToProcess.join(", ")}`);
|
|
757
|
+
console.log(`Limit: ${limit}`);
|
|
758
|
+
console.log(`Dry run: ${dryRun}`);
|
|
759
|
+
console.log("=====");
|
|
760
|
+
let totalProcessed = 0;
|
|
761
|
+
let totalTranslated = 0;
|
|
762
|
+
for (const language of languagesToProcess) {
|
|
763
|
+
if (totalProcessed >= limit) {
|
|
764
|
+
console.log(`
|
|
765
|
+
Reached limit of ${limit} translations`);
|
|
766
|
+
break;
|
|
767
|
+
}
|
|
768
|
+
console.log(`
|
|
769
|
+
Processing language: ${language}`);
|
|
770
|
+
const missing = getMissingForLanguage(projectRoot, language);
|
|
771
|
+
if (missing.length === 0) {
|
|
772
|
+
console.log(" No missing or empty translations");
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
console.log(` Found ${missing.length} translations to fill`);
|
|
776
|
+
const remainingLimit = limit - totalProcessed;
|
|
777
|
+
const itemsToProcess = missing.slice(0, remainingLimit);
|
|
778
|
+
for (const item of itemsToProcess) {
|
|
779
|
+
totalProcessed++;
|
|
780
|
+
try {
|
|
781
|
+
console.log(` [${totalProcessed}/${limit}] Translating ${item.namespace}.${item.key}`);
|
|
782
|
+
console.log(` EN: "${item.sourceValue}"`);
|
|
783
|
+
const translated = await translateText(item.sourceValue, language, config.sourceLanguage, apiKey);
|
|
784
|
+
console.log(` ${language.toUpperCase()}: "${translated}"`);
|
|
785
|
+
if (!dryRun) {
|
|
786
|
+
const translations = readTranslations(translationsPath, language);
|
|
787
|
+
if (!translations[item.namespace]) {
|
|
788
|
+
translations[item.namespace] = {};
|
|
789
|
+
}
|
|
790
|
+
translations[item.namespace][item.key] = translated;
|
|
791
|
+
const sorted = sortKeys(translations[item.namespace]);
|
|
792
|
+
writeTranslation(translationsPath, language, item.namespace, sorted);
|
|
793
|
+
console.log(" \u2713 Saved");
|
|
794
|
+
} else {
|
|
795
|
+
console.log(" \u2713 Dry run - not saved");
|
|
796
|
+
}
|
|
797
|
+
totalTranslated++;
|
|
798
|
+
if (delayMs > 0 && totalProcessed < limit) {
|
|
799
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
800
|
+
}
|
|
801
|
+
} catch (error) {
|
|
802
|
+
console.error(` \u2717 Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
console.log("\n=====");
|
|
807
|
+
console.log(`Total processed: ${totalProcessed}`);
|
|
808
|
+
console.log(`Total translated: ${totalTranslated}`);
|
|
809
|
+
if (dryRun) {
|
|
810
|
+
console.log("\u26A0 Dry run - no changes were saved");
|
|
811
|
+
}
|
|
812
|
+
console.log("=====");
|
|
813
|
+
}
|
|
814
|
+
async function fillNamespace(projectRoot, language, namespace, apiKey) {
|
|
815
|
+
const config = loadConfig(projectRoot);
|
|
816
|
+
const translationsPath = path6.join(projectRoot, config.translationsPath);
|
|
817
|
+
console.log(`Filling translations for ${language}/${namespace}.json`);
|
|
818
|
+
const sourceTranslations = readTranslations(translationsPath, config.sourceLanguage);
|
|
819
|
+
const targetTranslations = readTranslations(translationsPath, language);
|
|
820
|
+
const sourceKeys = sourceTranslations[namespace] || {};
|
|
821
|
+
const targetKeys = targetTranslations[namespace] || {};
|
|
822
|
+
let count = 0;
|
|
823
|
+
for (const [key, sourceValue] of Object.entries(sourceKeys)) {
|
|
824
|
+
const targetValue = targetKeys[key];
|
|
825
|
+
if (targetValue && targetValue.trim() !== "") {
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
console.log(` Translating ${key}...`);
|
|
829
|
+
const translated = await translateText(sourceValue, language, config.sourceLanguage, apiKey);
|
|
830
|
+
targetKeys[key] = translated;
|
|
831
|
+
count++;
|
|
832
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
833
|
+
}
|
|
834
|
+
if (count > 0) {
|
|
835
|
+
const sorted = sortKeys(targetKeys);
|
|
836
|
+
writeTranslation(translationsPath, language, namespace, sorted);
|
|
837
|
+
console.log(`\u2713 Filled ${count} translations`);
|
|
838
|
+
} else {
|
|
839
|
+
console.log("No translations to fill");
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// src/translations/cli/generate-types.ts
|
|
844
|
+
init_utils();
|
|
845
|
+
init_init();
|
|
846
|
+
import { execSync } from "child_process";
|
|
847
|
+
import * as fs3 from "fs";
|
|
848
|
+
import * as path7 from "path";
|
|
849
|
+
var typeTemplate = (translationKeys, namespaceKeys) => `
|
|
850
|
+
export const translationKeys = [${translationKeys.map((key) => `"${key}"`).join(", ")}] as const;
|
|
851
|
+
export const namespaceKeys = [${namespaceKeys.map((key) => `"${key}"`).join(", ")}] as const;
|
|
852
|
+
|
|
853
|
+
export type TranslationKey = typeof translationKeys[number];
|
|
854
|
+
export type TranslationNamespace = typeof namespaceKeys[number];
|
|
855
|
+
`;
|
|
856
|
+
function generateTranslationTypes(projectRoot = process.cwd()) {
|
|
857
|
+
console.log("=====");
|
|
858
|
+
console.time("i18n types generated");
|
|
859
|
+
console.log("Generating i18n types");
|
|
860
|
+
console.log("=====");
|
|
861
|
+
const config = loadConfig(projectRoot);
|
|
862
|
+
const translationsPath = path7.join(projectRoot, config.translationsPath);
|
|
863
|
+
const sourceLanguage = config.sourceLanguage;
|
|
864
|
+
const outputFilePath = path7.join(projectRoot, config.typesOutputPath);
|
|
865
|
+
const dirPath = path7.join(translationsPath, sourceLanguage);
|
|
866
|
+
if (!fs3.existsSync(dirPath)) {
|
|
867
|
+
throw new Error(`Source language directory not found: ${dirPath}`);
|
|
868
|
+
}
|
|
869
|
+
const namespaces = getNamespaces(translationsPath, sourceLanguage);
|
|
870
|
+
if (namespaces.length === 0) {
|
|
871
|
+
throw new Error(`No translation files found in ${dirPath}`);
|
|
872
|
+
}
|
|
873
|
+
const translations = readTranslations(translationsPath, sourceLanguage);
|
|
874
|
+
let allKeys = [];
|
|
875
|
+
for (const namespace of namespaces) {
|
|
876
|
+
const keys = Object.keys(translations[namespace] || {});
|
|
877
|
+
allKeys = allKeys.concat(keys);
|
|
878
|
+
}
|
|
879
|
+
const outputDir = path7.dirname(outputFilePath);
|
|
880
|
+
if (!fs3.existsSync(outputDir)) {
|
|
881
|
+
fs3.mkdirSync(outputDir, { recursive: true });
|
|
882
|
+
}
|
|
883
|
+
const typeString = typeTemplate(allKeys, namespaces);
|
|
884
|
+
fs3.writeFileSync(outputFilePath, typeString, "utf8");
|
|
885
|
+
console.log(`Generated types with ${allKeys.length} keys and ${namespaces.length} namespaces`);
|
|
886
|
+
console.log(`Output: ${outputFilePath}`);
|
|
887
|
+
try {
|
|
888
|
+
execSync(`pnpm biome format --write ${outputFilePath}`, { stdio: "inherit", cwd: projectRoot });
|
|
889
|
+
} catch {
|
|
890
|
+
console.warn("Failed to format with Biome, continuing without formatting...");
|
|
891
|
+
}
|
|
892
|
+
console.timeEnd("i18n types generated");
|
|
893
|
+
console.log("=====");
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// src/translations/cli/manage.ts
|
|
897
|
+
import * as fs4 from "fs";
|
|
898
|
+
import * as path8 from "path";
|
|
899
|
+
init_init();
|
|
900
|
+
async function manageTranslations(projectRoot = process.cwd(), options = {}) {
|
|
901
|
+
const { autoFill = false, apiKey, limit = 1e3, language, skipTypes = false, dryRun = false } = options;
|
|
902
|
+
console.log("=====");
|
|
903
|
+
console.log("Translation Management");
|
|
904
|
+
console.log("=====");
|
|
905
|
+
const configPath = path8.join(projectRoot, ".translationsrc.json");
|
|
906
|
+
const isInitialized = fs4.existsSync(configPath);
|
|
907
|
+
if (!isInitialized) {
|
|
908
|
+
console.log("\u{1F4C1} No translation configuration found. Initializing...\n");
|
|
909
|
+
initTranslations(projectRoot);
|
|
910
|
+
console.log("\n\u2705 Initialization complete!\n");
|
|
911
|
+
} else {
|
|
912
|
+
console.log("\u2713 Translation structure initialized\n");
|
|
913
|
+
}
|
|
914
|
+
const config = loadConfig(projectRoot);
|
|
915
|
+
const translationsPath = path8.join(projectRoot, config.translationsPath);
|
|
916
|
+
const sourceLangPath = path8.join(translationsPath, config.sourceLanguage);
|
|
917
|
+
if (!fs4.existsSync(sourceLangPath)) {
|
|
918
|
+
console.log(`\u26A0\uFE0F Source language directory not found: ${sourceLangPath}`);
|
|
919
|
+
console.log("Please add translation files to the source language directory.\n");
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
console.log("\u{1F50D} Validating translations...\n");
|
|
923
|
+
const validationResult = validateTranslations(projectRoot);
|
|
924
|
+
if (validationResult.valid) {
|
|
925
|
+
console.log("\n\u2705 All translations are complete!\n");
|
|
926
|
+
} else {
|
|
927
|
+
const totalMissing = validationResult.missing.length + validationResult.empty.length;
|
|
928
|
+
if (autoFill) {
|
|
929
|
+
if (!apiKey) {
|
|
930
|
+
console.log("\n\u26A0\uFE0F Auto-fill requested but no API key provided.");
|
|
931
|
+
console.log("Set GOOGLE_TRANSLATE_API_KEY or pass --api-key to enable auto-fill.\n");
|
|
932
|
+
} else {
|
|
933
|
+
console.log(`
|
|
934
|
+
\u{1F916} Auto-filling ${totalMissing} missing translations...
|
|
935
|
+
`);
|
|
936
|
+
await autoFillTranslations(projectRoot, {
|
|
937
|
+
apiKey,
|
|
938
|
+
limit,
|
|
939
|
+
language,
|
|
940
|
+
dryRun,
|
|
941
|
+
delayMs: 100
|
|
942
|
+
});
|
|
943
|
+
if (!dryRun) {
|
|
944
|
+
console.log("\n\u{1F50D} Re-validating after auto-fill...\n");
|
|
945
|
+
const revalidation = validateTranslations(projectRoot);
|
|
946
|
+
if (revalidation.valid) {
|
|
947
|
+
console.log("\n\u2705 All translations are now complete!\n");
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
} else {
|
|
952
|
+
console.log(`
|
|
953
|
+
\u{1F4A1} Tip: Run with --auto-fill to automatically translate missing keys.
|
|
954
|
+
`);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
if (!skipTypes && !dryRun) {
|
|
958
|
+
console.log("\u{1F4DD} Generating TypeScript types...\n");
|
|
959
|
+
generateTranslationTypes(projectRoot);
|
|
960
|
+
console.log("\n\u2705 Types generated!\n");
|
|
961
|
+
} else if (skipTypes) {
|
|
962
|
+
console.log("\u23ED\uFE0F Skipping type generation (--skip-types)\n");
|
|
963
|
+
} else if (dryRun) {
|
|
964
|
+
console.log("\u23ED\uFE0F Skipping type generation (--dry-run)\n");
|
|
965
|
+
}
|
|
966
|
+
console.log("=====");
|
|
967
|
+
console.log("Summary");
|
|
968
|
+
console.log("=====");
|
|
969
|
+
console.log(`Configuration: ${configPath}`);
|
|
970
|
+
console.log(`Translations: ${translationsPath}`);
|
|
971
|
+
console.log(`Languages: ${config.languages.join(", ")}`);
|
|
972
|
+
console.log(`Source language: ${config.sourceLanguage}`);
|
|
973
|
+
if (!validationResult.valid && !autoFill) {
|
|
974
|
+
console.log(`
|
|
975
|
+
\u26A0\uFE0F ${validationResult.missing.length} missing translations`);
|
|
976
|
+
console.log(`\u26A0\uFE0F ${validationResult.empty.length} empty translations`);
|
|
977
|
+
console.log("\nNext steps:");
|
|
978
|
+
console.log(" 1. Add missing translations manually, or");
|
|
979
|
+
console.log(" 2. Run with --auto-fill to translate automatically");
|
|
980
|
+
} else if (validationResult.valid) {
|
|
981
|
+
console.log("\n\u2705 All systems ready!");
|
|
982
|
+
}
|
|
983
|
+
console.log("=====\n");
|
|
984
|
+
}
|
|
985
|
+
export {
|
|
986
|
+
DEFAULT_CONFIG,
|
|
987
|
+
DEFAULT_LANGUAGES,
|
|
988
|
+
SUPPORTED_LANGUAGES,
|
|
989
|
+
TRANSLATION_CONFIG_SCHEMA,
|
|
990
|
+
addTranslationKey,
|
|
991
|
+
addTranslationKeys,
|
|
992
|
+
autoFillTranslations,
|
|
993
|
+
detectExistingTranslations,
|
|
994
|
+
ensureTranslationsStructure,
|
|
995
|
+
extractVariables,
|
|
996
|
+
fillNamespace,
|
|
997
|
+
generateTranslationTypes,
|
|
998
|
+
getAvailableLanguages,
|
|
999
|
+
getMissingForLanguage,
|
|
1000
|
+
getNamespaces,
|
|
1001
|
+
hasInterpolation,
|
|
1002
|
+
initTranslations,
|
|
1003
|
+
initTranslationsInteractive,
|
|
1004
|
+
isValidLanguage,
|
|
1005
|
+
loadConfig,
|
|
1006
|
+
manageTranslations,
|
|
1007
|
+
readTranslations,
|
|
1008
|
+
sortKeys,
|
|
1009
|
+
translateBatch,
|
|
1010
|
+
translateText,
|
|
1011
|
+
validateLanguages,
|
|
1012
|
+
validateTranslations,
|
|
1013
|
+
validateVariables,
|
|
1014
|
+
writeTranslation
|
|
1015
|
+
};
|
|
1016
|
+
//# sourceMappingURL=index.js.map
|