diff --git a/.github/workflows/localise.yml b/.github/workflows/localise.yml new file mode 100644 index 0000000000..2f9ccade78 --- /dev/null +++ b/.github/workflows/localise.yml @@ -0,0 +1,41 @@ +name: Localisation + +on: pull_request + +jobs: + update-localisation: + # Don't run if this is a draft. + if: github.event.pull_request.draft == true + name: Update Localisation + runs-on: ubuntu-latest + + steps: + - name: Checkout Git repository + uses: actions/checkout@v2 + # Comply with git-auto-commit + with: + ref: ${{ github.head_ref }} + + - name: Set up Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: 3.9 + + - name: install dependencies + run: | + python -m pip install --upgrade pip + pip install --upgrade orjson + + - name: run update_en_strings.py + # TODO: Find a git command that will run on other branches. + run: | + python ../../locale/update_en_strings.py ../data + git --no-pager diff --name-status $GITHUB_BASE_REF...$GITHUB_SHA | python ../../locale/handle_file_operations.py + + - uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Automatically update english strings. + # This shouldn't matter, but just in case. + file_pattern: *.json + # Give blame where it is due. + commit_author: Author diff --git a/locale/handle_file_operations.py b/locale/handle_file_operations.py new file mode 100644 index 0000000000..e40749e242 --- /dev/null +++ b/locale/handle_file_operations.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +import sys +import os +from pathlib import Path +import shutil + +# Ensure that the path is correct. +PATH_TO_LOCALE = Path(__file__).parent.absolute() +LOCALES = tuple( + PATH_TO_LOCALE.joinpath(d) for d in os.scandir(PATH_TO_LOCALE) if d.is_dir() +) + +for line in sys.stdin: + line = line.split("\t") + if len(line) == 3: + code, old_file, new_file = line.split("\t") + code = code[0] + else: + code, filepath = line + + # We only handle deleted or renamed files. + if code not in ("D", "R"): + continue + + # filepath starts relative to Community-Mod-Compilation/. Remove data/ + filepath = Path(*Path(filepath).parts[1:]) + old_file = Path(*Path(old_file).parts[1:]) + new_file = Path(*Path(new_file).parts[1:]) + + if code == "D": + for locale in LOCALES: + path = Path(locale, filepath) + # It is quite probable that the translation does not exist. + if path.is_file(): + path.unlink() + + elif code == "R": + for locale in LOCALES: + locale_new = Path(locale, new_file) + locale_old = Path(locale, old_file) + if locale_old.is_file(): + shutil.copy(locale_old, locale_new) + locale_old.unlink() diff --git a/locale/update_en_strings.py b/locale/update_en_strings.py new file mode 100644 index 0000000000..a54f4eba67 --- /dev/null +++ b/locale/update_en_strings.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python + +import orjson +from orjson import OPT_APPEND_NEWLINE, OPT_INDENT_2 +import os +from pathlib import Path +from stat import S_IXOTH + +# TODO: Make a script that doesn't rely on recreating every file. +PRETTY_PRINT = OPT_APPEND_NEWLINE | OPT_INDENT_2 + + +def generate_localisation_file(path: Path): + # Assume Community-Mod-Compilation/data/. + # Assume this script is in locale, and then make absolute. + locale_file = Path("en", *path.parts[2:]).resolve() + + if not locale_file.is_file(): + if not locale_file.parent.is_dir(): + locale_file.parent.mkdir(mode=S_IXOTH, parents=True, exist_ok=True) + # Get every directory starting from Community-Mod-Compilation/locale/en/ + # TODO: Replace this with a call to range(). + for i, p in enumerate(locale_file.parts[3:-1], 4): + parent_dir = os.path.join(locale_file.parts[:i]) + # Check if permission allows everybody to execute. + if os.stat(parent_dir)[0] != S_IXOTH: + os.chmod(parent_dir, S_IXOTH) + # Annoying, but this is the only file creation option on Windows and MacOS + with open(locale_file, "w"): + pass + + # Return path to the new file. + return locale_file + + +def get_translation_strings(path: Path): + tr_file = [] + try: + json = orjson.loads(path.read_bytes()) + except orjson.JSONDecodeError: + raise orjson.JSONDecodeError(f"File: {path}") + + if type(json) is not list: + return + + for jo in json: + if type(jo) is not dict: + continue + # TODO: Check if there are things without descriptions to be translated. + if jo.get("description") and jo.get("name"): + tr_file.append({"name": jo["name"], "description": jo["description"]}) + + return tr_file + + +def main(directory): + for root, dirnames, filenames in os.walk(directory): + for filename in [f for f in filenames if f.endswith(".json")]: + path = Path(root, filename) + tr_file = get_translation_strings(path) + # No point in generating a new file if it's going to be empty. + if not tr_file: + continue + local = generate_localisation_file(path) + with open(local, "wb") as f: + f.write(orjson.dumps(tr_file, option=PRETTY_PRINT)) + + +if __name__ == "__main__": + import sys + + main(sys.argv[1])