From ec94ddece8a8c016d52e027af07c19a56a3fdad1 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Mon, 15 Aug 2022 23:06:23 +0200 Subject: [PATCH] the code is now fully translatable --- assets/language/en.json | 92 ++++++++++++++++++- source/gui/__init__.py | 2 +- source/gui/install.py | 24 ++--- source/gui/preview/__init__.py | 3 +- source/mkw/ExtractedGame.py | 32 ++++--- source/mkw/Game.py | 25 ++--- source/mkw/MKWColor.py | 5 +- source/mkw/ModConfig.py | 9 +- source/mkw/ModSettings/__init__.py | 4 +- source/mkw/OriginalTrack.py | 5 +- source/mkw/Patch/Patch.py | 4 +- source/mkw/Patch/PatchDirectory.py | 3 +- source/mkw/Patch/PatchFile.py | 6 +- .../PatchOperation/BmgTxtEditor/__init__.py | 3 +- .../PatchOperation/ImageEditor/__init__.py | 3 +- source/mkw/Patch/PatchOperation/__init__.py | 4 +- source/mkw/Patch/__init__.py | 9 +- source/mkw/Track/AbstractTrack.py | 3 +- source/mkw/Track/Arena.py | 4 +- source/mkw/Track/CustomTrack.py | 5 +- source/mkw/Track/RealArenaTrack.py | 3 +- source/safe_eval/macros.py | 5 +- source/safe_eval/safe_eval.py | 22 +++-- source/safe_eval/safe_function.py | 6 +- source/wt/__init__.py | 8 +- source/wt/szs.py | 7 +- 26 files changed, 214 insertions(+), 82 deletions(-) diff --git a/assets/language/en.json b/assets/language/en.json index f496b03..94c6a00 100644 --- a/assets/language/en.json +++ b/assets/language/en.json @@ -5,6 +5,96 @@ "LANGUAGE_SELECTION": "Language", "TRACK_FILTER": "Track Filters", "ADVANCED_CONFIGURATION": "Advanced", - "HELP": "Help" + "HELP": "Help", + "INVALID_SOURCE_PATH": "Invalid path for source game", + "DISCORD": "Discord", + "GITHUB WIKI": "Github Wiki", + "READTHEDOCS": "ReadTheDocs", + "ORIGINAL_GAME_FILE": "Original Game File", + "SELECT_SOURCE_GAME": "Select the source game", + "WII GAMES": "Wii games", + "ERROR": "Error", + "ERROR_INVALID_SOURCE_GAME": "The source game path is invalid", + "GAME_DIRECTORY_DESTINATION": "Game Directory Destination", + "SELECT_DESTINATION_GAME": "Select the destination game", + "WARNING": "Warning", + "WARNING_DESTINATION_GAME_NOT_WRITABLE": "The destination game path is not writable", + "INSTALL": "Install", + "ERROR_INVALID_DESTINATION_GAME": "The destination game path is invalid", + "WARNING_LOW_SPACE_CONTINUE": "The space left on the drive is low. Continue ?", + "INSTALLATION_COMPLETED": "Installation Completed", + "INSTALLATION_FINISHED_WITH_SUCCESS": "The installation ended successfully !", + "OPEN_MYSTUFF_SETTINGS": "Open MyStuff settings", + "THREADS_USAGE": "Threads usage", + "USE": "Use", + "THREADS": "threads", + "MESSAGE_FROM_MOD_AUTHOR": "Message from the author", + "NO_MESSAGE_FROM_AUTHOR": "< The author didn't left any message >", + "GLOBAL_MOD_SETTINGS": "Global mod settings", + "SPECIFIC_MOD_SETTINGS": "Specific mod settings", + "CONFIGURE_MYSTUFF_PATCH": "Configure the MyStuff patchs", + "DISABLED": "Disabled", + "NEW_PROFILE": "New Profile", + "DELETE_PROFILE": "Delete Profile", + "ADD_MYSTUFF": "Add MyStuff patch", + "REMOVE_MYSTUFF": "Remove MyStuff patch", + "MYSTUFF_PROFILE_ALREADY_EXIST": "This MyStuff profile already exist !", + "MYSTUFF_PROFILE_FORBIDDEN_NAME": "This MyStuff profile name can't be used !", + "SELECT_MYSTUFF": "Select MyStuff patch", + "TYPE_PREVIEW_WINDOW": "Type of preview window", + "NOT_FOUND": "not found", + "TYPE_MOD_SETTINGS": "Type of mod settings", + "BMG_LAYER_MODE": "bmg layer mode", + "IS_NOT_IMPLEMENTED": "is not implemented", + "IMAGE_LAYER_TYPE": "image layer type", + "OPERATION": "Operation", + "PATH": "Path", + "MODE": "Mode", + "SOURCE": "Source", + "OUTSIDE_ALLOWED_RANGE": "outside of allowed range", + "IN_PATCH": "in patch", + "INSTALLING_PATCH": "Installing the patch", + "PATCHING": "Patching", + "FORBIDDEN_TRACK_ATTRIBUTE": "Forbidden track attribute", + "FORBIDDEN_ARENA_ATTRIBUTE": "Forbidden arena attribute", + "EXTRACTING_AUTOADD_FILES": "Extracting autoadd files...", + "EXTRACTING_ORIGINAL_TRACKS": "Extracting original tracks...", + "INSTALLING_MYSTUFF": "Installing MyStuff", + "INSTALLING_ALL_MYSTUFF_PATCHS": "Installing all the mystuff patchs", + "PREPARING": "Preparing", + "SPECIAL_FILE": "special file", + "REPACKING": "Repacking", + "ALL_ARCHIVES": "all archives", + "INSTALLING_ALL": "Installing all", + "CONVERTING_GAME_TO": "Converting game to", + "DELETING_EXTRACTED_GAME": "Deleting the extracted game...", + "NOT_MKW_GAME": "Not a Mario Kart Wii game", + "GAME_ALREADY_MODDED": "This game is already modded", + "COPYING_GAME": "Copying Game...", + "EXTRACTING": "Extracting", + "ESTIMATED_TIME_REMAINING": "estimated time remaining", + "CHANGING_GAME_METADATA": "Changing game metadata...", + "EXTRACTION": "Extraction", + "MYSTUFF": "MyStuff", + "PREPARING_FILES": "Preparing files", + "PRE-PATCHING": "Pre-Patching", + "CONVERTING_TO_GAME_FILE": "Converting to game file", + "CANNOT_FIND_COLOR": "Can't find color", + "NORMALIZING_TRACKS": "Normalizing tracks", + "CANNOT_FIND_ORIGINAL_TRACK": "Can't find original track", + "INVALID_MACRO": "Invalid macro", + "INVALID_AST_TYPE": "Invalid ast type", + "MAGIC_ATTRIBUTE_ARE_FORBIDDEN": "Magic attribute are forbidden", + "CANNOT_SET_ATTRIBUTE": "Can't set value of attribute", + "CANNOT_SET_ENVIRONMENT": "Can't set value of environment", + "CANNOT_SET_ARGUMENT": "Can't set value of argument", + "CALLING_FUNCTION_NOT_ALLOWED": "Calling this function is not allowed", + "FORBIDDEN_SYNTAX": "Forbidden syntax", + "MAGIC_METHOD_FORBIDDEN": "Magic method are not allowed", + "CANNOT_GET_ERROR_MESSAGE": "Can't get the error message", + "RAISED": "raised", + "CANNOT_FIND_TOOL": "Can't find tool", + "IN_TOOLS_DIRECTORY": "in the tools directory.", + "CANNOT_EXTRACT_A_DIRECTORY": "Can't extract a directory" } } \ No newline at end of file diff --git a/source/gui/__init__.py b/source/gui/__init__.py index e860f16..60425ca 100644 --- a/source/gui/__init__.py +++ b/source/gui/__init__.py @@ -13,7 +13,7 @@ def better_gui_error(func: Callable) -> Callable: def wrapper(*args, **kwargs): try: return func(*args, **kwargs) - except: + except Exception: exc = traceback.format_exc() with Path("error.log").open("a", encoding="utf8") as log_file: log_file.write(f"{'#' * 20}\n{time.strftime('%Y/%M/%d %H:%m:%S')}\n\n{exc}\n\n") diff --git a/source/gui/install.py b/source/gui/install.py index 3a9115b..fe3dbbf 100644 --- a/source/gui/install.py +++ b/source/gui/install.py @@ -24,12 +24,12 @@ from source.wt.wit import Extension class SourceGameError(Exception): def __init__(self, path: Path | str): - super().__init__(f"Invalid path for source game : {path}") + super().__init__(_(f"ERROR_INVALID_SOURCE_GAME", " : ", path)) class DestinationGameError(Exception): def __init__(self, path: Path | str): - super().__init__(f"Invalid path for destination game : {path}") + super().__init__(_("ERROR_INVALID_DESTINATION_GAME", " : ", path)) class InstallerState(enum.Enum): @@ -183,7 +183,7 @@ class Menu(tkinter.Menu): self.root = master.root master.add_cascade(label=_("ADVANCED_CONFIGURATION"), menu=self) - self.add_command(label=_("OPEN_MYSTUFF_WINDOW"), command= mystuff.Window) + self.add_command(label=_("OPEN_MYSTUFF_SETTINGS"), command= mystuff.Window) self.threads_used = self.ThreadsUsed(self) @@ -192,7 +192,7 @@ class Menu(tkinter.Menu): super().__init__(master, tearoff=False) self.root = master.root - master.add_cascade(label=_("THREADS_USED"), menu=self) + master.add_cascade(label=_("THREADS_USAGE"), menu=self) self.variable = tkinter.IntVar(value=self.root.options["threads"]) @@ -214,12 +214,12 @@ class Menu(tkinter.Menu): super().__init__(master, tearoff=False) self.root = master.root - master.add_cascade(label="Help", menu=self) + master.add_cascade(label=_("HELP"), menu=self) self.menu_id = self.master.index(tkinter.END) - self.add_command(label="Discord", command=lambda: webbrowser.open(discord_url)) - self.add_command(label="Github Wiki", command=lambda: webbrowser.open(github_wiki_url)) - self.add_command(label=_("ReadTheDocs"), command=lambda: webbrowser.open(readthedocs_url)) + self.add_command(label=_("DISCORD"), command=lambda: webbrowser.open(discord_url)) + self.add_command(label=_("GITHUB WIKI"), command=lambda: webbrowser.open(github_wiki_url)) + self.add_command(label=_("READTHEDOCS"), command=lambda: webbrowser.open(readthedocs_url)) def set_installation_state(self, state: InstallerState) -> bool: """ @@ -248,7 +248,7 @@ class Menu(tkinter.Menu): # Select game frame class SourceGame(ttk.LabelFrame): def __init__(self, master: tkinter.Tk): - super().__init__(master, text="Original Game File") + super().__init__(master, text=_("ORIGINAL_GAME_FILE")) self.root = master.root self.columnconfigure(1, weight=1) @@ -311,7 +311,7 @@ class SourceGame(ttk.LabelFrame): # Select game destination frame class DestinationGame(ttk.LabelFrame): def __init__(self, master: tkinter.Tk): - super().__init__(master, text="Game Directory Destination") + super().__init__(master, text=_("GAME_DIRECTORY_DESTINATION")) self.root = master.root self.columnconfigure(1, weight=1) @@ -379,7 +379,7 @@ class DestinationGame(ttk.LabelFrame): # Install button class ButtonInstall(ttk.Button): def __init__(self, master: tkinter.Tk): - super().__init__(master, text="Install", command=self.install) + super().__init__(master, text=_("INSTALL"), command=self.install) self.root = master.root @threaded @@ -431,7 +431,7 @@ class ButtonInstall(ttk.Button): messagebox.showinfo( _("INSTALLATION_COMPLETED"), - f"{_('INSTALLATION_FINISHED_WITH_SUCCESS')}\n{_('MESSAGE_FROM_MOD_AUTHOR')}:\n\n{message}" + f"{_('INSTALLATION_FINISHED_WITH_SUCCESS')}\n{_('MESSAGE_FROM_MOD_AUTHOR')} :\n\n{message}" ) finally: diff --git a/source/gui/preview/__init__.py b/source/gui/preview/__init__.py index 3eff68a..d7f46d8 100644 --- a/source/gui/preview/__init__.py +++ b/source/gui/preview/__init__.py @@ -1,6 +1,7 @@ import tkinter from abc import abstractmethod, ABC from typing import Type, TYPE_CHECKING +from source.translation import translate as _ if TYPE_CHECKING: from source.mkw.ModConfig import ModConfig @@ -8,7 +9,7 @@ if TYPE_CHECKING: class InvalidPreviewWindowName(Exception): def __init__(self, name: str): - super().__init__(f"Error : Type of preview window '{name}' not found.") + super().__init__(_("TYPE_PREVIEW_WINDOW", " '", name, "' ", "NOT_FOUND")) class AbstractPreviewWindow(tkinter.Toplevel, ABC): diff --git a/source/mkw/ExtractedGame.py b/source/mkw/ExtractedGame.py index d0b4c77..d8e552f 100644 --- a/source/mkw/ExtractedGame.py +++ b/source/mkw/ExtractedGame.py @@ -8,6 +8,7 @@ from source.mkw.Patch.Patch import Patch from source.progress import Progress from source.wt import szs, lec, wit from source.wt.wstrt import StrPath +from source.translation import translate as _ if TYPE_CHECKING: from source.mkw.Game import Game @@ -15,7 +16,7 @@ if TYPE_CHECKING: class PathOutsideMod(Exception): def __init__(self, forbidden_path: Path, allowed_range: Path): - super().__init__(f"Error : path {forbidden_path} outside of allowed range {allowed_range}") + super().__init__(_("PATH", ' "', forbidden_path, '" ', "OUTSIDE_ALLOWED_RANGE", ' "', allowed_range, '" ')) class ExtractedGame: @@ -33,7 +34,7 @@ class ExtractedGame: Extract all the autoadd files from the game to destination_path :param destination_path: directory where the autoadd files will be extracted """ - yield Progress(description="Extracting autoadd files...", determinate=False) + yield Progress(description=_("EXTRACTING_AUTOADD_FILES"), determinate=False) szs.autoadd(self.path / "files/Race/Course/", destination_path) def extract_original_tracks(self, destination_path: "Path | str") -> Generator[Progress, None, None]: @@ -43,9 +44,12 @@ class ExtractedGame: """ destination_path = Path(destination_path) destination_path.mkdir(parents=True, exist_ok=True) - yield Progress(description="Extracting original tracks...", determinate=False) + yield Progress(description=_("EXTRACTING_ORIGINAL_TRACKS"), determinate=False) for track_file in (self.path / "files/Race/Course/").glob("*.szs"): - yield Progress(description=f"Extracting original tracks ({track_file.name})...", determinate=False) + + yield Progress(description=_("EXTRACTING_ORIGINAL_TRACKS", " (", track_file.name, ") ..."), + determinate=False) + if not (destination_path / track_file.name).exists(): track_file.rename(destination_path / track_file.name) else: track_file.unlink() @@ -56,7 +60,7 @@ class ExtractedGame: :mystuff_path: path to the MyStuff directory :return: """ - yield Progress(description=f"Installing MyStuff '{mystuff_path}'...", determinate=False) + yield Progress(description=_("INSTALLING_MYSTUFF", ' "', mystuff_path, '" ...'), determinate=False) mystuff_path = Path(mystuff_path) mystuff_rootfiles: dict[str, Path] = {} @@ -73,7 +77,7 @@ class ExtractedGame: Install multiple mystuff patch :param mystuff_paths: paths to all the mystuff patch """ - yield Progress(description="Installing all the mystuff patchs") + yield Progress(description=_("INSTALLING_ALL_MYSTUFF_PATCHS")) for mystuff_path in mystuff_paths: yield from self.install_mystuff(mystuff_path) @@ -83,7 +87,7 @@ class ExtractedGame: Prepare special files for the patch :return: the special files dict """ - yield Progress(description="Preparing ct_icon special file...", determinate=False) + yield Progress(description=_("PREPARING", " ct_icon ", "SPECIAL_FILE", "..."), determinate=False) ct_icons = BytesIO() mod_config.get_full_cticon().save(ct_icons, format="PNG") ct_icons.seek(0) @@ -100,11 +104,11 @@ class ExtractedGame: """ Repack all the .d directory into .szs files. """ - yield Progress(description=f"Repacking all szs", determinate=False) + yield Progress(description=_("REPACKING", " ", "ALL_ARCHIVES"), determinate=False) for extracted_szs in filter(lambda path: path.is_dir(), self.path.rglob("*.d")): # for every directory that end with a .d in the extracted game, recreate the szs - yield Progress(description=f"Repacking {extracted_szs} to szs", determinate=False) + yield Progress(description=_("REPACKING", ' "', extracted_szs, '"'), determinate=False) szs.create(extracted_szs, extracted_szs.with_suffix(".szs"), overwrite=True) shutil.rmtree(str(extracted_szs.resolve())) @@ -118,7 +122,7 @@ class ExtractedGame: :param cache_directory: Path to the cache :param mod_config: mod configuration """ - yield Progress(description="Patching LECODE.bin") + yield Progress(description=_("PATCHING", " LECODE.bin")) cache_directory = Path(cache_directory) cttracks_directory = Path(cttracks_directory) ogtracks_directory = Path(ogtracks_directory) @@ -159,7 +163,7 @@ class ExtractedGame: Used before the lecode patch is applied :param mod_config: the mod to install """ - yield Progress(description="Installing all Pre-Patch...", determinate=False) + yield Progress(description=_("INSTALLING_ALL", " Pre-Patchs..."), determinate=False) yield from self._install_all_patch(mod_config, "_PREPATCH/") def install_all_patch(self, mod_config: ModConfig) -> Generator[Progress, None, None]: @@ -168,7 +172,7 @@ class ExtractedGame: Used after the lecode patch is applied :param mod_config: the mod to install """ - yield Progress(description="Installing all Patch...", determinate=False) + yield Progress(description=_("INSTALLING_ALL", " Patchs..."), determinate=False) yield from self._install_all_patch(mod_config, "_PATCH/") def convert_to(self, output_type: wit.Extension) -> Generator[Progress, None, wit.WITPath | None]: @@ -177,7 +181,7 @@ class ExtractedGame: :param output_type: path to the destination of the game :output_type: format of the destination game """ - yield Progress(description=f"Converting game to {output_type}", determinate=False) + yield Progress(description=_("CONVERTING_GAME_TO", " ", output_type.name), determinate=False) if output_type == wit.Extension.FST: return destination_file = self.path.with_suffix(self.path.suffix + output_type.value) @@ -193,7 +197,7 @@ class ExtractedGame: destination_file=destination_file, ) - yield Progress(description="Deleting the extracted game...", determinate=False) + yield Progress(description=_("DELETING_EXTRACTED_GAME"), determinate=False) shutil.rmtree(self.path) return converted_game diff --git a/source/mkw/Game.py b/source/mkw/Game.py index c359828..57bce8a 100644 --- a/source/mkw/Game.py +++ b/source/mkw/Game.py @@ -6,18 +6,19 @@ from source.mkw.ModConfig import ModConfig from source.option import Option from source.progress import Progress from source.wt.wit import WITPath, Region, Extension +from source.translation import translate as _ class NotMKWGameError(Exception): def __init__(self, path: "Path | str"): path = Path(path) - super().__init__(f'Not a Mario Kart Wii game : "{path.name}"') + super().__init__(_("NOT_MKW_GAME", ' : "', path.name, '"')) class NotVanillaError(Exception): def __init__(self, path: "Path | str"): path = Path(path) - super().__init__(f'This game is already modded : "{path.name}"') + super().__init__(_("GAME_ALREADY_MODDED", ' : "', path.name, '"')) class Game: @@ -46,15 +47,15 @@ class Game: gen = self.wit_path.progress_extract_all(dest) if self.wit_path.extension == Extension.FST: - for _ in gen: yield Progress(description="Copying Game...", determinate=False) + for __ in gen: yield Progress(description=_("COPYING_GAME"), determinate=False) try: next(gen) except StopIteration as e: return e.value else: for gen_data in gen: yield Progress( - description=f'Extracting - {gen_data["percentage"]}% - (estimated time remaining: ' - f'{gen_data["estimation"] if gen_data["estimation"] is not None else "-:--"})', + description=_("EXTRACTING", " - ", gen_data["percentage"], "% - (", "ESTIMATED_TIME_REMAINING", ": " + f'{gen_data["estimation"] if gen_data["estimation"] is not None else "-:--"})'), max_step=100, set_step=gen_data["percentage"], determinate=True @@ -64,7 +65,7 @@ class Game: return e.value def edit(self, mod_config: ModConfig) -> Generator[Progress, None, None]: - yield Progress(description="Changing game metadata...", determinate=False) + yield Progress(description=_("CHANGING_GAME_METADATA"), determinate=False) self.wit_path.edit( name=mod_config.name, game_id=self.wit_path.id[:4] + mod_config.variant @@ -117,16 +118,16 @@ class Game: if not self.is_vanilla(): raise NotVanillaError(self.wit_path.path) # extract the game - yield Progress(title="Extraction", set_part=1) + yield Progress(title=_("EXTRACTION"), set_part=1) yield from self.extract(extracted_game.path) # install mystuff - yield Progress(title="MyStuff", set_part=2) + yield Progress(title=_("MYSTUFF"), set_part=2) mystuff_data = options["mystuff_packs"].get(options["mystuff_pack_selected"]) if mystuff_data is not None: yield from extracted_game.install_multiple_mystuff(mystuff_data["paths"]) # prepare the cache - yield Progress(title="Preparing files", set_part=3) + yield Progress(title=_("PREPARING_FILES"), set_part=3) yield from extracted_game.extract_autoadd(cache_autoadd_directory) yield from extracted_game.extract_original_tracks(cache_ogtracks_directory) yield from mod_config.normalize_all_tracks( @@ -139,7 +140,7 @@ class Game: yield from extracted_game.prepare_special_file(mod_config) # prepatch the game - yield Progress(title="Pre-Patching", set_part=4) + yield Progress(title=_("PRE-PATCHING"), set_part=4) yield from extracted_game.install_all_prepatch(mod_config) yield Progress(title="LE-CODE", set_part=5) @@ -150,11 +151,11 @@ class Game: cache_ogtracks_directory, ) - yield Progress(title="Patching", set_part=6) + yield Progress(title=_("PATCHING"), set_part=6) yield from extracted_game.install_all_patch(mod_config) yield from extracted_game.recreate_all_szs() # convert the extracted game into a file - yield Progress(title="Converting to game file", set_part=7) + yield Progress(title=_("CONVERTING_TO_GAME_FILE"), set_part=7) converted_game: WITPath = yield from extracted_game.convert_to(output_type) if converted_game is not None: yield from Game(converted_game.path).edit(mod_config) diff --git a/source/mkw/MKWColor.py b/source/mkw/MKWColor.py index 3090619..c96f38b 100644 --- a/source/mkw/MKWColor.py +++ b/source/mkw/MKWColor.py @@ -1,6 +1,9 @@ +from source.translation import translate as _ + + class ColorNotFound(Exception): def __init__(self, color_data: any): - super().__init__(f'Can\'t find color "{color_data}"') + super().__init__(_("CANNOT_FIND_COLOR", ' "', color_data, '"')) class MKWColor: diff --git a/source/mkw/ModConfig.py b/source/mkw/ModConfig.py index 3a45b2a..4ae2c29 100644 --- a/source/mkw/ModConfig.py +++ b/source/mkw/ModConfig.py @@ -1,7 +1,7 @@ import shutil from pathlib import Path from typing import Generator, Callable, Iterator, Iterable, TYPE_CHECKING - +import json from PIL import Image from source import threaded @@ -10,12 +10,11 @@ from source.mkw.Cup import Cup from source.mkw.MKWColor import bmg_color_text, bmg_color_raw from source.mkw.ModSettings import AbstractModSettings from source.mkw.Track import CustomTrack, DefaultTrack, Arena -import json - from source.mkw.OriginalTrack import OriginalTrack from source.progress import Progress from source.safe_eval import safe_eval, multiple_safe_eval from source.wt.szs import SZSPath +from source.translation import translate as _ if TYPE_CHECKING: from source import TemplateMultipleSafeEval, TemplateSafeEval, Env @@ -414,7 +413,7 @@ class ModConfig: :param autoadd_path: autoadd directory :param destination_path: destination where the files are converted """ - yield Progress(description="Normalizing track...") + yield Progress(description=_("NORMALIZING_TRACKS")) destination_path = Path(destination_path) original_tracks_path = Path(original_tracks_path) destination_path.mkdir(parents=True, exist_ok=True) @@ -429,7 +428,7 @@ class ModConfig: nonlocal normalize_threads yield Progress( - description=f"Normalizing tracks :\n" + "\n".join(thread['name'] for thread in normalize_threads) + description=_("NORMALIZING_TRACKS", " :\n" + "\n".join(thread['name'] for thread in normalize_threads)) ) normalize_threads = list(filter(lambda thread: thread["thread"].is_alive(), normalize_threads)) diff --git a/source/mkw/ModSettings/__init__.py b/source/mkw/ModSettings/__init__.py index 43ae339..b8c875b 100644 --- a/source/mkw/ModSettings/__init__.py +++ b/source/mkw/ModSettings/__init__.py @@ -2,10 +2,12 @@ import tkinter from tkinter import ttk from abc import ABC, abstractmethod +from source.translation import translate as _ + class InvalidSettingsType(Exception): def __init__(self, settings_type: str): - super().__init__(f"Error : Type of mod settings '{settings_type}' not found.") + super().__init__(_("TYPE_MOD_SETTINGS", " '", settings_type, "' ", "NOT_FOUND")) class AbstractModSettings(ABC): diff --git a/source/mkw/OriginalTrack.py b/source/mkw/OriginalTrack.py index 202f37a..e82266b 100644 --- a/source/mkw/OriginalTrack.py +++ b/source/mkw/OriginalTrack.py @@ -1,6 +1,9 @@ +from source.translation import translate as _ + + class OriginalTrackNotFound(Exception): def __init__(self, track_data: any): - super().__init__(f'Can\'t find original track "{track_data}"') + super().__init__(_("CANNOT_FIND_ORIGINAL_TRACK", ' "', track_data, '" ')) class OriginalTrack: diff --git a/source/mkw/Patch/Patch.py b/source/mkw/Patch/Patch.py index 4b3685f..5da6a08 100644 --- a/source/mkw/Patch/Patch.py +++ b/source/mkw/Patch/Patch.py @@ -2,6 +2,8 @@ from pathlib import Path from typing import Generator, IO, TYPE_CHECKING from source.progress import Progress +from source.translation import translate as _ + if TYPE_CHECKING: from source.mkw.ModConfig import ModConfig @@ -27,7 +29,7 @@ class Patch: :param extracted_game: the extracted game """ from source.mkw.Patch.PatchDirectory import PatchDirectory - yield Progress(description=f"Installing the patch", determinate=False) + yield Progress(description=_("INSTALLING_PATCH"), determinate=False) # take all the files in the root directory, and patch them into the game. # Patch is not directly applied to the root to avoid custom configuration diff --git a/source/mkw/Patch/PatchDirectory.py b/source/mkw/Patch/PatchDirectory.py index bea88da..4ba6ae9 100644 --- a/source/mkw/Patch/PatchDirectory.py +++ b/source/mkw/Patch/PatchDirectory.py @@ -4,6 +4,7 @@ from typing import Generator, TYPE_CHECKING from source.mkw.Patch import PathOutsidePatch, InvalidPatchMode from source.mkw.Patch.PatchObject import PatchObject from source.progress import Progress +from source.translation import translate as _ if TYPE_CHECKING: from source.mkw.ExtractedGame import ExtractedGame @@ -27,7 +28,7 @@ class PatchDirectory(PatchObject): """ patch a subdirectory of the game with the PatchDirectory """ - yield Progress(description=f"Patching {game_subpath}") + yield Progress(description=_("PATCHING", " ", game_subpath)) # check if the directory should be patched if not self.is_enabled(extracted_game): return diff --git a/source/mkw/Patch/PatchFile.py b/source/mkw/Patch/PatchFile.py index a6d7e26..ac715bc 100644 --- a/source/mkw/Patch/PatchFile.py +++ b/source/mkw/Patch/PatchFile.py @@ -7,6 +7,7 @@ from source.mkw.Patch.PatchOperation import AbstractPatchOperation from source.mkw.Patch.PatchObject import PatchObject from source.progress import Progress from source.wt.szs import SZSPath +from source.translation import translate if TYPE_CHECKING: from source.mkw.ExtractedGame import ExtractedGame @@ -81,7 +82,8 @@ class PatchFile(PatchObject): """ patch a subfile of the game with the PatchFile """ - yield Progress(description=f"Patching {game_subpath}") + yield Progress(description=translate("PATCHING", " ", game_subpath)) + # translate is not renamed "_" here because it is used to drop useless value in unpacking # check if the file should be patched if not self.is_enabled(extracted_game): return @@ -119,7 +121,7 @@ class PatchFile(PatchObject): if not game_subfile.relative_to(extracted_game.path): raise PathOutsidePatch(game_subfile, extracted_game.path) - yield Progress(description=f"Patching {game_subfile}") + yield Progress(description=translate("PATCHING", " ", game_subfile)) # if the source is the game, then recalculate the content for every game subfile if self.configuration["source"] == "game": diff --git a/source/mkw/Patch/PatchOperation/BmgTxtEditor/__init__.py b/source/mkw/Patch/PatchOperation/BmgTxtEditor/__init__.py index 0381e1f..303e074 100644 --- a/source/mkw/Patch/PatchOperation/BmgTxtEditor/__init__.py +++ b/source/mkw/Patch/PatchOperation/BmgTxtEditor/__init__.py @@ -3,6 +3,7 @@ from typing import IO, TYPE_CHECKING from abc import ABC, abstractmethod from source.mkw.Patch.PatchOperation import AbstractPatchOperation +from source.translation import translate as _ if TYPE_CHECKING: from source.mkw.Patch import Patch @@ -10,7 +11,7 @@ if TYPE_CHECKING: class InvalidBmgLayerMode(Exception): def __init__(self, layer_mode: str): - super().__init__(f"Error : bmg layer mode \"{layer_mode}\" is not implemented") + super().__init__(_("BMG_LAYER_MODE", ' "', layer_mode, '" ', "IS_NOT_IMPLEMENTED")) class AbstractLayer(ABC): diff --git a/source/mkw/Patch/PatchOperation/ImageEditor/__init__.py b/source/mkw/Patch/PatchOperation/ImageEditor/__init__.py index 263ba7f..bb02b8a 100644 --- a/source/mkw/Patch/PatchOperation/ImageEditor/__init__.py +++ b/source/mkw/Patch/PatchOperation/ImageEditor/__init__.py @@ -4,6 +4,7 @@ from abc import abstractmethod, ABC from PIL import Image from source.mkw.Patch.PatchOperation import AbstractPatchOperation +from source.translation import translate as _ if TYPE_CHECKING: from source.mkw.Patch import Patch @@ -11,7 +12,7 @@ if TYPE_CHECKING: class InvalidImageLayerType(Exception): def __init__(self, layer_type: str): - super().__init__(f"Error : image layer type \"{layer_type}\" is not implemented") + super().__init__(_("IMAGE_LAYER_TYPE", ' "', layer_type, '" ', "IS_NOT_IMPLEMENTED")) class AbstractLayer(ABC): diff --git a/source/mkw/Patch/PatchOperation/__init__.py b/source/mkw/Patch/PatchOperation/__init__.py index fc13f66..fd69e34 100644 --- a/source/mkw/Patch/PatchOperation/__init__.py +++ b/source/mkw/Patch/PatchOperation/__init__.py @@ -1,13 +1,15 @@ from abc import ABC, abstractmethod from typing import IO, Type, TYPE_CHECKING +from source.translation import translate as _ + if TYPE_CHECKING: from source.mkw.Patch import Patch class InvalidPatchOperation(Exception): def __init__(self, operation: str): - super().__init__(f"Error : operation \"{operation}\" is not implemented") + super().__init__(_("OPERATION", ' "', operation, '" ', "IS_NOT_IMPLEMENTED")) class AbstractPatchOperation(ABC): diff --git a/source/mkw/Patch/__init__.py b/source/mkw/Patch/__init__.py index 8b60381..f52bf8b 100644 --- a/source/mkw/Patch/__init__.py +++ b/source/mkw/Patch/__init__.py @@ -1,5 +1,6 @@ from pathlib import Path from typing import TYPE_CHECKING +from source.translation import translate as _ if TYPE_CHECKING: from source.mkw.Patch import Patch @@ -8,14 +9,16 @@ if TYPE_CHECKING: class PathOutsidePatch(Exception): def __init__(self, forbidden_path: Path, allowed_range: Path): - super().__init__(f'Error : path "{forbidden_path}" outside of allowed range {allowed_range}') + super().__init__(_("PATH", ' "', forbidden_path, '" ', "OUTSIDE_ALLOWED_RANGE", ' "', {allowed_range}, '" ')) class InvalidPatchMode(Exception): def __init__(self, patch: "PatchObject", mode: str): - super().__init__(f'Error : mode "{mode}" is not implemented (in patch : "{patch.full_path}")') + super().__init__(_("MODE", ' "', mode, '" ', "IS_NOT_IMPLEMENTED", + "(", "IN_PATCH", ' : "', patch.full_path, '")')) class InvalidSourceMode(Exception): def __init__(self, patch: "PatchObject", source: str): - super().__init__(f'Error : source "{source}" is not implemented (in patch : "{patch.full_path}")') + super().__init__(_("SOURCE", ' "', source, '" ', "IS_NOT_IMPLEMENTED", + "(", "IN_PATCH", ' : "', patch.full_path, '")')) diff --git a/source/mkw/Track/AbstractTrack.py b/source/mkw/Track/AbstractTrack.py index 4cf97cb..206b1cc 100644 --- a/source/mkw/Track/AbstractTrack.py +++ b/source/mkw/Track/AbstractTrack.py @@ -2,6 +2,7 @@ from abc import ABC, abstractmethod from typing import Generator, TYPE_CHECKING from source.mkw import Slot, Tag, ModConfig +from source.translation import translate as _ if TYPE_CHECKING: from source import TemplateMultipleSafeEval @@ -9,7 +10,7 @@ if TYPE_CHECKING: class TrackForbiddenCustomAttribute(Exception): def __init__(self, attribute_name: str): - super().__init__(f"Forbidden track attribute : {attribute_name!r}") + super().__init__(_("FORBIDDEN_TRACK_ATTRIBUTE", " : ", repr(attribute_name))) class AbstractTrack(ABC): diff --git a/source/mkw/Track/Arena.py b/source/mkw/Track/Arena.py index f574df7..1dc91f7 100644 --- a/source/mkw/Track/Arena.py +++ b/source/mkw/Track/Arena.py @@ -2,14 +2,16 @@ from typing import TYPE_CHECKING from source.mkw import Slot, Tag from source.mkw.Track.RealArenaTrack import RealArenaTrack +from source.translation import translate as _ if TYPE_CHECKING: from source import TemplateMultipleSafeEval + from source.mkw.ModConfig import ModConfig class ArenaForbiddenCustomAttribute(Exception): def __init__(self, attribute_name: str): - super().__init__(f"Forbidden arena attribute : {attribute_name!r}") + super().__init__(_("FORBIDDEN_ARENA_ATTRIBUTE", " : ", repr(attribute_name))) class Arena(RealArenaTrack): diff --git a/source/mkw/Track/CustomTrack.py b/source/mkw/Track/CustomTrack.py index 8ab1cfa..b655e90 100644 --- a/source/mkw/Track/CustomTrack.py +++ b/source/mkw/Track/CustomTrack.py @@ -1,7 +1,10 @@ +from typing import TYPE_CHECKING + from source.mkw.Track.AbstractTrack import AbstractTrack from source.mkw.Track.RealArenaTrack import RealArenaTrack -ModConfig: any +if TYPE_CHECKING: + from source.mkw.ModConfig import ModConfig class CustomTrack(RealArenaTrack, AbstractTrack): diff --git a/source/mkw/Track/RealArenaTrack.py b/source/mkw/Track/RealArenaTrack.py index 191ece0..495a735 100644 --- a/source/mkw/Track/RealArenaTrack.py +++ b/source/mkw/Track/RealArenaTrack.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: from source import TemplateMultipleSafeEval + from source.mkw import Tag from source.mkw.ModConfig import ModConfig @@ -11,7 +12,7 @@ class RealArenaTrack: (For example, DefaultTrack is not considered a real track class) """ - tags: list + tags: list["Tag"] def get_tag_template(self, mod_config: "ModConfig", template_name: str, default: any = None) -> any: """ diff --git a/source/safe_eval/macros.py b/source/safe_eval/macros.py index d6a0c8e..e8a6520 100644 --- a/source/safe_eval/macros.py +++ b/source/safe_eval/macros.py @@ -1,6 +1,9 @@ import re from typing import TYPE_CHECKING +from source.translation import translate as _ + + if TYPE_CHECKING: from source import TemplateSafeEval @@ -9,7 +12,7 @@ MACRO_START, MACRO_END = "##", "##" class NotImplementedMacro(Exception): def __init__(self, macro: str): - super().__init__(f"Invalid macro while parsing macros:\n{macro}") + super().__init__(_("INVALID_MACRO", ' : "', macro, '"')) def replace_macro(template: str, macros: dict[str, "TemplateSafeEval"]) -> str: diff --git a/source/safe_eval/safe_eval.py b/source/safe_eval/safe_eval.py index 22058cd..ecb3f9c 100644 --- a/source/safe_eval/safe_eval.py +++ b/source/safe_eval/safe_eval.py @@ -4,6 +4,8 @@ from typing import TYPE_CHECKING, Iterable, Callable from source.safe_eval.macros import replace_macro from source.safe_eval.safe_function import get_all_safe_functions +from source.translation import translate as _ + if TYPE_CHECKING: from source import TemplateSafeEval, Env @@ -59,7 +61,7 @@ def safe_eval(template: "TemplateSafeEval", env: "Env" = None, macros: dict[str, # convert the template to an ast expression stmt: ast.stmt = ast.parse(template).body[0] if not isinstance(stmt, ast.Expr): - raise SafeEvalException(f'Invalid ast type : "{type(stmt).__name__}"') + raise SafeEvalException(_("INVALID_AST_TYPE", ' : "', type(stmt).__name__, '"')) # check every node for disabled expression for node in ast.walk(stmt): @@ -69,20 +71,20 @@ def safe_eval(template: "TemplateSafeEval", env: "Env" = None, macros: dict[str, case ast.Attribute: # ban all magical function, disabling the __class__.__bases__[0] ... tricks if "__" in node.attr: - raise SafeEvalException(f'Magic attribute are forbidden : "{node.attr}"') + raise SafeEvalException(_("MAGIC_ATTRIBUTE_ARE_FORBIDDEN", ' : "', node.attr, '"')) # ban modification to environment if isinstance(node.ctx, ast.Store): - raise SafeEvalException(f'Can\'t set value of attribute : "{node.attr}"') + raise SafeEvalException(_("CANNOT_SET_ATTRIBUTE", ' : "', node.attr, '"')) # when accessing any variable case ast.Name: # ban modification to environment, but allow custom variable to be changed if isinstance(node.ctx, ast.Store): if node.id in globals_ | locals_: - raise SafeEvalException(f'Can\'t set value of environment : "{node.id}"') + raise SafeEvalException(_("CANNOT_SET_ENVIRONMENT", ' : "', node.id, '"')) elif node.id in args: - raise SafeEvalException(f'Can\'t set value of argument : "{node.id}"') + raise SafeEvalException(_("CANNOT_SET_ARGUMENT", ' : "', node.id, '"')) # when calling any function case ast.Call: @@ -91,10 +93,10 @@ def safe_eval(template: "TemplateSafeEval", env: "Env" = None, macros: dict[str, if isinstance(callnode, ast.Attribute): for attrnode in ast.walk(callnode.value): if isinstance(attrnode, ast.Name): - if attrnode.id in globals_ | locals_: - raise SafeEvalException(f'Calling this function is not allowed : "{callnode.attr}"') - if attrnode.id in args: - raise SafeEvalException(f'Calling this function is not allowed : "{callnode.attr}"') + if attrnode.id in globals_ | locals_ or attrnode.id in args: + raise SafeEvalException( + _("CALLING_FUNCTION_NOT_ALLOWED", ' : "', callnode.attr, '"') + ) # when assigning a value with ":=" case ast.NamedExpr: @@ -115,7 +117,7 @@ def safe_eval(template: "TemplateSafeEval", env: "Env" = None, macros: dict[str, ast.ClassDef | # Declaring class could maybe allow for dangerous calls ast.AsyncFor | ast.AsyncWith | ast.AsyncFunctionDef | ast.Await # Just in case ): - raise SafeEvalException(f'Forbidden syntax : "{type(node).__name__}"') + raise SafeEvalException(_("FORBIDDEN_SYNTAX", ' : "', type(node).__name__, '"')) # embed the whole expression into a lambda expression stmt.value = ast.Lambda( diff --git a/source/safe_eval/safe_function.py b/source/safe_eval/safe_function.py index 9aa7d75..b3c1e59 100644 --- a/source/safe_eval/safe_function.py +++ b/source/safe_eval/safe_function.py @@ -1,7 +1,9 @@ from typing import Callable, Generator, TYPE_CHECKING +from source.translation import translate as _ + if TYPE_CHECKING: - from source import TemplateSafeEval, Env + from source import TemplateSafeEval def get_all_safe_functions() -> Generator[list[Callable], None, None]: @@ -33,7 +35,7 @@ class safe_function: """ Same as normal getattr, but magic attribute are banned """ - if "__" in name: raise Exception(f'Magic method are not allowed : "{name}"') + if "__" in name: raise Exception(_("MAGIC_METHOD_FORBIDDEN", ' : "', name, '"')) return getattr(obj, name, default) @staticmethod diff --git a/source/wt/__init__.py b/source/wt/__init__.py index a56b873..3571b93 100644 --- a/source/wt/__init__.py +++ b/source/wt/__init__.py @@ -3,6 +3,8 @@ from pathlib import Path import os from typing import Callable +from source.translation import translate as _ + class WTError(Exception): def __init__(self, tools_path: Path | str, return_code: int): @@ -14,14 +16,14 @@ class WTError(Exception): creationflags=subprocess.CREATE_NO_WINDOW, ).stdout.decode() except subprocess.CalledProcessError as e: - error = "- Can't get the error message -" + error = _("- ", "CANNOT_GET_ERROR_MESSAGE", " -") - super().__init__(f"{tools_path} raised {return_code} :\n{error}\n") + super().__init__(_(tools_path, " ", "RAISED", " ", return_code, ":\n", error, "\n")) class MissingWTError(Exception): def __init__(self, tool_name: str): - super().__init__(f"Can't find tools \"{tool_name}\" in the tools directory.") + super().__init__(_("CANNOT_FIND_TOOL", ' "', tool_name, '" ', "IN_TOOLS_DIRECTORY")) tools_dir = Path("./tools/") diff --git a/source/wt/szs.py b/source/wt/szs.py index fc19695..b400cf5 100644 --- a/source/wt/szs.py +++ b/source/wt/szs.py @@ -1,5 +1,6 @@ from source.wt import * -from source.wt import _run, _run_dict +from source.translation import translate as _ + tools_path = tools_szs_dir / "wszst" @@ -18,7 +19,7 @@ def autoadd(course_directory: Path | str, destination_path: Path | str) -> Path: :return: directory where the autoadd files were extracted """ destination_path = Path(destination_path) - _run(tools_path, "AUTOADD", course_directory, "-D", destination_path) + _tools_run("AUTOADD", course_directory, "-D", destination_path) return destination_path @@ -191,7 +192,7 @@ class SZSSubPath: :param dest: destination path :return: the extracted file path """ - if self.is_dir(): raise ValueError("Can't extract a directory") + if self.is_dir(): raise ValueError(_("CANNOT_EXTRACT_A_DIRECTORY")) dest: Path = Path(dest) if dest.is_dir(): dest /= self.basename()