From 2f6232ecc8e1c042aff5ac9eb20d15b0d17a4ea8 Mon Sep 17 00:00:00 2001 From: ParvinEyvazov Date: Fri, 22 Dec 2023 00:43:54 +0400 Subject: [PATCH 1/3] v.2.0.0 fallback logic & concurrency limit set & structure update --- README.md | 130 +-- package.json | 2 +- src/cli/cli.ts | 270 +++++-- src/cli/prompt.ts | 119 +++ src/core/core.ts | 71 -- src/core/json_file.ts | 97 ++- src/core/json_object.ts | 65 +- src/core/translator.ts | 224 ++--- src/global.d.ts | 3 - src/index.ts | 353 +------- src/modules/functions.ts | 136 ++++ src/modules/helpers.ts | 113 +++ src/modules/languages.ts | 808 +++++++++++++++++++ src/modules/modules.ts | 59 ++ src/test/core.spec.ts | 10 +- src/test/json-file.spec.ts | 83 +- src/test/json-object.spec.ts | 49 +- src/test/util.test.ts | 51 +- src/utils/console.ts | 81 +- src/utils/micro.ts | 24 +- src/utils/prompt.ts | 87 -- test/{util.test.ts => util.test.ts.disabled} | 2 - 22 files changed, 1871 insertions(+), 966 deletions(-) create mode 100644 src/cli/prompt.ts create mode 100644 src/modules/functions.ts create mode 100644 src/modules/helpers.ts create mode 100644 src/modules/languages.ts create mode 100644 src/modules/modules.ts delete mode 100644 src/utils/prompt.ts rename test/{util.test.ts => util.test.ts.disabled} (97%) diff --git a/README.md b/README.md index d338507..5120b9b 100644 --- a/README.md +++ b/README.md @@ -63,25 +63,83 @@ jsontt ## Options -- -V, --version output the version number -- -T, --translator specify translation service (choices: "google", "libre", "argos", "bing") -- -f, --from the translate language from it, e.g., --from en -- -t, --to the Languages to translate into, e.g., --to ar fr zh-CN -- -n, --name the name of the output file (optional), e.g., --name newFileName -- -h, --help display help for command +``` + -V, --version output the version number + -m, --module specify translation module + -f, --from from language + -t, --to to translates + -n, --name optional ↵ | output filename + -fb, --fallback optional ↵ | fallback logic, + try other translation modules on fail | yes, no | default: no + -cl, --concurrencylimit optional ↵ | set max concurrency limit + (higher faster, but easy to get banned) | default: 3 + -h, --help display help for command +``` ## Examples Translate a JSON file using Google Translate: ```bash -jsontt --translator google --from en --to ar fr zh-CN +jsontt --module google --from en --to ar fr zh-CN +``` + +- with output name + +```bash +jsontt --module google --from en --to ar fr zh-CN --name myFiles +``` + +- with fallback logic (try other possible translation modules on fail) + +```bash +jsontt --module google --from en --to ar fr zh-CN --name myFiles -fb yes +``` + +- set concurrency limit (higher faster, but easy to get banned | default: 3) + +```bash +jsontt --module google --from en --to ar fr zh-CN --name myFiles --fallback yes --concurrencylimit 10 ``` -With output name +### other usage examples + +- translate (json/yaml) + +```bash +jsontt file.json +``` ```bash -jsontt --translator google --from en --to ar fr zh-CN --name myFiles +jsontt folder/file.json +``` + +```bash +jsontt "folder\file.json" +``` + +```bash +jsontt "C:\folder1\folder\en.json" +``` + +- with proxy (only Google Translate module) + +```bash +jsontt file.json proxy.txt +``` + +Result will be in the same folder as the original JSON/YAML file. + +
+ +- help + +```bash +jsontt -h +``` + +```bash +jsontt --help ``` # **2. 💥 Package Usage** @@ -339,11 +397,9 @@ Let`s translate our json file into another language and save it into the same fo let path = 'C:/files/en.json'; // PATH OF YOUR JSON FILE (includes file name) -await translator.translateFile( - path, - translator.languages.English, - translator.languages.German -); +await translator.translateFile(path, translator.languages.English, [ + translator.languages.German, +]); ``` ```bash @@ -432,46 +488,6 @@ To ignore words on translation use `{{word}}` OR `{word}` style on your object. } ``` -## **7. CLI commands** - -- translate (json/yaml) - -```bash -jsontt file.json -``` - -```bash -jsontt folder/file.json -``` - -```bash -jsontt "folder\file.json" -``` - -```bash -jsontt "C:\folder1\folder\en.json" -``` - -- with proxy - -```bash -jsontt file.json proxy.txt -``` - -Result will be in the same folder as the original JSON/YAML file. - -
- -- help - -```bash -jsontt -h -``` - -```bash -jsontt --help -``` - ## How to contribute? - Clone it @@ -494,7 +510,7 @@ yarn - Update translation - Go to file `src/core/core.ts` + Go to file `src/modules/functions.ts` - Update JSON operations(deep dive, send translation request) @@ -580,6 +596,10 @@ Make sure your terminal has admin access while running these commands to prevent :heavy_check_mark: YAML file Translate +:heavy_check_mark: Fallback Translation (try new module on fail) + +:heavy_check_mark: Can set concurrency limit manually + - [ ] Libre Translate option (in code package) - [ ] Argos Translate option (in code package) diff --git a/package.json b/package.json index 76945e1..25c52f9 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "1.10.0", + "version": "2.0.0", "license": "MIT", "main": "dist/index.js", "description": "Translate your JSON file or object into another languages with Google Translate API", diff --git a/src/cli/cli.ts b/src/cli/cli.ts index 57b46a5..6ebedd0 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -1,4 +1,7 @@ -import { getLanguages, Sources, TRANSLATE_POSTFIX, translatorsNames } from '..'; +import { + translationModuleKeys, + getTranslationModuleByKey, +} from '../modules/helpers'; import { fileTranslator, getFileFromPath } from '../core/json_file'; import { error, @@ -9,19 +12,23 @@ import { } from '../utils/console'; import loading from 'loading-cli'; import { - capitalize, current_version, - getCodeFromLanguage, translationStatistic, + default_concurrency_limit, + default_fallback, + fallbacks, } from '../utils/micro'; import { readProxyFile } from '../core/proxy_file'; -import { Command, Option } from 'commander'; +import { Command, Option, OptionValues } from 'commander'; import { promptFrom, promptName, promptTo, - promptTranslator, -} from '../utils/prompt'; + promptModuleKey, + promptFallback, + promptConcurrencyLimit, +} from './prompt'; +import { TranslationConfig, TranslationModule } from '../modules/modules'; const program = new Command(); @@ -37,17 +44,27 @@ export async function initializeCli() { .description(messages.cli.description) .usage(messages.cli.usage) .addOption( - new Option( - `-T, --translator `, - messages.cli.translator - ).choices(translatorsNames) + new Option(`-m, --module `, messages.cli.module).choices( + translationModuleKeys() + ) ) .addOption(new Option(`-f, --from `, messages.cli.from)) .addOption(new Option(`-t, --to `, messages.cli.to)) - .addOption(new Option(`-n, --name `, messages.cli.newFileName)) + .addOption(new Option(`-n, --name `, messages.cli.new_file_name)) + .addOption( + new Option(`-fb, --fallback `, messages.cli.fallback).choices( + Object.keys(fallbacks) + ) + ) + .addOption( + new Option( + `-cl, --concurrencylimit `, + messages.cli.concurrency_limit + ) + ) .addHelpText( 'after', - `\n${messages.cli.usageWithProxy}\n${messages.cli.usageByOps}` + `\n${messages.cli.usage_with_proxy}\n${messages.cli.usage_by_ops}` ) .addHelpText('afterAll', supportedLanguagesUrl); @@ -71,7 +88,7 @@ export async function initializeCli() { 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' */ if (program.args[1] !== undefined && !program.args[1].includes('.txt')) { - error(messages.cli.proxy_File_notValid_or_not_empty_options); + error(messages.cli.proxy_file_notValid_or_not_empty_options); process.exit(1); } translate(); @@ -80,6 +97,7 @@ export async function initializeCli() { async function translate() { const commandArguments = program.args; const commandOptions = program.opts(); + if (commandArguments[1] && typeof commandArguments[1] === 'string') { const file_path = commandArguments[1]; await readProxyFile(file_path); @@ -94,72 +112,140 @@ async function translate() { } // no file in the path condition - let { json_obj } = await getFileFromPath(objectPath); - if (json_obj === undefined) { + let { jsonObj } = await getFileFromPath(objectPath); + if (jsonObj === undefined) { error(messages.file.no_file_in_path); return; } - let translatorInput = commandOptions.translator - ? commandOptions.translator - : undefined; - if (translatorInput && translatorInput !== '') { - if (translatorsNames.includes(translatorInput)) { - let translator = translatorsNames.find((el: string) => - el.includes(translatorInput as string) - ); - // Restore source name after splitting it for "translatorsNames" variable - global.source = [ - capitalize(translator as string), - TRANSLATE_POSTFIX, - ].join('') as Sources; - } else { - error(`${messages.cli.translator_not_available}`); - process.exit(1); - } + // get Translation Module + const TranslationConfig = await translationConfig(commandOptions); + + // get from language + const fromLanguageValue = await fromLanguage( + commandOptions, + TranslationConfig.TranslationModule + ); + + // get to languages + const toLanguageValues = await toLanguages( + commandOptions, + TranslationConfig.TranslationModule + ); + + // get filename + const fileNameValue = await fileName(commandOptions); + + // get fallback + const fallbackValue = await fallback(commandOptions); + TranslationConfig.fallback = fallbackValue; + + // get concurrency limit + const concurrencyLimitValue = await concurrencyLimit(commandOptions); + TranslationConfig.concurrencyLimit = concurrencyLimitValue; + + // set loading + const { load, refreshInterval } = setLoading(); + + await fileTranslator( + TranslationConfig, + objectPath, + fromLanguageValue, + toLanguageValues, + fileNameValue + ); + + load.succeed( + `DONE! ${translationStatistic( + global.totalTranslation, + global.totalTranslation + )}` + ); + clearInterval(refreshInterval); + + info(messages.cli.creation_done); +} + +// getting input from user +async function translationConfig( + commandOptions: OptionValues +): Promise { + let moduleKey = commandOptions.module ?? undefined; + let TranslationModule: TranslationModule; + + if (moduleKey && translationModuleKeys().includes(moduleKey)) { + // valid module key + TranslationModule = getTranslationModuleByKey(moduleKey); + } else if (moduleKey) { + // invalid module key + error(`${messages.cli.module_not_available}`); + process.exit(1); } else { - await promptTranslator(); + // no module key + moduleKey = await promptModuleKey(); + TranslationModule = getTranslationModuleByKey(moduleKey); } - const sourceLanguageInput: any = commandOptions.from - ? commandOptions.from - : undefined; - const targetLanguageInput: any = commandOptions.to - ? commandOptions.to - : undefined; + let translationConfig: TranslationConfig = { + moduleKey, + TranslationModule, + concurrencyLimit: default_concurrency_limit, + fallback: default_fallback, + }; + + return translationConfig; +} - let sourceLanguageISO: string; - let targetLanguageISOs: string[]; - let newFileName: string = commandOptions.name - ? commandOptions.name - : undefined; +async function fromLanguage( + commandOptions: OptionValues, + TranslationModule: TranslationModule +): Promise { + const fromLanguageInput: any = commandOptions.from ?? undefined; + let fromLanguageValue: string; - const listIOS = Object.values(getLanguages() as any); // get list after assigning global.source + const supportedLanguageValues = Object.values(TranslationModule.languages); - if (!sourceLanguageInput) { - const { from } = await promptFrom(); - sourceLanguageISO = getCodeFromLanguage(from); + if (!fromLanguageInput) { + const fromLanguageInput = await promptFrom(TranslationModule.languages); + fromLanguageValue = TranslationModule.languages[fromLanguageInput]; } else { - if (listIOS.includes(sourceLanguageInput)) { - sourceLanguageISO = sourceLanguageInput; + if (supportedLanguageValues.includes(fromLanguageInput)) { + fromLanguageValue = fromLanguageInput; } else { - error(`[${sourceLanguageInput}]: ${messages.cli.from_not_available}`); + error(`[${fromLanguageInput}]: ${messages.cli.from_not_available}`); process.exit(1); } } - if (!targetLanguageInput) { - const { to } = await promptTo(); - targetLanguageISOs = to.map((lang: string) => getCodeFromLanguage(lang)); + return fromLanguageValue; +} + +async function toLanguages( + commandOptions: OptionValues, + TranslationModule: TranslationModule +): Promise { + const toLanguageInputs: any = commandOptions.to ?? undefined; + let toLanguageValues: string[]; + + const supportedLanguageValues = Object.values(TranslationModule.languages); - if (targetLanguageISOs.length === 0 || targetLanguageISOs === undefined) { + if (!toLanguageInputs) { + const toLanguageKeys = await promptTo(TranslationModule.languages); + toLanguageValues = toLanguageKeys.map( + (key: string) => TranslationModule.languages[key] + ); + + // second chance to select languages + if (toLanguageValues.length === 0 || toLanguageValues === undefined) { warn(messages.cli.no_selected_language); - const { to } = await promptTo(); - targetLanguageISOs = to.map((lang: string) => getCodeFromLanguage(lang)); + const toLanguageKeys = await promptTo(TranslationModule.languages); + toLanguageValues = toLanguageKeys.map( + (key: string) => TranslationModule.languages[key] + ); } } else { - targetLanguageISOs = targetLanguageInput.map((lang: string) => { - if (listIOS.includes(lang)) { + toLanguageValues = toLanguageInputs.map((lang: string) => { + if (supportedLanguageValues.includes(lang)) { return lang; } else { error(`[${lang}]: ${messages.cli.to_not_available}`); @@ -168,13 +254,58 @@ async function translate() { }); } + return toLanguageValues; +} + +async function fileName(commandOptions: OptionValues): Promise { + let newFileName: string = commandOptions.name ?? undefined; + if (!newFileName) { - const { name } = await promptName(); + const name = await promptName(); newFileName = name; + } + + return newFileName; +} + +async function fallback(commandOptions: OptionValues): Promise { + let fallbackStr: string = commandOptions.fallback ?? undefined; + let fallback: boolean = false; + + if (!fallbackStr) { + fallbackStr = await promptFallback(); + + if (!Object.keys(fallbacks).includes(fallbackStr)) { + error(`[${fallbackStr}]: ${messages.cli.fallback_not_available}`); + process.exit(1); + } + } + + if (fallbackStr === 'yes') { + fallback = fallbacks.yes; } else { - newFileName = newFileName; + fallback = fallbacks.no; } + return fallback; +} + +async function concurrencyLimit(commandOptions: OptionValues): Promise { + let concurrencyLimitInput: number = + commandOptions.concurrencylimit ?? undefined; + + if (!concurrencyLimitInput) { + concurrencyLimitInput = await promptConcurrencyLimit(); + } + + let concurrencyLimit: number = Number(concurrencyLimitInput); + + return Number.isNaN(concurrencyLimit) + ? default_concurrency_limit + : Number(concurrencyLimit); +} + +function setLoading() { const load = loading({ text: `Translating. Please wait. ${translationStatistic( global.totalTranslated, @@ -193,20 +324,5 @@ async function translate() { )}`; }, 200); - await fileTranslator( - objectPath, - sourceLanguageISO, - targetLanguageISOs, - newFileName - ); - - load.succeed( - `DONE! ${translationStatistic( - global.totalTranslation, - global.totalTranslation - )}` - ); - clearInterval(refreshInterval); - - info(messages.cli.creation_done); + return { load, refreshInterval }; } diff --git a/src/cli/prompt.ts b/src/cli/prompt.ts new file mode 100644 index 0000000..53f1eb7 --- /dev/null +++ b/src/cli/prompt.ts @@ -0,0 +1,119 @@ +import { + translationModuleKeys, + getTranslationModuleByKey, +} from '../modules/helpers'; +import { messages } from '../utils/console'; +import { default_concurrency_limit } from '../utils/micro'; +var inquirer = require('inquirer'); + +export async function promptModuleKey(): Promise { + const module_key_choices = translationModuleKeys().map(key => { + return { + name: getTranslationModuleByKey(key).altName, + value: key, + short: key, + }; + }); + + let selectedModuleKey = ''; + + await inquirer + .prompt([ + { + type: 'list', + name: 'moduleKey', + message: messages.cli.select_module_message, + pageSize: 20, + choices: [...module_key_choices, new inquirer.Separator()], + }, + ]) + .then((answers: any) => { + selectedModuleKey = answers.moduleKey; + }); + + return selectedModuleKey; +} + +export async function promptFrom(languages: Record) { + const fromLanguageKeys = Object.keys(languages); + + const answers = await inquirer.prompt([ + { + type: 'list', + name: 'from', + message: messages.cli.from_message, + pageSize: 20, + choices: [...fromLanguageKeys, new inquirer.Separator()], + }, + ]); + + return answers.from; +} + +export async function promptTo( + languages: Record, + default_languages?: string[] +) { + let toLanguageKeys = Object.keys(languages); + toLanguageKeys = toLanguageKeys.filter(key => key !== `Automatic`); + + const answers = await inquirer.prompt([ + { + type: 'checkbox', + name: 'to', + pageSize: 20, + message: messages.cli.to_message, + choices: toLanguageKeys, + default: default_languages ? default_languages : [], + }, + ]); + + return answers.to; +} + +export async function promptName() { + const answers = await inquirer.prompt([ + { + type: 'string', + name: 'name', + message: messages.cli.new_file_name_message, + // default: default_name ? default_name : undefined, + }, + ]); + + return answers.name; +} + +export async function promptFallback() { + const answers = await inquirer.prompt([ + { + type: 'string', + name: 'fallback', + message: messages.cli.fallback_message, + default: '', + }, + ]); + + if (answers.fallback === '') { + return 'no'; + } + + return answers.fallback; +} + +export async function promptConcurrencyLimit() { + const answers = await inquirer.prompt([ + { + type: 'number', + name: 'concurrencylimit', + message: messages.cli.concurrency_limit_message, + default: '', + }, + ]); + + if (answers.concurrencylimit === '') { + return default_concurrency_limit; + } + + return answers.concurrencylimit; +} diff --git a/src/core/core.ts b/src/core/core.ts index 7ac983a..83daff2 100644 --- a/src/core/core.ts +++ b/src/core/core.ts @@ -2,7 +2,6 @@ import * as fs from 'fs/promises'; import * as YAML from 'yaml'; import { matchYamlExt } from '../utils/yaml'; import { error, messages } from '../utils/console'; -import { default_value, translation_value_limit } from '../utils/micro'; export async function getFile(objectPath: string) { let json_file: any = undefined; @@ -49,73 +48,3 @@ export async function saveFilePublic(path: string, data: any) { error(messages.file.cannot_save_file); }); } - -export function safeValueTransition(value: string) { - const value_safety: ValueSafety = valueIsSafe(value); - - if (value_safety.is_safe === true) { - return value; - } - - switch (value_safety.type) { - case nonSafeTypes.null: - case nonSafeTypes.undefined: - case nonSafeTypes.empty: - value = default_value; - break; - case nonSafeTypes.long: - value = value.substring(0, translation_value_limit); - break; - } - - return value; -} - -function valueIsSafe(value: string): ValueSafety { - let result: ValueSafety = { - is_safe: true, - type: undefined, - }; - - if (value === undefined) { - result.is_safe = false; - result['type'] = nonSafeTypes.undefined; - - return result; - } - - if (value === null) { - result.is_safe = false; - result['type'] = nonSafeTypes.null; - - return result; - } - - if (value.length >= translation_value_limit) { - result.is_safe = false; - result['type'] = nonSafeTypes.long; - - return result; - } - - if (value === '') { - result.is_safe = false; - result['type'] = nonSafeTypes.empty; - - return result; - } - - return result; -} - -interface ValueSafety { - is_safe: boolean; - type: nonSafeTypes | undefined; -} - -enum nonSafeTypes { - 'long', - 'undefined', - 'null', - 'empty', -} diff --git a/src/core/json_file.ts b/src/core/json_file.ts index 64286bc..44a6bdf 100644 --- a/src/core/json_file.ts +++ b/src/core/json_file.ts @@ -1,86 +1,79 @@ -import { LanguageCode, LanguageCodes, translatedObject } from '..'; +import { translatedObject } from '..'; import { error, messages, success } from '../utils/console'; -import { getLanguageFromCode } from '../utils/micro'; import { getFile, getRootFolder, saveFilePublic } from './core'; import { objectTranslator } from './json_object'; import { matchYamlExt } from '../utils/yaml'; +import { TranslationConfig } from '../modules/modules'; +import { getLanguageKeyFromValue } from '../modules/helpers'; export async function fileTranslator( - objectPath: string, - from: LanguageCode, - to: LanguageCode | LanguageCodes, + TranslationConfig: TranslationConfig, + tempObjectPath: string, + from: string, + to: string[], newFileName: string ) { - let file_from_path = await getFileFromPath(objectPath); - - let { json_obj } = file_from_path; - objectPath = file_from_path.objectPath; - - if (json_obj === undefined) { + // step: get file details -> data, path + let { jsonObj, objectPath } = await getFileFromPath(tempObjectPath); + if (jsonObj === undefined) { error(messages.file.no_file_in_path); return; } - json_obj = { data: JSON.parse(json_obj) }; - - let new_json_obj = await objectTranslator(json_obj, from, to); + jsonObj = { data: JSON.parse(jsonObj) }; - if (new_json_obj === undefined) { + // step: translate object + let newJsonObj = await objectTranslator(TranslationConfig, jsonObj, from, to); + if (newJsonObj === undefined) { error(messages.file.cannot_translate); return; } - let latest_path = objectPath.replace(/\\/g, '/'); - let root_folder = getRootFolder(latest_path); - - // Check if source file has YAML extension and return the extension ("yml" or "yaml"). - const source_file_match_yaml_ext = matchYamlExt(latest_path); - // When source file has "yml" or "yaml" extension, use the same in output file path. - // Otherwise, default "json" extension used. - const file_ext = source_file_match_yaml_ext || 'json'; - - if (Array.isArray(new_json_obj) === true && Array.isArray(to) === true) { - // multiple file saving - (new_json_obj as Array).forEach( - async (element, index) => { - const current_json_obj = element.data; - - let file_name = newFileName - ? `/${newFileName}.${to[index]}.${file_ext}` - : `/${to[index]}.${file_ext}`; + // step: save translated data + let latestPath = objectPath.replace(/\\/g, '/'); + const fileExt = getFileExt(latestPath); - await saveFilePublic(root_folder + file_name, current_json_obj); + let rootFolder = getRootFolder(latestPath); - success( - `For ${getLanguageFromCode(to[index])} --> ${file_name} created.` - ); - } - ); - } else { - new_json_obj = (new_json_obj as translatedObject).data; + (newJsonObj as Array).forEach(async (element, index) => { + const currentJsonObj = element.data; - let file_name = newFileName - ? `/${newFileName}.${to}.${file_ext}` - : `/${to}.${file_ext}`; + let fileName = newFileName + ? `/${newFileName}.${to[index]}.${fileExt}` + : `/${to[index]}.${fileExt}`; - await saveFilePublic(root_folder + file_name, new_json_obj); + await saveFilePublic(rootFolder + fileName, currentJsonObj); success( - `For ${getLanguageFromCode(to as string)} --> ${file_name} created.` + `For ${getLanguageKeyFromValue( + to[index], + TranslationConfig.TranslationModule.languages + )} --> ${fileName} created.` ); - } + }); } export async function getFileFromPath( objectPath: string -): Promise<{ json_obj: any; objectPath: string }> { - let json_obj: any = await getFile(objectPath); +): Promise<{ jsonObj: any; objectPath: string }> { + let jsonObj: any = await getFile(objectPath); - if (json_obj === undefined) { + if (jsonObj === undefined) { objectPath = __dirname + '\\' + objectPath; - json_obj = await getFile(objectPath); + jsonObj = await getFile(objectPath); } - return { json_obj, objectPath }; + return { jsonObj, objectPath }; +} + +function getFileExt(latestPath: string): string { + // Check if source file has YAML extension and return the extension ("yml" or "yaml"). + const sourceFileMatchYamlExt = matchYamlExt(latestPath); + + // When source file has "yml" or "yaml" extension, use the same in output file path. + // Otherwise, default "json" extension used. + const fileExt = sourceFileMatchYamlExt || 'json'; + + return fileExt; } diff --git a/src/core/json_object.ts b/src/core/json_object.ts index d2c1686..8ed7e26 100644 --- a/src/core/json_object.ts +++ b/src/core/json_object.ts @@ -1,40 +1,38 @@ -import { LanguageCode, LanguageCodes, translatedObject } from '..'; +import { translatedObject } from '..'; import { plaintranslate } from './translator'; import { TaskQueue } from 'cwait'; import { Promise as bluebirdPromise } from 'bluebird'; -const MAX_SIMULTANEOUS_REQUEST = 3; +import { TranslationConfig } from '../modules/modules'; +import { default_concurrency_limit } from '../utils/micro'; -var queue = new TaskQueue(bluebirdPromise, MAX_SIMULTANEOUS_REQUEST); +var queue = new TaskQueue(bluebirdPromise, default_concurrency_limit); export async function objectTranslator( + TranslationConfig: TranslationConfig, object: translatedObject, - from: LanguageCode, - to: LanguageCode | LanguageCodes -): Promise { - if (object && from && to) { - // need to translate to more than 1 languages - if (typeof to === 'object') { - let general_object: translatedObject[] | null[] = []; + from: string, + to: string[] +): Promise { + queue.concurrency = TranslationConfig.concurrencyLimit; - await Promise.all( - Object.keys(to as LanguageCodes).map(async function(index) { - const index_as_num = Number(index); - const copy_object = JSON.parse(JSON.stringify(object)); + if (object && from && to) { + let generalObject: translatedObject[] | null[] = []; - general_object[index_as_num] = await deepDiver( - copy_object, - from, - to[index_as_num] - ); - }) - ); + await Promise.all( + Object.keys(to).map(async function(index) { + const indexAsNum = Number(index); + const copyObject = JSON.parse(JSON.stringify(object)); - return general_object as translatedObject[]; - } else { - await deepDiver(object, from, to); + generalObject[indexAsNum] = await deepDiver( + TranslationConfig, + copyObject, + from, + to[indexAsNum] + ); + }) + ); - return object as translatedObject; - } + return generalObject as translatedObject[]; } else { throw new Error( `Undefined values detected. Available ones: object: ${!!object}, from: ${!!from}, to: ${!!to}` @@ -43,9 +41,10 @@ export async function objectTranslator( } export async function deepDiver( + TranslationConfig: TranslationConfig, object: translatedObject, - from: LanguageCode, - to: LanguageCode + from: string, + to: string ): Promise { var has = Object.prototype.hasOwnProperty.bind(object); @@ -58,13 +57,19 @@ export async function deepDiver( if (has(k)) { switch (typeof object[k]) { case 'object': - await deepDiver(object[k], from, to); + await deepDiver(TranslationConfig, object[k], from, to); break; case 'string': global.totalTranslation = global.totalTranslation + 1; return queue.add(async () => { - return await plaintranslate(object[k], from, to) + return await plaintranslate( + TranslationConfig, + object[k], + from, + to, + [] + ) .then(data => { object[k] = data; }) diff --git a/src/core/translator.ts b/src/core/translator.ts index 5f005e0..dfeb8e3 100644 --- a/src/core/translator.ts +++ b/src/core/translator.ts @@ -1,35 +1,37 @@ -import { translate } from '@vitalets/google-translate-api'; -import * as bingTranslator from 'bing-translate-api'; -import createHttpProxyAgent from 'http-proxy-agent'; -import { LanguageCode, Sources } from '..'; +import { + getLanguageVariant, + getTranslationModuleByKey, + translationModuleKeys, +} from '../modules/helpers'; +import { TranslationConfig } from '../modules/modules'; import { warn } from '../utils/console'; import { default_value } from '../utils/micro'; -import axios from 'axios'; import * as ignorer from './ignorer'; -import { safeValueTransition } from './core'; export async function plaintranslate( + TranslationConfig: TranslationConfig, str: string, - from: LanguageCode, - to: LanguageCode + from: string, + to: string, + skipModuleKeys: string[] ): Promise { - // STEP: map the subset of string need to be ignored + // step: map the subset of string need to be ignored let { word: ignored_str, double_brackets_map, single_brackets_map, } = ignorer.map(str); - // STEP: translate in try-catch to keep continuity + // step: translate in try-catch to keep continuity try { - // STEP: translate with proper source - let translatedStr = await translateSourceFunction(global.source)( + // step: translate with proper source + let translatedStr = await TranslationConfig.TranslationModule.translate( ignored_str, from, to ); - // STEP: put ignored values back + // step: put ignored values back translatedStr = ignorer.unMap( translatedStr, double_brackets_map, @@ -40,155 +42,87 @@ export async function plaintranslate( return translatedStr; } catch (e) { - // error case -> return - warn( - `\nerror while translating \n\t"${str}" \nassigned "--" instead of exit from cli.` + // error case + const clonedTranslationConfig = Object.assign({}, TranslationConfig); // cloning to escape ref value + const clonedSkipModuleKeys = Object.assign([], skipModuleKeys); // cloning to escape ref value + + clonedSkipModuleKeys.push(clonedTranslationConfig.moduleKey); + + const { newModuleKey, newFrom, newTo } = newTranslationModule( + clonedTranslationConfig.moduleKey, + clonedSkipModuleKeys, + from, + to ); - global.totalTranslated = global.totalTranslated + 1; - return default_value; - } -} + let stop: boolean = + !clonedTranslationConfig.fallback || newModuleKey === undefined; -function translateSourceFunction(source: string) { - switch (source) { - case Sources.LibreTranslate: - return translateWithLibre; - case Sources.ArgosTranslate: - return translateWithArgos; - case Sources.BingTranslate: - return translateWithBing; - default: - return translateWithGoogle; - } -} + if (stop) { + warn( + `\nerror while translating "${str}" using ${clonedTranslationConfig.moduleKey}. Assigned "--" instead of exit from cli.` + ); -async function translateWithLibre( - str: string, - from: LanguageCode, - to: LanguageCode -): Promise { - let body = { - q: safeValueTransition(str), - source: from, - target: to, - format: 'text', - api_key: '', - secret: '2NEKGMB', - }; + global.totalTranslated = global.totalTranslated + 1; - const { data } = await axios.post( - 'https://libretranslate.com/translate', - body, - { - headers: { - Origin: 'https://libretranslate.com', - }, + return default_value; } - ); - return data?.translatedText ? data?.translatedText : default_value; + warn( + `\nerror while translating "${str}" using ${clonedTranslationConfig.moduleKey}. Tried: ${clonedSkipModuleKeys}. Trying ${newModuleKey}.` + ); + + // update the TranslationModule for next try + clonedTranslationConfig.TranslationModule = getTranslationModuleByKey( + newModuleKey as string + ); + clonedTranslationConfig.moduleKey = newModuleKey as string; + + return plaintranslate( + clonedTranslationConfig, + str, + newFrom as string, + newTo as string, + clonedSkipModuleKeys + ); + } } -async function translateWithArgos( - str: string, - from: LanguageCode, - to: LanguageCode -): Promise { - let body = { - q: safeValueTransition(str), - source: from, - target: to, +function newTranslationModule( + sourceModuleKeys: string, + skipModuleKeys: string[], + from: string, + to: string +) { + const default_data = { + newModuleKey: undefined, + newFrom: undefined, + newTo: undefined, }; - const { data } = await axios.post( - 'https://translate.argosopentech.com/translate', - body, - { - headers: { - Origin: 'https://translate.argosopentech.com', - Referer: 'https://translate.argosopentech.com', - }, - } - ); - - return data?.translatedText ? data?.translatedText : default_value; -} + const allModuleKeys: string[] = translationModuleKeys(); -async function translateWithBing( - str: string, - from: LanguageCode, - to: LanguageCode -): Promise { - const { translation } = await bingTranslator.translate( - safeValueTransition(str), - from, - to, - false + const result: string[] = allModuleKeys.filter( + item => !skipModuleKeys.includes(item) ); - return translation; -} - -async function translateWithGoogle( - str: string, - from: LanguageCode, - to: LanguageCode -): Promise { - // STEP: if proxy list provided - if ( - global.proxyList && - global.proxyList.length > 0 && - global.proxyIndex !== -1 - ) { - let proxy = global.proxyList[global.proxyIndex]; - - // STEP: new proxy exist - if (proxy) { - let agent = createHttpProxyAgent(`http://${proxy}`); - - let translatedStr = await translateWithGoogleByProxySupport( - str, - from, - to, - { - agent, - timeout: 4000, - } - ); - - return translatedStr; - } else { - warn('No new proxy exists, continuing without proxy'); - global.proxyIndex = -1; + let newModuleKey = result[0]; - let translatedStr = await translateWithGoogleByProxySupport( - str, - from, - to - ); + if (!newModuleKey) { + return default_data; // default + } - return translatedStr; - } - } else { - // STEP: translate without proxy - let translatedStr = await translateWithGoogleByProxySupport(str, from, to); + let newFrom = getLanguageVariant(sourceModuleKeys, from, newModuleKey); + let newTo = getLanguageVariant(sourceModuleKeys, to, newModuleKey); - return translatedStr; + if (!newFrom || !newTo) { + return default_data; // default } -} -async function translateWithGoogleByProxySupport( - str: string, - from: LanguageCode, - to: LanguageCode, - options?: { agent: any; timeout: number } -) { - const { text } = await translate(safeValueTransition(str), { - from: from, - to: to, - fetchOptions: { agent: options !== undefined ? options.agent : undefined }, - }); - - return text; + // has valid newModuleKey & from & to + return { + newModuleKey, + newFrom, + newTo, + }; } diff --git a/src/global.d.ts b/src/global.d.ts index 23cea22..4792585 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,5 +1,3 @@ -import { Sources } from '.'; - export {}; declare global { @@ -7,5 +5,4 @@ declare global { var totalTranslated: number; var proxyIndex: number; var proxyList: string[]; - var source: Sources; } diff --git a/src/index.ts b/src/index.ts index e15372d..5db5862 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,346 +2,59 @@ import { initializeCli } from './cli/cli'; import { plaintranslate } from './core/translator'; import { fileTranslator } from './core/json_file'; import { objectTranslator } from './core/json_object'; +import { TranslationConfig, TranslationModules } from './modules/modules'; +import { default_concurrency_limit, default_fallback } from './utils/micro'; -export async function translateWord( - word: string, - from: LanguageCode, - to: LanguageCode -) { - return await plaintranslate(word, from, to); -} +// TODO: fix to get from user +export async function translateWord(word: string, from: string, to: string) { + let config: TranslationConfig = { + moduleKey: 'google', + TranslationModule: TranslationModules['google'], + concurrencyLimit: default_concurrency_limit, + fallback: default_fallback, + }; + return await plaintranslate(config, word, from, to, []); +} +// TODO: fix to get from user export async function translateObject( object: translatedObject, - from: LanguageCode, - to: LanguageCode | LanguageCodes + from: string, + to: string[] ): Promise { let hard_copy = JSON.parse(JSON.stringify(object)); - return objectTranslator(hard_copy, from, to); + let config: TranslationConfig = { + moduleKey: 'google', + TranslationModule: TranslationModules['google'], + concurrencyLimit: default_concurrency_limit, + fallback: default_fallback, + }; + + return objectTranslator(config, hard_copy, from, to); } export async function translateFile( objectPath: string, - from: LanguageCode, - to: LanguageCode | LanguageCodes, + from: string, + to: string[], newFileName: string ) { - return fileTranslator(objectPath, from, to, newFileName); + let config: TranslationConfig = { + moduleKey: 'google', + TranslationModule: TranslationModules['google'], + concurrencyLimit: default_concurrency_limit, + fallback: default_fallback, + }; + + return fileTranslator(config, objectPath, from, to, newFileName); } export async function runCli() { initializeCli(); } -export enum Sources { - GoogleTranslate = 'GoogleTranslate', - LibreTranslate = 'LibreTranslate', - ArgosTranslate = 'ArgosTranslate', - BingTranslate = 'BingTranslate', -} - -// default -global.source = Sources.GoogleTranslate; - -// Use postfix just for compatability, when Sources enum values used somewhere else. -export const TRANSLATE_POSTFIX = 'Translate'; -export const translatorsNames = Object.values(Sources).map(s => - s.split(TRANSLATE_POSTFIX)[0].toLowerCase() -); // TYPES export interface translatedObject { [key: string]: any; } - -export type LanguageCode = string; -export type LanguageCodes = LanguageCode[]; -export function getLanguages() { - if (global.source === Sources.LibreTranslate) { - return LibreTranslateLanguages; - } else if (global.source === Sources.ArgosTranslate) { - return ArgosTranslateLanguages; - } else if (global.source === Sources.BingTranslate) { - return BingTranslateLanguages; - } - - return GoogleTranslateLanguages; -} - -enum LibreTranslateLanguages { - Automatic = 'auto', - English = 'en', - Arabic = 'ar', - Azerbaijani = 'az', - Chinese = 'zh', - Czech = 'cs', - Danish = 'da', - Dutch = 'nl', - Esperanto = 'eo', - Finnish = 'fi', - French = 'fr', - German = 'de', - Greek = 'el', - Hebrew = 'iw', - Hindi = 'hi', - Hungarian = 'hu', - Indonesian = 'id', - Irish = 'ga', - Italian = 'it', - Japanese = 'ja', - Korean = 'ko', - Persian = 'fa', - Polish = 'pl', - Portuguese = 'pt', - Russian = 'ru', - Slovak = 'sk', - Spanish = 'es', - Swedish = 'sv', - Turkish = 'tr', - Ukrainian = 'uk', -} - -enum ArgosTranslateLanguages { - Automatic = 'auto', - English = 'en', - Arabic = 'ar', - Chinese = 'zh', - French = 'fr', - German = 'de', - Hindi = 'hi', - Indonesian = 'id', - Irish = 'ga', - Italian = 'it', - Japanese = 'ja', - Korean = 'ko', - Polish = 'pl', - Portuguese = 'pt', - Russian = 'ru', - Spanish = 'es', - Turkish = 'tr', - Vietnamese = 'vi', -} - -enum GoogleTranslateLanguages { - Automatic = 'auto', - Afrikaans = 'af', - Albanian = 'sq', - Amharic = 'am', - Arabic = 'ar', - Armenian = 'hy', - Azerbaijani = 'az', - Basque = 'eu', - Belarusian = 'be', - Bengali = 'bn', - Bosnian = 'bs', - Bulgarian = 'bg', - Catalan = 'ca', - Cebuano = 'ceb', - Chichewa = 'ny', - Chinese_Simplified = 'zh-CN', - Chinese_Traditional = 'zh-TW', - Corsican = 'co', - Croatian = 'hr', - Czech = 'cs', - Danish = 'da', - Dutch = 'nl', - English = 'en', - Esperanto = 'eo', - Estonian = 'et', - Filipino = 'tl', - Finnish = 'fi', - French = 'fr', - Frisian = 'fy', - Galician = 'gl', - Georgian = 'ka', - German = 'de', - Greek = 'el', - Gujarati = 'gu', - Haitian_Creole = 'ht', - Hausa = 'ha', - Hawaiian = 'haw', - Hebrew = 'iw', - Hindi = 'hi', - Hmong = 'hmn', - Hungarian = 'hu', - Icelandic = 'is', - Igbo = 'ig', - Indonesian = 'id', - Irish = 'ga', - Italian = 'it', - Japanese = 'ja', - Javanese = 'jw', - Kannada = 'kn', - Kazakh = 'kk', - Khmer = 'km', - Korean = 'ko', - Kurdish_Kurmanji = 'ku', - Kyrgyz = 'ky', - Lao = 'lo', - Latin = 'la', - Latvian = 'lv', - Lithuanian = 'lt', - Luxembourgish = 'lb', - Macedonian = 'mk', - Malagasy = 'mg', - Malay = 'ms', - Malayalam = 'ml', - Maltese = 'mt', - Maori = 'mi', - Marathi = 'mr', - Mongolian = 'mn', - Myanmar_Burmese = 'my', - Nepali = 'ne', - Norwegian = 'no', - Pashto = 'ps', - Persian = 'fa', - Polish = 'pl', - Portuguese = 'pt', - Punjabi = 'pa', - Romanian = 'ro', - Russian = 'ru', - Samoan = 'sm', - Scots_Gaelic = 'gd', - Serbian = 'sr', - Sesotho = 'st', - Shona = 'sn', - Sindhi = 'sd', - Sinhala = 'si', - Slovak = 'sk', - Slovenian = 'sl', - Somali = 'so', - Spanish = 'es', - Sundanese = 'su', - Swahili = 'sw', - Swedish = 'sv', - Tajik = 'tg', - Tamil = 'ta', - Telugu = 'te', - Thai = 'th', - Turkish = 'tr', - Ukrainian = 'uk', - Urdu = 'ur', - Uzbek = 'uz', - Vietnamese = 'vi', - Welsh = 'cy', - Xhosa = 'xh', - Yiddish = 'yi', - Yoruba = 'yo', - Zulu = 'zu', -} - -enum BingTranslateLanguages { - Automatic = 'auto-detect', - Afrikaans = 'af', - Albanian = 'sq', - Amharic = 'am', - Arabic = 'ar', - Armenian = 'hy', - Assamese = 'as', - Azerbaijani = 'az', - Bangla = 'bn', - Bashkir = 'ba', - Basque = 'eu', - Bosnian = 'bs', - Bulgarian = 'bg', - Cantonese_Traditional = 'yue', - Catalan = 'ca', - Chinese_Literary = 'lzh', - Chinese_Simplified = 'zh-Hans', - Chinese_Traditional = 'zh-Hant', - Croatian = 'hr', - Czech = 'cs', - Danish = 'da', - Dari = 'prs', - Divehi = 'dv', - Dutch = 'nl', - English = 'en', - Estonian = 'et', - Faroese = 'fo', - Fijian = 'fj', - Filipino = 'fil', - Finnish = 'fi', - French = 'fr', - French_Canada = 'fr-CA', - Galician = 'gl', - Georgian = 'ka', - German = 'de', - Greek = 'el', - Gujarati = 'gu', - Haitian_Creole = 'ht', - Hebrew = 'he', - Hindi = 'hi', - Hmong_Daw = 'mww', - Hungarian = 'hu', - Icelandic = 'is', - Indonesian = 'id', - Inuinnaqtun = 'ikt', - Inuktitut = 'iu', - Inuktitut_Latin = 'iu-Latn', - Irish = 'ga', - Italian = 'it', - Japanese = 'ja', - Kannada = 'kn', - Kazakh = 'kk', - Khmer = 'km', - Klingon_Latin = 'tlh-Latn', - Korean = 'ko', - Kurdish_Central = 'ku', - Kurdish_Northern = 'kmr', - Kyrgyz = 'ky', - Lao = 'lo', - Latvian = 'lv', - Lithuanian = 'lt', - Macedonian = 'mk', - Malagasy = 'mg', - Malay = 'ms', - Malayalam = 'ml', - Maltese = 'mt', - Marathi = 'mr', - Mongolian_Cyrillic = 'mn-Cyrl', - Mongolian_Traditional = 'mn-Mong', - Myanmar_Burmese = 'my', - Māori = 'mi', - Nepali = 'ne', - Norwegian = 'nb', - Odia = 'or', - Pashto = 'ps', - Persian = 'fa', - Polish = 'pl', - Portuguese_Brazil = 'pt', - Portuguese_Portugal = 'pt-PT', - Punjabi = 'pa', - Querétaro_Otomi = 'otq', - Romanian = 'ro', - Russian = 'ru', - Samoan = 'sm', - Serbian_Cyrillic = 'sr-Cyrl', - Serbian_Latin = 'sr-Latn', - Slovak = 'sk', - Slovenian = 'sl', - Somali = 'so', - Spanish = 'es', - Swahili = 'sw', - Swedish = 'sv', - Tahitian = 'ty', - Tamil = 'ta', - Tatar = 'tt', - Telugu = 'te', - Thai = 'th', - Tibetan = 'bo', - Tigrinya = 'ti', - Tongan = 'to', - Turkish = 'tr', - Turkmen = 'tk', - Ukrainian = 'uk', - Upper_Sorbian = 'hsb', - Urdu = 'ur', - Uyghur = 'ug', - Uzbek_Latin = 'uz', - Vietnamese = 'vi', - Welsh = 'cy', - Yucatec_Maya = 'yua', - Zulu = 'zu', -} - -export const languages = GoogleTranslateLanguages; diff --git a/src/modules/functions.ts b/src/modules/functions.ts new file mode 100644 index 0000000..a0de7ce --- /dev/null +++ b/src/modules/functions.ts @@ -0,0 +1,136 @@ +import { translate } from '@vitalets/google-translate-api'; +import * as bingTranslator from 'bing-translate-api'; +import createHttpProxyAgent from 'http-proxy-agent'; +import axios from 'axios'; +import { safeValueTransition } from './helpers'; +import { warn } from '../utils/console'; + +export async function translateWithLibre( + str: string, + from: string, + to: string +): Promise { + let body = { + q: safeValueTransition(str), + source: from, + target: to, + format: 'text', + api_key: '', + secret: 'YK4VRVW', + }; + + const { data } = await axios.post( + 'https://libretranslate.com/translate', + body, + { + headers: { + Origin: 'https://libretranslate.com', + }, + } + ); + + return data.translatedText; +} + +export async function translateWithArgos( + str: string, + from: string, + to: string +): Promise { + let body = { + q: safeValueTransition(str), + source: from, + target: to, + }; + + const { data } = await axios.post( + 'https://translate.argosopentech.com/translate', + body, + { + headers: { + Origin: 'https://translate.argosopentech.com', + Referer: 'https://translate.argosopentech.com', + }, + } + ); + + return data.translatedText; +} + +export async function translateWithBing( + str: string, + from: string, + to: string +): Promise { + const { translation } = await bingTranslator.translate( + safeValueTransition(str), + from, + to, + false + ); + + return translation; +} + +export async function translateWithGoogle( + str: string, + from: string, + to: string +): Promise { + // step: if proxy list provided + if ( + global.proxyList && + global.proxyList.length > 0 && + global.proxyIndex !== -1 + ) { + let proxy = global.proxyList[global.proxyIndex]; + + // step: new proxy exist + if (proxy) { + let agent = createHttpProxyAgent(`http://${proxy}`); + + let translatedStr = await translateWithGoogleByProxySupport( + str, + from, + to, + { + agent, + timeout: 4000, + } + ); + + return translatedStr; + } else { + warn('No new proxy exists, continuing without proxy'); + global.proxyIndex = -1; + + let translatedStr = await translateWithGoogleByProxySupport( + str, + from, + to + ); + + return translatedStr; + } + } else { + // step: translate without proxy + let translatedStr = await translateWithGoogleByProxySupport(str, from, to); + + return translatedStr; + } +} + +async function translateWithGoogleByProxySupport( + str: string, + from: string, + to: string, + options?: { agent: any; timeout: number } +) { + const { text } = await translate(safeValueTransition(str), { + from: from, + to: to, + fetchOptions: { agent: options !== undefined ? options.agent : undefined }, + }); + + return text; +} diff --git a/src/modules/helpers.ts b/src/modules/helpers.ts new file mode 100644 index 0000000..1a99478 --- /dev/null +++ b/src/modules/helpers.ts @@ -0,0 +1,113 @@ +import { default_value, translation_value_limit } from '../utils/micro'; +import { LanguageMapping } from './languages'; +import { TranslationModule, TranslationModules } from './modules'; + +export function safeValueTransition(value: string) { + const value_safety: ValueSafety = valueIsSafe(value); + + if (value_safety.is_safe === true) { + return value; + } + + switch (value_safety.type) { + case nonSafeTypes.null: + case nonSafeTypes.undefined: + case nonSafeTypes.empty: + value = default_value; + break; + case nonSafeTypes.long: + value = value.substring(0, translation_value_limit); + break; + } + + return value; +} + +function valueIsSafe(value: string): ValueSafety { + let result: ValueSafety = { + is_safe: true, + type: undefined, + }; + + if (value === undefined) { + result.is_safe = false; + result['type'] = nonSafeTypes.undefined; + + return result; + } + + if (value === null) { + result.is_safe = false; + result['type'] = nonSafeTypes.null; + + return result; + } + + if (value.length >= translation_value_limit) { + result.is_safe = false; + result['type'] = nonSafeTypes.long; + + return result; + } + + if (value === '') { + result.is_safe = false; + result['type'] = nonSafeTypes.empty; + + return result; + } + + return result; +} + +interface ValueSafety { + is_safe: boolean; + type: nonSafeTypes | undefined; +} + +enum nonSafeTypes { + 'long', + 'undefined', + 'null', + 'empty', +} + +export function translationModuleKeys(): string[] { + return Object.keys(TranslationModules); +} + +export function getTranslationModuleByKey(key: string): TranslationModule { + return TranslationModules[key]; +} + +export function getLanguageKeyFromValue( + value: string, + languages: Record +): string | undefined { + return Object.keys(languages).find(key => languages[key] === value); +} + +export function getLanguageValues(languages: Record): string[] { + return Object.values(languages); +} + +export function getLanguageVariant( + source: string, + sourceValue: string, + destination: string +): string | undefined { + let destinationValue: string | undefined = undefined; + + for (let key of Object.keys(LanguageMapping)) { + if ( + LanguageMapping[key][source] !== undefined && + LanguageMapping[key][source] === sourceValue && + LanguageMapping[key][destination] !== undefined + ) { + destinationValue = LanguageMapping[key][destination]; + break; + } + } + + return destinationValue; +} diff --git a/src/modules/languages.ts b/src/modules/languages.ts new file mode 100644 index 0000000..b537990 --- /dev/null +++ b/src/modules/languages.ts @@ -0,0 +1,808 @@ +export const GoogleTranslateLanguages: Record = { + Automatic: 'auto', + Afrikaans: 'af', + Albanian: 'sq', + Amharic: 'am', + Arabic: 'ar', + Armenian: 'hy', + Azerbaijani: 'az', + Basque: 'eu', + Belarusian: 'be', + Bengali: 'bn', + Bosnian: 'bs', + Bulgarian: 'bg', + Catalan: 'ca', + Cebuano: 'ceb', + Chichewa: 'ny', + Chinese_Simplified: 'zh-CN', + Chinese_Traditional: 'zh-TW', + Corsican: 'co', + Croatian: 'hr', + Czech: 'cs', + Danish: 'da', + Dutch: 'nl', + English: 'en', + Esperanto: 'eo', + Estonian: 'et', + Filipino: 'tl', + Finnish: 'fi', + French: 'fr', + Frisian: 'fy', + Galician: 'gl', + Georgian: 'ka', + German: 'de', + Greek: 'el', + Gujarati: 'gu', + Haitian_Creole: 'ht', + Hausa: 'ha', + Hawaiian: 'haw', + Hebrew: 'iw', + Hindi: 'hi', + Hmong: 'hmn', + Hungarian: 'hu', + Icelandic: 'is', + Igbo: 'ig', + Indonesian: 'id', + Irish: 'ga', + Italian: 'it', + Japanese: 'ja', + Javanese: 'jw', + Kannada: 'kn', + Kazakh: 'kk', + Khmer: 'km', + Korean: 'ko', + Kurdish_Kurmanji: 'ku', + Kyrgyz: 'ky', + Lao: 'lo', + Latin: 'la', + Latvian: 'lv', + Lithuanian: 'lt', + Luxembourgish: 'lb', + Macedonian: 'mk', + Malagasy: 'mg', + Malay: 'ms', + Malayalam: 'ml', + Maltese: 'mt', + Maori: 'mi', + Marathi: 'mr', + Mongolian: 'mn', + Myanmar_Burmese: 'my', + Nepali: 'ne', + Norwegian: 'no', + Pashto: 'ps', + Persian: 'fa', + Polish: 'pl', + Portuguese: 'pt', + Punjabi: 'pa', + Romanian: 'ro', + Russian: 'ru', + Samoan: 'sm', + Scots_Gaelic: 'gd', + Serbian: 'sr', + Sesotho: 'st', + Shona: 'sn', + Sindhi: 'sd', + Sinhala: 'si', + Slovak: 'sk', + Slovenian: 'sl', + Somali: 'so', + Spanish: 'es', + Sundanese: 'su', + Swahili: 'sw', + Swedish: 'sv', + Tajik: 'tg', + Tamil: 'ta', + Telugu: 'te', + Thai: 'th', + Turkish: 'tr', + Ukrainian: 'uk', + Urdu: 'ur', + Uzbek: 'uz', + Vietnamese: 'vi', + Welsh: 'cy', + Xhosa: 'xh', + Yiddish: 'yi', + Yoruba: 'yo', + Zulu: 'zu', +}; + +export const LibreTranslateLanguages: Record = { + Automatic: 'auto', + English: 'en', + Arabic: 'ar', + Azerbaijani: 'az', + Chinese: 'zh', + Czech: 'cs', + Danish: 'da', + Dutch: 'nl', + Esperanto: 'eo', + Finnish: 'fi', + French: 'fr', + German: 'de', + Greek: 'el', + Hebrew: 'iw', + Hindi: 'hi', + Hungarian: 'hu', + Indonesian: 'id', + Irish: 'ga', + Italian: 'it', + Japanese: 'ja', + Korean: 'ko', + Persian: 'fa', + Polish: 'pl', + Portuguese: 'pt', + Russian: 'ru', + Slovak: 'sk', + Spanish: 'es', + Swedish: 'sv', + Turkish: 'tr', + Ukrainian: 'uk', +}; + +export const ArgosTranslateLanguages: Record = { + Automatic: 'auto', + English: 'en', + Arabic: 'ar', + Chinese: 'zh', + French: 'fr', + German: 'de', + Hindi: 'hi', + Indonesian: 'id', + Irish: 'ga', + Italian: 'it', + Japanese: 'ja', + Korean: 'ko', + Polish: 'pl', + Portuguese: 'pt', + Russian: 'ru', + Spanish: 'es', + Turkish: 'tr', + Vietnamese: 'vi', +}; + +export const BingTranslateLanguages: Record = { + Automatic: 'auto-detect', + Afrikaans: 'af', + Albanian: 'sq', + Amharic: 'am', + Arabic: 'ar', + Armenian: 'hy', + Assamese: 'as', + Azerbaijani: 'az', + Bangla: 'bn', + Bashkir: 'ba', + Basque: 'eu', + Bosnian: 'bs', + Bulgarian: 'bg', + Cantonese_Traditional: 'yue', + Catalan: 'ca', + Chinese_Literary: 'lzh', + Chinese_Simplified: 'zh-Hans', + Chinese_Traditional: 'zh-Hant', + Croatian: 'hr', + Czech: 'cs', + Danish: 'da', + Dari: 'prs', + Divehi: 'dv', + Dutch: 'nl', + English: 'en', + Estonian: 'et', + Faroese: 'fo', + Fijian: 'fj', + Filipino: 'fil', + Finnish: 'fi', + French: 'fr', + French_Canada: 'fr-CA', + Galician: 'gl', + Georgian: 'ka', + German: 'de', + Greek: 'el', + Gujarati: 'gu', + Haitian_Creole: 'ht', + Hebrew: 'he', + Hindi: 'hi', + Hmong_Daw: 'mww', + Hungarian: 'hu', + Icelandic: 'is', + Indonesian: 'id', + Inuinnaqtun: 'ikt', + Inuktitut: 'iu', + Inuktitut_Latin: 'iu-Latn', + Irish: 'ga', + Italian: 'it', + Japanese: 'ja', + Kannada: 'kn', + Kazakh: 'kk', + Khmer: 'km', + Klingon_Latin: 'tlh-Latn', + Korean: 'ko', + Kurdish_Central: 'ku', + Kurdish_Northern: 'kmr', + Kyrgyz: 'ky', + Lao: 'lo', + Latvian: 'lv', + Lithuanian: 'lt', + Macedonian: 'mk', + Malagasy: 'mg', + Malay: 'ms', + Malayalam: 'ml', + Maltese: 'mt', + Marathi: 'mr', + Mongolian_Cyrillic: 'mn-Cyrl', + Mongolian_Traditional: 'mn-Mong', + Myanmar_Burmese: 'my', + Māori: 'mi', + Nepali: 'ne', + Norwegian: 'nb', + Odia: 'or', + Pashto: 'ps', + Persian: 'fa', + Polish: 'pl', + Portuguese_Brazil: 'pt', + Portuguese_Portugal: 'pt-PT', + Punjabi: 'pa', + Querétaro_Otomi: 'otq', + Romanian: 'ro', + Russian: 'ru', + Samoan: 'sm', + Serbian_Cyrillic: 'sr-Cyrl', + Serbian_Latin: 'sr-Latn', + Slovak: 'sk', + Slovenian: 'sl', + Somali: 'so', + Spanish: 'es', + Swahili: 'sw', + Swedish: 'sv', + Tahitian: 'ty', + Tamil: 'ta', + Tatar: 'tt', + Telugu: 'te', + Thai: 'th', + Tibetan: 'bo', + Tigrinya: 'ti', + Tongan: 'to', + Turkish: 'tr', + Turkmen: 'tk', + Ukrainian: 'uk', + Upper_Sorbian: 'hsb', + Urdu: 'ur', + Uyghur: 'ug', + Uzbek_Latin: 'uz', + Vietnamese: 'vi', + Welsh: 'cy', + Yucatec_Maya: 'yua', + Zulu: 'zu', +}; + +export const LanguageMapping: Record> = { + Automatic: { + google: GoogleTranslateLanguages.Automatic, + libre: LibreTranslateLanguages.Automatic, + argos: ArgosTranslateLanguages.Automatic, + bing: BingTranslateLanguages.Automatic, + }, + Afrikaans: { + google: GoogleTranslateLanguages.Afrikaans, + bing: BingTranslateLanguages.Afrikaans, + }, + Albanian: { + google: GoogleTranslateLanguages.Albanian, + bing: BingTranslateLanguages.Albanian, + }, + Amharic: { + google: GoogleTranslateLanguages.Amharic, + bing: BingTranslateLanguages.Amharic, + }, + Arabic: { + google: GoogleTranslateLanguages.Arabic, + libre: LibreTranslateLanguages.Arabic, + argos: ArgosTranslateLanguages.Arabic, + bing: BingTranslateLanguages.Arabic, + }, + Assamese: { + bing: BingTranslateLanguages.Assamese, + }, + Armenian: { + google: GoogleTranslateLanguages.Armenian, + bing: BingTranslateLanguages.Armenian, + }, + Azerbaijani: { + google: GoogleTranslateLanguages.Azerbaijani, + libre: LibreTranslateLanguages.Azerbaijani, + bing: BingTranslateLanguages.Azerbaijani, + }, + Bashkir: { + bing: BingTranslateLanguages.Bashkir, + }, + Basque: { + google: GoogleTranslateLanguages.Basque, + bing: BingTranslateLanguages.Basque, + }, + Belarusian: { + google: GoogleTranslateLanguages.Belarusian, + }, + Bengali: { + google: GoogleTranslateLanguages.Bengali, + bing: BingTranslateLanguages.Bangla, + }, + Bosnian: { + google: GoogleTranslateLanguages.Bosnian, + bing: BingTranslateLanguages.Bosnian, + }, + Bulgarian: { + google: GoogleTranslateLanguages.Bulgarian, + bing: BingTranslateLanguages.Bulgarian, + }, + Cantonese_Traditional: { + bing: BingTranslateLanguages.Cantonese_Traditional, + }, + Catalan: { + google: GoogleTranslateLanguages.Catalan, + bing: BingTranslateLanguages.Catalan, + }, + Cebuano: { + google: GoogleTranslateLanguages.Cebuano, + }, + Chichewa: { + google: GoogleTranslateLanguages.Chichewa, + }, + Chinese_Literary: { + bing: BingTranslateLanguages.Chinese_Literary, + }, + Chinese_Simplified: { + google: GoogleTranslateLanguages.Chinese_Simplified, + bing: BingTranslateLanguages.Chinese_Simplified, + libre: LibreTranslateLanguages.Chinese, + argos: ArgosTranslateLanguages.Chinese, + }, + Chinese_Traditional: { + google: GoogleTranslateLanguages.Chinese_Traditional, + bing: BingTranslateLanguages.Chinese_Traditional, + }, + Corsican: { + google: GoogleTranslateLanguages.Corsican, + }, + Croatian: { + google: GoogleTranslateLanguages.Croatian, + bing: BingTranslateLanguages.Croatian, + }, + Czech: { + google: GoogleTranslateLanguages.Czech, + bing: BingTranslateLanguages.Czech, + libre: LibreTranslateLanguages.Czech, + }, + Danish: { + google: GoogleTranslateLanguages.Danish, + bing: BingTranslateLanguages.Danish, + libre: LibreTranslateLanguages.Danish, + }, + Dari: { + bing: BingTranslateLanguages.Dari, + }, + Divehi: { + bing: BingTranslateLanguages.Divehi, + }, + Dutch: { + google: GoogleTranslateLanguages.Dutch, + bing: BingTranslateLanguages.Dutch, + libre: LibreTranslateLanguages.Dutch, + }, + English: { + google: GoogleTranslateLanguages.English, + bing: BingTranslateLanguages.English, + libre: LibreTranslateLanguages.English, + argos: ArgosTranslateLanguages.English, + }, + Esperanto: { + google: GoogleTranslateLanguages.Esperanto, + libre: LibreTranslateLanguages.Esperanto, + }, + Estonian: { + google: GoogleTranslateLanguages.Estonian, + bing: BingTranslateLanguages.Estonian, + }, + Faroese: { + bing: BingTranslateLanguages.Faroese, + }, + Fijian: { + bing: BingTranslateLanguages.Fijian, + }, + Filipino: { + google: GoogleTranslateLanguages.Filipino, + bing: BingTranslateLanguages.Filipino, + }, + Finnish: { + google: GoogleTranslateLanguages.Finnish, + bing: BingTranslateLanguages.Finnish, + libre: LibreTranslateLanguages.Finnish, + }, + French: { + google: GoogleTranslateLanguages.French, + bing: BingTranslateLanguages.French, + libre: LibreTranslateLanguages.French, + argos: ArgosTranslateLanguages.French, + }, + French_Canada: { + bing: BingTranslateLanguages.French_Canada, + }, + Frisian: { + google: GoogleTranslateLanguages.Frisian, + }, + Galician: { + google: GoogleTranslateLanguages.Galician, + bing: BingTranslateLanguages.Galician, + }, + Georgian: { + google: GoogleTranslateLanguages.Georgian, + bing: BingTranslateLanguages.Georgian, + }, + German: { + google: GoogleTranslateLanguages.German, + bing: BingTranslateLanguages.German, + libre: LibreTranslateLanguages.German, + argos: ArgosTranslateLanguages.German, + }, + Greek: { + google: GoogleTranslateLanguages.Greek, + bing: BingTranslateLanguages.Greek, + libre: LibreTranslateLanguages.Greek, + }, + Gujarati: { + google: GoogleTranslateLanguages.Gujarati, + bing: BingTranslateLanguages.Gujarati, + }, + Haitian_Creole: { + google: GoogleTranslateLanguages.Haitian_Creole, + bing: BingTranslateLanguages.Haitian_Creole, + }, + Hausa: { + google: GoogleTranslateLanguages.Hausa, + }, + Hawaiian: { + google: GoogleTranslateLanguages.Hawaiian, + }, + Hebrew: { + google: GoogleTranslateLanguages.Hebrew, + bing: BingTranslateLanguages.Hebrew, + libre: LibreTranslateLanguages.Hebrew, + }, + Hindi: { + google: GoogleTranslateLanguages.Hindi, + bing: BingTranslateLanguages.Hindi, + libre: LibreTranslateLanguages.Hindi, + argos: ArgosTranslateLanguages.Hindi, + }, + Hmong: { + google: GoogleTranslateLanguages.Hmong, + bing: BingTranslateLanguages.Hmong_Daw, + }, + Hungarian: { + google: GoogleTranslateLanguages.Hungarian, + bing: BingTranslateLanguages.Hungarian, + libre: LibreTranslateLanguages.Hungarian, + }, + Icelandic: { + google: GoogleTranslateLanguages.Icelandic, + bing: BingTranslateLanguages.Icelandic, + }, + Igbo: { + google: GoogleTranslateLanguages.Igbo, + }, + Indonesian: { + google: GoogleTranslateLanguages.Indonesian, + bing: BingTranslateLanguages.Indonesian, + libre: LibreTranslateLanguages.Indonesian, + argos: ArgosTranslateLanguages.Indonesian, + }, + Inuinnaqtun: { + bing: BingTranslateLanguages.Inuinnaqtun, + }, + Inuktitut: { + bing: BingTranslateLanguages.Inuktitut, + }, + Inuktitut_Latin: { + bing: BingTranslateLanguages.Inuktitut_Latin, + }, + Irish: { + google: GoogleTranslateLanguages.Irish, + bing: BingTranslateLanguages.Irish, + libre: LibreTranslateLanguages.Irish, + argos: ArgosTranslateLanguages.Irish, + }, + Italian: { + google: GoogleTranslateLanguages.Italian, + bing: BingTranslateLanguages.Italian, + libre: LibreTranslateLanguages.Italian, + argos: ArgosTranslateLanguages.Italian, + }, + Japanese: { + google: GoogleTranslateLanguages.Japanese, + bing: BingTranslateLanguages.Japanese, + libre: LibreTranslateLanguages.Japanese, + argos: ArgosTranslateLanguages.Japanese, + }, + Javanese: { + google: GoogleTranslateLanguages.Javanese, + }, + Kannada: { + google: GoogleTranslateLanguages.Kannada, + bing: BingTranslateLanguages.Kannada, + }, + Kazakh: { + google: GoogleTranslateLanguages.Kazakh, + bing: BingTranslateLanguages.Kazakh, + }, + Khmer: { + google: GoogleTranslateLanguages.Khmer, + bing: BingTranslateLanguages.Khmer, + }, + Klingon_Latin: { + bing: BingTranslateLanguages.Klingon_Latin, + }, + Korean: { + google: GoogleTranslateLanguages.Korean, + bing: BingTranslateLanguages.Korean, + libre: LibreTranslateLanguages.Korean, + argos: ArgosTranslateLanguages.Korean, + }, + Kurdish_Kurmanji: { + google: GoogleTranslateLanguages.Kurdish_Kurmanji, + bing: BingTranslateLanguages.Kurdish_Kurmanji, + }, + Kurdish_Northern: { + bing: BingTranslateLanguages.Kurdish_Northern, + }, + Kyrgyz: { + google: GoogleTranslateLanguages.Kyrgyz, + bing: BingTranslateLanguages.Kyrgyz, + }, + Lao: { + google: GoogleTranslateLanguages.Lao, + bing: BingTranslateLanguages.Lao, + }, + Latin: { + google: GoogleTranslateLanguages.Latin, + }, + Latvian: { + google: GoogleTranslateLanguages.Latvian, + bing: BingTranslateLanguages.Latvian, + }, + Lithuanian: { + google: GoogleTranslateLanguages.Lithuanian, + bing: BingTranslateLanguages.Lithuanian, + }, + Luxembourgish: { + google: GoogleTranslateLanguages.Luxembourgish, + }, + Macedonian: { + google: GoogleTranslateLanguages.Macedonian, + bing: BingTranslateLanguages.Macedonian, + }, + Malagasy: { + google: GoogleTranslateLanguages.Malagasy, + bing: BingTranslateLanguages.Malagasy, + }, + Malay: { + google: GoogleTranslateLanguages.Malay, + bing: BingTranslateLanguages.Malay, + }, + Malayalam: { + google: GoogleTranslateLanguages.Malayalam, + bing: BingTranslateLanguages.Malayalam, + }, + Maltese: { + google: GoogleTranslateLanguages.Maltese, + bing: BingTranslateLanguages.Maltese, + }, + Maori: { + google: GoogleTranslateLanguages.Maori, + bing: BingTranslateLanguages.Māori, + }, + Marathi: { + google: GoogleTranslateLanguages.Marathi, + bing: BingTranslateLanguages.Marathi, + }, + Mongolian: { + google: GoogleTranslateLanguages.Marathi, + bing: BingTranslateLanguages.Mongolian_Traditional, + }, + Mongolian_Cyrillic: { + bing: BingTranslateLanguages.Mongolian_Cyrillic, + }, + Myanmar_Burmese: { + google: GoogleTranslateLanguages.Myanmar_Burmese, + bing: BingTranslateLanguages.Myanmar_Burmese, + }, + Nepali: { + google: GoogleTranslateLanguages.Nepali, + bing: BingTranslateLanguages.Nepali, + }, + Norwegian: { + google: GoogleTranslateLanguages.Norwegian, + bing: BingTranslateLanguages.Norwegian, + }, + Odia: { + bing: BingTranslateLanguages.Odia, + }, + Pashto: { + google: GoogleTranslateLanguages.Pashto, + bing: BingTranslateLanguages.Pashto, + }, + Persian: { + google: GoogleTranslateLanguages.Persian, + bing: BingTranslateLanguages.Persian, + libre: LibreTranslateLanguages.Persian, + }, + Polish: { + google: GoogleTranslateLanguages.Polish, + bing: BingTranslateLanguages.Polish, + libre: LibreTranslateLanguages.Polish, + argos: ArgosTranslateLanguages.Polish, + }, + Portuguese: { + google: GoogleTranslateLanguages.Portuguese, + bing: BingTranslateLanguages.Portuguese_Portugal, + libre: LibreTranslateLanguages.Portuguese, + argos: ArgosTranslateLanguages.Portuguese, + }, + Portuguese_Brazil: { + bing: BingTranslateLanguages.Portuguese_Brazil, + }, + Punjabi: { + google: GoogleTranslateLanguages.Punjabi, + bing: BingTranslateLanguages.Punjabi, + }, + Querétaro_Otomi: { + bing: BingTranslateLanguages.Querétaro_Otomi, + }, + Romanian: { + google: GoogleTranslateLanguages.Romanian, + bing: BingTranslateLanguages.Romanian, + }, + Russian: { + google: GoogleTranslateLanguages.Russian, + bing: BingTranslateLanguages.Russian, + libre: LibreTranslateLanguages.Russian, + argos: ArgosTranslateLanguages.Russian, + }, + Samoan: { + google: GoogleTranslateLanguages.Samoan, + bing: BingTranslateLanguages.Samoan, + }, + Scots_Gaelic: { + google: GoogleTranslateLanguages.Scots_Gaelic, + }, + Serbian: { + google: GoogleTranslateLanguages.Serbian, + bing: BingTranslateLanguages.Serbian_Latin, + }, + Serbian_Cyrillic: { + bing: BingTranslateLanguages.Serbian_Cyrillic, + }, + Sesotho: { + google: GoogleTranslateLanguages.Sesotho, + }, + Shona: { + google: GoogleTranslateLanguages.Shona, + }, + Sindhi: { + google: GoogleTranslateLanguages.Sindhi, + }, + Sinhala: { + google: GoogleTranslateLanguages.Sinhala, + }, + Slovak: { + google: GoogleTranslateLanguages.Slovak, + bing: BingTranslateLanguages.Slovak, + libre: LibreTranslateLanguages.Slovak, + }, + Slovenian: { + google: GoogleTranslateLanguages.Slovenian, + bing: BingTranslateLanguages.Slovenian, + }, + Somali: { + google: GoogleTranslateLanguages.Somali, + bing: BingTranslateLanguages.Somali, + }, + Spanish: { + google: GoogleTranslateLanguages.Spanish, + bing: BingTranslateLanguages.Spanish, + libre: LibreTranslateLanguages.Spanish, + argos: ArgosTranslateLanguages.Spanish, + }, + Sundanese: { + google: GoogleTranslateLanguages.Sundanese, + }, + Swahili: { + google: GoogleTranslateLanguages.Swahili, + bing: BingTranslateLanguages.Swahili, + }, + Swedish: { + google: GoogleTranslateLanguages.Swedish, + bing: BingTranslateLanguages.Swedish, + libre: LibreTranslateLanguages.Swedish, + }, + Tahitian: { + bing: BingTranslateLanguages.Tahitian, + }, + Tajik: { + google: GoogleTranslateLanguages.Tajik, + }, + Tamil: { + google: GoogleTranslateLanguages.Tamil, + bing: BingTranslateLanguages.Tamil, + }, + Tatar: { + bing: BingTranslateLanguages.Tatar, + }, + Telugu: { + google: GoogleTranslateLanguages.Telugu, + bing: BingTranslateLanguages.Telugu, + }, + Thai: { + google: GoogleTranslateLanguages.Thai, + bing: BingTranslateLanguages.Thai, + }, + Tibetan: { + bing: BingTranslateLanguages.Tibetan, + }, + Tigrinya: { + bing: BingTranslateLanguages.Tigrinya, + }, + Tongan: { + bing: BingTranslateLanguages.Tongan, + }, + Turkish: { + google: GoogleTranslateLanguages.Turkish, + bing: BingTranslateLanguages.Turkish, + libre: LibreTranslateLanguages.Turkish, + argos: ArgosTranslateLanguages.Turkish, + }, + Turkmen: { + bing: BingTranslateLanguages.Turkmen, + }, + Ukrainian: { + google: GoogleTranslateLanguages.Ukrainian, + bing: BingTranslateLanguages.Ukrainian, + libre: LibreTranslateLanguages.Ukrainian, + }, + Upper_Sorbian: { + bing: BingTranslateLanguages.Upper_Sorbian, + }, + Urdu: { + google: GoogleTranslateLanguages.Urdu, + bing: BingTranslateLanguages.Urdu, + }, + Uyghur: { + bing: BingTranslateLanguages.Uyghur, + }, + Uzbek: { + google: GoogleTranslateLanguages.Uzbek, + bing: BingTranslateLanguages.Uzbek_Latin, + }, + Vietnamese: { + google: GoogleTranslateLanguages.Vietnamese, + bing: BingTranslateLanguages.Vietnamese, + argos: ArgosTranslateLanguages.Vietnamese, + }, + Welsh: { + google: GoogleTranslateLanguages.Welsh, + bing: BingTranslateLanguages.Welsh, + }, + Xhosa: { + google: GoogleTranslateLanguages.Xhosa, + }, + Yiddish: { + google: GoogleTranslateLanguages.Yiddish, + }, + Yoruba: { + google: GoogleTranslateLanguages.Yoruba, + }, + Yucatec_Maya: { + bing: BingTranslateLanguages.Yucatec_Maya, + }, + Zulu: { + google: GoogleTranslateLanguages.Zulu, + bing: BingTranslateLanguages.Zulu, + }, +}; diff --git a/src/modules/modules.ts b/src/modules/modules.ts new file mode 100644 index 0000000..fa04b7f --- /dev/null +++ b/src/modules/modules.ts @@ -0,0 +1,59 @@ +import { + translateWithGoogle, + translateWithBing, + translateWithLibre, + translateWithArgos, +} from './functions'; +import { + GoogleTranslateLanguages, + BingTranslateLanguages, + LibreTranslateLanguages, + ArgosTranslateLanguages, +} from './languages'; + +export type TranslationModules = { + [key: string]: TranslationModule; +}; + +export type TranslationConfig = { + moduleKey: string; + TranslationModule: TranslationModule; + concurrencyLimit: number; + fallback: boolean; +}; + +export interface TranslationModule { + name: string; + altName: string; + languages: Record; + init?: Function; + translate: Function; + onComplete?: Function; +} + +export const TranslationModules: TranslationModules = { + google: { + name: 'Google Translate', + altName: `Google Translate (104 languages)`, + languages: GoogleTranslateLanguages, + translate: translateWithGoogle, + }, + bing: { + name: 'Bing Translate', + altName: 'Bing Microsoft Translate (110 languages) \x1b[33m**NEW**\x1b[0m', + languages: BingTranslateLanguages, + translate: translateWithBing, + }, + libre: { + name: 'Libre Translate', + altName: `Libre Translate (29 languages)`, + languages: LibreTranslateLanguages, + translate: translateWithLibre, + }, + argos: { + name: 'Argos Translate', + altName: `Argos Translate (17 languages)`, + languages: ArgosTranslateLanguages, + translate: translateWithArgos, + }, +}; diff --git a/src/test/core.spec.ts b/src/test/core.spec.ts index 8e0bac6..35bf41b 100644 --- a/src/test/core.spec.ts +++ b/src/test/core.spec.ts @@ -1,19 +1,13 @@ -import { - getFile, - getRootFolder, - saveFilePublic, - safeValueTransition, -} from '../core/core'; +import { getFile, getRootFolder, saveFilePublic } from '../core/core'; import * as fs from 'fs/promises'; import * as appConsole from '../utils/console'; -import { Sources } from '..'; +import { safeValueTransition } from '../modules/helpers'; jest.mock('fs/promises'); declare global { var totalTranslation: number; var totalTranslated: number; - var source: Sources; var proxyList: string[]; var proxyIndex: number; } diff --git a/src/test/json-file.spec.ts b/src/test/json-file.spec.ts index ab13317..8c8c12b 100644 --- a/src/test/json-file.spec.ts +++ b/src/test/json-file.spec.ts @@ -1,13 +1,15 @@ import { fileTranslator } from '../core/json_file'; import * as appConsole from '../utils/console'; import * as jsonObject from '../core/json_object'; -import { languages, Sources } from '..'; import * as core from '../core/core'; +import { GoogleTranslateLanguages } from '../modules/languages'; +import { TranslationConfig, TranslationModules } from '../modules/modules'; +import { default_concurrency_limit, default_fallback } from '../utils/micro'; +import { translatedObject } from '..'; declare global { var totalTranslation: number; var totalTranslated: number; - var source: Sources; var proxyList: string[]; var proxyIndex: number; } @@ -20,8 +22,8 @@ describe(`JSON FILE`, () => { it('should get `no file` error on undefined file path', async () => { // arrange const mock_objectPath = 'mock_objectPath'; - const mock_from = languages.English; - const mock_to = languages.Dutch; + const mock_from = GoogleTranslateLanguages.English; + const mock_to = GoogleTranslateLanguages.Dutch; const mock_newFileName = ''; const mock_json_file = undefined; @@ -29,8 +31,21 @@ describe(`JSON FILE`, () => { jest.spyOn(core, 'getFile').mockResolvedValue(mock_json_file); jest.spyOn(appConsole, 'error').mockReturnValue(); + let config: TranslationConfig = { + moduleKey: 'google', + TranslationModule: TranslationModules['google'], + concurrencyLimit: default_concurrency_limit, + fallback: default_fallback, + }; + // act - await fileTranslator(mock_objectPath, mock_from, mock_to, mock_newFileName); + await fileTranslator( + config, + mock_objectPath, + mock_from, + [mock_to], + mock_newFileName + ); // assert expect(appConsole.error).toHaveBeenCalled(); @@ -42,8 +57,8 @@ describe(`JSON FILE`, () => { it('should get file & translate it to one language & saves it', async () => { // arrange const mock_objectPath = 'mock_objectPath'; - const mock_from = languages.English; - const mock_to = languages.German; + const mock_from = GoogleTranslateLanguages.English; + const mock_to = GoogleTranslateLanguages.German; const mock_newFileName = ''; const mock_json_object = JSON.stringify({ login: { @@ -52,15 +67,17 @@ describe(`JSON FILE`, () => { failure: 'Failed', }, }); - const mock_translated_json_object = { - data: { - login: { - title: 'Anmeldung', - email: 'Bitte geben Sie ihre E-Mail-Adresse ein', - failure: 'Fehlgeschlagen', + const mock_translated_json_object: translatedObject[] = [ + { + data: { + login: { + title: 'Anmeldung', + email: 'Bitte geben Sie ihre E-Mail-Adresse ein', + failure: 'Fehlgeschlagen', + }, }, }, - }; + ]; const mock_root_folder = 'mock_root_folder'; jest.spyOn(core, 'getFile').mockResolvedValue(mock_json_object); @@ -71,19 +88,34 @@ describe(`JSON FILE`, () => { jest.spyOn(core, 'saveFilePublic').mockResolvedValue(); jest.spyOn(appConsole, 'success').mockReturnValue(); + let config: TranslationConfig = { + moduleKey: 'google', + TranslationModule: TranslationModules['google'], + concurrencyLimit: default_concurrency_limit, + fallback: default_fallback, + }; + // act - await fileTranslator(mock_objectPath, mock_from, mock_to, mock_newFileName); + await fileTranslator( + config, + mock_objectPath, + mock_from, + [mock_to], + mock_newFileName + ); // assert expect(core.saveFilePublic).toBeCalledTimes(1); - expect(appConsole.success).toBeCalledTimes(1); }); it('should get file & translate it to multiple languages & saves it', async () => { // arrange const mock_objectPath = 'mock_objectPath'; - const mock_from = languages.English; - const mock_to = [languages.German, languages.French]; + const mock_from = GoogleTranslateLanguages.English; + const mock_to = [ + GoogleTranslateLanguages.German, + GoogleTranslateLanguages.French, + ]; const mock_newFileName = ''; const mock_json_object = JSON.stringify({ login: { @@ -122,8 +154,21 @@ describe(`JSON FILE`, () => { jest.spyOn(core, 'saveFilePublic').mockResolvedValue(); jest.spyOn(appConsole, 'success').mockReturnValue(); + let config: TranslationConfig = { + moduleKey: 'google', + TranslationModule: TranslationModules['google'], + concurrencyLimit: default_concurrency_limit, + fallback: default_fallback, + }; + // act - await fileTranslator(mock_objectPath, mock_from, mock_to, mock_newFileName); + await fileTranslator( + config, + mock_objectPath, + mock_from, + mock_to, + mock_newFileName + ); // assert expect(core.saveFilePublic).toBeCalled(); diff --git a/src/test/json-object.spec.ts b/src/test/json-object.spec.ts index 40af880..f0e1875 100644 --- a/src/test/json-object.spec.ts +++ b/src/test/json-object.spec.ts @@ -1,11 +1,12 @@ -import { languages, Sources } from '..'; import * as translator from '../core/translator'; import { deepDiver, objectTranslator } from '../core/json_object'; +import { GoogleTranslateLanguages } from '../modules/languages'; +import { TranslationConfig, TranslationModules } from '../modules/modules'; +import { default_concurrency_limit, default_fallback } from '../utils/micro'; declare global { var totalTranslation: number; var totalTranslated: number; - var source: Sources; var proxyList: string[]; var proxyIndex: number; } @@ -46,26 +47,33 @@ describe(`JSON OBJECT`, () => { ], }; - const from = languages.English; + const from = GoogleTranslateLanguages.English; - const to = languages.Dutch; + const to = GoogleTranslateLanguages.Dutch; afterEach(() => { jest.clearAllMocks(); }); const to_multiple = [ - languages.Bulgarian, - languages.Catalan, - languages.Turkish, + GoogleTranslateLanguages.Bulgarian, + GoogleTranslateLanguages.Catalan, + GoogleTranslateLanguages.Turkish, ]; it('should dive every value in the object', async () => { // arrange jest.spyOn(translator, 'plaintranslate').mockResolvedValue(''); + let config: TranslationConfig = { + moduleKey: 'google', + TranslationModule: TranslationModules['google'], + concurrencyLimit: default_concurrency_limit, + fallback: default_fallback, + }; + // act - await deepDiver(test_object, from, to); + await deepDiver(config, test_object, from, to); // assert expect(translator.plaintranslate).toBeCalledTimes(10); @@ -75,19 +83,38 @@ describe(`JSON OBJECT`, () => { // arrange jest.spyOn(translator, 'plaintranslate').mockResolvedValue(''); + let config: TranslationConfig = { + moduleKey: 'google', + TranslationModule: TranslationModules['google'], + concurrencyLimit: default_concurrency_limit, + fallback: default_fallback, + }; + // act - const response = await objectTranslator(test_object, from, to); + const response = await objectTranslator(config, test_object, from, [to]); // assert - expect(response).toMatchObject(test_object); + expect(response).toMatchObject([test_object]); }); it('should translate object into multiple languages', async () => { // arrange jest.spyOn(translator, 'plaintranslate').mockResolvedValue(''); + let config: TranslationConfig = { + moduleKey: 'google', + TranslationModule: TranslationModules['google'], + concurrencyLimit: default_concurrency_limit, + fallback: default_fallback, + }; + // act - const response = await objectTranslator(test_object, from, to_multiple); + const response = await objectTranslator( + config, + test_object, + from, + to_multiple + ); // assert expect(response.length).toEqual(to_multiple.length); diff --git a/src/test/util.test.ts b/src/test/util.test.ts index d08f45c..0c8496e 100644 --- a/src/test/util.test.ts +++ b/src/test/util.test.ts @@ -1,11 +1,8 @@ -import { Sources } from '..'; import { error, info, success, warn } from '../utils/console'; -import { getCodeFromLanguage, getLanguageFromCode } from '../utils/micro'; declare global { var totalTranslation: number; var totalTranslated: number; - var source: Sources; var proxyList: string[]; var proxyIndex: number; } @@ -16,41 +13,23 @@ describe(`UTIL`, () => { }); // arrange - const test_cases = [ - { - code: 'az', - language: 'Azerbaijani', - }, - { - code: 'eu', - language: 'Basque', - }, - { - code: 'da', - language: 'Danish', - }, - ]; + // const test_cases = [ + // { + // code: 'az', + // language: 'Azerbaijani', + // }, + // { + // code: 'eu', + // language: 'Basque', + // }, + // { + // code: 'da', + // language: 'Danish', + // }, + // ]; describe('MICRO', () => { - it('should get language from the code', () => { - test_cases.forEach(test_case => { - // act - const langauge = getLanguageFromCode(test_case.code); - - // assert - expect(langauge).toStrictEqual(test_case.language); - }); - }); - - it('should get code from the language', () => { - test_cases.forEach(test_case => { - // act - const code = getCodeFromLanguage(test_case.language); - - // assert - expect(code).toStrictEqual(test_case.code); - }); - }); + // TODO: add modules/helpers function test }); describe('CONSOLE', () => { diff --git a/src/utils/console.ts b/src/utils/console.ts index 87bb2ac..576caa7 100644 --- a/src/utils/console.ts +++ b/src/utils/console.ts @@ -1,28 +1,29 @@ -import { translatorsNames } from '..'; -import { current_version } from './micro'; +import { translationModuleKeys } from '../modules/helpers'; +import { current_version, fallbacks } from './micro'; var figlet = require('figlet'); const cli_name = 'jsontt'; +const default_color = '\x1b[0m'; const success_color = '\x1b[32m'; const error_color = '\x1b[31m'; const info_color = '\x1b[34m'; const warn_color = '\x1b[33m'; export function success(message: string) { - console.log(success_color, `${message}`, '\x1b[0m'); + console.log(success_color, `${message}`, default_color); } export function error(message: string) { - console.log(error_color, `${message}`, '\x1b[0m'); + console.log(error_color, `${message}`, default_color); } export function info(message: string) { - console.log(info_color, `${message}`, '\x1b[0m'); + console.log(info_color, `${message}`, default_color); } export function warn(message: string) { - console.log(warn_color, `${message}`, '\x1b[0m'); + console.log(warn_color, `${message}`, default_color); } export const commands = { @@ -30,45 +31,63 @@ export const commands = { help2: '-h', }; -export const language_choices: { [key: string]: string } = { - GoogleTranslate: `Google Translate (104 languages)`, - BingTranslate: - 'Bing Microsoft Translate (110 languages) \x1b[33m**NEW**\x1b[0m', - LibreTranslate: `Libre Translate (29 languages)`, - ArgosTranslate: `Argos Translate (17 languages)`, -}; -export const supportedLanguagesUrl = `\nsupported Languages: ${info_color}https://github.com/mololab/json-translator/blob/master/docs/LANGUAGES.md\x1b[0m\n`; +export const supportedLanguagesUrl = `\nsupported Languages: ${info_color}https://github.com/mololab/json-translator/blob/master/docs/LANGUAGES.md${default_color}\n`; export const messages = { cli: { - welcome: `\nWelcome to the\n${success_color + - figlet.textSync('jsontt')}\x1b[0m\n\t\t\t\tcli ${current_version}\n`, + // cli general messages + welcome: `\n${warn_color}Sponsored by Moniesto - Bridge between Traders and Investors in Crypto \nhttps://moniesto.com \n\n${default_color}Welcome to the\n${success_color + + figlet.textSync( + 'jsontt' + )}${default_color}\n\t\t\t\tcli ${current_version}\n`, description: - 'This package will provide you the ability to translate your JSON or YAML files or objects into different languages for free.', - usage: ` `, - usageWithProxy: `Usage with proxy list file: ${cli_name} `, - usageByOps: `Usage with options: ${cli_name} --translator --from --to `, + 'This CLI will provide you the ability to translate your JSON/YAML files or JSON objects into different languages for free.', + usage: ``, + usage_with_proxy: `Usage with proxy list file (only supported for Google module): ${cli_name} `, + usage_by_ops: `Usage with options: ${cli_name} --module --from --to `, paths: 'required json file path or json file with proxy list txt file path ', - translator: 'specify translation service', - from: 'the translate language from it, e.g., --from en', - to: 'the Languages to translate into, e.g., --to ar fr zh-CN', - newFileName: '(optional ↵) output filename, e.g., --name myApp', - from_source: 'From which source?', + + // cli usage messages + module: 'specify translation module | e.g., -m google', + from: 'from language | e.g., -f en', + to: 'to translates | e.g., -t ar fr zh-CN', + new_file_name: 'optional ↵ | output filename | e.g., -n myApp', + fallback: + 'optional ↵ | fallback logic, try other translation modules on fail | yes, no | default: no | e.g., -f yes', + concurrency_limit: + 'optional ↵ | set max concurrency limit (higher faster, but easy to get banned) | default: 3 | e.g., -cl 5', + + // cli prompt messages + select_module_message: 'Select translation module:', from_message: 'From which language?', to_message: 'To which language | languages? (Can select more than one with space bar)', - translator_not_available: `the Translator not available. (choices : ${translatorsNames})`, - from_not_available: `the translate language from it, is not available\n${supportedLanguagesUrl}`, - to_not_available: `the Languages to translate into is not available\n${supportedLanguagesUrl}`, + new_file_name_message: 'optional ↵ | Output filename', + fallback_message: + 'optional ↵ | fallback logic, try other translation modules when fail | yes, no | default: no', + concurrency_limit_message: + 'optional ↵ | set max concurrency limit (higher faster, but easy to get banned) | default: 3', + + // fail messages + module_not_available: `module is not available. (choices : ${translationModuleKeys})`, + from_not_available: `translate language from is not available\n${supportedLanguagesUrl}`, + to_not_available: `languages to translate into is not available\n${supportedLanguagesUrl}`, no_selected_language: 'You didn`t select any language. Please try it again and select languages with the space bar.', - proxy_File_notValid_or_not_empty_options: ` - - Please ensure that the value for the option "-T, --translator " is compatible + fallback_not_available: `fallback input is not available. (choices : ${Object.keys( + fallbacks + )})`, + proxy_file_notValid_or_not_empty_options: ` + - Please ensure that the value for the option "-m, --module " is compatible - Please ensure that the value for the option "-f, --from " is compatible - - Please ensure that the value for the option "-t, --to " is compatible + - nPlease ensure that the value for the option "-t, --to " is compatible - Please ensure that the value for the option "-n, --name " is valid + - Please ensure that the value for the option "-f, --fallback " is valid + - Please ensure that the value for the option "-cl, --concurrencylimit " is valid - Please make sure to provide a valid path for the proxy list file at "". `, + + // success messages creation_done: 'All files are created! You can find them in the same folder as the original file.', }, diff --git a/src/utils/micro.ts b/src/utils/micro.ts index 5a6ef52..7beafdd 100644 --- a/src/utils/micro.ts +++ b/src/utils/micro.ts @@ -1,23 +1,5 @@ -import { getLanguages } from '..'; import * as packageJSON from '../../package.json'; -export function getLanguageFromCode(language_code: string) { - return getEnumKeyByEnumValue(getLanguages(), language_code); -} - -export function getCodeFromLanguage(language: string) { - let languages = getLanguages(); - return (languages as any)[language as keyof typeof languages]; -} - -function getEnumKeyByEnumValue( - myEnum: any, - enumValue: number | string -): string { - let keys = Object.keys(myEnum).filter(x => myEnum[x] === enumValue); - return keys.length > 0 ? keys[0] : ''; -} - export function translationStatistic( totalTranslated: number, totalTranslation: number @@ -30,3 +12,9 @@ export function capitalize(str: string): string { export const current_version = packageJSON.version; export const default_value = '--'; export const translation_value_limit = 5000; +export const default_concurrency_limit = 3; +export const fallbacks = { + yes: true, + no: false, +}; +export const default_fallback = fallbacks.no; diff --git a/src/utils/prompt.ts b/src/utils/prompt.ts deleted file mode 100644 index 393f653..0000000 --- a/src/utils/prompt.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { LanguageCodes, Sources, getLanguages } from '..'; -import { language_choices, messages } from './console'; - -var inquirer = require('inquirer'); -export async function promptTranslator() { - const source_choices = Object.entries(Sources).map(([key, _]) => { - return { - name: language_choices[key], - value: key, - short: key, - }; - }); - await inquirer - .prompt([ - { - type: 'list', - name: 'source', - message: messages.cli.from_source, - pageSize: 20, - choices: [...source_choices, new inquirer.Separator()], - }, - ]) - .then((answers: any) => { - global.source = answers.source; - }); -} - -export async function promptFrom() { - const { from_choices } = getLanguageChoices(); - const answers = await inquirer.prompt([ - { - type: 'list', - name: 'from', - message: messages.cli.from_message, - pageSize: 20, - choices: [...from_choices, new inquirer.Separator()], - }, - ]); - - return answers; -} - -export async function promptTo(default_languages?: any) { - const { to_choices } = getLanguageChoices(); - - const answers = await inquirer.prompt([ - { - type: 'checkbox', - name: 'to', - pageSize: 20, - message: messages.cli.to_message, - choices: to_choices, - default: default_languages ? default_languages : [], - }, - ]); - - return answers; -} - -export async function promptName(default_name?: string) { - const newName = await inquirer.prompt([ - { - type: 'string', - name: 'name', - message: messages.cli.newFileName, - default: default_name ? default_name : undefined, - }, - ]); - - return newName; -} - -function getLanguageChoices(): { - from_choices: LanguageCodes; - to_choices: LanguageCodes; -} { - let from_choices = getFromChoices(); - let to_choices = from_choices.filter(language => language !== `Automatic`); - - return { from_choices, to_choices }; -} - -function getFromChoices(): LanguageCodes { - let languages = Object.entries(getLanguages() as any).map(([key, _]) => key); - - return languages; -} diff --git a/test/util.test.ts b/test/util.test.ts.disabled similarity index 97% rename from test/util.test.ts rename to test/util.test.ts.disabled index 0193f7e..fd0b842 100644 --- a/test/util.test.ts +++ b/test/util.test.ts.disabled @@ -1,11 +1,9 @@ -import { Sources } from '../src'; import { error, info, success, warn } from '../src/utils/console'; import { getCodeFromLanguage, getLanguageFromCode } from '../src/utils/micro'; declare global { var totalTranslation: number; var totalTranslated: number; - var source: Sources; var proxyList: string[]; var proxyIndex: number; } From e06c9211a38f4c26a4ab8d7e7f435622d229976f Mon Sep 17 00:00:00 2001 From: ParvinEyvazov Date: Fri, 22 Dec 2023 00:47:16 +0400 Subject: [PATCH 2/3] lint fix --- test/{util.test.ts.disabled => util.test.ts} | 49 ++++++-------------- 1 file changed, 15 insertions(+), 34 deletions(-) rename test/{util.test.ts.disabled => util.test.ts} (62%) diff --git a/test/util.test.ts.disabled b/test/util.test.ts similarity index 62% rename from test/util.test.ts.disabled rename to test/util.test.ts index fd0b842..cb255bc 100644 --- a/test/util.test.ts.disabled +++ b/test/util.test.ts @@ -1,5 +1,4 @@ import { error, info, success, warn } from '../src/utils/console'; -import { getCodeFromLanguage, getLanguageFromCode } from '../src/utils/micro'; declare global { var totalTranslation: number; @@ -14,41 +13,23 @@ describe(`UTIL`, () => { }); // arrange - const test_cases = [ - { - code: 'az', - language: 'Azerbaijani', - }, - { - code: 'eu', - language: 'Basque', - }, - { - code: 'da', - language: 'Danish', - }, - ]; + // const test_cases = [ + // { + // code: 'az', + // language: 'Azerbaijani', + // }, + // { + // code: 'eu', + // language: 'Basque', + // }, + // { + // code: 'da', + // language: 'Danish', + // }, + // ]; describe('MICRO', () => { - it('should get language from the code', () => { - test_cases.forEach(test_case => { - // act - const langauge = getLanguageFromCode(test_case.code); - - // assert - expect(langauge).toStrictEqual(test_case.language); - }); - }); - - it('should get code from the language', () => { - test_cases.forEach(test_case => { - // act - const code = getCodeFromLanguage(test_case.language); - - // assert - expect(code).toStrictEqual(test_case.code); - }); - }); + // TODO: add modules/helpers function test }); describe('CONSOLE', () => { From f217173a918a9f947b98ceea31faaa7e3626803f Mon Sep 17 00:00:00 2001 From: Parvin Eyvazov <32189770+ParvinEyvazov@users.noreply.github.com> Date: Fri, 22 Dec 2023 01:14:44 +0400 Subject: [PATCH 3/3] Update README.md --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5120b9b..8a7e3ee 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,13 @@ +

+ ✨ Sponsored by Moniesto - Bridge between Traders and Investors in Crypto ✨ +

+ +

+ ✨ https://moniesto.com ✨ +

+ + +

jsontt logo

@@ -93,7 +103,7 @@ jsontt --module google --from en --to ar fr zh-CN --nam - with fallback logic (try other possible translation modules on fail) ```bash -jsontt --module google --from en --to ar fr zh-CN --name myFiles -fb yes +jsontt --module google --from en --to ar fr zh-CN --name myFiles --fallback yes ``` - set concurrency limit (higher faster, but easy to get banned | default: 3)