From 423a02ce4c9929072c7f456f387c5a11f97bacc7 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Thu, 1 Sep 2022 18:10:38 +0200 Subject: [PATCH] remade all the translation in a easier, faster and more readable way --- Pack/MKWFaraphel/messages.json | 4 +- assets/language/en.json | 232 +++++++++--------- assets/language/fr.json | 231 ++++++++--------- source/gui/__init__.py | 4 +- source/gui/install.py | 88 ++++--- source/gui/mod_settings.py | 19 +- source/gui/mystuff.py | 18 +- source/gui/preview/__init__.py | 2 +- source/mkw/ExtractedGame.py | 53 ++-- source/mkw/Game.py | 41 ++-- source/mkw/ModConfig.py | 3 +- source/mkw/ModSettings/AbstractModSettings.py | 2 +- .../mkw/ModSettings/SettingsType/Boolean.py | 4 +- source/mkw/Patch/Patch.py | 2 +- source/mkw/Patch/PatchDirectory.py | 7 +- source/mkw/Patch/PatchFile.py | 9 +- .../PatchOperation/BmgTxtEditor/__init__.py | 2 +- .../PatchOperation/ImageEditor/ImageLayer.py | 4 +- .../PatchOperation/ImageEditor/TextLayer.py | 4 +- .../PatchOperation/ImageEditor/__init__.py | 2 +- source/mkw/Patch/PatchOperation/StrEditor.py | 4 +- source/mkw/Patch/PatchOperation/__init__.py | 2 +- source/mkw/Patch/__init__.py | 12 +- source/mkw/Track/AbstractTrack.py | 3 +- source/mkw/Track/Arena.py | 2 +- source/mkw/Track/DefaultTrack.py | 5 - source/mkw/Track/TrackGroup.py | 4 +- source/mkw/__init__.py | 11 + source/mkw/collection/MKWColor.py | 2 +- source/mkw/collection/Slot.py | 2 +- source/option.py | 2 +- source/safe_eval/__init__.py | 2 +- source/safe_eval/macros.py | 2 +- source/safe_eval/safe_eval.py | 19 +- source/safe_eval/safe_function.py | 6 +- source/translation.py | 8 +- source/wt/__init__.py | 8 +- source/wt/szs.py | 2 +- source/wt/wit.py | 2 +- 39 files changed, 394 insertions(+), 435 deletions(-) diff --git a/Pack/MKWFaraphel/messages.json b/Pack/MKWFaraphel/messages.json index 4cd1822..8ca05ac 100644 --- a/Pack/MKWFaraphel/messages.json +++ b/Pack/MKWFaraphel/messages.json @@ -7,8 +7,8 @@ }, "installation_completed": { "text": { - "fr": "Merci d'avoir téléchargé {{ mod_config.name }} !\nSi vous avez un problème en démarrant le jeu avec Dolphin, essayer dans Configurer > Avancé > Modifier la taille de la mémoire émulée et mettre MEM2 à 128MB\n\nAmusez-vous !", - "en": "Thanks for downloading {{ mod_config.name }} !\nIf You have an issue starting the game with Dolphin, try in Config > Advanced > Enable Emulated Memory Size Override and set MEM2 to 128MB\n\nHave fun !" + "fr": "Merci d'avoir téléchargé {{ mod_config.name }} !\nSi vous avez un problème en démarrant le jeu avec Dolphin, essayer de mettre MEM2 dans Configurer > Avancé > Modifier la taille de la mémoire émulée à 128MB\n\nAmusez-vous !", + "en": "Thanks for downloading {{ mod_config.name }} !\nIf You have an issue when starting the game with Dolphin, try to set MEM2 in Config > Advanced > Enable Emulated Memory Size Override to 128MB\n\nHave fun !" } } } \ No newline at end of file diff --git a/assets/language/en.json b/assets/language/en.json index 29759e2..fdeef96 100644 --- a/assets/language/en.json +++ b/assets/language/en.json @@ -1,124 +1,112 @@ { - "name": "English", - "translation": { - "INSTALLER_TITLE": "MKWF-Install", - "LANGUAGE_SELECTION": "Language", - "TRACK_FILTER": "Track Filters", - "ADVANCED_CONFIGURATION": "Advanced", - "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", - "GLOBAL_MOD_SETTINGS": "Global mod settings", - "SPECIFIC_MOD_SETTINGS": "Specific mod settings", - "CONFIGURE_MYSTUFF_PATCH": "Configure the MyStuff patchs", - "DISABLED": "Disabled", - "ENABLED": "Enabled", - "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", - "PATCH_TITLE": "Patch", - "PRE-PATCH_TITLE": "Pre-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", - "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", - "CANNOT_FIND_SLOT": "Can't find slot", - "FORBIDDEN_TRACKGROUP_ATTRIBUTE": "Forbidden TrackGroup attribute", - "SAFE_EVAL_ERROR": "Safe eval error", - "TEMPLATE_USED": "Template used", - "MORE_IN_ERROR_LOG": "More information in the error.log file", - "COPY_FUNCTION_FORBIDDEN": "Copying functions is forbidden", - "GET_METHOD_FORBIDDEN": "Using getattr on a method is forbidden", - "CAN_ONLY_CALL_METHOD_OF_CONSTANT": "You can only call methods on constant", - "CAN_ONLY_CALL_FUNCTION_IN_ENV": "You can only call function from the environment", - "ENABLE_DEVELOPER_MODE": "Enable the developer mode", - "TESTING_MOD_SETTINGS": "Test mod settings", - "SETTINGS_FILE": "Settings file", - "EXPORT_SETTINGS": "Export settings", - "IMPORT_SETTINGS": "Import settings", - "PREPARING_RIIVOLUTION": "Preparing for Riivolution", - "CONVERTING_TO_RIIVOLUTION": "Converting to Riivolution patch", - "PATCHS": "patchs", - "PRE-PATCHS": "pre-patchs", - "CALCULATING_HASH_FOR": "Calculating hash for", - "WARNING_NOT_ROOT": "The application require root permission. You should start the application with 'sudo'. Continue anyway ?", - "WARNING_INSTALLER_PERMISSION": "The application need writing and execution permissions to its own files. You can use 'sudo chmod 777 -R ' to fix that. Continue anyway ?", - "WARNING_EMPTY_CACHE": "By emptying the cache, you will gain %.2fGB, but reinstalling a mod will be take way longer. Continue ?", - "EMPTY_CACHE": "Empty the cache" - } + "name": "English", + "translation": { + "TITLE_INSTALL": "MKWF-Install", + "TITLE_MOD_SETTINGS": "Mod Settings", + "TITLE_MYSTUFF_SETTINGS": "MyStuff Settings", + + "MENU_LANGUAGE_SELECTION": "Language", + "MENU_ADVANCED": "Advanced", + "MENU_ADVANCED_MYSTUFF": "MyStuff settings", + "MENU_ADVANCED_THREADS": "Thread usage", + "MENU_ADVANCED_THREADS_SELECTION": "Use %i threads", + "MENU_ADVANCED_EMPTY_CACHE": "Empty the cache", + "MENU_ADVANCED_DEVELOPER_MODE": "Enable developer settings", + "MENU_HELP": "Help", + + "PART_EXTRACTION": "Extraction", + "PART_PRE_RIIVOLUTION": "Pre-Riivolution", + "PART_MYSTUFF": "MyStuff", + "PART_PREPARING_FILES": "Preparing files", + "PART_PREPATCH": "Pre-Patch", + "PART_LECODE": "LE-CODE", + "PART_PATCH": "Patch", + "PART_RIIVOLUTION": "Riivolution", + "PART_CONVERSION": "Conversion", + + "TEXT_SOURCE_GAME": "Source Game", + "TEXT_GAME_DESTINATION": "Game Destination", + "TEXT_INSTALL": "Install", + "TEXT_SELECT_SOURCE_GAME": "Select a source game", + "TEXT_SELECT_GAME_DESTINATION": "Select the game destination", + "TEXT_WII_GAMES": "Wii games", + "TEXT_INSTALLATION_COMPLETED": "Installation Completed", + "TEXT_INSTALLATION_FINISHED_SUCCESSFULLY": "The installation finished successfully !", + "TEXT_MESSAGE_FROM_AUTHOR": "Message from the author", + "TEXT_MOD_GLOBAL_SETTINGS": "Global settings", + "TEXT_MOD_SPECIFIC_SETTINGS": "Specific settings", + "TEXT_MOD_TEST_SETTINGS": "Test settings", + "TEXT_DISABLED": "Disabled", + "TEXT_ENABLED": "Enabled", + "TEXT_IMPORT_SETTINGS": "Import settings", + "TEXT_EXPORT_SETTINGS": "Export settings", + "TEXT_SETTINGS_FILE": "Settings file", + "TEXT_NEW_PROFILE": "New profile", + "TEXT_DELETE_PROFILE": "Delete profile", + "TEXT_ADD_MYSTUFF": "Add MyStuff", + "TEXT_REMOVE_MYSTUFF": "Remove MyStuff", + "TEXT_SELECT_MYSTUFF": "Select MyStuff", + "TEXT_COPYING_GAME": "Copying game", + "TEXT_EXTRACTING_GAME": "Extracting game - %i%% (estimated time remaining : %s)", + "TEXT_CHANGING_GAME_METADATA": "Changing game metadata", + "TEXT_EXTRACTING_AUTOADD": "Extracting autoadd files", + "TEXT_EXTRACTING_ORIGINAL_TRACKS": "Extracting original tracks \"%s\"", + "TEXT_INSTALLING_MYSTUFF": "Installing MyStuff \"%s\"", + "TEXT_PREPARING_SPECIAL_FILE": "Preparing special file \"%s\"", + "TEXT_PREPARING_MAIN_DOL": "Preparing main.dol", + "TEXT_REPACKING_ARCHIVE": "Repacking archive \"%s\"", + "TEXT_PATCHING_LECODE": "Patching LECODE.bin", + "TEXT_CONVERT_GAME_TO": "Converting game to %s", + "TEXT_DELETING_EXTRACTED_GAME": "Deleting extracted game", + "TEXT_CONVERTING_TO_RIIVOLUTION": "Converting to Riivolution", + "TEXT_CALCULATING_HASH": "Calculating hash for \"%s\"", + "TEXT_NORMALIZING_TRACKS": "Normalizing tracks :\n%s", + "TEXT_PATCHING": "Patching \"%s\"", + + "WARNING": "Warning", + "WARNING_EMPTY_CACHE": "By emptying the cache, you will gain %.2fGB, but reinstalling a mod will be take way longer. Continue ?", + "WARNING_DESTINATION_NOT_WRITABLE": "The game destination directory is not writable. Continue anyway ?", + "WARNING_LOW_SPACE_CONTINUE": "Low space remaining on disk %s (%.2fGB). Continue anyway ?", + "WARNING_NOT_ROOT": "Root permissions are required. You should start the application with 'sudo'. Continue anyway ?", + "WARNING_INSTALLER_PERMISSION": "Writing and execution permissions are required. You can use 'sudo chmod 777 -R ' to fix that. Continue anyway ?", + + "ERROR": "Error", + "ERROR_SEE_LOGS": "See error.log for more information", + "ERROR_INVALID_SOURCE_GAME": "Invalid source game : \"%s\"", + "ERROR_INVALID_GAME_DESTINATION": "Invalid game destination : \"%s\"", + "ERROR_MYSTUFF_PROFILE_ALREADY_EXIST": "The MyStuff profile \"%s\" already exist !", + "ERROR_MYSTUFF_PROFILE_FORBIDDEN_NAME": "The MyStuff profile name \"%s\" is forbidden !", + "ERROR_NOT_MKW_GAME": "This game is not a Mario Kart Wii game : \"%s\"", + "ERROR_GAME_ALREADY_MODDED": "This game is already modded : \"%s\"", + "ERROR_PATH_OUTSIDE_RANGE": "Path \"%s\" is outside allowed range : \"%s\"", + "ERROR_PREVIEW_WINDOW_NOT_FOUND": "Preview window type \"%s\" not found", + "ERROR_CANNOT_FIND_COLOR": "Can't find color : \"%s\"", + "ERROR_CANNOT_FIND_SLOT": "Can't find slot : \"%s\"", + "ERROR_MOD_SETTINGS_NOT_FOUND": "Mod settings type \"%s\" not found", + "ERROR_FORBIDDEN_TRACK_ATTRIBUTE": "Forbidden track attribute : \"%s\"", + "ERROR_FORBIDDEN_ARENA_ATTRIBUTE": "Forbidden arena attribute : \"%s\"", + "ERROR_FORBIDDEN_TRACKGROUP_ATTRIBUTE": "Forbidden track group attribute : \"%s\"", + "ERROR_SAFEEVAL": "Safe eval error (template used : \"%s\")", + "ERROR_INVALID_MACRO": "Invalid macro : \"%s\"", + "ERROR_INVALID_AST_TYPE": "Invalid AST type : \"%s\"", + "ERROR_FORBIDDEN_MAGIC_ATTRIBUTE": "Magic attribute are forbidden : \"%s\"", + "ERROR_FORBIDDEN_MAGIC_METHOD": "Magic method are forbidden : \"%s\"", + "ERROR_GETTING_METHOD_FORBIDDEN": "Getting method is forbidden : \"%s\"", + "ERROR_CANNOT_SET_ATTRIBUTE": "Setting attribute is forbidden : \"%s\"", + "ERROR_CANNOT_SET_ENVIRONMENT": "Setting environment variable is forbidden : \"%s\"", + "ERROR_CANNOT_SET_ARGUMENT": "Setting argument variable is forbidden : \"%s\"", + "ERROR_CAN_ONLY_CALL_CONSTANT_METHOD": "Only calling method of constant is allowed", + "ERROR_CAN_ONLY_CALL_ENV_FUNCTION": "You only can call function from the environment", + "ERROR_FORBIDDEN_SYNTAX": "Forbidden syntax : \"%s\"", + "ERROR_FUNCTION_COPY_FORBIDDEN": "Copying a function is forbidden", + "ERROR_CANNOT_GET_ERROR_MESSAGE": "Can't get the error message", + "ERROR_WT": "%s raised %i :\n%s", + "ERROR_CANNOT_FIND_TOOL": "Can't find tool \"%s\" in the tools directory", + "ERROR_CANNOT_EXTRACT_DIRECTORY": "Directory can't be extracted", + "ERROR_PATCH_MODE_NOT_IMPLEMENTED": "Patch mode \"%s\" is not implemented (in patch : \"%s\")", + "ERROR_SOURCE_NOT_IMPLEMENTED": "Source \"%s\" is not implemented (in patch : \"%s\")", + "ERROR_OPERATION_NOT_IMPLEMENTED": "Operation \"%s\" is not implemented", + "ERROR_BMG_LAYER_MODE_NOT_IMPLEMENTED": "Bmg layer mode \"%s\" not implemented", + "ERROR_IMAGE_LAYER_TYPE_NOT_IMPLEMENTED": "Image layer type \"%s\" not implemented" + } } \ No newline at end of file diff --git a/assets/language/fr.json b/assets/language/fr.json index 9467e56..40469cb 100644 --- a/assets/language/fr.json +++ b/assets/language/fr.json @@ -1,125 +1,112 @@ { - "name": "Français", - "translation": { - "INSTALLER_TITLE": "MKWF-Install", - "LANGUAGE_SELECTION": "Langue", - "TRACK_FILTER": "filtrer les courses", - "ADVANCED_CONFIGURATION": "Avancée", - "HELP": "Aide", + "name": "Français", + "translation": { + "TITLE_INSTALL": "MKWF-Install", + "TITLE_MOD_SETTINGS": "Paramètres du mod", + "TITLE_MYSTUFF_SETTINGS": "Paramètres MyStuff", - "INVALID_SOURCE_PATH": "Chemin invalide pour le jeu source", - "DISCORD": "Discord", - "GITHUB WIKI": "Wiki Github", - "READTHEDOCS": "ReadTheDocs", - "ORIGINAL_GAME_FILE": "Fichier du jeu original", - "SELECT_SOURCE_GAME": "Sélectionner le jeu source", - "WII GAMES": "jeux Wii", - "ERROR": "Erreur", - "ERROR_INVALID_SOURCE_GAME": "Chemin invalide pour le jeu source", - "GAME_DIRECTORY_DESTINATION": "Dossier de destination du jeu", - "SELECT_DESTINATION_GAME": "Sélectionner le dossier de destination du jeu", - "WARNING": "Attention", - "WARNING_DESTINATION_GAME_NOT_WRITABLE": "Le chemin de destination n'est pas modifiable", - "INSTALL": "Installer", - "ERROR_INVALID_DESTINATION_GAME": "Chemin invalide pour la destination du jeu", - "WARNING_LOW_SPACE_CONTINUE": "L'espace restant sur le disque dur est faible. Continuer ?", - "INSTALLATION_COMPLETED": "Installation Complété", - "INSTALLATION_FINISHED_WITH_SUCCESS": "L'installation s'est terminé avec succès !", - "OPEN_MYSTUFF_SETTINGS": "Ouvrir les paramètres MyStuff", - "THREADS_USAGE": "Utilisation des Threads", - "USE": "Utiliser", - "THREADS": "threads", - "MESSAGE_FROM_MOD_AUTHOR": "Message de l'auteur", - "GLOBAL_MOD_SETTINGS": "Paramètre global de mod", - "SPECIFIC_MOD_SETTINGS": "Paramètre spécifique de mod", - "CONFIGURE_MYSTUFF_PATCH": "Configurer les patchs MyStuff", - "DISABLED": "Désactiver", - "ENABLED": "Activer", - "NEW_PROFILE": "Nouveau profil", - "DELETE_PROFILE": "Retirer un Profil", - "ADD_MYSTUFF": "Ajouter un patch MyStuff", - "REMOVE_MYSTUFF": "Retirer un patch MyStuff", - "MYSTUFF_PROFILE_ALREADY_EXIST": "Ce profil MyStuff existe déjà !", - "MYSTUFF_PROFILE_FORBIDDEN_NAME": "Ce nom de profil MyStuff ne peut pas être utilisé !", - "SELECT_MYSTUFF": "Sélectionner un patch MyStuff", - "TYPE_PREVIEW_WINDOW": "Type de fenêtre de prévisualisation", - "NOT_FOUND": "introuvable", - "TYPE_MOD_SETTINGS": "Type de paramètre de mod", - "BMG_LAYER_MODE": "mode de couche bmg", - "IS_NOT_IMPLEMENTED": "n'est pas implémenté", - "IMAGE_LAYER_TYPE": "type de couche d'image", - "OPERATION": "Opération", - "PATH": "Chemin", - "MODE": "Mode", - "SOURCE": "Source", - "OUTSIDE_ALLOWED_RANGE": "En dehors de la portée autorisée", - "IN_PATCH": "dans le patch", - "INSTALLING_PATCH": "Installation du patch", - "PATCH_TITLE": "Patch", - "PRE-PATCH_TITLE": "Pre-patch", - "PATCHING": "Patch de", - "FORBIDDEN_TRACK_ATTRIBUTE": "Attribut de course interdit", - "FORBIDDEN_ARENA_ATTRIBUTE": "Attribut d'arène interdit", - "EXTRACTING_AUTOADD_FILES": "Extraction des fichiers autoadd...", - "EXTRACTING_ORIGINAL_TRACKS": "Extraction des courses originelles...", - "INSTALLING_MYSTUFF": "Installation de MyStuff", - "INSTALLING_ALL_MYSTUFF_PATCHS": "Installation de tous les patchs MyStuff", - "PREPARING": "Preparation", - "SPECIAL_FILE": "fichier spécial", - "REPACKING": "Ré-archivage", - "ALL_ARCHIVES": "toutes les archives", - "INSTALLING_ALL": "Installation de tous", - "CONVERTING_GAME_TO": "Conversion du jeu en", - "DELETING_EXTRACTED_GAME": "Suppression du jeu extrait...", - "NOT_MKW_GAME": "N'est pas un jeu Mario Kart Wii", - "GAME_ALREADY_MODDED": "Ce jeu est déjà moddée", - "COPYING_GAME": "Copie du jeu...", - "EXTRACTING": "Extraction", - "ESTIMATED_TIME_REMAINING": "temps restant estimé", - "CHANGING_GAME_METADATA": "Changement des métadonnées du jeu...", - "EXTRACTION": "Extraction", - "MYSTUFF": "MyStuff", - "PREPARING_FILES": "Preparation des fichiers", - "PRE-PATCHING": "Pre-Patching", - "CONVERTING_TO_GAME_FILE": "Conversion en fichier de jeu", - "CANNOT_FIND_COLOR": "Impossible de trouver la couleur", - "NORMALIZING_TRACKS": "Normalisation des courses", - "INVALID_MACRO": "Macro invalide", - "INVALID_AST_TYPE": "Type d'Ast invalide", - "MAGIC_ATTRIBUTE_ARE_FORBIDDEN": "Les attributs magique sont interdit", - "CANNOT_SET_ATTRIBUTE": "Impossible de changer la valeur de l'attribut", - "CANNOT_SET_ENVIRONMENT": "Impossible de changer la valeur de l'environnement", - "CANNOT_SET_ARGUMENT": "Impossible de changer la valeur de l'argument", - "CALLING_FUNCTION_NOT_ALLOWED": "Appeler cette fonction n'est pas autorisé", - "FORBIDDEN_SYNTAX": "Syntax interdite", - "MAGIC_METHOD_FORBIDDEN": "Les méthodes magique ne sont pas autorisé", - "CANNOT_GET_ERROR_MESSAGE": "Impossible d'obtenir le message d'erreur", - "RAISED": "à levé", - "CANNOT_FIND_TOOL": "Impossible de trouver l'outil", - "IN_TOOLS_DIRECTORY": "dans le dossier des outils.", - "CANNOT_EXTRACT_A_DIRECTORY": "Impossible d'extraire un dossier", - "CANNOT_FIND_SLOT": "Impossible de trouver le slot", - "FORBIDDEN_TRACKGROUP_ATTRIBUTE": "Attribut de groupe de course interdit", - "SAFE_EVAL_ERROR": "Erreur lors d'un safe eval", - "TEMPLATE_USED": "Modèle utilisé", - "MORE_IN_ERROR_LOG": "Plus d'information dans le fichier error.log", - "COPY_FUNCTION_FORBIDDEN": "Impossible de copier une fonction", - "GET_METHOD_FORBIDDEN": "Impossible d'utiliser getattr sur une méthode", - "CAN_ONLY_CALL_METHOD_OF_CONSTANT": "Vous ne pouvez appeler que des méthodes sur des constantes", - "CAN_ONLY_CALL_FUNCTION_IN_ENV": "Vous ne pouvez appeler que des fonctions dans l'environnement", - "ENABLE_DEVELOPER_MODE": "Activer le mode développeur", - "TESTING_MOD_SETTINGS": "Paramètre de test", - "SETTINGS_FILE": "Fichier de paramètres", - "EXPORT_SETTINGS": "Exporter les paramètres", - "IMPORT_SETTINGS": "Importer les paramètres", - "PREPARING_RIIVOLUTION": "Préparation pour Riivolution", - "CONVERTING_TO_RIIVOLUTION": "Conversion en patch Riivolution", - "PATCHS": "patchs", - "PRE-PATCHS": "pre-patchs", - "CALCULATING_HASH_FOR": "Calcule du hash pour", - "WARNING_NOT_ROOT": "Cette application nécessite les permissions root. Vous devriez lancer l'application avec 'sudo'. Continuer quand même ?", - "WARNING_INSTALLER_PERMISSION": "Cette application nécessite les permissions d'écriture et d'exécution sur ses propres fichiers. Vous pouvez utiliser 'sudo chmod 777 -R ' pour corriger cela. Continuer quand même ?", - "WARNING_EMPTY_CACHE": "En vidant le cache, vous allez gagner %.2fGo, mais réinstaller un mod sera beaucoup plus long. Continuer ?", - "EMPTY_CACHE": "Vider le cache" - } + "MENU_LANGUAGE_SELECTION": "Langue", + "MENU_ADVANCED": "Avancée", + "MENU_ADVANCED_MYSTUFF": "Paramètres MyStuff", + "MENU_ADVANCED_THREADS": "Utilisation des threads", + "MENU_ADVANCED_THREADS_SELECTION": "Utiliser %i threads", + "MENU_ADVANCED_EMPTY_CACHE": "Vider le cache", + "MENU_ADVANCED_DEVELOPER_MODE": "Activer les paramètres développeur", + "MENU_HELP": "Aide", + + "PART_EXTRACTION": "Extraction", + "PART_PRE_RIIVOLUTION": "Pre-Riivolution", + "PART_MYSTUFF": "MyStuff", + "PART_PREPARING_FILES": "Preparation des fichiers", + "PART_PREPATCH": "Pre-Patch", + "PART_LECODE": "LE-CODE", + "PART_PATCH": "Patch", + "PART_RIIVOLUTION": "Riivolution", + "PART_CONVERSION": "Conversion", + + "TEXT_SOURCE_GAME": "Jeu Source", + "TEXT_GAME_DESTINATION": "Destination du Jeu", + "TEXT_INSTALL": "Installer", + "TEXT_SELECT_SOURCE_GAME": "Sélectionner un jeu source", + "TEXT_SELECT_GAME_DESTINATION": "Sélectionner la destination du jeu", + "TEXT_WII_GAMES": "Jeux Wii", + "TEXT_INSTALLATION_COMPLETED": "Installation Complétée", + "TEXT_INSTALLATION_FINISHED_SUCCESSFULLY": "L'installation s'est terminée avec succès !", + "TEXT_MESSAGE_FROM_AUTHOR": "Message de l'auteur", + "TEXT_MOD_GLOBAL_SETTINGS": "Paramètres globaux", + "TEXT_MOD_SPECIFIC_SETTINGS": "Paramètres spécifique", + "TEXT_MOD_TEST_SETTINGS": "Paramètres de test", + "TEXT_DISABLED": "Désactivé", + "TEXT_ENABLED": "Activé", + "TEXT_IMPORT_SETTINGS": "Importer des paramètres", + "TEXT_EXPORT_SETTINGS": "Exporter des paramètres", + "TEXT_SETTINGS_FILE": "Fichier paramètres", + "TEXT_NEW_PROFILE": "Nouveau profil", + "TEXT_DELETE_PROFILE": "Supprimer le profil", + "TEXT_ADD_MYSTUFF": "Ajouter MyStuff", + "TEXT_REMOVE_MYSTUFF": "Retirer MyStuff", + "TEXT_SELECT_MYSTUFF": "Sélectionner un MyStuff", + "TEXT_COPYING_GAME": "Copie du jeu", + "TEXT_EXTRACTING_GAME": "Extraction du jeu - %i%% (temps restant estimé : %s)", + "TEXT_CHANGING_GAME_METADATA": "Changement des métadonnées du jeu", + "TEXT_EXTRACTING_AUTOADD": "Extraction des fichiers autoadd", + "TEXT_EXTRACTING_ORIGINAL_TRACKS": "Extraction des courses originales \"%s\"", + "TEXT_INSTALLING_MYSTUFF": "Installation des MyStuff \"%s\"", + "TEXT_PREPARING_SPECIAL_FILE": "Preparion du fichier spécial \"%s\"", + "TEXT_PREPARING_MAIN_DOL": "Preparation de main.dol", + "TEXT_REPACKING_ARCHIVE": "Archivage de \"%s\"", + "TEXT_PATCHING_LECODE": "Patch de LECODE.bin", + "TEXT_CONVERT_GAME_TO": "Conversion du jeu en %s", + "TEXT_DELETING_EXTRACTED_GAME": "Suppression du jeu extrait", + "TEXT_CONVERTING_TO_RIIVOLUTION": "Conversion en Riivolution", + "TEXT_CALCULATING_HASH": "Calcul du hash pour \"%s\"", + "TEXT_NORMALIZING_TRACKS": "Normalisation des courses :\n%s", + "TEXT_PATCHING": "Patch de \"%s\"", + + "WARNING": "Attention", + "WARNING_EMPTY_CACHE": "En vidant le cache, vous allez gagner %.2fGB, mais reinstaller un mod prendra beaucoup plus de temps. Continuer ?", + "WARNING_DESTINATION_NOT_WRITABLE": "Le dossier de destination du jeu ne peut pas être modifié. Continuer quand même ?", + "WARNING_LOW_SPACE_CONTINUE": "Espace faible sur le disque %s (%.2fGB). Continuer quand même ?", + "WARNING_NOT_ROOT": "Permissions administrateur requise. Vous devriez lancer l'application avec 'sudo'. Continuer quand même ?", + "WARNING_INSTALLER_PERMISSION": "Les permissions d'écriture et d'exécution sont requise. Vous pouvez utiliser 'sudo chmod 777 -R ' pour corriger cela. Continuer quand même ?", + + "ERROR": "Erreur", + "ERROR_SEE_LOGS": "Voir le fichier error.log pour plus d'information", + "ERROR_INVALID_SOURCE_GAME": "Jeu source invalide : \"%s\"", + "ERROR_INVALID_GAME_DESTINATION": "Destination de jeu invalide : \"%s\"", + "ERROR_MYSTUFF_PROFILE_ALREADY_EXIST": "Le profil MyStuff \"%s\" existe déjà !", + "ERROR_MYSTUFF_PROFILE_FORBIDDEN_NAME": "Le nom de profil MyStuff \"%s\" est interdit !", + "ERROR_NOT_MKW_GAME": "Ce jeu n'est pas un jeu Mario Kart Wii : \"%s\"", + "ERROR_GAME_ALREADY_MODDED": "Ce jeu est déjà moddé : \"%s\"", + "ERROR_PATH_OUTSIDE_RANGE": "Le chemin \"%s\" est en dehors de la portée autorisée : \"%s\"", + "ERROR_PREVIEW_WINDOW_NOT_FOUND": "Type de fenêtre de prévisualisation \"%s\" introuvable", + "ERROR_CANNOT_FIND_COLOR": "Impossible de trouver la couleur : \"%s\"", + "ERROR_CANNOT_FIND_SLOT": "Impossible de trouver le slot : \"%s\"", + "ERROR_MOD_SETTINGS_NOT_FOUND": "Type de paramètre de mod \"%s\" introuvable", + "ERROR_FORBIDDEN_TRACK_ATTRIBUTE": "Attribut de course interdit : \"%s\"", + "ERROR_FORBIDDEN_ARENA_ATTRIBUTE": "Attribut d'arène interdit : \"%s\"", + "ERROR_FORBIDDEN_TRACKGROUP_ATTRIBUTE": "Attribut de groupe de course interdit : \"%s\"", + "ERROR_SAFEEVAL": "Erreur de safe eval (modèle utilisé : \"%s\")", + "ERROR_INVALID_MACRO": "Macro invalide : \"%s\"", + "ERROR_INVALID_AST_TYPE": "Type d'AST invalide : \"%s\"", + "ERROR_FORBIDDEN_MAGIC_ATTRIBUTE": "Les attributs magiques sont interdit : \"%s\"", + "ERROR_FORBIDDEN_MAGIC_METHOD": "Les méthodes magiques sont interdites : \"%s\"", + "ERROR_GETTING_METHOD_FORBIDDEN": "Récupérer une méthode est interdit : \"%s\"", + "ERROR_CANNOT_SET_ATTRIBUTE": "Changer un attribut est interdit : \"%s\"", + "ERROR_CANNOT_SET_ENVIRONMENT": "Changer une variable d'environnement est interdit : \"%s\"", + "ERROR_CANNOT_SET_ARGUMENT": "Changer une variable d'argument est interdit : \"%s\"", + "ERROR_CAN_ONLY_CALL_CONSTANT_METHOD": "Seulement appeler la méthode d'une constante est autorisé", + "ERROR_CAN_ONLY_CALL_ENV_FUNCTION": "Seulement appeler une fonction de l'environnement est autorisé", + "ERROR_FORBIDDEN_SYNTAX": "Syntaxe interdite : \"%s\"", + "ERROR_FUNCTION_COPY_FORBIDDEN": "Copier une fonction est interdit", + "ERROR_CANNOT_GET_ERROR_MESSAGE": "Impossible de récupérer le message d'erreur", + "ERROR_WT": "%s à généré l'erreur %i :\n%s", + "ERROR_CANNOT_FIND_TOOL": "Impossible de trouver l'outil \"%s\" dans le dossier tools", + "ERROR_CANNOT_EXTRACT_DIRECTORY": "Un dossier ne peut pas être extrait", + "ERROR_PATCH_MODE_NOT_IMPLEMENTED": "Le mode de Patch \"%s\" n'est pas implémenté (du patch : \"%s\")", + "ERROR_SOURCE_NOT_IMPLEMENTED": "La source \"%s\" n'est pas implémenté (du patch : \"%s\")", + "ERROR_OPERATION_NOT_IMPLEMENTED": "L'opération \"%s\" n'est pas implémenté", + "ERROR_BMG_LAYER_MODE_NOT_IMPLEMENTED": "Le mode de couche bmg \"%s\" n'est pas implémenté", + "ERROR_IMAGE_LAYER_TYPE_NOT_IMPLEMENTED": "Le type de couche d'image \"%s\" n'est pas implémenté" + } } \ No newline at end of file diff --git a/source/gui/__init__.py b/source/gui/__init__.py index 7f38bd2..a451ca1 100644 --- a/source/gui/__init__.py +++ b/source/gui/__init__.py @@ -29,8 +29,8 @@ def better_gui_error(func: Callable) -> Callable: exc_split = exc.splitlines() if len(exc_split) > 10: # if the traceback is too long, only keep the 5 first and 5 last lines of the traceback - exc_split = exc_split[:5] + ["..."] + exc_split[-5:] + ["", "", _("MORE_IN_ERROR_LOG")] + exc_split = exc_split[:5] + ["..."] + exc_split[-5:] + ["", "", _("ERROR_SEE_LOGS")] - messagebox.showerror(_("Error"), "\n".join(exc_split)) + messagebox.showerror(_("ERROR"), "\n".join(exc_split)) return wrapper diff --git a/source/gui/install.py b/source/gui/install.py index cc87e06..1e2a2cb 100644 --- a/source/gui/install.py +++ b/source/gui/install.py @@ -23,16 +23,6 @@ import os from source.mkw.collection.Extension import Extension -class SourceGameError(Exception): - def __init__(self, path: Path | str): - super().__init__(_(f"ERROR_INVALID_SOURCE_GAME", " : ", path)) - - -class DestinationGameError(Exception): - def __init__(self, path: Path | str): - super().__init__(_("ERROR_INVALID_DESTINATION_GAME", " : ", path)) - - class InstallerState(enum.Enum): IDLE = 0 INSTALLING = 1 @@ -50,7 +40,7 @@ class Window(tkinter.Tk): self.source_path = tkinter.StringVar() self.destination_path = tkinter.StringVar() - self.title(_("INSTALLER_TITLE")) + self.title(_("TITLE_INSTALL")) self.resizable(False, False) self.icon = tkinter.PhotoImage(file="./assets/icon.png") @@ -136,7 +126,7 @@ class Menu(tkinter.Menu): super().__init__(master, tearoff=False) self.root = master.root - master.add_cascade(label=_("LANGUAGE_SELECTION"), menu=self) + master.add_cascade(label=_("MENU_LANGUAGE_SELECTION"), menu=self) self.lang_variable = tkinter.StringVar(value=self.root.options.language.get()) @@ -155,19 +145,19 @@ class Menu(tkinter.Menu): super().__init__(master, tearoff=False) self.root = master.root - master.add_cascade(label=_("ADVANCED_CONFIGURATION"), menu=self) + master.add_cascade(label=_("MENU_ADVANCED"), menu=self) self.add_command( - label=_("OPEN_MYSTUFF_SETTINGS"), + label=_("MENU_ADVANCED_MYSTUFF"), command=lambda: mystuff.Window(self.root.mod_config, self.root.options) ) self.threads_used = self.ThreadsUsed(self) - self.add_command(label=_("EMPTY_CACHE"), command=self.empty_cache) + self.add_command(label=_("MENU_ADVANCED_EMPTY_CACHE"), command=self.empty_cache) self.add_separator() self.variable_developer_mode = tkinter.BooleanVar(value=self.root.options.developer_mode.get()) self.add_checkbutton( - label=_("ENABLE_DEVELOPER_MODE"), + label=_("MENU_ADVANCED_DEVELOPER_MODE"), variable=self.variable_developer_mode, command=lambda: self.root.options.developer_mode.set(self.variable_developer_mode.get()) ) @@ -184,14 +174,14 @@ class Menu(tkinter.Menu): def __init__(self, master: tkinter.Menu): super().__init__(master, tearoff=False) self.root = master.root - - master.add_cascade(label=_("THREADS_USAGE"), menu=self) + + master.add_cascade(label=_("MENU_ADVANCED_THREADS"), menu=self) self.variable = tkinter.IntVar(value=self.root.options.threads.get()) for i in [1, 2, 4, 8, 12, 16]: self.add_radiobutton( - label=_("USE", f" {i} ", "THREADS"), + label=_("MENU_ADVANCED_THREADS_SELECTION") % i, value=i, variable=self.variable, command=(lambda amount: (lambda: self.root.options.threads.set(amount)))(i), @@ -203,12 +193,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=_("MENU_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", 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: """ @@ -237,7 +227,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=_("TEXT_SOURCE_GAME")) self.root = master.root self.columnconfigure(1, weight=1) @@ -254,8 +244,8 @@ class SourceGame(ttk.LabelFrame): :return: """ raw_path = tkinter.filedialog.askopenfilename( - title=_("SELECT_SOURCE_GAME"), - filetypes=[(_("WII GAMES"), "*.iso *.ciso *.wbfs *.dol")], + title=_("TEXT_SELECT_SOURCE_GAME"), + filetypes=[(_("TEXT_WII_GAMES"), "*.iso *.ciso *.wbfs *.dol")], ) # if the user didn't select any file, return None @@ -298,7 +288,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=_("TEXT_GAME_DESTINATION")) self.root = master.root self.columnconfigure(1, weight=1) @@ -320,7 +310,7 @@ class DestinationGame(ttk.LabelFrame): :return: """ raw_path = tkinter.filedialog.askdirectory( - title=_("SELECT_DESTINATION_GAME"), + title=_("TEXT_SELECT_GAME_DESTINATION"), ) # if the user didn't select any directory, return None @@ -333,7 +323,7 @@ class DestinationGame(ttk.LabelFrame): def set_path(self, path: Path): if not os.access(path, os.W_OK): - messagebox.showwarning(_("WARNING"), _("WARNING_DESTINATION_GAME_NOT_WRITABLE")) + messagebox.showwarning(_("WARNING"), _("WARNING_DESTINATION_NOT_WRITABLE")) self.entry.delete(0, tkinter.END) self.entry.insert(0, str(path.absolute())) @@ -355,7 +345,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=_("TEXT_INSTALL"), command=self.install) self.root = master.root @threaded @@ -364,29 +354,35 @@ class ButtonInstall(ttk.Button): try: self.root.set_state(InstallerState.INSTALLING) - # check if the user entered a source path + # check if the user entered a source path. If the string is ".", then the user didn't input any path source_path = Path(self.root.source_path.get()) - if not source_path.exists(): raise SourceGameError(source_path) - if str(source_path) == ".": - messagebox.showerror(_("ERROR"), _("ERROR_INVALID_SOURCE_GAME")) + if not source_path.exists() or str(source_path) == ".": + messagebox.showerror(_("ERROR"), _("ERROR_INVALID_SOURCE_GAME") % source_path) return - # check if the user entered a destination path + # check if the user entered a destination path. If the string is ".", then the user didn't input any path destination_path = Path(self.root.destination_path.get()) - if not destination_path.exists(): raise DestinationGameError(destination_path) - if str(destination_path) == ".": - messagebox.showerror(_("ERROR"), _("ERROR_INVALID_DESTINATION_GAME")) + if not destination_path.exists() or str(destination_path) == ".": + messagebox.showerror(_("ERROR"), _("ERROR_INVALID_GAME_DESTINATION") % source_path) return + available_space_local = shutil.disk_usage(".").free + available_space_destination = shutil.disk_usage(destination_path).free + # if there is no more space on the installer drive, show a warning - if shutil.disk_usage(".").free < minimum_space_available: - if not messagebox.askokcancel(_("WARNING"), _("WARNING_LOW_SPACE_CONTINUE")): + if available_space_local < minimum_space_available: + if not messagebox.askokcancel( + _("WARNING"), + _("WARNING_LOW_SPACE_CONTINUE") % (Path(".").resolve().drive, available_space_local/Go) + ): return # if there is no more space on the destination drive, show a warning - elif shutil.disk_usage(destination_path).free < minimum_space_available: - if not messagebox.askokcancel(_("WARNING"), _("WARNING_LOW_SPACE_CONTINUE")): - return + elif available_space_destination < minimum_space_available: + if not messagebox.askokcancel( + _("WARNING"), + _("WARNING_LOW_SPACE_CONTINUE") % (destination_path.resolve().drive, available_space_destination/Go) + ): return if system == "lin64": # if linux if os.getuid() != 0: # if the user is not root @@ -417,9 +413,9 @@ class ButtonInstall(ttk.Button): ) messagebox.showinfo( - _("INSTALLATION_COMPLETED"), - f"{_('INSTALLATION_FINISHED_WITH_SUCCESS')}" + ( - f"\n{_('MESSAGE_FROM_MOD_AUTHOR')} :\n\n{message}" if message != "" else "" + _("TEXT_INSTALLATION_COMPLETED"), + f"{_('TEXT_INSTALLATION_FINISHED_SUCCESSFULLY')}" + ( + f"\n{_('TEXT_MESSAGE_FROM_AUTHOR')} :\n\n{message}" if message != "" else "" ) ) diff --git a/source/gui/mod_settings.py b/source/gui/mod_settings.py index 5703006..6dc7e82 100644 --- a/source/gui/mod_settings.py +++ b/source/gui/mod_settings.py @@ -21,6 +21,7 @@ class Window(tkinter.Toplevel): self.resizable(False, False) self.grab_set() + self.title(_("TITLE_MOD_SETTINGS")) self.rowconfigure(1, weight=1) self.columnconfigure(1, weight=1) @@ -32,19 +33,19 @@ class Window(tkinter.Toplevel): self.frame_global_settings = FrameSettings( self.panel_window, - _("GLOBAL_MOD_SETTINGS"), + _("TEXT_MOD_GLOBAL_SETTINGS"), self.mod_config.global_settings ) self.frame_specific_settings = FrameSettings( self.panel_window, - _("SPECIFIC_MOD_SETTINGS"), + _("TEXT_MOD_SPECIFIC_SETTINGS"), self.mod_config.specific_settings ) if self.options.developer_mode.get(): self.frame_testing_settings = FrameTesting( self.panel_window, - _("TESTING_MOD_SETTINGS") + _("TEXT_MOD_TEST_SETTINGS") ) self.frame_action = FrameAction(self) @@ -143,10 +144,10 @@ class FrameAction(ttk.Frame): super().__init__(master) self.root = master.root - self.button_import_settings = ttk.Button(self, text=_("IMPORT_SETTINGS"), width=20, + self.button_import_settings = ttk.Button(self, text=_("TEXT_IMPORT_SETTINGS"), width=20, command=self.import_settings) self.button_import_settings.grid(row=1, column=1) - self.button_export_settings = ttk.Button(self, text=_("EXPORT_SETTINGS"), width=20, + self.button_export_settings = ttk.Button(self, text=_("TEXT_EXPORT_SETTINGS"), width=20, command=self.export_settings) self.button_export_settings.grid(row=1, column=2) @@ -156,8 +157,8 @@ class FrameAction(ttk.Frame): """ path = filedialog.askopenfilename( - title=_("IMPORT_SETTINGS"), - filetypes=[(_("SETTINGS_FILE"), f"*{SETTINGS_FILE_EXTENSION}")] + title=_("TEXT_IMPORT_SETTINGS"), + filetypes=[(_("TEXT_SETTINGS_FILE"), f"*{SETTINGS_FILE_EXTENSION}")] ) # si le fichier n'a pas été choisi, ignore @@ -178,8 +179,8 @@ class FrameAction(ttk.Frame): """ path = filedialog.asksaveasfilename( - title=_("EXPORT_SETTINGS"), - filetypes=[(_("SETTINGS_FILE"), f"*{SETTINGS_FILE_EXTENSION}")] + title=_("TEXT_EXPORT_SETTINGS"), + filetypes=[(_("TEXT_SETTINGS_FILE"), f"*{SETTINGS_FILE_EXTENSION}")] ) # si le fichier n'a pas été choisi, ignore diff --git a/source/gui/mystuff.py b/source/gui/mystuff.py index 6913f2c..87f9efd 100644 --- a/source/gui/mystuff.py +++ b/source/gui/mystuff.py @@ -24,11 +24,11 @@ class Window(tkinter.Toplevel): self.mod_config = mod_config self.options = options - self.title(_("CONFIGURE_MYSTUFF_PATCH")) + self.title(_("TITLE_MYSTUFF_SETTINGS")) self.resizable(False, False) self.grab_set() # the others window will be disabled, keeping only this one activated - self.disabled_text: str = _("<", "DISABLED", ">") + self.disabled_text: str = f"<{_('TEXT_DISABLED')}>" self.frame_profile = ttk.Frame(self) self.frame_profile.grid(row=1, column=1, sticky="NEWS") @@ -40,14 +40,14 @@ class Window(tkinter.Toplevel): self.button_new_profile = ttk.Button( self.frame_profile, - text=_("NEW_PROFILE"), + text=_("TEXT_NEW_PROFILE"), command=self.new_profile ) self.button_new_profile.grid(row=1, column=2, sticky="NEWS") self.button_delete_profile = ttk.Button( self.frame_profile, - text=_("DELETE_PROFILE"), + text=_("TEXT_DELETE_PROFILE"), command=self.delete_profile ) self.button_delete_profile.grid(row=1, column=3, sticky="NEWS") @@ -76,14 +76,14 @@ class Window(tkinter.Toplevel): self.button_add_mystuff_path = ttk.Button( self.frame_mystuff_paths_action, - text=_("ADD_MYSTUFF"), + text=_("TEXT_ADD_MYSTUFF"), command=self.add_mystuff_path ) self.button_add_mystuff_path.grid(row=1, column=1) self.button_remove_mystuff_path = ttk.Button( self.frame_mystuff_paths_action, - text=_("REMOVE_MYSTUFF"), + text=_("TEXT_REMOVE_MYSTUFF"), command=self.remove_mystuff_path ) self.button_remove_mystuff_path.grid(row=1, column=2) @@ -138,12 +138,12 @@ class Window(tkinter.Toplevel): profile_name: str = self.combobox_profile.get() if profile_name in mystuff_packs: - messagebox.showerror(_("ERROR"), _("MYSTUFF_PROFILE_ALREADY_EXIST")) + messagebox.showerror(_("ERROR"), _("ERROR_MYSTUFF_PROFILE_ALREADY_EXIST") % profile_name) return for banned_char in "<>": if banned_char in profile_name: - messagebox.showerror(_("ERROR"), _("MYSTUFF_PROFILE_FORBIDDEN_NAME")) + messagebox.showerror(_("ERROR"), _("ERROR_MYSTUFF_PROFILE_FORBIDDEN_NAME") % profile_name) return mystuff_packs[profile_name] = {"paths": []} @@ -167,7 +167,7 @@ class Window(tkinter.Toplevel): Add a new path to the currently selected MyStuff profile """ - if (mystuff_path := filedialog.askdirectory(title=_("SELECT_MYSTUFF"), mustexist=True)) is None: return + if (mystuff_path := filedialog.askdirectory(title=_("TEXT_SELECT_MYSTUFF"), mustexist=True)) == "": return mystuff_path = Path(mystuff_path) mystuff_packs = self.root.options.mystuff_packs.get() diff --git a/source/gui/preview/__init__.py b/source/gui/preview/__init__.py index e5c8895..68e8cb6 100644 --- a/source/gui/preview/__init__.py +++ b/source/gui/preview/__init__.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: class InvalidPreviewWindowName(Exception): def __init__(self, name: str): - super().__init__(_("TYPE_PREVIEW_WINDOW", " '", name, "' ", "NOT_FOUND")) + super().__init__(_("ERROR_PREVIEW_WINDOW_NOT_FOUND") % name) class AbstractPreviewWindow(tkinter.Toplevel, ABC): diff --git a/source/mkw/ExtractedGame.py b/source/mkw/ExtractedGame.py index d53fd14..113c046 100644 --- a/source/mkw/ExtractedGame.py +++ b/source/mkw/ExtractedGame.py @@ -5,6 +5,7 @@ from pathlib import Path from typing import Generator, IO, TYPE_CHECKING from source import file_block_size +from source.mkw import PathOutsideAllowedRange from source.mkw.ModConfig import ModConfig from source.mkw.Patch.Patch import Patch from source.mkw.collection.Extension import Extension @@ -21,11 +22,6 @@ if TYPE_CHECKING: RIIVOLUTION_FOLDER_NAME: str = "riivolution" -class PathOutsideMod(Exception): - def __init__(self, forbidden_path: Path, allowed_range: Path): - super().__init__(_("PATH", ' "', forbidden_path, '" ', "OUTSIDE_ALLOWED_RANGE", ' "', allowed_range, '" ')) - - class ExtractedGame: """ Class that represents an extracted game @@ -41,7 +37,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=_("TEXT_EXTRACTING_AUTOADD"), determinate=False) szs.autoadd(self.path / "files/Race/Course/", destination_path) def extract_original_tracks(self, destination_path: "Path | str") -> Generator[Progress, None, None]: @@ -54,14 +50,13 @@ class ExtractedGame: original_tracks: list[Path] = list((self.path / "files/Race/Course/").glob("*.szs")) yield Progress( - description=_("EXTRACTING_ORIGINAL_TRACKS"), determinate=True, max_step=len(original_tracks), set_step=0, ) for track_file in original_tracks: - yield Progress(description=_("EXTRACTING_ORIGINAL_TRACKS", " (", track_file.name, ") ..."), step=1) + yield Progress(description=_("TEXT_EXTRACTING_ORIGINAL_TRACKS") % track_file.name, step=1) if not (destination_path / track_file.name).exists(): track_file.rename(destination_path / track_file.name) else: track_file.unlink() @@ -73,7 +68,7 @@ class ExtractedGame: :mystuff_path: path to the MyStuff directory :return: """ - yield Progress(description=_("INSTALLING_MYSTUFF", ' "', mystuff_path, '" ...')) + yield Progress(description=_("TEXT_INSTALLING_MYSTUFF") % mystuff_path) mystuff_path = Path(mystuff_path) mystuff_rootfiles: dict[str, Path] = {} @@ -90,12 +85,7 @@ class ExtractedGame: Install multiple mystuff patch :param mystuff_paths: paths to all the mystuff patch """ - yield Progress( - description=_("INSTALLING_ALL_MYSTUFF_PATCHS"), - determinate=True, - max_step=len(mystuff_paths), - set_step=0 - ) + yield Progress(determinate=True, max_step=len(mystuff_paths), set_step=0) for mystuff_path in mystuff_paths: yield Progress(step=1) @@ -106,7 +96,7 @@ class ExtractedGame: Prepare special files for the patch :return: the special files dict """ - yield Progress(description=_("PREPARING", " ct_icons ", "SPECIAL_FILE", "..."), determinate=False) + yield Progress(description=_("TEXT_PREPARING_SPECIAL_FILE") % "ct_icons", determinate=False) ct_icons = BytesIO() mod_config.get_full_cticon().save(ct_icons, format="PNG") ct_icons.seek(0) @@ -116,7 +106,7 @@ class ExtractedGame: """ Prepare main.dol and StaticR.rel files (clean them and add lecode) """ - yield Progress(description="Preparing main.dol...", determinate=False) + yield Progress(description=_("TEXT_PREPARING_MAIN_DOL"), determinate=False) StrPath(self.path / "sys/main.dol").patch(clean_dol=True, add_lecode=True) def recreate_all_szs(self) -> Generator[Progress, None, None]: @@ -125,7 +115,6 @@ class ExtractedGame: """ all_extracted_szs: list[Path] = list(filter(lambda path: path.is_dir(), self.path.rglob("*.d"))) yield Progress( - description=_("REPACKING", " ", "ALL_ARCHIVES"), determinate=True, max_step=len(all_extracted_szs), set_step=0, @@ -134,7 +123,7 @@ class ExtractedGame: for extracted_szs in all_extracted_szs: # for every directory that end with a .d in the extracted game, recreate the szs yield Progress( - description=_("REPACKING", ' "', extracted_szs.relative_to(self.path), '"'), + description=_("TEXT_REPACKING_ARCHIVE") % extracted_szs.relative_to(self.path), step=1 ) @@ -150,7 +139,7 @@ class ExtractedGame: :param cache_directory: Path to the cache :param mod_config: mod configuration """ - yield Progress(description=_("PATCHING", " LECODE.bin")) + yield Progress(description=_("TEXT_PATCHING_LECODE")) cache_directory = Path(cache_directory) cttracks_directory = Path(cttracks_directory) ogtracks_directory = Path(ogtracks_directory) @@ -161,7 +150,7 @@ class ExtractedGame: lpar_dir: Path = mod_config.path.parent / "_LPAR/" lpar: Path = lpar_dir / mod_config.multiple_safe_eval(mod_config.lpar_template)() - if not lpar.is_relative_to(lpar_dir): raise PathOutsideMod(lpar, lpar_dir) + if not lpar.is_relative_to(lpar_dir): raise PathOutsideAllowedRange(lpar, lpar_dir) for lecode_file in (self.path / "files/rel/").glob("lecode-*.bin"): lec.patch( @@ -178,12 +167,16 @@ class ExtractedGame: for all the subdirectory named by the patch_directory_name, apply the patch :param mod_config: the mod to install """ - # yield an empty dict so that if nothing is yielded by the Patch, still is considered a generator - yield Progress() - + patch_directories: list[Path] = [] for part_directory in mod_config.get_mod_directory().glob("[!_]*"): for patch_directory in part_directory.glob(patch_directory_name): - yield from Patch(patch_directory, mod_config, self._special_file).install(self) + patch_directories.append(patch_directory) + + yield Progress(determinate=True, max_step=len(patch_directories)+1, set_step=0) + + for patch_directory in patch_directories: + yield Progress(step=1) + yield from Patch(patch_directory, mod_config, self._special_file).install(self) def install_all_prepatch(self, mod_config: ModConfig) -> Generator[Progress, None, None]: """ @@ -191,7 +184,6 @@ class ExtractedGame: Used before the lecode patch is applied :param mod_config: the mod to install """ - 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]: @@ -200,7 +192,6 @@ class ExtractedGame: Used after the lecode patch is applied :param mod_config: the mod to install """ - yield Progress(description=_("INSTALLING_ALL", " ", "PATCHS", "..."), determinate=False) yield from self._install_all_patch(mod_config, "_PATCH/") def convert_to(self, output_type: Extension) -> Generator[Progress, None, wit.WITPath | None]: @@ -209,7 +200,7 @@ class ExtractedGame: :param output_type: path to the destination of the game :output_type: format of the destination game """ - yield Progress(description=_("CONVERTING_GAME_TO", " ", output_type.name), determinate=False) + yield Progress(description=_("TEXT_CONVERT_GAME_TO") % output_type.name, determinate=False) if output_type == Extension.FST: return destination_file = self.path.with_suffix(self.path.suffix + output_type.value) @@ -225,7 +216,7 @@ class ExtractedGame: destination_file=destination_file, ) - yield Progress(description=_("DELETING_EXTRACTED_GAME"), determinate=False) + yield Progress(description=_("TEXT_DELETING_EXTRACTED_GAME"), determinate=False) shutil.rmtree(self.path) return converted_game @@ -241,7 +232,7 @@ class ExtractedGame: game_files: list[Path] = list(filter(lambda file: file.is_file(), self.path.rglob("*"))) yield Progress( - description=_("CONVERTING_TO_RIIVOLUTION"), + description=_("TEXT_CONVERTING_TO_RIIVOLUTION"), determinate=True, max_step=len(game_files), set_step=0, @@ -316,7 +307,7 @@ class ExtractedGame: rel_path: str = str(fp.relative_to(self.path)) yield Progress( - description=_(f"CALCULATING_HASH_FOR", ' "', rel_path, '"'), + description=_("TEXT_CALCULATING_HASH") % rel_path, step=1 ) diff --git a/source/mkw/Game.py b/source/mkw/Game.py index 4d6b618..9d596cb 100644 --- a/source/mkw/Game.py +++ b/source/mkw/Game.py @@ -13,14 +13,12 @@ from source.translation import translate as _ class NotMKWGameError(Exception): def __init__(self, path: "Path | str"): - path = Path(path) - super().__init__(_("NOT_MKW_GAME", ' : "', path.name, '"')) + super().__init__(_("ERROR_NOT_MKW_GAME") % Path(path).name) class NotVanillaError(Exception): def __init__(self, path: "Path | str"): - path = Path(path) - super().__init__(_("GAME_ALREADY_MODDED", ' : "', path.name, '"')) + super().__init__(_("ERROR_GAME_ALREADY_MODDED") % Path(path).name) class Game: @@ -49,7 +47,7 @@ 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=_("TEXT_COPYING_GAME"), determinate=False) try: next(gen) except StopIteration as e: return e.value @@ -57,17 +55,20 @@ class Game: yield Progress(determinate=True, max_step=100) for gen_data in gen: + percentage: int = gen_data["percentage"] + estimation: str = gen_data["estimation"] if gen_data["estimation"] is not None else "-:--" + yield Progress( - description=_("EXTRACTING", " - ", gen_data["percentage"], "% - (", "ESTIMATED_TIME_REMAINING", ": " - f'{gen_data["estimation"] if gen_data["estimation"] is not None else "-:--"})'), - set_step=gen_data["percentage"], + description=_("TEXT_EXTRACTING_GAME") % (percentage, estimation), + set_step=percentage, ) + try: next(gen) except StopIteration as e: return e.value def edit(self, mod_config: ModConfig) -> Generator[Progress, None, None]: - yield Progress(description=_("CHANGING_GAME_METADATA"), determinate=False) + yield Progress(description=_("TEXT_CHANGING_GAME_METADATA"), determinate=False) self.wit_path.edit( name=mod_config.name, game_id=self.wit_path.id[:4] + mod_config.variant @@ -120,23 +121,23 @@ 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=_("PART_EXTRACTION"), set_part=1) yield from self.extract(extracted_game.path) # Get the original file hash map for comparaison with the post-patched game - yield Progress(title=_("PREPARING_RIIVOLUTION"), set_part=2) + yield Progress(title=_("PART_PRE_RIIVOLUTION"), set_part=2) riivolution_original_hash_map: dict[str, str] | None = None if output_type.is_riivolution(): riivolution_original_hash_map = yield from extracted_game.get_hash_map() # install mystuff - yield Progress(title=_("MYSTUFF"), set_part=3) + yield Progress(title=_("PART_MYSTUFF"), set_part=3) mystuff_packs = options.mystuff_packs.get() mystuff_data = mystuff_packs.get(options.mystuff_pack_selected.get()) 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=4) + yield Progress(title=_("PART_PREPARING_FILES"), set_part=4) 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( @@ -149,10 +150,10 @@ class Game: yield from extracted_game.prepare_special_file(mod_config) # prepatch the game - yield Progress(title=_("PRE-PATCH_TITLE"), set_part=5) - yield from extracted_game.install_all_prepatch(mod_config) # PROGRESS + yield Progress(title=_("PART_PREPATCH"), set_part=5) + yield from extracted_game.install_all_prepatch(mod_config) - yield Progress(title="LE-CODE", set_part=6) + yield Progress(title=_("PART_LECODE"), set_part=6) yield from extracted_game.patch_lecode( # PROGRESS mod_config, cache_directory, @@ -160,17 +161,17 @@ class Game: cache_ogtracks_directory, ) - yield Progress(title=_("PATCH_TITLE"), set_part=7) - yield from extracted_game.install_all_patch(mod_config) # PROGRESS + yield Progress(title=_("PART_PATCH"), set_part=7) + yield from extracted_game.install_all_patch(mod_config) yield from extracted_game.recreate_all_szs() if output_type.is_riivolution(): - yield Progress(title=_("CONVERTING_TO_RIIVOLUTION"), set_part=8) + yield Progress(title=_("PART_RIIVOLUTION"), set_part=8) yield from extracted_game.convert_to_riivolution(mod_config, riivolution_original_hash_map) else: # convert the extracted game into a file - yield Progress(title=_("CONVERTING_TO_GAME_FILE"), set_part=8) + yield Progress(title=_("PART_CONVERSION"), set_part=8) 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/ModConfig.py b/source/mkw/ModConfig.py index a687c05..78d5da2 100644 --- a/source/mkw/ModConfig.py +++ b/source/mkw/ModConfig.py @@ -422,7 +422,7 @@ class ModConfig: nonlocal normalize_threads yield Progress( - description=_("NORMALIZING_TRACKS", " :\n" + "\n".join(thread['name'] for thread in normalize_threads)) + description=_("TEXT_NORMALIZING_TRACKS") % "\n".join(thread['name'] for thread in normalize_threads) ) normalize_threads = list(filter(lambda thread: thread["thread"].is_alive(), normalize_threads)) @@ -433,7 +433,6 @@ class ModConfig: ) yield Progress( - description=_("NORMALIZING_TRACKS"), determinate=True, max_step=len(all_arenas_tracks)+1, set_step=0, diff --git a/source/mkw/ModSettings/AbstractModSettings.py b/source/mkw/ModSettings/AbstractModSettings.py index 0148ef5..f7b90e0 100644 --- a/source/mkw/ModSettings/AbstractModSettings.py +++ b/source/mkw/ModSettings/AbstractModSettings.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: class InvalidSettingsType(Exception): def __init__(self, settings_type: str): - super().__init__(_("TYPE_MOD_SETTINGS", " '", settings_type, "' ", "NOT_FOUND")) + super().__init__(_("ERROR_MOD_SETTINGS_NOT_FOUND") % settings_type) class AbstractModSettings(ABC): diff --git a/source/mkw/ModSettings/SettingsType/Boolean.py b/source/mkw/ModSettings/SettingsType/Boolean.py index e395e07..fb02eab 100644 --- a/source/mkw/ModSettings/SettingsType/Boolean.py +++ b/source/mkw/ModSettings/SettingsType/Boolean.py @@ -17,9 +17,9 @@ class Boolean(AbstractModSettings): super().tkinter_show(master, checkbox) variable = self.tkinter_variable(tkinter.BooleanVar) - radiobutton_on = ttk.Radiobutton(master, text=_("DISABLED"), variable=variable, value=False) + radiobutton_on = ttk.Radiobutton(master, text=_("TEXT_DISABLED"), variable=variable, value=False) radiobutton_on.grid(row=1, column=1, sticky="E") - radiobutton_off = ttk.Radiobutton(master, text=_("ENABLED"), variable=variable, value=True) + radiobutton_off = ttk.Radiobutton(master, text=_("TEXT_ENABLED"), variable=variable, value=True) radiobutton_off.grid(row=1, column=2, sticky="E") self.tkinter_bind(master, checkbox) diff --git a/source/mkw/Patch/Patch.py b/source/mkw/Patch/Patch.py index 5da6a08..a52de5c 100644 --- a/source/mkw/Patch/Patch.py +++ b/source/mkw/Patch/Patch.py @@ -29,7 +29,7 @@ class Patch: :param extracted_game: the extracted game """ from source.mkw.Patch.PatchDirectory import PatchDirectory - yield Progress(description=_("INSTALLING_PATCH"), determinate=False) + yield Progress() # 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 1b6c93b..f01c98b 100644 --- a/source/mkw/Patch/PatchDirectory.py +++ b/source/mkw/Patch/PatchDirectory.py @@ -1,7 +1,8 @@ from pathlib import Path from typing import Generator, TYPE_CHECKING -from source.mkw.Patch import PathOutsidePatch, InvalidPatchMode +from source.mkw import PathOutsideAllowedRange +from source.mkw.Patch import InvalidPatchMode from source.mkw.Patch.PatchObject import PatchObject from source.progress import Progress from source.translation import translate as _ @@ -28,7 +29,7 @@ class PatchDirectory(PatchObject): """ patch a subdirectory of the game with the PatchDirectory """ - yield Progress(description=_("PATCHING", ' "', game_subpath.relative_to(extracted_game.path), '"')) + yield Progress(description=_("TEXT_PATCHING") % game_subpath.relative_to(extracted_game.path)) # check if the directory should be patched if not self.is_enabled(extracted_game): return @@ -47,7 +48,7 @@ class PatchDirectory(PatchObject): for game_subfile in game_subpath.parent.glob(self.configuration["match_regex"]): # disallow patching files outside of the game if not game_subfile.relative_to(extracted_game.path): - raise PathOutsidePatch(game_subfile, extracted_game.path) + raise PathOutsideAllowedRange(game_subfile, extracted_game.path) # patch the game with the subpatch # if the subfile is a szs archive, replace it with a .d extension diff --git a/source/mkw/Patch/PatchFile.py b/source/mkw/Patch/PatchFile.py index 6558f0f..7c445b9 100644 --- a/source/mkw/Patch/PatchFile.py +++ b/source/mkw/Patch/PatchFile.py @@ -2,7 +2,8 @@ from io import BytesIO from pathlib import Path from typing import Generator, IO, TYPE_CHECKING -from source.mkw.Patch import PathOutsidePatch, InvalidPatchMode, InvalidSourceMode +from source.mkw import PathOutsideAllowedRange +from source.mkw.Patch import InvalidPatchMode, InvalidSourceMode from source.mkw.Patch.PatchOperation import AbstractPatchOperation from source.mkw.Patch.PatchObject import PatchObject from source.progress import Progress @@ -82,7 +83,7 @@ class PatchFile(PatchObject): """ patch a subfile of the game with the PatchFile """ - yield Progress(description=translate("PATCHING", ' "', game_subpath.relative_to(extracted_game.path), '"')) + yield Progress(description=translate("TEXT_PATCHING") % game_subpath.relative_to(extracted_game.path)) # translate is not renamed "_" here because it is used to drop useless value in unpacking # check if the file should be patched @@ -119,9 +120,9 @@ class PatchFile(PatchObject): for game_subfile in game_subpath.parent.glob(self.configuration["match_regex"]): # disallow patching files outside of the game if not game_subfile.relative_to(extracted_game.path): - raise PathOutsidePatch(game_subfile, extracted_game.path) + raise PathOutsideAllowedRange(game_subfile, extracted_game.path) - yield Progress(description=translate("PATCHING", " ", game_subfile)) + yield Progress(description=translate("TEXT_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 303e074..6559b6d 100644 --- a/source/mkw/Patch/PatchOperation/BmgTxtEditor/__init__.py +++ b/source/mkw/Patch/PatchOperation/BmgTxtEditor/__init__.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: class InvalidBmgLayerMode(Exception): def __init__(self, layer_mode: str): - super().__init__(_("BMG_LAYER_MODE", ' "', layer_mode, '" ', "IS_NOT_IMPLEMENTED")) + super().__init__(_("ERROR_BMG_LAYER_MODE_NOT_IMPLEMENTED") % layer_mode) class AbstractLayer(ABC): diff --git a/source/mkw/Patch/PatchOperation/ImageEditor/ImageLayer.py b/source/mkw/Patch/PatchOperation/ImageEditor/ImageLayer.py index bdce754..5cf099d 100644 --- a/source/mkw/Patch/PatchOperation/ImageEditor/ImageLayer.py +++ b/source/mkw/Patch/PatchOperation/ImageEditor/ImageLayer.py @@ -2,8 +2,8 @@ from typing import TYPE_CHECKING from PIL import Image +from source.mkw import PathOutsideAllowedRange from source.mkw.Patch.PatchOperation.ImageEditor import AbstractLayer -from source.mkw.Patch import PathOutsidePatch if TYPE_CHECKING: from source.mkw.Patch import Patch @@ -27,7 +27,7 @@ class ImageLayer(AbstractLayer): # check if the path is outside of the allowed directory layer_image_path = patch.path / self.image_path if not layer_image_path.is_relative_to(patch.path): - raise PathOutsidePatch(layer_image_path, patch.path) + raise PathOutsideAllowedRange(layer_image_path, patch.path) # load the image that will be pasted layer_image = Image.open(layer_image_path.resolve()) \ diff --git a/source/mkw/Patch/PatchOperation/ImageEditor/TextLayer.py b/source/mkw/Patch/PatchOperation/ImageEditor/TextLayer.py index 2ac169b..9cc1dcb 100644 --- a/source/mkw/Patch/PatchOperation/ImageEditor/TextLayer.py +++ b/source/mkw/Patch/PatchOperation/ImageEditor/TextLayer.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING from PIL import ImageFont, ImageDraw, Image -from source.mkw.Patch import PathOutsidePatch +from source.mkw import PathOutsideAllowedRange from source.mkw.Patch.PatchOperation.ImageEditor import AbstractLayer if TYPE_CHECKING: @@ -33,7 +33,7 @@ class TextLayer(AbstractLayer): if self.font_path is not None: font_image_path = patch.path / self.font_path if not font_image_path.is_relative_to(patch.path): - raise PathOutsidePatch(font_image_path, patch.path) + raise PathOutsideAllowedRange(font_image_path, patch.path) else: font_image_path = None diff --git a/source/mkw/Patch/PatchOperation/ImageEditor/__init__.py b/source/mkw/Patch/PatchOperation/ImageEditor/__init__.py index bb02b8a..db87cb5 100644 --- a/source/mkw/Patch/PatchOperation/ImageEditor/__init__.py +++ b/source/mkw/Patch/PatchOperation/ImageEditor/__init__.py @@ -12,7 +12,7 @@ if TYPE_CHECKING: class InvalidImageLayerType(Exception): def __init__(self, layer_type: str): - super().__init__(_("IMAGE_LAYER_TYPE", ' "', layer_type, '" ', "IS_NOT_IMPLEMENTED")) + super().__init__(_("ERROR_IMAGE_LAYER_TYPE_NOT_IMPLEMENTED") % layer_type) class AbstractLayer(ABC): diff --git a/source/mkw/Patch/PatchOperation/StrEditor.py b/source/mkw/Patch/PatchOperation/StrEditor.py index 08a6175..951b68b 100644 --- a/source/mkw/Patch/PatchOperation/StrEditor.py +++ b/source/mkw/Patch/PatchOperation/StrEditor.py @@ -2,7 +2,7 @@ from io import BytesIO from pathlib import Path from typing import IO, TYPE_CHECKING -from source.mkw.Patch import PathOutsidePatch +from source.mkw import PathOutsideAllowedRange from source.mkw.Patch.PatchOperation import AbstractPatchOperation from source.wt import wstrt @@ -31,7 +31,7 @@ class StrEditor(AbstractPatchOperation): for section in self.sections if self.sections is not None else []: section_path = patch.path / section if not section_path.is_relative_to(patch.path): - raise PathOutsidePatch(section_path, patch.path) + raise PathOutsideAllowedRange(section_path, patch.path) checked_sections += section_path # for every file in the sections, check if they are inside the patch. diff --git a/source/mkw/Patch/PatchOperation/__init__.py b/source/mkw/Patch/PatchOperation/__init__.py index fd69e34..2fc405b 100644 --- a/source/mkw/Patch/PatchOperation/__init__.py +++ b/source/mkw/Patch/PatchOperation/__init__.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: class InvalidPatchOperation(Exception): def __init__(self, operation: str): - super().__init__(_("OPERATION", ' "', operation, '" ', "IS_NOT_IMPLEMENTED")) + super().__init__(_("ERROR_OPERATION_NOT_IMPLEMENTED") % operation) class AbstractPatchOperation(ABC): diff --git a/source/mkw/Patch/__init__.py b/source/mkw/Patch/__init__.py index f52bf8b..ebaea65 100644 --- a/source/mkw/Patch/__init__.py +++ b/source/mkw/Patch/__init__.py @@ -1,4 +1,3 @@ -from pathlib import Path from typing import TYPE_CHECKING from source.translation import translate as _ @@ -7,18 +6,11 @@ if TYPE_CHECKING: from source.mkw.Patch.PatchObject import PatchObject -class PathOutsidePatch(Exception): - def __init__(self, forbidden_path: Path, allowed_range: Path): - super().__init__(_("PATH", ' "', forbidden_path, '" ', "OUTSIDE_ALLOWED_RANGE", ' "', {allowed_range}, '" ')) - - class InvalidPatchMode(Exception): def __init__(self, patch: "PatchObject", mode: str): - super().__init__(_("MODE", ' "', mode, '" ', "IS_NOT_IMPLEMENTED", - "(", "IN_PATCH", ' : "', patch.full_path, '")')) + super().__init__(_("ERROR_PATCH_MODE_NOT_IMPLEMENTED") % (mode, patch.full_path)) class InvalidSourceMode(Exception): def __init__(self, patch: "PatchObject", source: str): - super().__init__(_("SOURCE", ' "', source, '" ', "IS_NOT_IMPLEMENTED", - "(", "IN_PATCH", ' : "', patch.full_path, '")')) + super().__init__(_("ERROR_SOURCE_NOT_IMPLEMENTED") % (source, patch.full_path)) diff --git a/source/mkw/Track/AbstractTrack.py b/source/mkw/Track/AbstractTrack.py index 26a0fb7..385e621 100644 --- a/source/mkw/Track/AbstractTrack.py +++ b/source/mkw/Track/AbstractTrack.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: class TrackForbiddenCustomAttribute(Exception): def __init__(self, attribute_name: str): - super().__init__(_("FORBIDDEN_TRACK_ATTRIBUTE", " : ", repr(attribute_name))) + super().__init__(_("ERROR_FORBIDDEN_TRACK_ATTRIBUTE") % attribute_name) class AbstractTrack(ABC): @@ -35,7 +35,6 @@ class AbstractTrack(ABC): for key, value in kwargs.items(): # if the attribute start with __, this is a magic attribute, and it should not be modified if "__" in key: raise TrackForbiddenCustomAttribute(key) - # TODO: check potential security issue with setattr and already implemented method and attribute setattr(self, key, value) def __repr__(self): diff --git a/source/mkw/Track/Arena.py b/source/mkw/Track/Arena.py index 4e83daf..a69897d 100644 --- a/source/mkw/Track/Arena.py +++ b/source/mkw/Track/Arena.py @@ -12,7 +12,7 @@ if TYPE_CHECKING: class ArenaForbiddenCustomAttribute(Exception): def __init__(self, attribute_name: str): - super().__init__(_("FORBIDDEN_ARENA_ATTRIBUTE", " : ", repr(attribute_name))) + super().__init__(_("ERROR_FORBIDDEN_ARENA_ATTRIBUTE") % attribute_name) class Arena(RealArenaTrack): diff --git a/source/mkw/Track/DefaultTrack.py b/source/mkw/Track/DefaultTrack.py index b46231d..461921c 100644 --- a/source/mkw/Track/DefaultTrack.py +++ b/source/mkw/Track/DefaultTrack.py @@ -1,10 +1,5 @@ -from typing import TYPE_CHECKING - from source.mkw.Track.AbstractTrack import AbstractTrack -if TYPE_CHECKING: - from source.mkw.ModConfig import ModConfig - class DefaultTrack(AbstractTrack): def repr_format(self, template: str) -> str: diff --git a/source/mkw/Track/TrackGroup.py b/source/mkw/Track/TrackGroup.py index 48f4f67..91247a4 100644 --- a/source/mkw/Track/TrackGroup.py +++ b/source/mkw/Track/TrackGroup.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: class TrackGroupForbiddenCustomAttribute(Exception): def __init__(self, attribute_name: str): - super().__init__(_("FORBIDDEN_TRACKGROUP_ATTRIBUTE", " : ", repr(attribute_name))) + super().__init__(_("ERROR_FORBIDDEN_TRACKGROUP_ATTRIBUTE") % attribute_name) class TrackGroup: @@ -61,7 +61,7 @@ class TrackGroup: return the ctfile of the track group :return: ctfile """ - ctfile = f'T T11; T11; 0x02; "-"; "info"; "-"\n' + ctfile = f'T T11; T11; 0x02; "-"; "-"; "-"\n' for track in self.get_tracks(): ctfile += track.get_ctfile(template=template, hidden=True) diff --git a/source/mkw/__init__.py b/source/mkw/__init__.py index c5d1a3f..3655772 100644 --- a/source/mkw/__init__.py +++ b/source/mkw/__init__.py @@ -1 +1,12 @@ +from typing import TYPE_CHECKING +from source.translation import translate as _ + +if TYPE_CHECKING: + from pathlib import Path + Tag = str + + +class PathOutsideAllowedRange(Exception): + def __init__(self, forbidden_path: "Path", allowed_range: "Path"): + super().__init__(_("ERROR_PATH_OUTSIDE_RANGE") % (forbidden_path, allowed_range)) \ No newline at end of file diff --git a/source/mkw/collection/MKWColor.py b/source/mkw/collection/MKWColor.py index 383cb94..00aefa4 100644 --- a/source/mkw/collection/MKWColor.py +++ b/source/mkw/collection/MKWColor.py @@ -5,7 +5,7 @@ from source.translation import translate as _ class ColorNotFound(Exception): def __init__(self, color_data: any): - super().__init__(_("CANNOT_FIND_COLOR", ' "', color_data, '"')) + super().__init__(_("ERROR_CANNOT_FIND_COLOR") % color_data) @dataclass(init=True, slots=True) diff --git a/source/mkw/collection/Slot.py b/source/mkw/collection/Slot.py index 79974a0..559cbe0 100644 --- a/source/mkw/collection/Slot.py +++ b/source/mkw/collection/Slot.py @@ -4,7 +4,7 @@ from source.translation import translate as _ class SlotNotFound(Exception): def __init__(self, slot_data: any): - super().__init__(_("CANNOT_FIND_SLOT", ' "', slot_data, '" ')) + super().__init__(_("ERROR_CANNOT_FIND_SLOT") % slot_data) @dataclass(init=True, slots=True, frozen=True, repr=True) diff --git a/source/option.py b/source/option.py index aef9fa8..e9c7f11 100644 --- a/source/option.py +++ b/source/option.py @@ -6,7 +6,7 @@ from source.utils import restart_program class OptionLoadingError(Exception): def __init__(self): - super().__init__(f"An error occured while loading options. Try deleting the option.json file.") + super().__init__(f"An error occurred while loading options. Try deleting the option.json file.") class Option: diff --git a/source/safe_eval/__init__.py b/source/safe_eval/__init__.py index aca6197..0fbca10 100644 --- a/source/safe_eval/__init__.py +++ b/source/safe_eval/__init__.py @@ -4,7 +4,7 @@ from source.translation import translate as _ class BetterSafeEvalError(Exception): def __init__(self, template: str): - super().__init__(_("SAFE_EVAL_ERROR", " (", "TEMPLATE_USED", " : ", repr(template), ")")) + super().__init__(_("ERROR_SAFEEVAL") % template) def better_safe_eval_error(func: Callable, template: str): diff --git a/source/safe_eval/macros.py b/source/safe_eval/macros.py index e8a6520..3701bc5 100644 --- a/source/safe_eval/macros.py +++ b/source/safe_eval/macros.py @@ -12,7 +12,7 @@ MACRO_START, MACRO_END = "##", "##" class NotImplementedMacro(Exception): def __init__(self, macro: str): - super().__init__(_("INVALID_MACRO", ' : "', macro, '"')) + super().__init__(_("ERROR_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 de93c8b..6e17b9d 100644 --- a/source/safe_eval/safe_eval.py +++ b/source/safe_eval/safe_eval.py @@ -60,7 +60,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(_("INVALID_AST_TYPE", ' : "', type(stmt).__name__, '"')) + raise SafeEvalException(_("ERROR_INVALID_AST_TYPE") % type(stmt).__name__) # check every node for disabled expression for node in ast.walk(stmt): @@ -70,20 +70,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(_("MAGIC_ATTRIBUTE_ARE_FORBIDDEN", ' : "', node.attr, '"')) + raise SafeEvalException(_("ERROR_FORBIDDEN_MAGIC_ATTRIBUTE") % node.attr) # ban modification to environment if isinstance(node.ctx, ast.Store): - raise SafeEvalException(_("CANNOT_SET_ATTRIBUTE", ' : "', node.attr, '"')) + raise SafeEvalException(_("ERROR_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(_("CANNOT_SET_ENVIRONMENT", ' : "', node.id, '"')) + raise SafeEvalException(_("ERROR_CANNOT_SET_ENVIRONMENT") % node.id) elif node.id in args: - raise SafeEvalException(_("CANNOT_SET_ARGUMENT", ' : "', node.id, '"')) + raise SafeEvalException(_("ERROR_CANNOT_SET_ARGUMENT") % node.id) # when assigning a value with ":=" case ast.NamedExpr: @@ -96,13 +96,14 @@ def safe_eval(template: "TemplateSafeEval", env: "Env" = None, macros: dict[str, case ast.Call: if isinstance(node.func, ast.Attribute): # if this is a method if not isinstance(node.func.value, ast.Constant): # if the method is not on a constant - raise SafeEvalException(_("CAN_ONLY_CALL_METHOD_OF_CONSTANT")) + raise SafeEvalException(_("ERROR_CAN_ONLY_CALL_CONSTANT_METHOD")) elif isinstance(node.func, ast.Name): # if this is a direct function call if node.func.id not in globals_ | locals_: # if the function is not in env - raise SafeEvalException(_("CAN_ONLY_CALL_FUNCTION_IN_ENV")) + raise SafeEvalException(_("ERROR_CAN_ONLY_CALL_ENV_FUNCTION")) - else: raise SafeEvalException(_("CAN_ONLY_CALL_FUNCTION_IN_ENV")) # else don't allow the function call + else: # else don't allow the function call + raise SafeEvalException(_("ERROR_CAN_ONLY_CALL_ENV_FUNCTION")) # Forbidden type. Some of them can't be accessed with the eval mode, but just in case, still ban them case ( @@ -117,7 +118,7 @@ def safe_eval(template: "TemplateSafeEval", env: "Env" = None, macros: dict[str, # comprehension are extremely dangerous since their can associate value ast.ListComp | ast.SetComp | ast.DictComp | ast.GeneratorExp ): - raise SafeEvalException(_("FORBIDDEN_SYNTAX", ' : "', type(node).__name__, '"')) + raise SafeEvalException(_("ERROR_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 897e730..790caa3 100644 --- a/source/safe_eval/safe_function.py +++ b/source/safe_eval/safe_function.py @@ -36,9 +36,9 @@ class safe_function: """ Same as normal getattr, but magic attribute are banned """ - if "__" in name: raise Exception(_("MAGIC_METHOD_FORBIDDEN", ' : "', name, '"')) + if "__" in name: raise Exception(_("ERROR_FORBIDDEN_MAGIC_METHOD") % name) attr = getattr(obj, name, default) - if callable(attr): raise Exception(_("GET_METHOD_FORBIDDEN", ' : "', name, '"')) + if callable(attr): raise Exception(_("ERROR_GETTING_METHOD_FORBIDDEN") % name) return attr @staticmethod @@ -63,5 +63,5 @@ class safe_function: :param obj: the object to copy :return: the copied object """ - if callable(obj): raise Exception(_("COPY_FUNCTION_FORBIDDEN", ' : "', obj.__name__, '"')) + if callable(obj): raise Exception(_("ERROR_FUNCTION_COPY_FORBIDDEN") % obj.__name__) return copy.deepcopy(obj) diff --git a/source/translation.py b/source/translation.py index f279fa0..317c188 100644 --- a/source/translation.py +++ b/source/translation.py @@ -19,17 +19,13 @@ def load_language(language: str): self._language_data = json.loads(Path(f"./assets/language/{language}.json").read_text(encoding="utf8")) -def translate(*text) -> str: +def translate(text) -> str: """ Translate a text to the loaded language. :param text: list of text to translate :return: translated text """ - return "".join([ - self._language_data.get("translation", {}).get(word, word) if isinstance(word, str) - else str(word) - for word in text - ]) + return self._language_data.get("translation", {}).get(text, text) def translate_external(mod_config: "ModConfig", language: str, message_texts: dict[str, str], default: str = "") -> str: diff --git a/source/wt/__init__.py b/source/wt/__init__.py index 2d749b7..df0aba9 100644 --- a/source/wt/__init__.py +++ b/source/wt/__init__.py @@ -21,15 +21,15 @@ class WTError(Exception): check=True, **subprocess_kwargs ).stdout.decode() - except subprocess.CalledProcessError as e: - error = _("- ", "CANNOT_GET_ERROR_MESSAGE", " -") + except subprocess.CalledProcessError: + error = _("ERROR_CANNOT_GET_ERROR_MESSAGE") - super().__init__(_(tools_path, " ", "RAISED", " ", return_code, ":\n", error, "\n")) + super().__init__(_("ERROR_WT") % (tools_path, return_code, error)) class MissingWTError(Exception): def __init__(self, tool_name: str): - super().__init__(_("CANNOT_FIND_TOOL", ' "', tool_name, '" ', "IN_TOOLS_DIRECTORY")) + super().__init__(_("ERROR_CANNOT_FIND_TOOL") % tool_name) try: tools_szs_dir = next(tools_dir.glob("./szs*/")) / system diff --git a/source/wt/szs.py b/source/wt/szs.py index b400cf5..8d43d1f 100644 --- a/source/wt/szs.py +++ b/source/wt/szs.py @@ -192,7 +192,7 @@ class SZSSubPath: :param dest: destination path :return: the extracted file path """ - if self.is_dir(): raise ValueError(_("CANNOT_EXTRACT_A_DIRECTORY")) + if self.is_dir(): raise ValueError(_("ERROR_CANNOT_EXTRACT_DIRECTORY")) dest: Path = Path(dest) if dest.is_dir(): dest /= self.basename() diff --git a/source/wt/wit.py b/source/wt/wit.py index e147a2e..817a4ba 100644 --- a/source/wt/wit.py +++ b/source/wt/wit.py @@ -202,7 +202,7 @@ class WITSubPath: if self.wit_path.extension == Extension.FST: # if flat is used, extract the file / dir into the destination directory, without subdirectory if flat: - os.makedirs(dest, exist_ok=True) + dest.mkdir(parents=True, exist_ok=True) # if we are extracting a directory, we need to extract every file recursively if self.is_dir(): for file in (self._get_fst_path()).rglob("*"):