diff options
author | 0neGal <mail@0negal.com> | 2024-02-18 23:03:07 +0100 |
---|---|---|
committer | 0neGal <mail@0negal.com> | 2024-02-18 23:15:50 +0100 |
commit | ccde19d82a1a8fd3c5ea20efa31a0e18393e14fe (patch) | |
tree | 8b3fcebd1583d1c15c72f41d20bbdea5a111168d /scripts | |
parent | 4385ad45b0c4cb9a46f387ed3b54bd363f3de2ef (diff) | |
download | Viper-ccde19d82a1a8fd3c5ea20efa31a0e18393e14fe.tar.gz Viper-ccde19d82a1a8fd3c5ea20efa31a0e18393e14fe.zip |
added a lot more functionality to scripts/langs.js
It's not capable of formatting language files, and now has a prompt
interface for editing and adding missing localization strings.
This removes the need for manually editing localization files beyond
`en.json`, it'll still be edited manually. But maintainers will no
longer have to open any localization files.
I also updated the documentation for contributing to localizations.
Diffstat (limited to 'scripts')
-rw-r--r-- | scripts/langs.js | 399 |
1 files changed, 352 insertions, 47 deletions
diff --git a/scripts/langs.js b/scripts/langs.js index a91b243..8c87e9c 100644 --- a/scripts/langs.js +++ b/scripts/langs.js @@ -1,76 +1,381 @@ const fs = require("fs"); +const dialog = require("enquirer"); +const flat = require("flattenizer"); +const args = require("minimist")(process.argv.slice(2), { + boolean: [ + "help", + "check", + "format", + "localize" + ], -let problems = false; -let maintainers = require("../src/lang/maintainers.json"); + default: { + "help": false, + "check": false, + "format": false, + "localize": false + } +}) + +console = require("../src/modules/console"); + +// help message +if (args["help"]) { + console.log(`options: + --help shows this help message + --check checks for incorrectly formatted lang files + and missing localizations + --format formats all lang files correctly if the files + can be read and parsed + --localize allows you add missing incorrectly + localizations, and edit old ones + `.trim()) // the trim removes the last blank newline + + process.exit(0); +} -function flatten_obj(data) { - var obj = {}; +// move into `scripts` folder, makes sure all file system requests work +// identically to `require()` +process.chdir(__dirname); - for (let i in data) { - if (! data.hasOwnProperty(i)) { - continue; +// get list of files in `src/lang/`, except for `maintainers.json` these +// should all be language files +let langs = fs.readdirSync("../src/lang"); + +// get the English language file and flatten it +let lang = flat.flatten(require("../src/lang/en.json")); + +// formats all files automatically, nothing too fancy, it ignores +// `en.json` however, as its manually edited. +let format = (logging = true) => { + // run through langs + langs.forEach((locale_file) => { + // ignore these files + if (locale_file == "en.json" + || locale_file == "maintainers.json") { + return; } - if (typeof data[i] == "object" && data[i] !== null) { - var flattened = flatten_obj(data[i]); - for (var ii in flattened) { - if (! flattened.hasOwnProperty(ii)) { - continue; - } + // path to lang file + let file_path = "../src/lang/" + locale_file; + + try { + // attempt read, parse and flatten `file_path` + let json = flat.flatten(require(file_path)); + + // sort `json` + json = Object.fromEntries( + Object.entries(json).sort() + ) - obj[i + "." + ii] = flattened[ii]; + // delete keys that are only found in `locale_file` but not + // in the English localization file, if something doesn't + // exist in the English localization file, then it shouldn't + // exist at all! + for (let i in json) { + if (! lang[i]) { + delete json[i]; + } } - } else { - obj[i] = data[i]; + + json = flat.unflatten(json); + + // attempt to stringify earlier JSON, with default + // formatting, directly into `file_path` + fs.writeFileSync( + file_path, JSON.stringify(json, null, "\t") + ) + }catch(err) { + // something went wrong! + console.error("Couldn't format: " + locale_file); } - } + }) - return obj; + console.ok("Formatted all localization files."); } -let lang = flatten_obj(require("../src/lang/en.json")); +// starts up a prompt interface to edit localization strings, letting +// you both add missing ones, and change old ones +let localize = async () => { + // check if there's any missing keys + let problems = check(false); + + // this'll have the `choices` for language picking prompt + let lang_list = []; + + // run through langs + langs.forEach((locale_file) => { + // ignore these files + if (locale_file == "en.json" + || locale_file == "maintainers.json") { + return; + } + + // default value + let lang_name = locale_file; + + // are we missing any keys? if so, add a "(missing)" label at + // the end of the language name + let missing = (problems[lang_name] && problems[lang_name].length); + if (missing) { + lang_name += ` (missing: ${missing})`; + } -langs = fs.readdirSync("src/lang") -langs.forEach((localefile) => { - if (localefile == "maintainers.json") {return} + // add to list of langs + lang_list.push(lang_name); + }) + + // prompt for which lang to edit + let picked_lang = await new dialog.AutoComplete({ + choices: lang_list, + message: "Pick a language to edit:", + }).run() + + // remove extra labels after the lang file name itself + picked_lang = picked_lang.replace(/ \(.*/, ""); + + // this'll contain the languages flattened contents + let lang_keys; - let missing = []; - let langmaintainers = maintainers.list[localefile.replace(/\..*$/, "")]; - let locale = false; try { - locale = flatten_obj(require("../src/lang/" + localefile)); - }catch(err) { - console.log(`\x1b[101m!! ${localefile} is not formatted right !!\x1b[0m`); - return + // attempt to read, parse and flatten the language file + lang_keys = flat.flatten(require("../src/lang/" + picked_lang)); + }catch (err) { + // something went wrong! + console.error("Couldn't read and parse language file"); + process.exit(1); } - for (let i in lang) { - if (! locale[i]) { - missing.push(i); + // should we just show the keys that are missing, or everything? + let just_missing = false; + + // get just the flattened keys of the language + let keys = Object.keys(lang_keys); + + console.log(problems) + // are there any missing keys? + if (problems[picked_lang].length) { + // prompt for whether we should only show missing keys + just_missing = await new dialog.Confirm({ + message: "Add just missing keys without editing all keys?", + }).run() + + // if we should just show missing keys, remove all other keys, + // if we're allowed to show other keys, then we'll at least add + // the missing keys + if (just_missing) { + keys = problems[picked_lang]; + } else { + keys = [ + ...problems[picked_lang], + ...keys, + ] } } - if (missing.length > 0) { - problems = true; + // add "Save changes" option + keys = [ + "Save changes", + ...keys + ] - console.error(`${localefile} is missing:`) - for (let i in missing) { - console.log(`\x1b[31m ${missing[i]}\x1b[0m`) + // add "(missing)" label to missing keys + for (let i = 0; i < keys.length; i++) { + if (! just_missing && problems[picked_lang].includes(keys[i])) { + keys[i] = keys[i] + " \x1b[91m(missing)\x1b[0m"; } + } - console.log() + // this'll hold the flattened edits we make + let edited_keys = {}; - console.log("Maintainers: ") - for (let i in langmaintainers) { - console.log(` ${langmaintainers[i]}`) + // starts the process of editing a key + let edit_key = async () => { + // prompt for which key to edit + let key_to_edit = await new dialog.AutoComplete({ + limit: 15, + choices: [...keys], + message: "Pick a key to edit:" + }).run() + + // if "Save changes" was picked then return all the edits we've + // made and stop prompting for new edits + if (key_to_edit == "Save changes") { + return edited_keys; } - console.log("\n") + // strip labels from chosen key name + key_to_edit = key_to_edit.split(" ")[0]; + + // prompt for what to set the key to + let edited_key = await new dialog.Input({ + type: "input", + message: `Editing: ${key_to_edit}\n` + + " Original string: " + lang[key_to_edit] + "\n" + }).run() + + // add the edited key in `edited_keys` + edited_keys[key_to_edit] = edited_key; + + // add "(edited)" to the label of this key + for (let i = 0; i < keys.length; i++) { + if (keys[i].split(" ")[0] == key_to_edit) { + keys[i] = key_to_edit + " \x1b[94m(edited)\x1b[0m"; + } + } + + // clear screen and ask for the next edit to be made + console.clear(); + return edit_key(); } -}) -if (! problems) { - console.log("\x1b[32mAll localizations are complete and formatted properly.\x1b[0m"); -} else { - process.exit(1); + // start the process of key editing, whenever the below function + // returns with the list of edits, it also only first returns when + // all the changes have been made and "Save changes" has been + // selected in the menu + let changes = await edit_key(); + + console.clear(); + + try { + // merge edits and original lang file, then unflatten them + let final_json = flat.unflatten({ + ...lang_keys, + ...changes + }) + + // attempt to write `final_json` to the language file + fs.writeFileSync( + "../src/lang/" + picked_lang, + JSON.stringify(final_json, null, "\t") + ) + + console.ok("Saved changes: " + picked_lang); + + // check for changes + check(true); + + // format everything + format(true); + }catch(err) { + // something went wrong! + console.error("Failed to save changes: " + picked_lang); + } +} + +// checks whether or not language files are missing any keys, and +// whether they're even parseable. +// +// an object will be returned containing information about each file and +// which, if any, keys are missing from them +let check = (logging = true) => { + // this'll contain the missing keys for all the files, if any + let problems = {}; + + // this'll be changed to `true` if any errors at any point arise + let has_problems = false; + + // get list of maintainers for each language + let maintainers = require("../src/lang/maintainers.json"); + + // run through langs + langs.forEach((locale_file) => { + // ignore this file, it's not a language file + if (locale_file == "maintainers.json") {return} + + // this'll contain missing keys + let missing = []; + + // this'll contain the flattened language file contents + let locale = false; + + // this is the list of maintainers for this language + let lang_maintainers = maintainers.list[ + locale_file.replace(/\..*$/, "") + ] + + // attempt read, parse and flatten language file + try { + locale = flat.flatten(require("../src/lang/" + locale_file)); + }catch(err) { + // we couldn't parse it! + if (logging) { + has_problems = true; + console.error(`!! ${locale_file} is not formatted right !!`); + } + + return; + } + + // run through keys, and note ones that are missing from + // `en.json` in this lang + for (let i in lang) { + if (! locale[i]) { + missing.push(i); + } + } + + // add missing keys to `problems` + problems[locale_file] = missing; + + // was there any missing keys? + if (missing.length > 0) { + // this is a problem + has_problems = true; + + // do nothing if we're not supposed to log anything + if (! logging) { + return; + } + + // log language file with missing keys + console.error(`${locale_file} is missing:`); + + // log missing keys + for (let i in missing) { + console.error(` ${missing[i]}`); + } + + // spacing + console.log(); + + // log the maintainers for this language + console.log("Maintainers: "); + for (let i in lang_maintainers) { + console.log(` ${lang_maintainers[i]}`); + } + + console.log("\n"); + } + }) + + // if no problems occurred, and we can log things, then print that + // everything went just fine! + if (! has_problems && logging) { + console.ok("All localizations are complete and parseable."); + } + + return problems; +} + +// run `check()` if `--check()` is set +if (args["check"]) { + let problems = check(); + + // exit with the correct exit code + if (problems.length) { + process.exit(1); + } else { + process.exit(); + } +} + +// run `format()` if `--format` is set +if (args["format"]) { + format(); +} + +// run `localize()` if `--localize` is set +if (args["localize"]) { + localize(); } |