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", "LANGUAGE_SELECTION": "Language",
"TRACK_FILTER": "Track Filters", "TRACK_FILTER": "Track Filters",
"ADVANCED_CONFIGURATION": "Advanced", "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): def wrapper(*args, **kwargs):
try: return func(*args, **kwargs) try: return func(*args, **kwargs)
except: except Exception:
exc = traceback.format_exc() exc = traceback.format_exc()
with Path("error.log").open("a", encoding="utf8") as log_file: 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") 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): class SourceGameError(Exception):
def __init__(self, path: Path | str): 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): class DestinationGameError(Exception):
def __init__(self, path: Path | str): 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): class InstallerState(enum.Enum):
@ -183,7 +183,7 @@ class Menu(tkinter.Menu):
self.root = master.root self.root = master.root
master.add_cascade(label=_("ADVANCED_CONFIGURATION"), menu=self) 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) self.threads_used = self.ThreadsUsed(self)
@ -192,7 +192,7 @@ class Menu(tkinter.Menu):
super().__init__(master, tearoff=False) super().__init__(master, tearoff=False)
self.root = master.root 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"]) self.variable = tkinter.IntVar(value=self.root.options["threads"])
@ -214,12 +214,12 @@ class Menu(tkinter.Menu):
super().__init__(master, tearoff=False) super().__init__(master, tearoff=False)
self.root = master.root 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.menu_id = self.master.index(tkinter.END)
self.add_command(label="Discord", command=lambda: webbrowser.open(discord_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=_("GITHUB WIKI"), command=lambda: webbrowser.open(github_wiki_url))
self.add_command(label=_("ReadTheDocs"), command=lambda: webbrowser.open(readthedocs_url)) self.add_command(label=_("READTHEDOCS"), command=lambda: webbrowser.open(readthedocs_url))
def set_installation_state(self, state: InstallerState) -> bool: def set_installation_state(self, state: InstallerState) -> bool:
""" """
@ -248,7 +248,7 @@ class Menu(tkinter.Menu):
# Select game frame # Select game frame
class SourceGame(ttk.LabelFrame): class SourceGame(ttk.LabelFrame):
def __init__(self, master: tkinter.Tk): 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.root = master.root
self.columnconfigure(1, weight=1) self.columnconfigure(1, weight=1)
@ -311,7 +311,7 @@ class SourceGame(ttk.LabelFrame):
# Select game destination frame # Select game destination frame
class DestinationGame(ttk.LabelFrame): class DestinationGame(ttk.LabelFrame):
def __init__(self, master: tkinter.Tk): 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.root = master.root
self.columnconfigure(1, weight=1) self.columnconfigure(1, weight=1)
@ -379,7 +379,7 @@ class DestinationGame(ttk.LabelFrame):
# Install button # Install button
class ButtonInstall(ttk.Button): class ButtonInstall(ttk.Button):
def __init__(self, master: tkinter.Tk): 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 self.root = master.root
@threaded @threaded
@ -431,7 +431,7 @@ class ButtonInstall(ttk.Button):
messagebox.showinfo( messagebox.showinfo(
_("INSTALLATION_COMPLETED"), _("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: finally:

View file

@ -1,6 +1,7 @@
import tkinter import tkinter
from abc import abstractmethod, ABC from abc import abstractmethod, ABC
from typing import Type, TYPE_CHECKING from typing import Type, TYPE_CHECKING
from source.translation import translate as _
if TYPE_CHECKING: if TYPE_CHECKING:
from source.mkw.ModConfig import ModConfig from source.mkw.ModConfig import ModConfig
@ -8,7 +9,7 @@ if TYPE_CHECKING:
class InvalidPreviewWindowName(Exception): class InvalidPreviewWindowName(Exception):
def __init__(self, name: str): 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): 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.progress import Progress
from source.wt import szs, lec, wit from source.wt import szs, lec, wit
from source.wt.wstrt import StrPath from source.wt.wstrt import StrPath
from source.translation import translate as _
if TYPE_CHECKING: if TYPE_CHECKING:
from source.mkw.Game import Game from source.mkw.Game import Game
@ -15,7 +16,7 @@ if TYPE_CHECKING:
class PathOutsideMod(Exception): class PathOutsideMod(Exception):
def __init__(self, forbidden_path: Path, allowed_range: Path): 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: class ExtractedGame:
@ -33,7 +34,7 @@ class ExtractedGame:
Extract all the autoadd files from the game to destination_path Extract all the autoadd files from the game to destination_path
:param destination_path: directory where the autoadd files will be extracted :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) szs.autoadd(self.path / "files/Race/Course/", destination_path)
def extract_original_tracks(self, destination_path: "Path | str") -> Generator[Progress, None, None]: 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 = Path(destination_path)
destination_path.mkdir(parents=True, exist_ok=True) 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"): 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) if not (destination_path / track_file.name).exists(): track_file.rename(destination_path / track_file.name)
else: track_file.unlink() else: track_file.unlink()
@ -56,7 +60,7 @@ class ExtractedGame:
:mystuff_path: path to the MyStuff directory :mystuff_path: path to the MyStuff directory
:return: :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_path = Path(mystuff_path)
mystuff_rootfiles: dict[str, Path] = {} mystuff_rootfiles: dict[str, Path] = {}
@ -73,7 +77,7 @@ class ExtractedGame:
Install multiple mystuff patch Install multiple mystuff patch
:param mystuff_paths: paths to all the 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: for mystuff_path in mystuff_paths:
yield from self.install_mystuff(mystuff_path) yield from self.install_mystuff(mystuff_path)
@ -83,7 +87,7 @@ class ExtractedGame:
Prepare special files for the patch Prepare special files for the patch
:return: the special files dict :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() ct_icons = BytesIO()
mod_config.get_full_cticon().save(ct_icons, format="PNG") mod_config.get_full_cticon().save(ct_icons, format="PNG")
ct_icons.seek(0) ct_icons.seek(0)
@ -100,11 +104,11 @@ class ExtractedGame:
""" """
Repack all the .d directory into .szs files. 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 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 # 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) szs.create(extracted_szs, extracted_szs.with_suffix(".szs"), overwrite=True)
shutil.rmtree(str(extracted_szs.resolve())) shutil.rmtree(str(extracted_szs.resolve()))
@ -118,7 +122,7 @@ class ExtractedGame:
:param cache_directory: Path to the cache :param cache_directory: Path to the cache
:param mod_config: mod configuration :param mod_config: mod configuration
""" """
yield Progress(description="Patching LECODE.bin") yield Progress(description=_("PATCHING", " LECODE.bin"))
cache_directory = Path(cache_directory) cache_directory = Path(cache_directory)
cttracks_directory = Path(cttracks_directory) cttracks_directory = Path(cttracks_directory)
ogtracks_directory = Path(ogtracks_directory) ogtracks_directory = Path(ogtracks_directory)
@ -159,7 +163,7 @@ class ExtractedGame:
Used before the lecode patch is applied Used before the lecode patch is applied
:param mod_config: the mod to install :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/") yield from self._install_all_patch(mod_config, "_PREPATCH/")
def install_all_patch(self, mod_config: ModConfig) -> Generator[Progress, None, None]: 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 Used after the lecode patch is applied
:param mod_config: the mod to install :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/") yield from self._install_all_patch(mod_config, "_PATCH/")
def convert_to(self, output_type: wit.Extension) -> Generator[Progress, None, wit.WITPath | None]: 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 :param output_type: path to the destination of the game
:output_type: format of the destination 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 if output_type == wit.Extension.FST: return
destination_file = self.path.with_suffix(self.path.suffix + output_type.value) destination_file = self.path.with_suffix(self.path.suffix + output_type.value)
@ -193,7 +197,7 @@ class ExtractedGame:
destination_file=destination_file, 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) shutil.rmtree(self.path)
return converted_game return converted_game

View file

@ -6,18 +6,19 @@ from source.mkw.ModConfig import ModConfig
from source.option import Option from source.option import Option
from source.progress import Progress from source.progress import Progress
from source.wt.wit import WITPath, Region, Extension from source.wt.wit import WITPath, Region, Extension
from source.translation import translate as _
class NotMKWGameError(Exception): class NotMKWGameError(Exception):
def __init__(self, path: "Path | str"): def __init__(self, path: "Path | str"):
path = Path(path) path = Path(path)
super().__init__(f'Not a Mario Kart Wii game : "{path.name}"') super().__init__(_("NOT_MKW_GAME", ' : "', path.name, '"'))
class NotVanillaError(Exception): class NotVanillaError(Exception):
def __init__(self, path: "Path | str"): def __init__(self, path: "Path | str"):
path = Path(path) path = Path(path)
super().__init__(f'This game is already modded : "{path.name}"') super().__init__(_("GAME_ALREADY_MODDED", ' : "', path.name, '"'))
class Game: class Game:
@ -46,15 +47,15 @@ class Game:
gen = self.wit_path.progress_extract_all(dest) gen = self.wit_path.progress_extract_all(dest)
if self.wit_path.extension == Extension.FST: 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) try: next(gen)
except StopIteration as e: return e.value except StopIteration as e: return e.value
else: else:
for gen_data in gen: for gen_data in gen:
yield Progress( yield Progress(
description=f'Extracting - {gen_data["percentage"]}% - (estimated time remaining: ' description=_("EXTRACTING", " - ", gen_data["percentage"], "% - (", "ESTIMATED_TIME_REMAINING", ": "
f'{gen_data["estimation"] if gen_data["estimation"] is not None else "-:--"})', f'{gen_data["estimation"] if gen_data["estimation"] is not None else "-:--"})'),
max_step=100, max_step=100,
set_step=gen_data["percentage"], set_step=gen_data["percentage"],
determinate=True determinate=True
@ -64,7 +65,7 @@ class Game:
return e.value return e.value
def edit(self, mod_config: ModConfig) -> Generator[Progress, None, None]: 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( self.wit_path.edit(
name=mod_config.name, name=mod_config.name,
game_id=self.wit_path.id[:4] + mod_config.variant 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) if not self.is_vanilla(): raise NotVanillaError(self.wit_path.path)
# extract the game # extract the game
yield Progress(title="Extraction", set_part=1) yield Progress(title=_("EXTRACTION"), set_part=1)
yield from self.extract(extracted_game.path) yield from self.extract(extracted_game.path)
# install mystuff # 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"]) 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"]) if mystuff_data is not None: yield from extracted_game.install_multiple_mystuff(mystuff_data["paths"])
# prepare the cache # 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_autoadd(cache_autoadd_directory)
yield from extracted_game.extract_original_tracks(cache_ogtracks_directory) yield from extracted_game.extract_original_tracks(cache_ogtracks_directory)
yield from mod_config.normalize_all_tracks( yield from mod_config.normalize_all_tracks(
@ -139,7 +140,7 @@ class Game:
yield from extracted_game.prepare_special_file(mod_config) yield from extracted_game.prepare_special_file(mod_config)
# prepatch the game # 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 from extracted_game.install_all_prepatch(mod_config)
yield Progress(title="LE-CODE", set_part=5) yield Progress(title="LE-CODE", set_part=5)
@ -150,11 +151,11 @@ class Game:
cache_ogtracks_directory, 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.install_all_patch(mod_config)
yield from extracted_game.recreate_all_szs() yield from extracted_game.recreate_all_szs()
# convert the extracted game into a file # 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) 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) 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): class ColorNotFound(Exception):
def __init__(self, color_data: any): def __init__(self, color_data: any):
super().__init__(f'Can\'t find color "{color_data}"') super().__init__(_("CANNOT_FIND_COLOR", ' "', color_data, '"'))
class MKWColor: class MKWColor:

View file

@ -1,7 +1,7 @@
import shutil import shutil
from pathlib import Path from pathlib import Path
from typing import Generator, Callable, Iterator, Iterable, TYPE_CHECKING from typing import Generator, Callable, Iterator, Iterable, TYPE_CHECKING
import json
from PIL import Image from PIL import Image
from source import threaded 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.MKWColor import bmg_color_text, bmg_color_raw
from source.mkw.ModSettings import AbstractModSettings from source.mkw.ModSettings import AbstractModSettings
from source.mkw.Track import CustomTrack, DefaultTrack, Arena from source.mkw.Track import CustomTrack, DefaultTrack, Arena
import json
from source.mkw.OriginalTrack import OriginalTrack from source.mkw.OriginalTrack import OriginalTrack
from source.progress import Progress from source.progress import Progress
from source.safe_eval import safe_eval, multiple_safe_eval from source.safe_eval import safe_eval, multiple_safe_eval
from source.wt.szs import SZSPath from source.wt.szs import SZSPath
from source.translation import translate as _
if TYPE_CHECKING: if TYPE_CHECKING:
from source import TemplateMultipleSafeEval, TemplateSafeEval, Env from source import TemplateMultipleSafeEval, TemplateSafeEval, Env
@ -414,7 +413,7 @@ class ModConfig:
:param autoadd_path: autoadd directory :param autoadd_path: autoadd directory
:param destination_path: destination where the files are converted :param destination_path: destination where the files are converted
""" """
yield Progress(description="Normalizing track...") yield Progress(description=_("NORMALIZING_TRACKS"))
destination_path = Path(destination_path) destination_path = Path(destination_path)
original_tracks_path = Path(original_tracks_path) original_tracks_path = Path(original_tracks_path)
destination_path.mkdir(parents=True, exist_ok=True) destination_path.mkdir(parents=True, exist_ok=True)
@ -429,7 +428,7 @@ class ModConfig:
nonlocal normalize_threads nonlocal normalize_threads
yield Progress( 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)) 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 tkinter import ttk
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from source.translation import translate as _
class InvalidSettingsType(Exception): class InvalidSettingsType(Exception):
def __init__(self, settings_type: str): 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): class AbstractModSettings(ABC):

View file

@ -1,6 +1,9 @@
from source.translation import translate as _
class OriginalTrackNotFound(Exception): class OriginalTrackNotFound(Exception):
def __init__(self, track_data: any): 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: class OriginalTrack:

View file

@ -2,6 +2,8 @@ from pathlib import Path
from typing import Generator, IO, TYPE_CHECKING from typing import Generator, IO, TYPE_CHECKING
from source.progress import Progress from source.progress import Progress
from source.translation import translate as _
if TYPE_CHECKING: if TYPE_CHECKING:
from source.mkw.ModConfig import ModConfig from source.mkw.ModConfig import ModConfig
@ -27,7 +29,7 @@ class Patch:
:param extracted_game: the extracted game :param extracted_game: the extracted game
""" """
from source.mkw.Patch.PatchDirectory import PatchDirectory 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. # 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 # 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 import PathOutsidePatch, InvalidPatchMode
from source.mkw.Patch.PatchObject import PatchObject from source.mkw.Patch.PatchObject import PatchObject
from source.progress import Progress from source.progress import Progress
from source.translation import translate as _
if TYPE_CHECKING: if TYPE_CHECKING:
from source.mkw.ExtractedGame import ExtractedGame from source.mkw.ExtractedGame import ExtractedGame
@ -27,7 +28,7 @@ class PatchDirectory(PatchObject):
""" """
patch a subdirectory of the game with the PatchDirectory 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 # check if the directory should be patched
if not self.is_enabled(extracted_game): return 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.mkw.Patch.PatchObject import PatchObject
from source.progress import Progress from source.progress import Progress
from source.wt.szs import SZSPath from source.wt.szs import SZSPath
from source.translation import translate
if TYPE_CHECKING: if TYPE_CHECKING:
from source.mkw.ExtractedGame import ExtractedGame from source.mkw.ExtractedGame import ExtractedGame
@ -81,7 +82,8 @@ class PatchFile(PatchObject):
""" """
patch a subfile of the game with the PatchFile 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 # check if the file should be patched
if not self.is_enabled(extracted_game): return if not self.is_enabled(extracted_game): return
@ -119,7 +121,7 @@ class PatchFile(PatchObject):
if not game_subfile.relative_to(extracted_game.path): if not game_subfile.relative_to(extracted_game.path):
raise PathOutsidePatch(game_subfile, 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 the source is the game, then recalculate the content for every game subfile
if self.configuration["source"] == "game": if self.configuration["source"] == "game":

View file

@ -3,6 +3,7 @@ from typing import IO, TYPE_CHECKING
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from source.mkw.Patch.PatchOperation import AbstractPatchOperation from source.mkw.Patch.PatchOperation import AbstractPatchOperation
from source.translation import translate as _
if TYPE_CHECKING: if TYPE_CHECKING:
from source.mkw.Patch import Patch from source.mkw.Patch import Patch
@ -10,7 +11,7 @@ if TYPE_CHECKING:
class InvalidBmgLayerMode(Exception): class InvalidBmgLayerMode(Exception):
def __init__(self, layer_mode: str): 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): class AbstractLayer(ABC):

View file

@ -4,6 +4,7 @@ from abc import abstractmethod, ABC
from PIL import Image from PIL import Image
from source.mkw.Patch.PatchOperation import AbstractPatchOperation from source.mkw.Patch.PatchOperation import AbstractPatchOperation
from source.translation import translate as _
if TYPE_CHECKING: if TYPE_CHECKING:
from source.mkw.Patch import Patch from source.mkw.Patch import Patch
@ -11,7 +12,7 @@ if TYPE_CHECKING:
class InvalidImageLayerType(Exception): class InvalidImageLayerType(Exception):
def __init__(self, layer_type: str): 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): class AbstractLayer(ABC):

View file

@ -1,13 +1,15 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import IO, Type, TYPE_CHECKING from typing import IO, Type, TYPE_CHECKING
from source.translation import translate as _
if TYPE_CHECKING: if TYPE_CHECKING:
from source.mkw.Patch import Patch from source.mkw.Patch import Patch
class InvalidPatchOperation(Exception): class InvalidPatchOperation(Exception):
def __init__(self, operation: str): def __init__(self, operation: str):
super().__init__(f"Error : operation \"{operation}\" is not implemented") super().__init__(_("OPERATION", ' "', operation, '" ', "IS_NOT_IMPLEMENTED"))
class AbstractPatchOperation(ABC): class AbstractPatchOperation(ABC):

View file

@ -1,5 +1,6 @@
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from source.translation import translate as _
if TYPE_CHECKING: if TYPE_CHECKING:
from source.mkw.Patch import Patch from source.mkw.Patch import Patch
@ -8,14 +9,16 @@ if TYPE_CHECKING:
class PathOutsidePatch(Exception): class PathOutsidePatch(Exception):
def __init__(self, forbidden_path: Path, allowed_range: Path): 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): class InvalidPatchMode(Exception):
def __init__(self, patch: "PatchObject", mode: str): 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): class InvalidSourceMode(Exception):
def __init__(self, patch: "PatchObject", source: str): 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 typing import Generator, TYPE_CHECKING
from source.mkw import Slot, Tag, ModConfig from source.mkw import Slot, Tag, ModConfig
from source.translation import translate as _
if TYPE_CHECKING: if TYPE_CHECKING:
from source import TemplateMultipleSafeEval from source import TemplateMultipleSafeEval
@ -9,7 +10,7 @@ if TYPE_CHECKING:
class TrackForbiddenCustomAttribute(Exception): class TrackForbiddenCustomAttribute(Exception):
def __init__(self, attribute_name: str): 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): class AbstractTrack(ABC):

