bctranslate 1.0.0 → 1.0.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/bctranslate.js +132 -132
- package/package.json +1 -1
- package/python/translator.py +7 -0
- package/src/bridges/python.js +61 -55
- package/src/index.js +158 -97
- package/src/parsers/html.js +3 -3
- package/src/parsers/js.js +4 -4
- package/src/parsers/json.js +1 -1
- package/src/parsers/react.js +55 -60
- package/src/parsers/vue.js +5 -5
- package/src/utils.js +102 -23
package/bin/bctranslate.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
|
-
import { resolve, basename, dirname } from 'path';
|
|
5
|
+
import { resolve, basename, dirname } from 'path';
|
|
6
6
|
import { existsSync, statSync } from 'fs';
|
|
7
7
|
import { glob } from 'glob';
|
|
8
|
-
import {
|
|
8
|
+
import { translateAllFiles, generateDiff } from '../src/index.js';
|
|
9
9
|
import { detectProject } from '../src/detect.js';
|
|
10
10
|
import { ensureI18nSetup } from '../src/generators/setup.js';
|
|
11
|
-
import { checkPythonBridge } from '../src/bridges/python.js';
|
|
11
|
+
import { checkPythonBridge, installArgostranslate } from '../src/bridges/python.js';
|
|
12
12
|
import { loadConfig, saveConfig } from '../src/config.js';
|
|
13
13
|
|
|
14
14
|
const program = new Command();
|
|
@@ -18,7 +18,7 @@ program
|
|
|
18
18
|
.description('Transform source files into i18n-ready code with automatic translation')
|
|
19
19
|
.version('1.0.0');
|
|
20
20
|
|
|
21
|
-
// ──
|
|
21
|
+
// ── Language lists ────────────────────────────────────────────────────────────
|
|
22
22
|
const COMMON_LANGUAGES = [
|
|
23
23
|
{ name: 'French (fr)', value: 'fr' },
|
|
24
24
|
{ name: 'Spanish (es)', value: 'es' },
|
|
@@ -27,7 +27,7 @@ const COMMON_LANGUAGES = [
|
|
|
27
27
|
{ name: 'Portuguese (pt)', value: 'pt' },
|
|
28
28
|
{ name: 'Dutch (nl)', value: 'nl' },
|
|
29
29
|
{ name: 'Russian (ru)', value: 'ru' },
|
|
30
|
-
{ name: 'Chinese
|
|
30
|
+
{ name: 'Chinese (zh)', value: 'zh' },
|
|
31
31
|
{ name: 'Japanese (ja)', value: 'ja' },
|
|
32
32
|
{ name: 'Korean (ko)', value: 'ko' },
|
|
33
33
|
{ name: 'Arabic (ar)', value: 'ar' },
|
|
@@ -43,70 +43,60 @@ const COMMON_LANGUAGES = [
|
|
|
43
43
|
];
|
|
44
44
|
|
|
45
45
|
const SOURCE_LANGUAGES = [
|
|
46
|
-
{ name: 'English
|
|
47
|
-
{ name: 'French
|
|
48
|
-
{ name: 'Spanish
|
|
49
|
-
{ name: 'German
|
|
50
|
-
{ name: 'Italian
|
|
51
|
-
{ name: 'Portuguese(pt)', value: 'pt' },
|
|
52
|
-
{ name: 'Chinese
|
|
46
|
+
{ name: 'English (en)', value: 'en' },
|
|
47
|
+
{ name: 'French (fr)', value: 'fr' },
|
|
48
|
+
{ name: 'Spanish (es)', value: 'es' },
|
|
49
|
+
{ name: 'German (de)', value: 'de' },
|
|
50
|
+
{ name: 'Italian (it)', value: 'it' },
|
|
51
|
+
{ name: 'Portuguese (pt)', value: 'pt' },
|
|
52
|
+
{ name: 'Chinese (zh)', value: 'zh' },
|
|
53
53
|
{ name: 'Other (type below)', value: '__other__' },
|
|
54
54
|
];
|
|
55
55
|
|
|
56
56
|
// ── File patterns per project type ───────────────────────────────────────────
|
|
57
57
|
const FILE_PATTERNS = {
|
|
58
|
-
vue:
|
|
59
|
-
react:
|
|
58
|
+
vue: ['**/*.vue', 'src/**/*.js', 'src/**/*.ts'],
|
|
59
|
+
react: ['**/*.jsx', '**/*.tsx', 'src/**/*.js', 'src/**/*.ts'],
|
|
60
60
|
vanilla: ['**/*.html', '**/*.htm', '**/*.js'],
|
|
61
61
|
};
|
|
62
62
|
|
|
63
63
|
const IGNORE_PATTERNS = [
|
|
64
|
-
'**/node_modules/**',
|
|
65
|
-
'**/
|
|
66
|
-
'**/build/**',
|
|
67
|
-
'**/.git/**',
|
|
68
|
-
'**/coverage/**',
|
|
69
|
-
'**/*.min.js',
|
|
64
|
+
'**/node_modules/**', '**/dist/**', '**/build/**',
|
|
65
|
+
'**/.git/**', '**/coverage/**', '**/*.min.js',
|
|
70
66
|
];
|
|
71
67
|
|
|
72
|
-
// ──
|
|
68
|
+
// ── File resolution ───────────────────────────────────────────────────────────
|
|
73
69
|
async function resolveFiles(pathArg, cwd, project) {
|
|
74
70
|
const patterns = FILE_PATTERNS[project.type] || FILE_PATTERNS.vanilla;
|
|
75
71
|
|
|
76
72
|
if (!pathArg) {
|
|
77
73
|
const files = [];
|
|
78
74
|
for (const p of patterns) {
|
|
79
|
-
|
|
80
|
-
files.push(...matched);
|
|
75
|
+
files.push(...await glob(p, { cwd, absolute: true, ignore: IGNORE_PATTERNS }));
|
|
81
76
|
}
|
|
82
77
|
return [...new Set(files)];
|
|
83
78
|
}
|
|
84
79
|
|
|
85
80
|
const resolved = resolve(cwd, pathArg);
|
|
86
|
-
|
|
87
81
|
if (existsSync(resolved)) {
|
|
88
82
|
if (statSync(resolved).isDirectory()) {
|
|
89
83
|
const files = [];
|
|
90
84
|
for (const p of patterns) {
|
|
91
|
-
|
|
92
|
-
files.push(...matched);
|
|
85
|
+
files.push(...await glob(p, { cwd: resolved, absolute: true, ignore: IGNORE_PATTERNS }));
|
|
93
86
|
}
|
|
94
87
|
return [...new Set(files)];
|
|
95
88
|
}
|
|
96
89
|
return [resolved];
|
|
97
90
|
}
|
|
98
|
-
|
|
99
|
-
// Treat as glob pattern
|
|
100
91
|
return glob(pathArg, { cwd, absolute: true, ignore: IGNORE_PATTERNS });
|
|
101
92
|
}
|
|
102
93
|
|
|
103
|
-
// ──
|
|
94
|
+
// ── Translation runner — uses global batching (Python spawned once) ───────────
|
|
104
95
|
async function runTranslation({ pathArg, from, to, localesDir, dryRun, outdir, verbose, jsonMode, setup, cwd }) {
|
|
105
96
|
const project = detectProject(cwd);
|
|
106
97
|
console.log(chalk.green(` ✓ Project type: ${chalk.bold(project.type)}`));
|
|
107
98
|
|
|
108
99
|
const files = await resolveFiles(pathArg, cwd, project);
|
|
109
|
-
|
|
110
100
|
if (files.length === 0) {
|
|
111
101
|
console.log(chalk.yellow(' ⚠ No files found to translate.'));
|
|
112
102
|
return { totalStrings: 0, totalFiles: 0 };
|
|
@@ -114,44 +104,39 @@ async function runTranslation({ pathArg, from, to, localesDir, dryRun, outdir, v
|
|
|
114
104
|
|
|
115
105
|
console.log(chalk.cyan(` → ${files.length} file(s) [${from} → ${to}]...\n`));
|
|
116
106
|
|
|
117
|
-
// Ensure i18n setup (locale files + framework config)
|
|
118
107
|
if (setup !== false) {
|
|
119
108
|
const resolvedLocalesDir = localesDir ? resolve(cwd, localesDir) : undefined;
|
|
120
109
|
await ensureI18nSetup(cwd, project, from, to, resolvedLocalesDir);
|
|
121
110
|
}
|
|
122
111
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
verbose,
|
|
136
|
-
jsonMode,
|
|
137
|
-
localesDir: localesDir ? resolve(cwd, localesDir) : undefined,
|
|
138
|
-
});
|
|
112
|
+
// Global batch: all files parsed first, ONE Python call, then all writes
|
|
113
|
+
const results = await translateAllFiles(files, {
|
|
114
|
+
from,
|
|
115
|
+
to,
|
|
116
|
+
dryRun,
|
|
117
|
+
outdir,
|
|
118
|
+
project,
|
|
119
|
+
cwd,
|
|
120
|
+
verbose,
|
|
121
|
+
jsonMode,
|
|
122
|
+
localesDir: localesDir ? resolve(cwd, localesDir) : undefined,
|
|
123
|
+
});
|
|
139
124
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
|
|
125
|
+
let totalStrings = 0;
|
|
126
|
+
let totalFiles = 0;
|
|
127
|
+
|
|
128
|
+
for (const result of results) {
|
|
129
|
+
if (result.count > 0) {
|
|
130
|
+
totalFiles++;
|
|
131
|
+
totalStrings += result.count;
|
|
132
|
+
const label = dryRun ? chalk.yellow('[DRY RUN]') : chalk.green('[DONE] ');
|
|
133
|
+
const skipNote = result.skipped > 0 ? chalk.gray(` (${result.skipped} already done)`) : '';
|
|
134
|
+
console.log(` ${label} ${chalk.white(result.relativePath)} — ${result.count} new${skipNote}`);
|
|
135
|
+
if (result.diff) {
|
|
136
|
+
console.log(chalk.gray(result.diff));
|
|
151
137
|
}
|
|
152
|
-
}
|
|
153
|
-
console.
|
|
154
|
-
if (verbose) console.error(err.stack);
|
|
138
|
+
} else if (verbose) {
|
|
139
|
+
console.log(` ${chalk.gray('[SKIP] ')} ${result.relativePath}`);
|
|
155
140
|
}
|
|
156
141
|
}
|
|
157
142
|
|
|
@@ -161,22 +146,18 @@ async function runTranslation({ pathArg, from, to, localesDir, dryRun, outdir, v
|
|
|
161
146
|
// ── init subcommand ───────────────────────────────────────────────────────────
|
|
162
147
|
program
|
|
163
148
|
.command('init')
|
|
164
|
-
.description('Interactive setup wizard —
|
|
149
|
+
.description('Interactive setup wizard — languages, locales folder, auto-install')
|
|
165
150
|
.action(async () => {
|
|
166
151
|
const { default: inquirer } = await import('inquirer');
|
|
167
|
-
const cwd
|
|
152
|
+
const cwd = process.cwd();
|
|
168
153
|
const existing = loadConfig(cwd);
|
|
169
|
-
const project
|
|
154
|
+
const project = detectProject(cwd);
|
|
170
155
|
|
|
171
|
-
const defaultLocalesDir =
|
|
172
|
-
|
|
173
|
-
const defaultI18nFile =
|
|
174
|
-
project.type === 'vue' || project.type === 'react' ? './src/i18n.js' : './i18n.js';
|
|
156
|
+
const defaultLocalesDir = project.type === 'vanilla' ? './locales' : './src/locales';
|
|
157
|
+
const defaultI18nFile = project.type === 'vanilla' ? './i18n.js' : './src/i18n.js';
|
|
175
158
|
|
|
176
159
|
console.log(chalk.cyan.bold('\n ⚡ bctranslate init\n'));
|
|
177
|
-
if (existing)
|
|
178
|
-
console.log(chalk.gray(` Existing config found — press Enter to keep current values.\n`));
|
|
179
|
-
}
|
|
160
|
+
if (existing) console.log(chalk.gray(' Existing config found — press Enter to keep values.\n'));
|
|
180
161
|
|
|
181
162
|
const answers = await inquirer.prompt([
|
|
182
163
|
{
|
|
@@ -195,9 +176,9 @@ program
|
|
|
195
176
|
{
|
|
196
177
|
type: 'input',
|
|
197
178
|
name: 'fromCustom',
|
|
198
|
-
message: 'Source language code:',
|
|
179
|
+
message: 'Source language code (e.g. zh-TW):',
|
|
199
180
|
when: (ans) => ans.from === '__other__',
|
|
200
|
-
validate: (v) =>
|
|
181
|
+
validate: (v) => v.trim().length >= 2 || 'Enter a valid BCP 47 code',
|
|
201
182
|
},
|
|
202
183
|
{
|
|
203
184
|
type: 'checkbox',
|
|
@@ -205,34 +186,30 @@ program
|
|
|
205
186
|
message: 'Target language(s) — Space to select, Enter to confirm:',
|
|
206
187
|
choices: COMMON_LANGUAGES,
|
|
207
188
|
default: existing?.to,
|
|
208
|
-
validate: (v) =>
|
|
209
|
-
v.length > 0 || 'Select at least one target language (Space to select)',
|
|
189
|
+
validate: (v) => v.length > 0 || 'Select at least one target language',
|
|
210
190
|
},
|
|
211
191
|
{
|
|
212
192
|
type: 'input',
|
|
213
193
|
name: 'extraTo',
|
|
214
|
-
message: '
|
|
194
|
+
message: 'Extra language codes (comma-separated, e.g. zh-TW,sr — blank to skip):',
|
|
215
195
|
default: '',
|
|
216
196
|
},
|
|
217
197
|
{
|
|
218
198
|
type: 'input',
|
|
219
199
|
name: 'i18nFile',
|
|
220
|
-
message: 'i18n setup file path
|
|
200
|
+
message: 'i18n setup file path:',
|
|
221
201
|
default: existing?.i18nFile || defaultI18nFile,
|
|
222
202
|
},
|
|
223
203
|
]);
|
|
224
204
|
|
|
225
|
-
// Resolve from language
|
|
226
205
|
const fromLang = answers.from === '__other__' ? answers.fromCustom.trim() : answers.from;
|
|
227
|
-
|
|
228
|
-
// Merge checkbox selection + extra codes
|
|
229
|
-
const extraTo = answers.extraTo
|
|
206
|
+
const extraTo = answers.extraTo
|
|
230
207
|
? answers.extraTo.split(',').map((s) => s.trim()).filter(Boolean)
|
|
231
208
|
: [];
|
|
232
209
|
const toLangs = [...new Set([...answers.to, ...extraTo])].filter((l) => l !== fromLang);
|
|
233
210
|
|
|
234
211
|
if (toLangs.length === 0) {
|
|
235
|
-
console.log(chalk.red('\n ✗ No target languages selected
|
|
212
|
+
console.log(chalk.red('\n ✗ No target languages selected.\n'));
|
|
236
213
|
process.exit(1);
|
|
237
214
|
}
|
|
238
215
|
|
|
@@ -240,17 +217,56 @@ program
|
|
|
240
217
|
from: fromLang,
|
|
241
218
|
to: toLangs,
|
|
242
219
|
localesDir: answers.localesDir.trim(),
|
|
243
|
-
i18nFile:
|
|
220
|
+
i18nFile: answers.i18nFile.trim(),
|
|
244
221
|
};
|
|
245
222
|
|
|
246
223
|
const configPath = saveConfig(config, cwd);
|
|
247
|
-
|
|
248
224
|
console.log(chalk.green(`\n ✓ Config saved → ${basename(configPath)}`));
|
|
249
225
|
console.log(chalk.cyan(`\n Source : ${chalk.bold(config.from)}`));
|
|
250
226
|
console.log(chalk.cyan(` Targets : ${chalk.bold(config.to.join(', '))}`));
|
|
251
227
|
console.log(chalk.cyan(` Locales : ${chalk.bold(config.localesDir)}`));
|
|
252
228
|
console.log(chalk.cyan(` i18n : ${chalk.bold(config.i18nFile)}`));
|
|
253
|
-
|
|
229
|
+
|
|
230
|
+
// ── Auto-install dependencies ─────────────────────────────────────────────
|
|
231
|
+
const { installDeps } = await inquirer.prompt([{
|
|
232
|
+
type: 'confirm',
|
|
233
|
+
name: 'installDeps',
|
|
234
|
+
message: 'Install/update argostranslate now? (required for offline translation)',
|
|
235
|
+
default: !existing,
|
|
236
|
+
}]);
|
|
237
|
+
|
|
238
|
+
if (installDeps) {
|
|
239
|
+
process.stdout.write(chalk.cyan('\n Installing argostranslate... '));
|
|
240
|
+
try {
|
|
241
|
+
await installArgostranslate();
|
|
242
|
+
console.log(chalk.green('done'));
|
|
243
|
+
} catch (err) {
|
|
244
|
+
console.log(chalk.red(`failed\n ${err.message}`));
|
|
245
|
+
console.log(chalk.gray(' Run manually: pip install argostranslate'));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ── Offer to pre-download language models ─────────────────────────────────
|
|
250
|
+
const { downloadModels } = await inquirer.prompt([{
|
|
251
|
+
type: 'confirm',
|
|
252
|
+
name: 'downloadModels',
|
|
253
|
+
message: `Download language models for ${toLangs.join(', ')}? (can take a few minutes, required before first use)`,
|
|
254
|
+
default: true,
|
|
255
|
+
}]);
|
|
256
|
+
|
|
257
|
+
if (downloadModels) {
|
|
258
|
+
for (const to of toLangs) {
|
|
259
|
+
process.stdout.write(chalk.cyan(` Downloading ${fromLang} → ${to}... `));
|
|
260
|
+
try {
|
|
261
|
+
await checkPythonBridge(fromLang, to);
|
|
262
|
+
console.log(chalk.green('ready'));
|
|
263
|
+
} catch (err) {
|
|
264
|
+
console.log(chalk.red(`failed: ${err.message}`));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
console.log(chalk.gray('\n Run `bctranslate` to start translating.\n'));
|
|
254
270
|
});
|
|
255
271
|
|
|
256
272
|
// ── Main translation command ──────────────────────────────────────────────────
|
|
@@ -259,34 +275,29 @@ program
|
|
|
259
275
|
.argument('[from]', 'Source language code (e.g. en)')
|
|
260
276
|
.argument('[keyword]', '"to" keyword')
|
|
261
277
|
.argument('[lang]', 'Target language code (e.g. fr)')
|
|
262
|
-
.option('-t, --to <lang>', 'Target language
|
|
278
|
+
.option('-t, --to <lang>', 'Target language(s), comma-separated (e.g. fr or fr,es)')
|
|
263
279
|
.option('-d, --dry-run', 'Preview changes without writing files', false)
|
|
264
280
|
.option('-o, --outdir <dir>', 'Output directory for translated files')
|
|
265
281
|
.option('--no-setup', 'Skip i18n setup file generation')
|
|
266
282
|
.option('--json-mode <mode>', 'JSON translation mode: values or full', 'values')
|
|
267
|
-
.option('-v, --verbose', '
|
|
283
|
+
.option('-v, --verbose', 'Show per-file diffs and skipped files', false)
|
|
268
284
|
.action(async (pathArg, fromArg, keyword, langArg, opts) => {
|
|
269
285
|
console.log(chalk.cyan.bold('\n ⚡ bctranslate\n'));
|
|
270
286
|
|
|
271
|
-
const invokedCwd = process.cwd();
|
|
272
|
-
const config
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// ── Determine from/to/targets ─────────────────────────────────────────────
|
|
287
|
+
const invokedCwd = process.cwd();
|
|
288
|
+
const config = loadConfig(invokedCwd);
|
|
289
|
+
|
|
290
|
+
let cwd = invokedCwd;
|
|
291
|
+
if (!config && pathArg) {
|
|
292
|
+
const resolvedTarget = resolve(invokedCwd, pathArg);
|
|
293
|
+
if (existsSync(resolvedTarget)) {
|
|
294
|
+
try {
|
|
295
|
+
const st = statSync(resolvedTarget);
|
|
296
|
+
cwd = st.isDirectory() ? resolvedTarget : dirname(resolvedTarget);
|
|
297
|
+
} catch { cwd = invokedCwd; }
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
290
301
|
const hasExplicitArgs = !!(pathArg || fromArg || keyword || langArg);
|
|
291
302
|
|
|
292
303
|
if (!hasExplicitArgs && !config) {
|
|
@@ -297,24 +308,20 @@ program
|
|
|
297
308
|
process.exit(0);
|
|
298
309
|
}
|
|
299
310
|
|
|
300
|
-
// Resolve from language
|
|
301
311
|
const from = fromArg || config?.from || 'en';
|
|
302
312
|
|
|
303
|
-
// Resolve target language(s)
|
|
304
313
|
let targets;
|
|
305
314
|
if (keyword === 'to' && langArg) {
|
|
306
|
-
// bctranslate ./src en to fr — or bctranslate ./src en to fr,es
|
|
307
315
|
targets = langArg.split(',').map((s) => s.trim()).filter(Boolean);
|
|
308
316
|
} else if (opts.to) {
|
|
309
|
-
// --to fr or --to fr,es
|
|
310
317
|
targets = opts.to.split(',').map((s) => s.trim()).filter(Boolean);
|
|
311
318
|
} else if (config?.to) {
|
|
312
319
|
targets = Array.isArray(config.to) ? config.to : [config.to];
|
|
313
320
|
} else {
|
|
314
|
-
targets = ['fr'];
|
|
321
|
+
targets = ['fr'];
|
|
315
322
|
}
|
|
316
323
|
|
|
317
|
-
//
|
|
324
|
+
// Check Python + download models for all pairs
|
|
318
325
|
for (const to of targets) {
|
|
319
326
|
try {
|
|
320
327
|
await checkPythonBridge(from, to);
|
|
@@ -325,25 +332,22 @@ program
|
|
|
325
332
|
}
|
|
326
333
|
}
|
|
327
334
|
|
|
328
|
-
// ── Run translation for each target language ──────────────────────────────
|
|
329
335
|
let grandTotal = 0;
|
|
330
336
|
let grandFiles = 0;
|
|
331
337
|
|
|
332
338
|
for (const to of targets) {
|
|
333
|
-
if (targets.length > 1) {
|
|
334
|
-
console.log(chalk.cyan.bold(`\n ── Translating to ${to} ──`));
|
|
335
|
-
}
|
|
339
|
+
if (targets.length > 1) console.log(chalk.cyan.bold(`\n ── Translating to ${to} ──`));
|
|
336
340
|
|
|
337
341
|
const { totalStrings, totalFiles } = await runTranslation({
|
|
338
342
|
pathArg,
|
|
339
343
|
from,
|
|
340
344
|
to,
|
|
341
345
|
localesDir: config?.localesDir,
|
|
342
|
-
dryRun:
|
|
343
|
-
outdir:
|
|
344
|
-
verbose:
|
|
345
|
-
jsonMode:
|
|
346
|
-
setup:
|
|
346
|
+
dryRun: opts.dryRun,
|
|
347
|
+
outdir: opts.outdir,
|
|
348
|
+
verbose: opts.verbose,
|
|
349
|
+
jsonMode: opts.jsonMode,
|
|
350
|
+
setup: opts.setup,
|
|
347
351
|
cwd,
|
|
348
352
|
});
|
|
349
353
|
|
|
@@ -352,16 +356,12 @@ program
|
|
|
352
356
|
}
|
|
353
357
|
|
|
354
358
|
const langStr = targets.join(', ');
|
|
355
|
-
console.log(
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
);
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
console.log(chalk.gray(` Root : ${cwd}`));
|
|
363
|
-
console.log(chalk.gray(` Source : ${from}`));
|
|
364
|
-
console.log(chalk.gray(` Target : ${langStr}\n`));
|
|
365
|
-
});
|
|
359
|
+
console.log(chalk.cyan.bold(
|
|
360
|
+
`\n Done: ${grandTotal} new string(s) in ${grandFiles} file(s) [→ ${langStr}]\n`
|
|
361
|
+
));
|
|
362
|
+
console.log(chalk.gray(` Root : ${cwd}`));
|
|
363
|
+
console.log(chalk.gray(` Source : ${from}`));
|
|
364
|
+
console.log(chalk.gray(` Target : ${langStr}\n`));
|
|
365
|
+
});
|
|
366
366
|
|
|
367
367
|
program.parse();
|
package/package.json
CHANGED
package/python/translator.py
CHANGED
|
@@ -8,8 +8,15 @@ Usage: python translator.py <from_code> <to_code>
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
import sys
|
|
11
|
+
import io
|
|
11
12
|
import json
|
|
12
13
|
|
|
14
|
+
# Force UTF-8 on all platforms (critical on Windows where default may be cp1252)
|
|
15
|
+
sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8')
|
|
16
|
+
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', line_buffering=True)
|
|
17
|
+
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', line_buffering=True)
|
|
18
|
+
|
|
19
|
+
|
|
13
20
|
def ensure_model(from_code, to_code):
|
|
14
21
|
"""Ensure the translation model is installed, downloading if needed."""
|
|
15
22
|
import argostranslate.package
|