@djangocfg/i18n 2.1.216 → 2.1.218
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 +20 -159
- package/dist/cli/index.mjs +3 -366
- package/dist/cli/index.mjs.map +1 -1
- package/package.json +5 -9
- package/src/cli/commands/index.ts +0 -2
- package/src/cli/index.ts +0 -7
- package/src/cli/utils/index.ts +0 -1
- package/src/cli/utils/locales.ts +2 -1
- package/src/cli/commands/sync.ts +0 -233
- package/src/cli/commands/translate.ts +0 -208
- package/src/cli/utils/translator.ts +0 -97
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Translate text or JSON using LLM
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { defineCommand } from 'citty';
|
|
6
|
-
import { consola } from 'consola';
|
|
7
|
-
import * as path from 'node:path';
|
|
8
|
-
import * as fs from 'node:fs';
|
|
9
|
-
import {
|
|
10
|
-
detectLocaleConfig,
|
|
11
|
-
getDefaultLocalesDir,
|
|
12
|
-
loadLocale,
|
|
13
|
-
readLocaleFile,
|
|
14
|
-
writeLocaleFile,
|
|
15
|
-
getExportName,
|
|
16
|
-
translateText,
|
|
17
|
-
translateJson,
|
|
18
|
-
getTranslatorStats,
|
|
19
|
-
} from '../utils';
|
|
20
|
-
|
|
21
|
-
export const translateCommand = defineCommand({
|
|
22
|
-
meta: {
|
|
23
|
-
name: 'translate',
|
|
24
|
-
description: 'Translate text or locale file using LLM',
|
|
25
|
-
},
|
|
26
|
-
args: {
|
|
27
|
-
text: {
|
|
28
|
-
type: 'positional',
|
|
29
|
-
description: 'Text to translate or locale code (e.g., "Hello" or "ru")',
|
|
30
|
-
required: false,
|
|
31
|
-
},
|
|
32
|
-
dir: {
|
|
33
|
-
type: 'string',
|
|
34
|
-
alias: 'd',
|
|
35
|
-
description: 'Locales directory',
|
|
36
|
-
},
|
|
37
|
-
from: {
|
|
38
|
-
type: 'string',
|
|
39
|
-
alias: 'f',
|
|
40
|
-
description: 'Source locale (default: en)',
|
|
41
|
-
default: 'en',
|
|
42
|
-
},
|
|
43
|
-
to: {
|
|
44
|
-
type: 'string',
|
|
45
|
-
alias: 't',
|
|
46
|
-
description: 'Target locales (comma-separated, e.g., "ru,ko,ja")',
|
|
47
|
-
},
|
|
48
|
-
json: {
|
|
49
|
-
type: 'boolean',
|
|
50
|
-
alias: 'j',
|
|
51
|
-
description: 'Output as JSON',
|
|
52
|
-
default: false,
|
|
53
|
-
},
|
|
54
|
-
file: {
|
|
55
|
-
type: 'boolean',
|
|
56
|
-
description: 'Translate entire locale file',
|
|
57
|
-
default: false,
|
|
58
|
-
},
|
|
59
|
-
stats: {
|
|
60
|
-
type: 'boolean',
|
|
61
|
-
alias: 's',
|
|
62
|
-
description: 'Show translation cache stats',
|
|
63
|
-
default: false,
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
async run({ args }) {
|
|
67
|
-
// Show stats
|
|
68
|
-
if (args.stats) {
|
|
69
|
-
const stats = getTranslatorStats();
|
|
70
|
-
if (stats) {
|
|
71
|
-
consola.info('Translation Cache Stats:');
|
|
72
|
-
consola.log(` Memory size: ${stats.memorySize}`);
|
|
73
|
-
consola.log(` Hits: ${stats.hits}`);
|
|
74
|
-
consola.log(` Misses: ${stats.misses}`);
|
|
75
|
-
if (stats.languagePairs.length > 0) {
|
|
76
|
-
consola.log(' Language pairs:');
|
|
77
|
-
for (const { pair, translations } of stats.languagePairs) {
|
|
78
|
-
consola.log(` ${pair}: ${translations} translations`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
} else {
|
|
82
|
-
consola.info('No translations performed yet.');
|
|
83
|
-
}
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const localesDir = args.dir
|
|
88
|
-
? path.resolve(process.cwd(), args.dir)
|
|
89
|
-
: getDefaultLocalesDir();
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
const config = detectLocaleConfig(localesDir);
|
|
93
|
-
const sourceLocale = args.from || 'en';
|
|
94
|
-
|
|
95
|
-
// Determine target locales
|
|
96
|
-
let targetLocales: string[];
|
|
97
|
-
if (args.to) {
|
|
98
|
-
targetLocales = args.to.split(',').map((l) => l.trim());
|
|
99
|
-
} else {
|
|
100
|
-
// All locales except source
|
|
101
|
-
targetLocales = config.locales.filter((l) => l !== sourceLocale);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Translate entire file
|
|
105
|
-
if (args.file || (args.text && config.locales.includes(args.text))) {
|
|
106
|
-
const targetLocale = args.text || targetLocales[0];
|
|
107
|
-
if (!targetLocale) {
|
|
108
|
-
consola.error('Please specify target locale');
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
consola.info(`Translating ${sourceLocale} -> ${targetLocale}`);
|
|
113
|
-
consola.info(`Directory: ${localesDir}`);
|
|
114
|
-
|
|
115
|
-
const sourceData = await loadLocale(config, sourceLocale);
|
|
116
|
-
const startTime = Date.now();
|
|
117
|
-
|
|
118
|
-
// Translate section-by-section to avoid LLM output truncation on large files
|
|
119
|
-
const topLevelKeys = Object.keys(sourceData);
|
|
120
|
-
const translated: Record<string, unknown> = {};
|
|
121
|
-
|
|
122
|
-
for (const section of topLevelKeys) {
|
|
123
|
-
const sectionData = sourceData[section];
|
|
124
|
-
consola.info(` [${section}] translating...`);
|
|
125
|
-
const sectionTranslated = await translateJson(
|
|
126
|
-
sectionData as Record<string, unknown>,
|
|
127
|
-
targetLocale,
|
|
128
|
-
sourceLocale,
|
|
129
|
-
);
|
|
130
|
-
translated[section] = sectionTranslated;
|
|
131
|
-
consola.success(` [${section}] done`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
135
|
-
consola.success(`Translated in ${elapsed}s`);
|
|
136
|
-
|
|
137
|
-
// Write to file
|
|
138
|
-
if (config.fileExtension === '.ts') {
|
|
139
|
-
const exportName = getExportName(targetLocale);
|
|
140
|
-
const { content } = readLocaleFile(config, targetLocale);
|
|
141
|
-
const jsonStr = JSON.stringify(translated, null, 2);
|
|
142
|
-
// Replace the exported object (from `export const X... = {` to end of file)
|
|
143
|
-
const newContent = content.replace(
|
|
144
|
-
/(export const \w+[^=]*=\s*)\{[\s\S]*$/,
|
|
145
|
-
`$1${jsonStr};\n`
|
|
146
|
-
);
|
|
147
|
-
const finalContent = newContent !== content
|
|
148
|
-
? newContent
|
|
149
|
-
: `import type { DocsTranslations } from './en';\n\nexport const ${exportName}: DocsTranslations = ${jsonStr};\n`;
|
|
150
|
-
writeLocaleFile(config, targetLocale, finalContent);
|
|
151
|
-
consola.success(`Written to ${targetLocale}.ts`);
|
|
152
|
-
} else {
|
|
153
|
-
writeLocaleFile(config, targetLocale, JSON.stringify(translated, null, 2));
|
|
154
|
-
consola.success(`Written to ${targetLocale}.json`);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (args.json) {
|
|
158
|
-
console.log(JSON.stringify(translated, null, 2));
|
|
159
|
-
} else {
|
|
160
|
-
// Show sample
|
|
161
|
-
const sample = JSON.stringify(translated, null, 2).slice(0, 300);
|
|
162
|
-
consola.log(sample + (sample.length >= 300 ? '\n...' : ''));
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Translate text
|
|
169
|
-
const text = args.text;
|
|
170
|
-
if (!text) {
|
|
171
|
-
consola.error('Please provide text to translate or use --file flag');
|
|
172
|
-
consola.log('');
|
|
173
|
-
consola.log('Examples:');
|
|
174
|
-
consola.log(' pnpm i18n translate "Hello World" --to ru,ko');
|
|
175
|
-
consola.log(' pnpm i18n translate --file --to ru');
|
|
176
|
-
consola.log(' pnpm i18n translate ru # Translate en.ts to ru');
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
consola.info(`Translating: "${text}"`);
|
|
181
|
-
consola.log('');
|
|
182
|
-
|
|
183
|
-
const translations: Record<string, string> = {
|
|
184
|
-
[sourceLocale]: text,
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
for (const locale of targetLocales) {
|
|
188
|
-
try {
|
|
189
|
-
const translated = await translateText(text, locale, sourceLocale);
|
|
190
|
-
translations[locale] = translated;
|
|
191
|
-
consola.log(` ${locale}: ${translated}`);
|
|
192
|
-
} catch (error) {
|
|
193
|
-
consola.warn(` ${locale}: [FAILED] ${error instanceof Error ? error.message : error}`);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
consola.log('');
|
|
198
|
-
if (args.json) {
|
|
199
|
-
console.log(JSON.stringify(translations, null, 2));
|
|
200
|
-
} else {
|
|
201
|
-
consola.log('Copy-paste JSON:');
|
|
202
|
-
console.log(JSON.stringify(translations));
|
|
203
|
-
}
|
|
204
|
-
} catch (error) {
|
|
205
|
-
consola.error(error);
|
|
206
|
-
}
|
|
207
|
-
},
|
|
208
|
-
});
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LLM Translator utility for i18n CLI
|
|
3
|
-
*
|
|
4
|
-
* Uses @djangocfg/llm for translations
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { createLLMClient, createTranslator, type JsonTranslator } from '@djangocfg/llm';
|
|
8
|
-
|
|
9
|
-
/** Default API key for testing */
|
|
10
|
-
const DEFAULT_API_KEY = 'test-api-key';
|
|
11
|
-
|
|
12
|
-
let translator: JsonTranslator | null = null;
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Get or create translator instance
|
|
16
|
-
*/
|
|
17
|
-
export function getTranslator(): JsonTranslator {
|
|
18
|
-
if (!translator) {
|
|
19
|
-
// Use env key or default test key
|
|
20
|
-
const apiKey = process.env.SDKROUTER_API_KEY ||
|
|
21
|
-
process.env.OPENAI_API_KEY ||
|
|
22
|
-
process.env.ANTHROPIC_API_KEY ||
|
|
23
|
-
DEFAULT_API_KEY;
|
|
24
|
-
|
|
25
|
-
const llm = createLLMClient({
|
|
26
|
-
provider: 'sdkrouter',
|
|
27
|
-
apiKey
|
|
28
|
-
});
|
|
29
|
-
translator = createTranslator(llm);
|
|
30
|
-
}
|
|
31
|
-
return translator;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Translate text to target language
|
|
36
|
-
*/
|
|
37
|
-
export async function translateText(
|
|
38
|
-
text: string,
|
|
39
|
-
targetLocale: string,
|
|
40
|
-
sourceLocale: string = 'en'
|
|
41
|
-
): Promise<string> {
|
|
42
|
-
const t = getTranslator();
|
|
43
|
-
return t.translateText(text, targetLocale, { sourceLanguage: sourceLocale });
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Translate JSON object to target language
|
|
48
|
-
*/
|
|
49
|
-
export async function translateJson<T extends Record<string, unknown>>(
|
|
50
|
-
data: T,
|
|
51
|
-
targetLocale: string,
|
|
52
|
-
sourceLocale: string = 'en'
|
|
53
|
-
): Promise<T> {
|
|
54
|
-
const t = getTranslator();
|
|
55
|
-
const result = await t.translate(data, targetLocale, { sourceLanguage: sourceLocale });
|
|
56
|
-
return result.data;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Translate to multiple languages in parallel
|
|
61
|
-
*/
|
|
62
|
-
export async function translateToMany<T extends Record<string, unknown>>(
|
|
63
|
-
data: T,
|
|
64
|
-
targetLocales: string[],
|
|
65
|
-
sourceLocale: string = 'en'
|
|
66
|
-
): Promise<Map<string, T>> {
|
|
67
|
-
const t = getTranslator();
|
|
68
|
-
const results = await t.translateToMany(data, targetLocales, { sourceLanguage: sourceLocale });
|
|
69
|
-
|
|
70
|
-
const output = new Map<string, T>();
|
|
71
|
-
for (const [locale, result] of results) {
|
|
72
|
-
output.set(locale, result.data);
|
|
73
|
-
}
|
|
74
|
-
return output;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Get translator stats
|
|
79
|
-
*/
|
|
80
|
-
export function getTranslatorStats(): {
|
|
81
|
-
memorySize: number;
|
|
82
|
-
hits: number;
|
|
83
|
-
misses: number;
|
|
84
|
-
languagePairs: Array<{ pair: string; translations: number }>;
|
|
85
|
-
} | null {
|
|
86
|
-
if (!translator) return null;
|
|
87
|
-
return translator.getStats();
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Clear translator cache
|
|
92
|
-
*/
|
|
93
|
-
export function clearTranslatorCache() {
|
|
94
|
-
if (translator) {
|
|
95
|
-
translator.clearCache();
|
|
96
|
-
}
|
|
97
|
-
}
|