Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
mololab
GitHub Repository: mololab/json-translator
Path: blob/master/src/cli/cli.ts
235 views
1
import {
2
translationModuleKeys,
3
getTranslationModuleByKey,
4
} from '../modules/helpers';
5
import { fileTranslator, getFileFromPath } from '../core/json_file';
6
import {
7
error,
8
info,
9
messages,
10
supportedLanguagesUrl,
11
warn,
12
} from '../utils/console';
13
import loading from 'loading-cli';
14
import {
15
current_version,
16
translationStatistic,
17
default_concurrency_limit,
18
default_fallback,
19
fallbacks, cacheEnableds,
20
} from '../utils/micro';
21
import { readProxyFile } from '../core/proxy_file';
22
import { Command, Option, OptionValues } from 'commander';
23
import {
24
promptFrom,
25
promptName,
26
promptTo,
27
promptModuleKey,
28
promptFallback,
29
promptConcurrencyLimit,
30
promptCacheEnabled
31
} from './prompt';
32
import { TranslationConfig, TranslationModule } from '../modules/modules';
33
34
const program = new Command();
35
36
export async function initializeCli() {
37
global.totalTranslation = 0;
38
global.totalTranslated = 0;
39
global.skipInCache = 0;
40
global.proxyIndex = 0;
41
global.proxyList = [];
42
43
program
44
.version(current_version)
45
.addHelpText('beforeAll', messages.cli.welcome)
46
.description(messages.cli.description)
47
.usage(messages.cli.usage)
48
.addOption(
49
new Option(`-m, --module <Module>`, messages.cli.module).choices(
50
translationModuleKeys()
51
)
52
)
53
.addOption(new Option(`-f, --from <Language>`, messages.cli.from))
54
.addOption(new Option(`-t, --to <Languages...>`, messages.cli.to))
55
.addOption(new Option(`-n, --name <string>`, messages.cli.new_file_name))
56
.addOption(
57
new Option(`-fb, --fallback <string>`, messages.cli.fallback).choices(
58
Object.keys(fallbacks)
59
)
60
)
61
.addOption(
62
new Option(
63
`-cl, --concurrencylimit <number>`,
64
messages.cli.concurrency_limit
65
)
66
)
67
.addOption(new Option(`-c, --cache <boolean>`, messages.cli.cache_enabled))
68
.addHelpText(
69
'after',
70
`\n${messages.cli.usage_with_proxy}\n${messages.cli.usage_by_ops}`
71
)
72
.addHelpText('afterAll', supportedLanguagesUrl);
73
74
program.showSuggestionAfterError();
75
program.exitOverride();
76
77
try {
78
program.parse();
79
} catch (err) {
80
process.exit();
81
}
82
83
if (!process.argv.slice(2).length) {
84
program.outputHelp();
85
return;
86
}
87
88
/*
89
If the user adds an option without a value or forgets the value of the option, the value of the next option is applied to the proxy file path.
90
It is actually a problem in commander.js
91
I've come to this temporary solution, which is if the proxy path does not end with .txt display error 'messages.cli.proxy_File_notValid_or_not_empty_options'
92
*/
93
if (program.args[1] !== undefined && !program.args[1].includes('.txt')) {
94
error(messages.cli.proxy_file_notValid_or_not_empty_options);
95
process.exit(1);
96
}
97
translate();
98
}
99
100
async function translate() {
101
const commandArguments = program.args;
102
const commandOptions = program.opts();
103
104
if (commandArguments[1] && typeof commandArguments[1] === 'string') {
105
const file_path = commandArguments[1];
106
await readProxyFile(file_path);
107
}
108
109
// no path condition
110
let objectPath = commandArguments[0];
111
if (objectPath === undefined || objectPath === '') {
112
error(messages.file.no_path);
113
info(`([path] ${messages.cli.paths})`);
114
return;
115
}
116
117
// no file in the path condition
118
let { jsonObj } = await getFileFromPath(objectPath);
119
if (jsonObj === undefined) {
120
error(messages.file.no_file_in_path);
121
return;
122
}
123
124
// get Translation Module
125
const TranslationConfig = await translationConfig(commandOptions);
126
127
// get from language
128
const fromLanguageValue = await fromLanguage(
129
commandOptions,
130
TranslationConfig.TranslationModule
131
);
132
133
// get to languages
134
const toLanguageValues = await toLanguages(
135
commandOptions,
136
TranslationConfig.TranslationModule
137
);
138
139
// get filename
140
const fileNameValue = await fileName(commandOptions);
141
142
// get fallback
143
const fallbackValue = await fallback(commandOptions);
144
TranslationConfig.fallback = fallbackValue;
145
146
// get concurrency limit
147
const concurrencyLimitValue = await concurrencyLimit(commandOptions);
148
TranslationConfig.concurrencyLimit = concurrencyLimitValue;
149
150
const cacheEnabledValue = await cacheEnabled(commandOptions);
151
TranslationConfig.cacheEnabled = cacheEnabledValue;
152
153
// set loading
154
const { load, refreshInterval } = setLoading();
155
156
await fileTranslator(
157
TranslationConfig,
158
objectPath,
159
fromLanguageValue,
160
toLanguageValues,
161
fileNameValue
162
);
163
164
load.succeed(
165
`DONE! ${translationStatistic(
166
global.totalTranslation,
167
global.totalTranslation,
168
global.skipInCache
169
)}`
170
);
171
clearInterval(refreshInterval);
172
173
info(messages.cli.creation_done);
174
}
175
176
// getting input from user
177
async function translationConfig(
178
commandOptions: OptionValues
179
): Promise<TranslationConfig> {
180
let moduleKey = commandOptions.module ?? undefined;
181
let TranslationModule: TranslationModule;
182
183
if (moduleKey && translationModuleKeys().includes(moduleKey)) {
184
// valid module key
185
TranslationModule = getTranslationModuleByKey(moduleKey);
186
} else if (moduleKey) {
187
// invalid module key
188
error(`${messages.cli.module_not_available}`);
189
process.exit(1);
190
} else {
191
// no module key
192
moduleKey = await promptModuleKey();
193
TranslationModule = getTranslationModuleByKey(moduleKey);
194
}
195
196
let translationConfig: TranslationConfig = {
197
moduleKey,
198
TranslationModule,
199
concurrencyLimit: default_concurrency_limit,
200
fallback: default_fallback,
201
cacheEnabled: false,
202
};
203
204
return translationConfig;
205
}
206
207
async function fromLanguage(
208
commandOptions: OptionValues,
209
TranslationModule: TranslationModule
210
): Promise<string> {
211
const fromLanguageInput: any = commandOptions.from ?? undefined;
212
let fromLanguageValue: string;
213
214
const supportedLanguageValues = Object.values(TranslationModule.languages);
215
216
if (!fromLanguageInput) {
217
const fromLanguageInput = await promptFrom(TranslationModule.languages);
218
fromLanguageValue = TranslationModule.languages[fromLanguageInput];
219
} else {
220
if (supportedLanguageValues.includes(fromLanguageInput)) {
221
fromLanguageValue = fromLanguageInput;
222
} else {
223
error(`[${fromLanguageInput}]: ${messages.cli.from_not_available}`);
224
process.exit(1);
225
}
226
}
227
228
return fromLanguageValue;
229
}
230
231
async function toLanguages(
232
commandOptions: OptionValues,
233
TranslationModule: TranslationModule
234
): Promise<string[]> {
235
const toLanguageInputs: any = commandOptions.to ?? undefined;
236
let toLanguageValues: string[];
237
238
const supportedLanguageValues = Object.values(TranslationModule.languages);
239
240
if (!toLanguageInputs) {
241
const toLanguageKeys = await promptTo(TranslationModule.languages);
242
toLanguageValues = toLanguageKeys.map(
243
(key: string) => TranslationModule.languages[key]
244
);
245
246
// second chance to select languages
247
if (toLanguageValues.length === 0 || toLanguageValues === undefined) {
248
warn(messages.cli.no_selected_language);
249
const toLanguageKeys = await promptTo(TranslationModule.languages);
250
toLanguageValues = toLanguageKeys.map(
251
(key: string) => TranslationModule.languages[key]
252
);
253
}
254
} else {
255
toLanguageValues = toLanguageInputs.map((lang: string) => {
256
if (supportedLanguageValues.includes(lang)) {
257
return lang;
258
} else {
259
error(`[${lang}]: ${messages.cli.to_not_available}`);
260
process.exit(1);
261
}
262
});
263
}
264
265
return toLanguageValues;
266
}
267
268
async function fileName(commandOptions: OptionValues): Promise<string> {
269
let newFileName: string = commandOptions.name ?? undefined;
270
271
if (newFileName == undefined) {
272
newFileName = await promptName();
273
return newFileName;
274
}
275
276
return newFileName;
277
}
278
279
async function fallback(commandOptions: OptionValues): Promise<boolean> {
280
let fallbackStr: string = commandOptions.fallback ?? undefined;
281
let fallback: boolean = false;
282
283
if (!fallbackStr) {
284
fallbackStr = await promptFallback();
285
286
if (!Object.keys(fallbacks).includes(fallbackStr)) {
287
error(`[${fallbackStr}]: ${messages.cli.fallback_not_available}`);
288
process.exit(1);
289
}
290
}
291
292
if (fallbackStr === 'yes') {
293
fallback = fallbacks.yes;
294
} else {
295
fallback = fallbacks.no;
296
}
297
298
return fallback;
299
}
300
301
async function cacheEnabled(commandOptions: OptionValues): Promise<boolean> {
302
let cacheEnabledStr: string = commandOptions.cache ?? undefined;
303
let cacheEnabled: boolean = false;
304
305
if (!cacheEnabledStr) {
306
cacheEnabledStr = await promptCacheEnabled();
307
308
if (!Object.keys(cacheEnableds).includes(cacheEnabledStr)) {
309
error(`[${cacheEnabledStr}]: ${messages.cli.cache_enabled}`);
310
process.exit(1);
311
}
312
}
313
314
if (cacheEnabledStr === 'yes') {
315
cacheEnabled = cacheEnableds.yes;
316
} else {
317
cacheEnabled = cacheEnableds.no;
318
}
319
320
return cacheEnabled;
321
}
322
323
async function concurrencyLimit(commandOptions: OptionValues): Promise<number> {
324
let concurrencyLimitInput: number =
325
commandOptions.concurrencylimit ?? undefined;
326
327
if (!concurrencyLimitInput) {
328
concurrencyLimitInput = await promptConcurrencyLimit();
329
}
330
331
let concurrencyLimit: number = Number(concurrencyLimitInput);
332
333
return Number.isNaN(concurrencyLimit)
334
? default_concurrency_limit
335
: Number(concurrencyLimit);
336
}
337
338
function setLoading() {
339
const load = loading({
340
text: `Translating. Please wait. ${translationStatistic(
341
global.totalTranslated,
342
global.totalTranslation,
343
global.skipInCache,
344
)}`,
345
color: 'yellow',
346
interval: 100,
347
stream: process.stdout,
348
frames: ['.', 'o', 'O', '°', 'O', 'o', '.'],
349
}).start();
350
351
const refreshInterval = setInterval(() => {
352
load.text = `Translating. Please wait. ${translationStatistic(
353
global.totalTranslated,
354
global.totalTranslation,
355
global.skipInCache
356
)}`;
357
}, 200);
358
359
return { load, refreshInterval };
360
}
361
362