View file

@ -2,14 +2,16 @@ from typing import TYPE_CHECKING
from source.mkw import Slot, Tag from source.mkw import Slot, Tag
from source.mkw.Track.RealArenaTrack import RealArenaTrack from source.mkw.Track.RealArenaTrack import RealArenaTrack
from source.translation import translate as _
if TYPE_CHECKING: if TYPE_CHECKING:
from source import TemplateMultipleSafeEval from source import TemplateMultipleSafeEval
from source.mkw.ModConfig import ModConfig
class ArenaForbiddenCustomAttribute(Exception): class ArenaForbiddenCustomAttribute(Exception):
def __init__(self, attribute_name: str): 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): 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.AbstractTrack import AbstractTrack
from source.mkw.Track.RealArenaTrack import RealArenaTrack from source.mkw.Track.RealArenaTrack import RealArenaTrack
ModConfig: any if TYPE_CHECKING:
from source.mkw.ModConfig import ModConfig
class CustomTrack(RealArenaTrack, AbstractTrack): class CustomTrack(RealArenaTrack, AbstractTrack):

View file

@ -2,6 +2,7 @@ from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from source import TemplateMultipleSafeEval from source import TemplateMultipleSafeEval
from source.mkw import Tag
from source.mkw.ModConfig import ModConfig from source.mkw.ModConfig import ModConfig
@ -11,7 +12,7 @@ class RealArenaTrack:
(For example, DefaultTrack is not considered a real track class) (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: def get_tag_template(self, mod_config: "ModConfig", template_name: str, default: any = None) -> any:
""" """

View file

@ -1,6 +1,9 @@
import re import re
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from source.translation import translate as _
if TYPE_CHECKING: if TYPE_CHECKING:
from source import TemplateSafeEval from source import TemplateSafeEval
@ -9,7 +12,7 @@ MACRO_START, MACRO_END = "##", "##"
class NotImplementedMacro(Exception): class NotImplementedMacro(Exception):
def __init__(self, macro: str): 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: 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.macros import replace_macro
from source.safe_eval.safe_function import get_all_safe_functions from source.safe_eval.safe_function import get_all_safe_functions
from source.translation import translate as _
if TYPE_CHECKING: if TYPE_CHECKING:
from source import TemplateSafeEval, Env 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 # convert the template to an ast expression
stmt: ast.stmt = ast.parse(template).body[0] stmt: ast.stmt = ast.parse(template).body[0]
if not isinstance(stmt, ast.Expr): 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 # check every node for disabled expression
for node in ast.walk(stmt): for node in ast.walk(stmt):
@ -69,20 +71,20 @@ def safe_eval(template: "TemplateSafeEval", env: "Env" = None, macros: dict[str,
case ast.Attribute: case ast.Attribute:
# ban all magical function, disabling the __class__.__bases__[0] ... tricks # ban all magical function, disabling the __class__.__bases__[0] ... tricks
if "__" in node.attr: 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 # ban modification to environment
if isinstance(node.ctx, ast.Store): 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 # when accessing any variable
case ast.Name: case ast.Name:
# ban modification to environment, but allow custom variable to be changed # ban modification to environment, but allow custom variable to be changed
if isinstance(node.ctx, ast.Store): if isinstance(node.ctx, ast.Store):
if node.id in globals_ | locals_: 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: 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 # when calling any function
case ast.Call: case ast.Call:
@ -91,10 +93,10 @@ def safe_eval(template: "TemplateSafeEval", env: "Env" = None, macros: dict[str,
if isinstance(callnode, ast.Attribute): if isinstance(callnode, ast.Attribute):
for attrnode in ast.walk(callnode.value): for attrnode in ast.walk(callnode.value):
if isinstance(attrnode, ast.Name): if isinstance(attrnode, ast.Name):
if attrnode.id in globals_ | locals_: if attrnode.id in globals_ | locals_ or attrnode.id in args:
raise SafeEvalException(f'Calling this function is not allowed : "{callnode.attr}"') raise SafeEvalException(
if attrnode.id in args: _("CALLING_FUNCTION_NOT_ALLOWED", ' : "', callnode.attr, '"')
raise SafeEvalException(f'Calling this function is not allowed : "{callnode.attr}"') )
# when assigning a value with ":=" # when assigning a value with ":="
case ast.NamedExpr: 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.ClassDef | # Declaring class could maybe allow for dangerous calls
ast.AsyncFor | ast.AsyncWith | ast.AsyncFunctionDef | ast.Await # Just in case 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 # embed the whole expression into a lambda expression
stmt.value = ast.Lambda( stmt.value = ast.Lambda(

View file

@ -1,7 +1,9 @@
from typing import Callable, Generator, TYPE_CHECKING from typing import Callable, Generator, TYPE_CHECKING
from source.translation import translate as _
if TYPE_CHECKING: if TYPE_CHECKING:
from source import TemplateSafeEval, Env from source import TemplateSafeEval
def get_all_safe_functions() -> Generator[list[Callable], None, None]: 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 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) return getattr(obj, name, default)
@staticmethod @staticmethod

View file

@ -3,6 +3,8 @@ from pathlib import Path
import os import os
from typing import Callable from typing import Callable
from source.translation import translate as _
class WTError(Exception): class WTError(Exception):
def __init__(self, tools_path: Path | str, return_code: int): def __init__(self, tools_path: Path | str, return_code: int):
@ -14,14 +16,14 @@ class WTError(Exception):
creationflags=subprocess.CREATE_NO_WINDOW, creationflags=subprocess.CREATE_NO_WINDOW,
).stdout.decode() ).stdout.decode()
except subprocess.CalledProcessError as e: 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): class MissingWTError(Exception):
def __init__(self, tool_name: str): 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/") tools_dir = Path("./tools/")

View file

@ -1,5 +1,6 @@
from source.wt import * from source.wt import *
from source.wt import _run, _run_dict from source.translation import translate as _
tools_path = tools_szs_dir / "wszst" 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 :return: directory where the autoadd files were extracted
""" """
destination_path = Path(destination_path) 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 return destination_path
@ -191,7 +192,7 @@ class SZSSubPath:
:param dest: destination path :param dest: destination path
:return: the extracted file 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) dest: Path = Path(dest)
if dest.is_dir(): dest /= self.basename() if dest.is_dir(): dest /= self.basename()