the code is now fully translatable

This commit is contained in:
Faraphel 2022-08-15 23:06:23 +02:00
parent 65e7fb7118
commit ec94ddece8
26 changed files with 214 additions and 82 deletions

View file

@ -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"
}
}

View file

@ -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")

View file

@ -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:

View file

@ -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):

View file

@ -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

View file

@ -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)

View file

@ -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:

View file

@ -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))

View file

@ -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):

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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":

View file

@ -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):

View file

@ -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):

View file

@ -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):

View file

@ -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, '")'))

View file

@ -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):

View file

@ -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):

View file

@ -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):

View file

@ -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:
"""

View file

@ -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:

View file

@ -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(

View file

@ -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

View file

@ -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/")

View file

@ -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()