aboutsummaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
author0neGal <mail@0negal.com>2024-02-18 23:03:07 +0100
committer0neGal <mail@0negal.com>2024-02-18 23:15:50 +0100
commitccde19d82a1a8fd3c5ea20efa31a0e18393e14fe (patch)
tree8b3fcebd1583d1c15c72f41d20bbdea5a111168d /scripts
parent4385ad45b0c4cb9a46f387ed3b54bd363f3de2ef (diff)
downloadViper-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.js399
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();
}