remade all the translation in a easier, faster and more readable way

This commit is contained in:
Faraphel 2022-09-01 18:10:38 +02:00
parent 97477bba79
commit 423a02ce4c
39 changed files with 394 additions and 435 deletions

View file

@ -7,8 +7,8 @@
}, },
"installation_completed": { "installation_completed": {
"text": { "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 !", "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 starting the game with Dolphin, try in Config > Advanced > Enable Emulated Memory Size Override and set MEM2 to 128MB\n\nHave fun !" "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 !"
} }
} }
} }

View file

@ -1,124 +1,112 @@
{ {
"name": "English", "name": "English",
"translation": { "translation": {
"INSTALLER_TITLE": "MKWF-Install", "TITLE_INSTALL": "MKWF-Install",
"LANGUAGE_SELECTION": "Language", "TITLE_MOD_SETTINGS": "Mod Settings",
"TRACK_FILTER": "Track Filters", "TITLE_MYSTUFF_SETTINGS": "MyStuff Settings",
"ADVANCED_CONFIGURATION": "Advanced",
"HELP": "Help", "MENU_LANGUAGE_SELECTION": "Language",
"INVALID_SOURCE_PATH": "Invalid path for source game", "MENU_ADVANCED": "Advanced",
"DISCORD": "Discord", "MENU_ADVANCED_MYSTUFF": "MyStuff settings",
"GITHUB WIKI": "Github Wiki", "MENU_ADVANCED_THREADS": "Thread usage",
"READTHEDOCS": "ReadTheDocs", "MENU_ADVANCED_THREADS_SELECTION": "Use %i threads",
"ORIGINAL_GAME_FILE": "Original Game File", "MENU_ADVANCED_EMPTY_CACHE": "Empty the cache",
"SELECT_SOURCE_GAME": "Select the source game", "MENU_ADVANCED_DEVELOPER_MODE": "Enable developer settings",
"WII GAMES": "Wii games", "MENU_HELP": "Help",
"ERROR": "Error",
"ERROR_INVALID_SOURCE_GAME": "The source game path is invalid", "PART_EXTRACTION": "Extraction",
"GAME_DIRECTORY_DESTINATION": "Game Directory Destination", "PART_PRE_RIIVOLUTION": "Pre-Riivolution",
"SELECT_DESTINATION_GAME": "Select the destination game", "PART_MYSTUFF": "MyStuff",
"WARNING": "Warning", "PART_PREPARING_FILES": "Preparing files",
"WARNING_DESTINATION_GAME_NOT_WRITABLE": "The destination game path is not writable", "PART_PREPATCH": "Pre-Patch",
"INSTALL": "Install", "PART_LECODE": "LE-CODE",
"ERROR_INVALID_DESTINATION_GAME": "The destination game path is invalid", "PART_PATCH": "Patch",
"WARNING_LOW_SPACE_CONTINUE": "The space left on the drive is low. Continue ?", "PART_RIIVOLUTION": "Riivolution",
"INSTALLATION_COMPLETED": "Installation Completed", "PART_CONVERSION": "Conversion",
"INSTALLATION_FINISHED_WITH_SUCCESS": "The installation ended successfully !",
"OPEN_MYSTUFF_SETTINGS": "Open MyStuff settings", "TEXT_SOURCE_GAME": "Source Game",
"THREADS_USAGE": "Threads usage", "TEXT_GAME_DESTINATION": "Game Destination",
"USE": "Use", "TEXT_INSTALL": "Install",
"THREADS": "threads", "TEXT_SELECT_SOURCE_GAME": "Select a source game",
"MESSAGE_FROM_MOD_AUTHOR": "Message from the author", "TEXT_SELECT_GAME_DESTINATION": "Select the game destination",
"GLOBAL_MOD_SETTINGS": "Global mod settings", "TEXT_WII_GAMES": "Wii games",
"SPECIFIC_MOD_SETTINGS": "Specific mod settings", "TEXT_INSTALLATION_COMPLETED": "Installation Completed",
"CONFIGURE_MYSTUFF_PATCH": "Configure the MyStuff patchs", "TEXT_INSTALLATION_FINISHED_SUCCESSFULLY": "The installation finished successfully !",
"DISABLED": "Disabled", "TEXT_MESSAGE_FROM_AUTHOR": "Message from the author",
"ENABLED": "Enabled", "TEXT_MOD_GLOBAL_SETTINGS": "Global settings",
"NEW_PROFILE": "New Profile", "TEXT_MOD_SPECIFIC_SETTINGS": "Specific settings",
"DELETE_PROFILE": "Delete Profile", "TEXT_MOD_TEST_SETTINGS": "Test settings",
"ADD_MYSTUFF": "Add MyStuff patch", "TEXT_DISABLED": "Disabled",
"REMOVE_MYSTUFF": "Remove MyStuff patch", "TEXT_ENABLED": "Enabled",
"MYSTUFF_PROFILE_ALREADY_EXIST": "This MyStuff profile already exist !", "TEXT_IMPORT_SETTINGS": "Import settings",
"MYSTUFF_PROFILE_FORBIDDEN_NAME": "This MyStuff profile name can't be used !", "TEXT_EXPORT_SETTINGS": "Export settings",
"SELECT_MYSTUFF": "Select MyStuff patch", "TEXT_SETTINGS_FILE": "Settings file",
"TYPE_PREVIEW_WINDOW": "Type of preview window", "TEXT_NEW_PROFILE": "New profile",
"NOT_FOUND": "not found", "TEXT_DELETE_PROFILE": "Delete profile",
"TYPE_MOD_SETTINGS": "Type of mod settings", "TEXT_ADD_MYSTUFF": "Add MyStuff",
"BMG_LAYER_MODE": "bmg layer mode", "TEXT_REMOVE_MYSTUFF": "Remove MyStuff",
"IS_NOT_IMPLEMENTED": "is not implemented", "TEXT_SELECT_MYSTUFF": "Select MyStuff",
"IMAGE_LAYER_TYPE": "image layer type", "TEXT_COPYING_GAME": "Copying game",
"OPERATION": "Operation", "TEXT_EXTRACTING_GAME": "Extracting game - %i%% (estimated time remaining : %s)",
"PATH": "Path", "TEXT_CHANGING_GAME_METADATA": "Changing game metadata",
"MODE": "Mode", "TEXT_EXTRACTING_AUTOADD": "Extracting autoadd files",
"SOURCE": "Source", "TEXT_EXTRACTING_ORIGINAL_TRACKS": "Extracting original tracks \"%s\"",
"OUTSIDE_ALLOWED_RANGE": "outside of allowed range", "TEXT_INSTALLING_MYSTUFF": "Installing MyStuff \"%s\"",
"IN_PATCH": "in patch", "TEXT_PREPARING_SPECIAL_FILE": "Preparing special file \"%s\"",
"INSTALLING_PATCH": "Installing the patch", "TEXT_PREPARING_MAIN_DOL": "Preparing main.dol",
"PATCH_TITLE": "Patch", "TEXT_REPACKING_ARCHIVE": "Repacking archive \"%s\"",
"PRE-PATCH_TITLE": "Pre-patch", "TEXT_PATCHING_LECODE": "Patching LECODE.bin",
"PATCHING": "Patching", "TEXT_CONVERT_GAME_TO": "Converting game to %s",
"FORBIDDEN_TRACK_ATTRIBUTE": "Forbidden track attribute", "TEXT_DELETING_EXTRACTED_GAME": "Deleting extracted game",
"FORBIDDEN_ARENA_ATTRIBUTE": "Forbidden arena attribute", "TEXT_CONVERTING_TO_RIIVOLUTION": "Converting to Riivolution",
"EXTRACTING_AUTOADD_FILES": "Extracting autoadd files...", "TEXT_CALCULATING_HASH": "Calculating hash for \"%s\"",
"EXTRACTING_ORIGINAL_TRACKS": "Extracting original tracks...", "TEXT_NORMALIZING_TRACKS": "Normalizing tracks :\n%s",
"INSTALLING_MYSTUFF": "Installing MyStuff", "TEXT_PATCHING": "Patching \"%s\"",
"INSTALLING_ALL_MYSTUFF_PATCHS": "Installing all the mystuff patchs",
"PREPARING": "Preparing", "WARNING": "Warning",
"SPECIAL_FILE": "special file", "WARNING_EMPTY_CACHE": "By emptying the cache, you will gain %.2fGB, but reinstalling a mod will be take way longer. Continue ?",
"REPACKING": "Repacking", "WARNING_DESTINATION_NOT_WRITABLE": "The game destination directory is not writable. Continue anyway ?",
"ALL_ARCHIVES": "all archives", "WARNING_LOW_SPACE_CONTINUE": "Low space remaining on disk %s (%.2fGB). Continue anyway ?",
"INSTALLING_ALL": "Installing all", "WARNING_NOT_ROOT": "Root permissions are required. You should start the application with 'sudo'. Continue anyway ?",
"CONVERTING_GAME_TO": "Converting game to", "WARNING_INSTALLER_PERMISSION": "Writing and execution permissions are required. You can use 'sudo chmod 777 -R <installer-path>' to fix that. Continue anyway ?",
"DELETING_EXTRACTED_GAME": "Deleting the extracted game...",
"NOT_MKW_GAME": "Not a Mario Kart Wii game", "ERROR": "Error",
"GAME_ALREADY_MODDED": "This game is already modded", "ERROR_SEE_LOGS": "See error.log for more information",
"COPYING_GAME": "Copying Game...", "ERROR_INVALID_SOURCE_GAME": "Invalid source game : \"%s\"",
"EXTRACTING": "Extracting", "ERROR_INVALID_GAME_DESTINATION": "Invalid game destination : \"%s\"",
"ESTIMATED_TIME_REMAINING": "estimated time remaining", "ERROR_MYSTUFF_PROFILE_ALREADY_EXIST": "The MyStuff profile \"%s\" already exist !",
"CHANGING_GAME_METADATA": "Changing game metadata...", "ERROR_MYSTUFF_PROFILE_FORBIDDEN_NAME": "The MyStuff profile name \"%s\" is forbidden !",
"EXTRACTION": "Extraction", "ERROR_NOT_MKW_GAME": "This game is not a Mario Kart Wii game : \"%s\"",
"MYSTUFF": "MyStuff", "ERROR_GAME_ALREADY_MODDED": "This game is already modded : \"%s\"",
"PREPARING_FILES": "Preparing files", "ERROR_PATH_OUTSIDE_RANGE": "Path \"%s\" is outside allowed range : \"%s\"",
"PRE-PATCHING": "Pre-Patching", "ERROR_PREVIEW_WINDOW_NOT_FOUND": "Preview window type \"%s\" not found",
"CONVERTING_TO_GAME_FILE": "Converting to game file", "ERROR_CANNOT_FIND_COLOR": "Can't find color : \"%s\"",
"CANNOT_FIND_COLOR": "Can't find color", "ERROR_CANNOT_FIND_SLOT": "Can't find slot : \"%s\"",
"NORMALIZING_TRACKS": "Normalizing tracks", "ERROR_MOD_SETTINGS_NOT_FOUND": "Mod settings type \"%s\" not found",
"INVALID_MACRO": "Invalid macro", "ERROR_FORBIDDEN_TRACK_ATTRIBUTE": "Forbidden track attribute : \"%s\"",
"INVALID_AST_TYPE": "Invalid ast type", "ERROR_FORBIDDEN_ARENA_ATTRIBUTE": "Forbidden arena attribute : \"%s\"",
"MAGIC_ATTRIBUTE_ARE_FORBIDDEN": "Magic attribute are forbidden", "ERROR_FORBIDDEN_TRACKGROUP_ATTRIBUTE": "Forbidden track group attribute : \"%s\"",
"CANNOT_SET_ATTRIBUTE": "Can't set value of attribute", "ERROR_SAFEEVAL": "Safe eval error (template used : \"%s\")",
"CANNOT_SET_ENVIRONMENT": "Can't set value of environment", "ERROR_INVALID_MACRO": "Invalid macro : \"%s\"",
"CANNOT_SET_ARGUMENT": "Can't set value of argument", "ERROR_INVALID_AST_TYPE": "Invalid AST type : \"%s\"",
"CALLING_FUNCTION_NOT_ALLOWED": "Calling this function is not allowed", "ERROR_FORBIDDEN_MAGIC_ATTRIBUTE": "Magic attribute are forbidden : \"%s\"",
"FORBIDDEN_SYNTAX": "Forbidden syntax", "ERROR_FORBIDDEN_MAGIC_METHOD": "Magic method are forbidden : \"%s\"",
"MAGIC_METHOD_FORBIDDEN": "Magic method are not allowed", "ERROR_GETTING_METHOD_FORBIDDEN": "Getting method is forbidden : \"%s\"",
"CANNOT_GET_ERROR_MESSAGE": "Can't get the error message", "ERROR_CANNOT_SET_ATTRIBUTE": "Setting attribute is forbidden : \"%s\"",
"RAISED": "raised", "ERROR_CANNOT_SET_ENVIRONMENT": "Setting environment variable is forbidden : \"%s\"",
"CANNOT_FIND_TOOL": "Can't find tool", "ERROR_CANNOT_SET_ARGUMENT": "Setting argument variable is forbidden : \"%s\"",
"IN_TOOLS_DIRECTORY": "in the tools directory.", "ERROR_CAN_ONLY_CALL_CONSTANT_METHOD": "Only calling method of constant is allowed",
"CANNOT_EXTRACT_A_DIRECTORY": "Can't extract a directory", "ERROR_CAN_ONLY_CALL_ENV_FUNCTION": "You only can call function from the environment",
"CANNOT_FIND_SLOT": "Can't find slot", "ERROR_FORBIDDEN_SYNTAX": "Forbidden syntax : \"%s\"",
"FORBIDDEN_TRACKGROUP_ATTRIBUTE": "Forbidden TrackGroup attribute", "ERROR_FUNCTION_COPY_FORBIDDEN": "Copying a function is forbidden",
"SAFE_EVAL_ERROR": "Safe eval error", "ERROR_CANNOT_GET_ERROR_MESSAGE": "Can't get the error message",
"TEMPLATE_USED": "Template used", "ERROR_WT": "%s raised %i :\n%s",
"MORE_IN_ERROR_LOG": "More information in the error.log file", "ERROR_CANNOT_FIND_TOOL": "Can't find tool \"%s\" in the tools directory",
"COPY_FUNCTION_FORBIDDEN": "Copying functions is forbidden", "ERROR_CANNOT_EXTRACT_DIRECTORY": "Directory can't be extracted",
"GET_METHOD_FORBIDDEN": "Using getattr on a method is forbidden", "ERROR_PATCH_MODE_NOT_IMPLEMENTED": "Patch mode \"%s\" is not implemented (in patch : \"%s\")",
"CAN_ONLY_CALL_METHOD_OF_CONSTANT": "You can only call methods on constant", "ERROR_SOURCE_NOT_IMPLEMENTED": "Source \"%s\" is not implemented (in patch : \"%s\")",
"CAN_ONLY_CALL_FUNCTION_IN_ENV": "You can only call function from the environment", "ERROR_OPERATION_NOT_IMPLEMENTED": "Operation \"%s\" is not implemented",
"ENABLE_DEVELOPER_MODE": "Enable the developer mode", "ERROR_BMG_LAYER_MODE_NOT_IMPLEMENTED": "Bmg layer mode \"%s\" not implemented",
"TESTING_MOD_SETTINGS": "Test mod settings", "ERROR_IMAGE_LAYER_TYPE_NOT_IMPLEMENTED": "Image layer type \"%s\" not implemented"
"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 <installer-path>' 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"
}
} }

View file

@ -1,125 +1,112 @@
{ {
"name": "Français", "name": "Français",
"translation": { "translation": {
"INSTALLER_TITLE": "MKWF-Install", "TITLE_INSTALL": "MKWF-Install",
"LANGUAGE_SELECTION": "Langue", "TITLE_MOD_SETTINGS": "Paramètres du mod",
"TRACK_FILTER": "filtrer les courses", "TITLE_MYSTUFF_SETTINGS": "Paramètres MyStuff",
"ADVANCED_CONFIGURATION": "Avancée",
"HELP": "Aide",
"INVALID_SOURCE_PATH": "Chemin invalide pour le jeu source", "MENU_LANGUAGE_SELECTION": "Langue",
"DISCORD": "Discord", "MENU_ADVANCED": "Avancée",
"GITHUB WIKI": "Wiki Github", "MENU_ADVANCED_MYSTUFF": "Paramètres MyStuff",
"READTHEDOCS": "ReadTheDocs", "MENU_ADVANCED_THREADS": "Utilisation des threads",
"ORIGINAL_GAME_FILE": "Fichier du jeu original", "MENU_ADVANCED_THREADS_SELECTION": "Utiliser %i threads",
"SELECT_SOURCE_GAME": "Sélectionner le jeu source", "MENU_ADVANCED_EMPTY_CACHE": "Vider le cache",
"WII GAMES": "jeux Wii", "MENU_ADVANCED_DEVELOPER_MODE": "Activer les paramètres développeur",
"ERROR": "Erreur", "MENU_HELP": "Aide",
"ERROR_INVALID_SOURCE_GAME": "Chemin invalide pour le jeu source",
"GAME_DIRECTORY_DESTINATION": "Dossier de destination du jeu", "PART_EXTRACTION": "Extraction",
"SELECT_DESTINATION_GAME": "Sélectionner le dossier de destination du jeu", "PART_PRE_RIIVOLUTION": "Pre-Riivolution",
"WARNING": "Attention", "PART_MYSTUFF": "MyStuff",
"WARNING_DESTINATION_GAME_NOT_WRITABLE": "Le chemin de destination n'est pas modifiable", "PART_PREPARING_FILES": "Preparation des fichiers",
"INSTALL": "Installer", "PART_PREPATCH": "Pre-Patch",
"ERROR_INVALID_DESTINATION_GAME": "Chemin invalide pour la destination du jeu", "PART_LECODE": "LE-CODE",
"WARNING_LOW_SPACE_CONTINUE": "L'espace restant sur le disque dur est faible. Continuer ?", "PART_PATCH": "Patch",
"INSTALLATION_COMPLETED": "Installation Complété", "PART_RIIVOLUTION": "Riivolution",
"INSTALLATION_FINISHED_WITH_SUCCESS": "L'installation s'est terminé avec succès !", "PART_CONVERSION": "Conversion",
"OPEN_MYSTUFF_SETTINGS": "Ouvrir les paramètres MyStuff",
"THREADS_USAGE": "Utilisation des Threads", "TEXT_SOURCE_GAME": "Jeu Source",
"USE": "Utiliser", "TEXT_GAME_DESTINATION": "Destination du Jeu",
"THREADS": "threads", "TEXT_INSTALL": "Installer",
"MESSAGE_FROM_MOD_AUTHOR": "Message de l'auteur", "TEXT_SELECT_SOURCE_GAME": "Sélectionner un jeu source",
"GLOBAL_MOD_SETTINGS": "Paramètre global de mod", "TEXT_SELECT_GAME_DESTINATION": "Sélectionner la destination du jeu",
"SPECIFIC_MOD_SETTINGS": "Paramètre spécifique de mod", "TEXT_WII_GAMES": "Jeux Wii",
"CONFIGURE_MYSTUFF_PATCH": "Configurer les patchs MyStuff", "TEXT_INSTALLATION_COMPLETED": "Installation Complétée",
"DISABLED": "Désactiver", "TEXT_INSTALLATION_FINISHED_SUCCESSFULLY": "L'installation s'est terminée avec succès !",
"ENABLED": "Activer", "TEXT_MESSAGE_FROM_AUTHOR": "Message de l'auteur",
"NEW_PROFILE": "Nouveau profil", "TEXT_MOD_GLOBAL_SETTINGS": "Paramètres globaux",
"DELETE_PROFILE": "Retirer un Profil", "TEXT_MOD_SPECIFIC_SETTINGS": "Paramètres spécifique",
"ADD_MYSTUFF": "Ajouter un patch MyStuff", "TEXT_MOD_TEST_SETTINGS": "Paramètres de test",
"REMOVE_MYSTUFF": "Retirer un patch MyStuff", "TEXT_DISABLED": "Désactivé",
"MYSTUFF_PROFILE_ALREADY_EXIST": "Ce profil MyStuff existe déjà !", "TEXT_ENABLED": "Activé",
"MYSTUFF_PROFILE_FORBIDDEN_NAME": "Ce nom de profil MyStuff ne peut pas être utilisé !", "TEXT_IMPORT_SETTINGS": "Importer des paramètres",
"SELECT_MYSTUFF": "Sélectionner un patch MyStuff", "TEXT_EXPORT_SETTINGS": "Exporter des paramètres",
"TYPE_PREVIEW_WINDOW": "Type de fenêtre de prévisualisation", "TEXT_SETTINGS_FILE": "Fichier paramètres",
"NOT_FOUND": "introuvable", "TEXT_NEW_PROFILE": "Nouveau profil",
"TYPE_MOD_SETTINGS": "Type de paramètre de mod", "TEXT_DELETE_PROFILE": "Supprimer le profil",
"BMG_LAYER_MODE": "mode de couche bmg", "TEXT_ADD_MYSTUFF": "Ajouter MyStuff",
"IS_NOT_IMPLEMENTED": "n'est pas implémenté", "TEXT_REMOVE_MYSTUFF": "Retirer MyStuff",
"IMAGE_LAYER_TYPE": "type de couche d'image", "TEXT_SELECT_MYSTUFF": "Sélectionner un MyStuff",
"OPERATION": "Opération", "TEXT_COPYING_GAME": "Copie du jeu",
"PATH": "Chemin", "TEXT_EXTRACTING_GAME": "Extraction du jeu - %i%% (temps restant estimé : %s)",
"MODE": "Mode", "TEXT_CHANGING_GAME_METADATA": "Changement des métadonnées du jeu",
"SOURCE": "Source", "TEXT_EXTRACTING_AUTOADD": "Extraction des fichiers autoadd",
"OUTSIDE_ALLOWED_RANGE": "En dehors de la portée autorisée", "TEXT_EXTRACTING_ORIGINAL_TRACKS": "Extraction des courses originales \"%s\"",
"IN_PATCH": "dans le patch", "TEXT_INSTALLING_MYSTUFF": "Installation des MyStuff \"%s\"",
"INSTALLING_PATCH": "Installation du patch", "TEXT_PREPARING_SPECIAL_FILE": "Preparion du fichier spécial \"%s\"",
"PATCH_TITLE": "Patch", "TEXT_PREPARING_MAIN_DOL": "Preparation de main.dol",
"PRE-PATCH_TITLE": "Pre-patch", "TEXT_REPACKING_ARCHIVE": "Archivage de \"%s\"",
"PATCHING": "Patch de", "TEXT_PATCHING_LECODE": "Patch de LECODE.bin",
"FORBIDDEN_TRACK_ATTRIBUTE": "Attribut de course interdit", "TEXT_CONVERT_GAME_TO": "Conversion du jeu en %s",
"FORBIDDEN_ARENA_ATTRIBUTE": "Attribut d'arène interdit", "TEXT_DELETING_EXTRACTED_GAME": "Suppression du jeu extrait",
"EXTRACTING_AUTOADD_FILES": "Extraction des fichiers autoadd...", "TEXT_CONVERTING_TO_RIIVOLUTION": "Conversion en Riivolution",
"EXTRACTING_ORIGINAL_TRACKS": "Extraction des courses originelles...", "TEXT_CALCULATING_HASH": "Calcul du hash pour \"%s\"",
"INSTALLING_MYSTUFF": "Installation de MyStuff", "TEXT_NORMALIZING_TRACKS": "Normalisation des courses :\n%s",
"INSTALLING_ALL_MYSTUFF_PATCHS": "Installation de tous les patchs MyStuff", "TEXT_PATCHING": "Patch de \"%s\"",
"PREPARING": "Preparation",
"SPECIAL_FILE": "fichier spécial", "WARNING": "Attention",
"REPACKING": "Ré-archivage", "WARNING_EMPTY_CACHE": "En vidant le cache, vous allez gagner %.2fGB, mais reinstaller un mod prendra beaucoup plus de temps. Continuer ?",
"ALL_ARCHIVES": "toutes les archives", "WARNING_DESTINATION_NOT_WRITABLE": "Le dossier de destination du jeu ne peut pas être modifié. Continuer quand même ?",
"INSTALLING_ALL": "Installation de tous", "WARNING_LOW_SPACE_CONTINUE": "Espace faible sur le disque %s (%.2fGB). Continuer quand même ?",
"CONVERTING_GAME_TO": "Conversion du jeu en", "WARNING_NOT_ROOT": "Permissions administrateur requise. Vous devriez lancer l'application avec 'sudo'. Continuer quand même ?",
"DELETING_EXTRACTED_GAME": "Suppression du jeu extrait...", "WARNING_INSTALLER_PERMISSION": "Les permissions d'écriture et d'exécution sont requise. Vous pouvez utiliser 'sudo chmod 777 -R <chemin-de-l-installer>' pour corriger cela. Continuer quand même ?",
"NOT_MKW_GAME": "N'est pas un jeu Mario Kart Wii",
"GAME_ALREADY_MODDED": "Ce jeu est déjà moddée", "ERROR": "Erreur",
"COPYING_GAME": "Copie du jeu...", "ERROR_SEE_LOGS": "Voir le fichier error.log pour plus d'information",
"EXTRACTING": "Extraction", "ERROR_INVALID_SOURCE_GAME": "Jeu source invalide : \"%s\"",
"ESTIMATED_TIME_REMAINING": "temps restant estimé", "ERROR_INVALID_GAME_DESTINATION": "Destination de jeu invalide : \"%s\"",
"CHANGING_GAME_METADATA": "Changement des métadonnées du jeu...", "ERROR_MYSTUFF_PROFILE_ALREADY_EXIST": "Le profil MyStuff \"%s\" existe déjà !",
"EXTRACTION": "Extraction", "ERROR_MYSTUFF_PROFILE_FORBIDDEN_NAME": "Le nom de profil MyStuff \"%s\" est interdit !",
"MYSTUFF": "MyStuff", "ERROR_NOT_MKW_GAME": "Ce jeu n'est pas un jeu Mario Kart Wii : \"%s\"",
"PREPARING_FILES": "Preparation des fichiers", "ERROR_GAME_ALREADY_MODDED": "Ce jeu est déjà moddé : \"%s\"",
"PRE-PATCHING": "Pre-Patching", "ERROR_PATH_OUTSIDE_RANGE": "Le chemin \"%s\" est en dehors de la portée autorisée : \"%s\"",
"CONVERTING_TO_GAME_FILE": "Conversion en fichier de jeu", "ERROR_PREVIEW_WINDOW_NOT_FOUND": "Type de fenêtre de prévisualisation \"%s\" introuvable",
"CANNOT_FIND_COLOR": "Impossible de trouver la couleur", "ERROR_CANNOT_FIND_COLOR": "Impossible de trouver la couleur : \"%s\"",
"NORMALIZING_TRACKS": "Normalisation des courses", "ERROR_CANNOT_FIND_SLOT": "Impossible de trouver le slot : \"%s\"",
"INVALID_MACRO": "Macro invalide", "ERROR_MOD_SETTINGS_NOT_FOUND": "Type de paramètre de mod \"%s\" introuvable",
"INVALID_AST_TYPE": "Type d'Ast invalide", "ERROR_FORBIDDEN_TRACK_ATTRIBUTE": "Attribut de course interdit : \"%s\"",
"MAGIC_ATTRIBUTE_ARE_FORBIDDEN": "Les attributs magique sont interdit", "ERROR_FORBIDDEN_ARENA_ATTRIBUTE": "Attribut d'arène interdit : \"%s\"",
"CANNOT_SET_ATTRIBUTE": "Impossible de changer la valeur de l'attribut", "ERROR_FORBIDDEN_TRACKGROUP_ATTRIBUTE": "Attribut de groupe de course interdit : \"%s\"",
"CANNOT_SET_ENVIRONMENT": "Impossible de changer la valeur de l'environnement", "ERROR_SAFEEVAL": "Erreur de safe eval (modèle utilisé : \"%s\")",
"CANNOT_SET_ARGUMENT": "Impossible de changer la valeur de l'argument", "ERROR_INVALID_MACRO": "Macro invalide : \"%s\"",
"CALLING_FUNCTION_NOT_ALLOWED": "Appeler cette fonction n'est pas autorisé", "ERROR_INVALID_AST_TYPE": "Type d'AST invalide : \"%s\"",
"FORBIDDEN_SYNTAX": "Syntax interdite", "ERROR_FORBIDDEN_MAGIC_ATTRIBUTE": "Les attributs magiques sont interdit : \"%s\"",
"MAGIC_METHOD_FORBIDDEN": "Les méthodes magique ne sont pas autorisé", "ERROR_FORBIDDEN_MAGIC_METHOD": "Les méthodes magiques sont interdites : \"%s\"",
"CANNOT_GET_ERROR_MESSAGE": "Impossible d'obtenir le message d'erreur", "ERROR_GETTING_METHOD_FORBIDDEN": "Récupérer une méthode est interdit : \"%s\"",
"RAISED": "à levé", "ERROR_CANNOT_SET_ATTRIBUTE": "Changer un attribut est interdit : \"%s\"",
"CANNOT_FIND_TOOL": "Impossible de trouver l'outil", "ERROR_CANNOT_SET_ENVIRONMENT": "Changer une variable d'environnement est interdit : \"%s\"",
"IN_TOOLS_DIRECTORY": "dans le dossier des outils.", "ERROR_CANNOT_SET_ARGUMENT": "Changer une variable d'argument est interdit : \"%s\"",
"CANNOT_EXTRACT_A_DIRECTORY": "Impossible d'extraire un dossier", "ERROR_CAN_ONLY_CALL_CONSTANT_METHOD": "Seulement appeler la méthode d'une constante est autorisé",
"CANNOT_FIND_SLOT": "Impossible de trouver le slot", "ERROR_CAN_ONLY_CALL_ENV_FUNCTION": "Seulement appeler une fonction de l'environnement est autorisé",
"FORBIDDEN_TRACKGROUP_ATTRIBUTE": "Attribut de groupe de course interdit", "ERROR_FORBIDDEN_SYNTAX": "Syntaxe interdite : \"%s\"",
"SAFE_EVAL_ERROR": "Erreur lors d'un safe eval", "ERROR_FUNCTION_COPY_FORBIDDEN": "Copier une fonction est interdit",
"TEMPLATE_USED": "Modèle utilisé", "ERROR_CANNOT_GET_ERROR_MESSAGE": "Impossible de récupérer le message d'erreur",
"MORE_IN_ERROR_LOG": "Plus d'information dans le fichier error.log", "ERROR_WT": "%s à généré l'erreur %i :\n%s",
"COPY_FUNCTION_FORBIDDEN": "Impossible de copier une fonction", "ERROR_CANNOT_FIND_TOOL": "Impossible de trouver l'outil \"%s\" dans le dossier tools",
"GET_METHOD_FORBIDDEN": "Impossible d'utiliser getattr sur une méthode", "ERROR_CANNOT_EXTRACT_DIRECTORY": "Un dossier ne peut pas être extrait",
"CAN_ONLY_CALL_METHOD_OF_CONSTANT": "Vous ne pouvez appeler que des méthodes sur des constantes", "ERROR_PATCH_MODE_NOT_IMPLEMENTED": "Le mode de Patch \"%s\" n'est pas implémenté (du patch : \"%s\")",
"CAN_ONLY_CALL_FUNCTION_IN_ENV": "Vous ne pouvez appeler que des fonctions dans l'environnement", "ERROR_SOURCE_NOT_IMPLEMENTED": "La source \"%s\" n'est pas implémenté (du patch : \"%s\")",
"ENABLE_DEVELOPER_MODE": "Activer le mode développeur", "ERROR_OPERATION_NOT_IMPLEMENTED": "L'opération \"%s\" n'est pas implémenté",
"TESTING_MOD_SETTINGS": "Paramètre de test", "ERROR_BMG_LAYER_MODE_NOT_IMPLEMENTED": "Le mode de couche bmg \"%s\" n'est pas implémenté",
"SETTINGS_FILE": "Fichier de paramètres", "ERROR_IMAGE_LAYER_TYPE_NOT_IMPLEMENTED": "Le type de couche d'image \"%s\" n'est pas implémenté"
"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 <installer-path>' 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"
}
} }

View file

@ -29,8 +29,8 @@ def better_gui_error(func: Callable) -> Callable:
exc_split = exc.splitlines() exc_split = exc.splitlines()
if len(exc_split) > 10: if len(exc_split) > 10:
# if the traceback is too long, only keep the 5 first and 5 last lines of the traceback # 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 return wrapper

View file

@ -23,16 +23,6 @@ import os
from source.mkw.collection.Extension import Extension 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): class InstallerState(enum.Enum):
IDLE = 0 IDLE = 0
INSTALLING = 1 INSTALLING = 1
@ -50,7 +40,7 @@ class Window(tkinter.Tk):
self.source_path = tkinter.StringVar() self.source_path = tkinter.StringVar()
self.destination_path = tkinter.StringVar() self.destination_path = tkinter.StringVar()
self.title(_("INSTALLER_TITLE")) self.title(_("TITLE_INSTALL"))
self.resizable(False, False) self.resizable(False, False)
self.icon = tkinter.PhotoImage(file="./assets/icon.png") self.icon = tkinter.PhotoImage(file="./assets/icon.png")
@ -136,7 +126,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=_("LANGUAGE_SELECTION"), menu=self) master.add_cascade(label=_("MENU_LANGUAGE_SELECTION"), menu=self)
self.lang_variable = tkinter.StringVar(value=self.root.options.language.get()) self.lang_variable = tkinter.StringVar(value=self.root.options.language.get())
@ -155,19 +145,19 @@ 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=_("ADVANCED_CONFIGURATION"), menu=self) master.add_cascade(label=_("MENU_ADVANCED"), menu=self)
self.add_command( self.add_command(
label=_("OPEN_MYSTUFF_SETTINGS"), label=_("MENU_ADVANCED_MYSTUFF"),
command=lambda: mystuff.Window(self.root.mod_config, self.root.options) command=lambda: mystuff.Window(self.root.mod_config, self.root.options)
) )
self.threads_used = self.ThreadsUsed(self) 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.add_separator()
self.variable_developer_mode = tkinter.BooleanVar(value=self.root.options.developer_mode.get()) self.variable_developer_mode = tkinter.BooleanVar(value=self.root.options.developer_mode.get())
self.add_checkbutton( self.add_checkbutton(
label=_("ENABLE_DEVELOPER_MODE"), label=_("MENU_ADVANCED_DEVELOPER_MODE"),
variable=self.variable_developer_mode, variable=self.variable_developer_mode,
command=lambda: self.root.options.developer_mode.set(self.variable_developer_mode.get()) command=lambda: self.root.options.developer_mode.set(self.variable_developer_mode.get())
) )
@ -185,13 +175,13 @@ 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_USAGE"), menu=self) master.add_cascade(label=_("MENU_ADVANCED_THREADS"), menu=self)
self.variable = tkinter.IntVar(value=self.root.options.threads.get()) self.variable = tkinter.IntVar(value=self.root.options.threads.get())
for i in [1, 2, 4, 8, 12, 16]: for i in [1, 2, 4, 8, 12, 16]:
self.add_radiobutton( self.add_radiobutton(
label=_("USE", f" {i} ", "THREADS"), label=_("MENU_ADVANCED_THREADS_SELECTION") % i,
value=i, value=i,
variable=self.variable, variable=self.variable,
command=(lambda amount: (lambda: self.root.options.threads.set(amount)))(i), command=(lambda amount: (lambda: self.root.options.threads.set(amount)))(i),
@ -203,12 +193,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=_("MENU_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", 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:
""" """
@ -237,7 +227,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=_("TEXT_SOURCE_GAME"))
self.root = master.root self.root = master.root
self.columnconfigure(1, weight=1) self.columnconfigure(1, weight=1)
@ -254,8 +244,8 @@ class SourceGame(ttk.LabelFrame):
:return: :return:
""" """
raw_path = tkinter.filedialog.askopenfilename( raw_path = tkinter.filedialog.askopenfilename(
title=_("SELECT_SOURCE_GAME"), title=_("TEXT_SELECT_SOURCE_GAME"),
filetypes=[(_("WII GAMES"), "*.iso *.ciso *.wbfs *.dol")], filetypes=[(_("TEXT_WII_GAMES"), "*.iso *.ciso *.wbfs *.dol")],
) )
# if the user didn't select any file, return None # if the user didn't select any file, return None
@ -298,7 +288,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=_("TEXT_GAME_DESTINATION"))
self.root = master.root self.root = master.root
self.columnconfigure(1, weight=1) self.columnconfigure(1, weight=1)
@ -320,7 +310,7 @@ class DestinationGame(ttk.LabelFrame):
:return: :return:
""" """
raw_path = tkinter.filedialog.askdirectory( raw_path = tkinter.filedialog.askdirectory(
title=_("SELECT_DESTINATION_GAME"), title=_("TEXT_SELECT_GAME_DESTINATION"),
) )
# if the user didn't select any directory, return None # if the user didn't select any directory, return None
@ -333,7 +323,7 @@ class DestinationGame(ttk.LabelFrame):
def set_path(self, path: Path): def set_path(self, path: Path):
if not os.access(path, os.W_OK): 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.delete(0, tkinter.END)
self.entry.insert(0, str(path.absolute())) self.entry.insert(0, str(path.absolute()))
@ -355,7 +345,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=_("TEXT_INSTALL"), command=self.install)
self.root = master.root self.root = master.root
@threaded @threaded
@ -364,29 +354,35 @@ class ButtonInstall(ttk.Button):
try: try:
self.root.set_state(InstallerState.INSTALLING) 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()) source_path = Path(self.root.source_path.get())
if not source_path.exists(): raise SourceGameError(source_path) if not source_path.exists() or str(source_path) == ".":
if str(source_path) == ".": messagebox.showerror(_("ERROR"), _("ERROR_INVALID_SOURCE_GAME") % source_path)
messagebox.showerror(_("ERROR"), _("ERROR_INVALID_SOURCE_GAME"))
return 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()) destination_path = Path(self.root.destination_path.get())
if not destination_path.exists(): raise DestinationGameError(destination_path) if not destination_path.exists() or str(destination_path) == ".":
if str(destination_path) == ".": messagebox.showerror(_("ERROR"), _("ERROR_INVALID_GAME_DESTINATION") % source_path)
messagebox.showerror(_("ERROR"), _("ERROR_INVALID_DESTINATION_GAME"))
return 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 there is no more space on the installer drive, show a warning
if shutil.disk_usage(".").free < minimum_space_available: if available_space_local < minimum_space_available:
if not messagebox.askokcancel(_("WARNING"), _("WARNING_LOW_SPACE_CONTINUE")): if not messagebox.askokcancel(
_("WARNING"),
_("WARNING_LOW_SPACE_CONTINUE") % (Path(".").resolve().drive, available_space_local/Go)
):
return return
# if there is no more space on the destination drive, show a warning # if there is no more space on the destination drive, show a warning
elif shutil.disk_usage(destination_path).free < minimum_space_available: elif available_space_destination < minimum_space_available:
if not messagebox.askokcancel(_("WARNING"), _("WARNING_LOW_SPACE_CONTINUE")): if not messagebox.askokcancel(
return _("WARNING"),
_("WARNING_LOW_SPACE_CONTINUE") % (destination_path.resolve().drive, available_space_destination/Go)
): return
if system == "lin64": # if linux if system == "lin64": # if linux
if os.getuid() != 0: # if the user is not root if os.getuid() != 0: # if the user is not root
@ -417,9 +413,9 @@ class ButtonInstall(ttk.Button):
) )
messagebox.showinfo( messagebox.showinfo(
_("INSTALLATION_COMPLETED"), _("TEXT_INSTALLATION_COMPLETED"),
f"{_('INSTALLATION_FINISHED_WITH_SUCCESS')}" + ( f"{_('TEXT_INSTALLATION_FINISHED_SUCCESSFULLY')}" + (
f"\n{_('MESSAGE_FROM_MOD_AUTHOR')} :\n\n{message}" if message != "" else "" f"\n{_('TEXT_MESSAGE_FROM_AUTHOR')} :\n\n{message}" if message != "" else ""
) )
) )

View file

@ -21,6 +21,7 @@ class Window(tkinter.Toplevel):
self.resizable(False, False) self.resizable(False, False)
self.grab_set() self.grab_set()
self.title(_("TITLE_MOD_SETTINGS"))
self.rowconfigure(1, weight=1) self.rowconfigure(1, weight=1)
self.columnconfigure(1, weight=1) self.columnconfigure(1, weight=1)
@ -32,19 +33,19 @@ class Window(tkinter.Toplevel):
self.frame_global_settings = FrameSettings( self.frame_global_settings = FrameSettings(
self.panel_window, self.panel_window,
_("GLOBAL_MOD_SETTINGS"), _("TEXT_MOD_GLOBAL_SETTINGS"),
self.mod_config.global_settings self.mod_config.global_settings
) )
self.frame_specific_settings = FrameSettings( self.frame_specific_settings = FrameSettings(
self.panel_window, self.panel_window,
_("SPECIFIC_MOD_SETTINGS"), _("TEXT_MOD_SPECIFIC_SETTINGS"),
self.mod_config.specific_settings self.mod_config.specific_settings
) )
if self.options.developer_mode.get(): if self.options.developer_mode.get():
self.frame_testing_settings = FrameTesting( self.frame_testing_settings = FrameTesting(
self.panel_window, self.panel_window,
_("TESTING_MOD_SETTINGS") _("TEXT_MOD_TEST_SETTINGS")
) )
self.frame_action = FrameAction(self) self.frame_action = FrameAction(self)
@ -143,10 +144,10 @@ class FrameAction(ttk.Frame):
super().__init__(master) super().__init__(master)
self.root = master.root 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) command=self.import_settings)
self.button_import_settings.grid(row=1, column=1) 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) command=self.export_settings)
self.button_export_settings.grid(row=1, column=2) self.button_export_settings.grid(row=1, column=2)
@ -156,8 +157,8 @@ class FrameAction(ttk.Frame):
""" """
path = filedialog.askopenfilename( path = filedialog.askopenfilename(
title=_("IMPORT_SETTINGS"), title=_("TEXT_IMPORT_SETTINGS"),
filetypes=[(_("SETTINGS_FILE"), f"*{SETTINGS_FILE_EXTENSION}")] filetypes=[(_("TEXT_SETTINGS_FILE"), f"*{SETTINGS_FILE_EXTENSION}")]
) )
# si le fichier n'a pas été choisi, ignore # si le fichier n'a pas été choisi, ignore
@ -178,8 +179,8 @@ class FrameAction(ttk.Frame):
""" """
path = filedialog.asksaveasfilename( path = filedialog.asksaveasfilename(
title=_("EXPORT_SETTINGS"), title=_("TEXT_EXPORT_SETTINGS"),
filetypes=[(_("SETTINGS_FILE"), f"*{SETTINGS_FILE_EXTENSION}")] filetypes=[(_("TEXT_SETTINGS_FILE"), f"*{SETTINGS_FILE_EXTENSION}")]
) )
# si le fichier n'a pas été choisi, ignore # si le fichier n'a pas été choisi, ignore

View file

@ -24,11 +24,11 @@ class Window(tkinter.Toplevel):
self.mod_config = mod_config self.mod_config = mod_config
self.options = options self.options = options
self.title(_("CONFIGURE_MYSTUFF_PATCH")) self.title(_("TITLE_MYSTUFF_SETTINGS"))
self.resizable(False, False) self.resizable(False, False)
self.grab_set() # the others window will be disabled, keeping only this one activated 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 = ttk.Frame(self)
self.frame_profile.grid(row=1, column=1, sticky="NEWS") 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.button_new_profile = ttk.Button(
self.frame_profile, self.frame_profile,
text=_("NEW_PROFILE"), text=_("TEXT_NEW_PROFILE"),
command=self.new_profile command=self.new_profile
) )
self.button_new_profile.grid(row=1, column=2, sticky="NEWS") self.button_new_profile.grid(row=1, column=2, sticky="NEWS")
self.button_delete_profile = ttk.Button( self.button_delete_profile = ttk.Button(
self.frame_profile, self.frame_profile,
text=_("DELETE_PROFILE"), text=_("TEXT_DELETE_PROFILE"),
command=self.delete_profile command=self.delete_profile
) )
self.button_delete_profile.grid(row=1, column=3, sticky="NEWS") 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.button_add_mystuff_path = ttk.Button(
self.frame_mystuff_paths_action, self.frame_mystuff_paths_action,
text=_("ADD_MYSTUFF"), text=_("TEXT_ADD_MYSTUFF"),
command=self.add_mystuff_path command=self.add_mystuff_path
) )
self.button_add_mystuff_path.grid(row=1, column=1) self.button_add_mystuff_path.grid(row=1, column=1)
self.button_remove_mystuff_path = ttk.Button( self.button_remove_mystuff_path = ttk.Button(
self.frame_mystuff_paths_action, self.frame_mystuff_paths_action,
text=_("REMOVE_MYSTUFF"), text=_("TEXT_REMOVE_MYSTUFF"),
command=self.remove_mystuff_path command=self.remove_mystuff_path
) )
self.button_remove_mystuff_path.grid(row=1, column=2) 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() profile_name: str = self.combobox_profile.get()
if profile_name in mystuff_packs: if profile_name in mystuff_packs:
messagebox.showerror(_("ERROR"), _("MYSTUFF_PROFILE_ALREADY_EXIST")) messagebox.showerror(_("ERROR"), _("ERROR_MYSTUFF_PROFILE_ALREADY_EXIST") % profile_name)
return return
for banned_char in "<>": for banned_char in "<>":
if banned_char in profile_name: if banned_char in profile_name:
messagebox.showerror(_("ERROR"), _("MYSTUFF_PROFILE_FORBIDDEN_NAME")) messagebox.showerror(_("ERROR"), _("ERROR_MYSTUFF_PROFILE_FORBIDDEN_NAME") % profile_name)
return return
mystuff_packs[profile_name] = {"paths": []} mystuff_packs[profile_name] = {"paths": []}
@ -167,7 +167,7 @@ class Window(tkinter.Toplevel):
Add a new path to the currently selected MyStuff profile 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_path = Path(mystuff_path)
mystuff_packs = self.root.options.mystuff_packs.get() mystuff_packs = self.root.options.mystuff_packs.get()

View file

@ -9,7 +9,7 @@ if TYPE_CHECKING:
class InvalidPreviewWindowName(Exception): class InvalidPreviewWindowName(Exception):
def __init__(self, name: str): 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): class AbstractPreviewWindow(tkinter.Toplevel, ABC):

View file

@ -5,6 +5,7 @@ from pathlib import Path
from typing import Generator, IO, TYPE_CHECKING from typing import Generator, IO, TYPE_CHECKING
from source import file_block_size from source import file_block_size
from source.mkw import PathOutsideAllowedRange
from source.mkw.ModConfig import ModConfig from source.mkw.ModConfig import ModConfig
from source.mkw.Patch.Patch import Patch from source.mkw.Patch.Patch import Patch
from source.mkw.collection.Extension import Extension from source.mkw.collection.Extension import Extension
@ -21,11 +22,6 @@ if TYPE_CHECKING:
RIIVOLUTION_FOLDER_NAME: str = "riivolution" 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 ExtractedGame:
""" """
Class that represents an extracted game Class that represents an extracted game
@ -41,7 +37,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=_("TEXT_EXTRACTING_AUTOADD"), 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]:
@ -54,14 +50,13 @@ class ExtractedGame:
original_tracks: list[Path] = list((self.path / "files/Race/Course/").glob("*.szs")) original_tracks: list[Path] = list((self.path / "files/Race/Course/").glob("*.szs"))
yield Progress( yield Progress(
description=_("EXTRACTING_ORIGINAL_TRACKS"),
determinate=True, determinate=True,
max_step=len(original_tracks), max_step=len(original_tracks),
set_step=0, set_step=0,
) )
for track_file in original_tracks: 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) if not (destination_path / track_file.name).exists(): track_file.rename(destination_path / track_file.name)
else: track_file.unlink() else: track_file.unlink()
@ -73,7 +68,7 @@ class ExtractedGame:
:mystuff_path: path to the MyStuff directory :mystuff_path: path to the MyStuff directory
:return: :return:
""" """
yield Progress(description=_("INSTALLING_MYSTUFF", ' "', mystuff_path, '" ...')) yield Progress(description=_("TEXT_INSTALLING_MYSTUFF") % mystuff_path)
mystuff_path = Path(mystuff_path) mystuff_path = Path(mystuff_path)
mystuff_rootfiles: dict[str, Path] = {} mystuff_rootfiles: dict[str, Path] = {}
@ -90,12 +85,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( yield Progress(determinate=True, max_step=len(mystuff_paths), set_step=0)
description=_("INSTALLING_ALL_MYSTUFF_PATCHS"),
determinate=True,
max_step=len(mystuff_paths),
set_step=0
)
for mystuff_path in mystuff_paths: for mystuff_path in mystuff_paths:
yield Progress(step=1) yield Progress(step=1)
@ -106,7 +96,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_icons ", "SPECIAL_FILE", "..."), determinate=False) yield Progress(description=_("TEXT_PREPARING_SPECIAL_FILE") % "ct_icons", 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)
@ -116,7 +106,7 @@ class ExtractedGame:
""" """
Prepare main.dol and StaticR.rel files (clean them and add lecode) 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) StrPath(self.path / "sys/main.dol").patch(clean_dol=True, add_lecode=True)
def recreate_all_szs(self) -> Generator[Progress, None, None]: 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"))) all_extracted_szs: list[Path] = list(filter(lambda path: path.is_dir(), self.path.rglob("*.d")))
yield Progress( yield Progress(
description=_("REPACKING", " ", "ALL_ARCHIVES"),
determinate=True, determinate=True,
max_step=len(all_extracted_szs), max_step=len(all_extracted_szs),
set_step=0, set_step=0,
@ -134,7 +123,7 @@ class ExtractedGame:
for extracted_szs in all_extracted_szs: for extracted_szs in all_extracted_szs:
# 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( yield Progress(
description=_("REPACKING", ' "', extracted_szs.relative_to(self.path), '"'), description=_("TEXT_REPACKING_ARCHIVE") % extracted_szs.relative_to(self.path),
step=1 step=1
) )
@ -150,7 +139,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=_("TEXT_PATCHING_LECODE"))
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)
@ -161,7 +150,7 @@ class ExtractedGame:
lpar_dir: Path = mod_config.path.parent / "_LPAR/" lpar_dir: Path = mod_config.path.parent / "_LPAR/"
lpar: Path = lpar_dir / mod_config.multiple_safe_eval(mod_config.lpar_template)() 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"): for lecode_file in (self.path / "files/rel/").glob("lecode-*.bin"):
lec.patch( lec.patch(
@ -178,12 +167,16 @@ class ExtractedGame:
for all the subdirectory named by the patch_directory_name, apply the patch for all the subdirectory named by the patch_directory_name, apply the patch
:param mod_config: the mod to install :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 patch_directories: list[Path] = []
yield Progress()
for part_directory in mod_config.get_mod_directory().glob("[!_]*"): for part_directory in mod_config.get_mod_directory().glob("[!_]*"):
for patch_directory in part_directory.glob(patch_directory_name): 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]: 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 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-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]:
@ -200,7 +192,6 @@ 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", " ", "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: Extension) -> Generator[Progress, None, wit.WITPath | None]: 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 :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=_("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 if output_type == 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)
@ -225,7 +216,7 @@ class ExtractedGame:
destination_file=destination_file, 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) shutil.rmtree(self.path)
return converted_game return converted_game
@ -241,7 +232,7 @@ class ExtractedGame:
game_files: list[Path] = list(filter(lambda file: file.is_file(), self.path.rglob("*"))) game_files: list[Path] = list(filter(lambda file: file.is_file(), self.path.rglob("*")))
yield Progress( yield Progress(
description=_("CONVERTING_TO_RIIVOLUTION"), description=_("TEXT_CONVERTING_TO_RIIVOLUTION"),
determinate=True, determinate=True,
max_step=len(game_files), max_step=len(game_files),
set_step=0, set_step=0,
@ -316,7 +307,7 @@ class ExtractedGame:
rel_path: str = str(fp.relative_to(self.path)) rel_path: str = str(fp.relative_to(self.path))
yield Progress( yield Progress(
description=_(f"CALCULATING_HASH_FOR", ' "', rel_path, '"'), description=_("TEXT_CALCULATING_HASH") % rel_path,
step=1 step=1
) )

View file

@ -13,14 +13,12 @@ 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) super().__init__(_("ERROR_NOT_MKW_GAME") % Path(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) super().__init__(_("ERROR_GAME_ALREADY_MODDED") % Path(path).name)
super().__init__(_("GAME_ALREADY_MODDED", ' : "', path.name, '"'))
class Game: class Game:
@ -49,7 +47,7 @@ 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=_("TEXT_COPYING_GAME"), determinate=False)
try: next(gen) try: next(gen)
except StopIteration as e: return e.value except StopIteration as e: return e.value
@ -57,17 +55,20 @@ class Game:
yield Progress(determinate=True, max_step=100) yield Progress(determinate=True, max_step=100)
for gen_data in gen: 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( yield Progress(
description=_("EXTRACTING", " - ", gen_data["percentage"], "% - (", "ESTIMATED_TIME_REMAINING", ": " description=_("TEXT_EXTRACTING_GAME") % (percentage, estimation),
f'{gen_data["estimation"] if gen_data["estimation"] is not None else "-:--"})'), set_step=percentage,
set_step=gen_data["percentage"],
) )
try: next(gen) try: next(gen)
except StopIteration as e: except StopIteration as e:
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=_("TEXT_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
@ -120,23 +121,23 @@ 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=_("PART_EXTRACTION"), set_part=1)
yield from self.extract(extracted_game.path) yield from self.extract(extracted_game.path)
# Get the original file hash map for comparaison with the post-patched game # 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 riivolution_original_hash_map: dict[str, str] | None = None
if output_type.is_riivolution(): if output_type.is_riivolution():
riivolution_original_hash_map = yield from extracted_game.get_hash_map() riivolution_original_hash_map = yield from extracted_game.get_hash_map()
# install mystuff # install mystuff
yield Progress(title=_("MYSTUFF"), set_part=3) yield Progress(title=_("PART_MYSTUFF"), set_part=3)
mystuff_packs = options.mystuff_packs.get() mystuff_packs = options.mystuff_packs.get()
mystuff_data = mystuff_packs.get(options.mystuff_pack_selected.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"]) 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=4) yield Progress(title=_("PART_PREPARING_FILES"), set_part=4)
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(
@ -149,10 +150,10 @@ 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-PATCH_TITLE"), set_part=5) yield Progress(title=_("PART_PREPATCH"), set_part=5)
yield from extracted_game.install_all_prepatch(mod_config) # PROGRESS 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 yield from extracted_game.patch_lecode( # PROGRESS
mod_config, mod_config,
cache_directory, cache_directory,
@ -160,17 +161,17 @@ class Game:
cache_ogtracks_directory, cache_ogtracks_directory,
) )
yield Progress(title=_("PATCH_TITLE"), set_part=7) yield Progress(title=_("PART_PATCH"), set_part=7)
yield from extracted_game.install_all_patch(mod_config) # PROGRESS yield from extracted_game.install_all_patch(mod_config)
yield from extracted_game.recreate_all_szs() yield from extracted_game.recreate_all_szs()
if output_type.is_riivolution(): 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) yield from extracted_game.convert_to_riivolution(mod_config, riivolution_original_hash_map)
else: else:
# convert the extracted game into a file # 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) 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

@ -422,7 +422,7 @@ class ModConfig:
nonlocal normalize_threads nonlocal normalize_threads
yield Progress( 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)) normalize_threads = list(filter(lambda thread: thread["thread"].is_alive(), normalize_threads))
@ -433,7 +433,6 @@ class ModConfig:
) )
yield Progress( yield Progress(
description=_("NORMALIZING_TRACKS"),
determinate=True, determinate=True,
max_step=len(all_arenas_tracks)+1, max_step=len(all_arenas_tracks)+1,
set_step=0, set_step=0,

View file

@ -9,7 +9,7 @@ if TYPE_CHECKING:
class InvalidSettingsType(Exception): class InvalidSettingsType(Exception):
def __init__(self, settings_type: str): 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): class AbstractModSettings(ABC):

View file

@ -17,9 +17,9 @@ class Boolean(AbstractModSettings):
super().tkinter_show(master, checkbox) super().tkinter_show(master, checkbox)
variable = self.tkinter_variable(tkinter.BooleanVar) 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_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") radiobutton_off.grid(row=1, column=2, sticky="E")
self.tkinter_bind(master, checkbox) self.tkinter_bind(master, checkbox)

View file

@ -29,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=_("INSTALLING_PATCH"), determinate=False) yield Progress()
# 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

@ -1,7 +1,8 @@
from pathlib import Path from pathlib import Path
from typing import Generator, TYPE_CHECKING 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.mkw.Patch.PatchObject import PatchObject
from source.progress import Progress from source.progress import Progress
from source.translation import translate as _ from source.translation import translate as _
@ -28,7 +29,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=_("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 # check if the directory should be patched
if not self.is_enabled(extracted_game): return 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"]): for game_subfile in game_subpath.parent.glob(self.configuration["match_regex"]):
# disallow patching files outside of the game # disallow patching files outside of the game
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 PathOutsideAllowedRange(game_subfile, extracted_game.path)
# patch the game with the subpatch # patch the game with the subpatch
# if the subfile is a szs archive, replace it with a .d extension # if the subfile is a szs archive, replace it with a .d extension

View file

@ -2,7 +2,8 @@ from io import BytesIO
from pathlib import Path from pathlib import Path
from typing import Generator, IO, TYPE_CHECKING 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.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
@ -82,7 +83,7 @@ class PatchFile(PatchObject):
""" """
patch a subfile of the game with the PatchFile 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 # 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
@ -119,9 +120,9 @@ class PatchFile(PatchObject):
for game_subfile in game_subpath.parent.glob(self.configuration["match_regex"]): for game_subfile in game_subpath.parent.glob(self.configuration["match_regex"]):
# disallow patching files outside of the game # disallow patching files outside of the game
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 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 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

@ -11,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__(_("BMG_LAYER_MODE", ' "', layer_mode, '" ', "IS_NOT_IMPLEMENTED")) super().__init__(_("ERROR_BMG_LAYER_MODE_NOT_IMPLEMENTED") % layer_mode)
class AbstractLayer(ABC): class AbstractLayer(ABC):

View file

@ -2,8 +2,8 @@ from typing import TYPE_CHECKING
from PIL import Image from PIL import Image
from source.mkw import PathOutsideAllowedRange
from source.mkw.Patch.PatchOperation.ImageEditor import AbstractLayer from source.mkw.Patch.PatchOperation.ImageEditor import AbstractLayer
from source.mkw.Patch import PathOutsidePatch
if TYPE_CHECKING: if TYPE_CHECKING:
from source.mkw.Patch import Patch from source.mkw.Patch import Patch
@ -27,7 +27,7 @@ class ImageLayer(AbstractLayer):
# check if the path is outside of the allowed directory # check if the path is outside of the allowed directory
layer_image_path = patch.path / self.image_path layer_image_path = patch.path / self.image_path
if not layer_image_path.is_relative_to(patch.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 # load the image that will be pasted
layer_image = Image.open(layer_image_path.resolve()) \ layer_image = Image.open(layer_image_path.resolve()) \

View file

@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
from PIL import ImageFont, ImageDraw, Image 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 from source.mkw.Patch.PatchOperation.ImageEditor import AbstractLayer
if TYPE_CHECKING: if TYPE_CHECKING:
@ -33,7 +33,7 @@ class TextLayer(AbstractLayer):
if self.font_path is not None: if self.font_path is not None:
font_image_path = patch.path / self.font_path font_image_path = patch.path / self.font_path
if not font_image_path.is_relative_to(patch.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: else:
font_image_path = None font_image_path = None

View file

@ -12,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__(_("IMAGE_LAYER_TYPE", ' "', layer_type, '" ', "IS_NOT_IMPLEMENTED")) super().__init__(_("ERROR_IMAGE_LAYER_TYPE_NOT_IMPLEMENTED") % layer_type)
class AbstractLayer(ABC): class AbstractLayer(ABC):

View file

@ -2,7 +2,7 @@ from io import BytesIO
from pathlib import Path from pathlib import Path
from typing import IO, TYPE_CHECKING 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.mkw.Patch.PatchOperation import AbstractPatchOperation
from source.wt import wstrt from source.wt import wstrt
@ -31,7 +31,7 @@ class StrEditor(AbstractPatchOperation):
for section in self.sections if self.sections is not None else []: for section in self.sections if self.sections is not None else []:
section_path = patch.path / section section_path = patch.path / section
if not section_path.is_relative_to(patch.path): 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 checked_sections += section_path
# for every file in the sections, check if they are inside the patch. # for every file in the sections, check if they are inside the patch.

View file

@ -9,7 +9,7 @@ if TYPE_CHECKING:
class InvalidPatchOperation(Exception): class InvalidPatchOperation(Exception):
def __init__(self, operation: str): def __init__(self, operation: str):
super().__init__(_("OPERATION", ' "', operation, '" ', "IS_NOT_IMPLEMENTED")) super().__init__(_("ERROR_OPERATION_NOT_IMPLEMENTED") % operation)
class AbstractPatchOperation(ABC): class AbstractPatchOperation(ABC):

View file

@ -1,4 +1,3 @@
from pathlib import Path
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from source.translation import translate as _ from source.translation import translate as _
@ -7,18 +6,11 @@ if TYPE_CHECKING:
from source.mkw.Patch.PatchObject import PatchObject 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): class InvalidPatchMode(Exception):
def __init__(self, patch: "PatchObject", mode: str): def __init__(self, patch: "PatchObject", mode: str):
super().__init__(_("MODE", ' "', mode, '" ', "IS_NOT_IMPLEMENTED", super().__init__(_("ERROR_PATCH_MODE_NOT_IMPLEMENTED") % (mode, patch.full_path))
"(", "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__(_("SOURCE", ' "', source, '" ', "IS_NOT_IMPLEMENTED", super().__init__(_("ERROR_SOURCE_NOT_IMPLEMENTED") % (source, patch.full_path))
"(", "IN_PATCH", ' : "', patch.full_path, '")'))

View file

@ -11,7 +11,7 @@ if TYPE_CHECKING:
class TrackForbiddenCustomAttribute(Exception): class TrackForbiddenCustomAttribute(Exception):
def __init__(self, attribute_name: str): 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): class AbstractTrack(ABC):
@ -35,7 +35,6 @@ class AbstractTrack(ABC):
for key, value in kwargs.items(): for key, value in kwargs.items():
# if the attribute start with __, this is a magic attribute, and it should not be modified # if the attribute start with __, this is a magic attribute, and it should not be modified
if "__" in key: raise TrackForbiddenCustomAttribute(key) if "__" in key: raise TrackForbiddenCustomAttribute(key)
# TODO: check potential security issue with setattr and already implemented method and attribute
setattr(self, key, value) setattr(self, key, value)
def __repr__(self): def __repr__(self):

View file

@ -12,7 +12,7 @@ if TYPE_CHECKING:
class ArenaForbiddenCustomAttribute(Exception): class ArenaForbiddenCustomAttribute(Exception):
def __init__(self, attribute_name: str): 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): class Arena(RealArenaTrack):

View file

@ -1,10 +1,5 @@
from typing import TYPE_CHECKING
from source.mkw.Track.AbstractTrack import AbstractTrack from source.mkw.Track.AbstractTrack import AbstractTrack
if TYPE_CHECKING:
from source.mkw.ModConfig import ModConfig
class DefaultTrack(AbstractTrack): class DefaultTrack(AbstractTrack):
def repr_format(self, template: str) -> str: def repr_format(self, template: str) -> str:

View file

@ -11,7 +11,7 @@ if TYPE_CHECKING:
class TrackGroupForbiddenCustomAttribute(Exception): class TrackGroupForbiddenCustomAttribute(Exception):
def __init__(self, attribute_name: str): def __init__(self, attribute_name: str):
super().__init__(_("FORBIDDEN_TRACKGROUP_ATTRIBUTE", " : ", repr(attribute_name))) super().__init__(_("ERROR_FORBIDDEN_TRACKGROUP_ATTRIBUTE") % attribute_name)
class TrackGroup: class TrackGroup:
@ -61,7 +61,7 @@ class TrackGroup:
return the ctfile of the track group return the ctfile of the track group
:return: ctfile :return: ctfile
""" """
ctfile = f'T T11; T11; 0x02; "-"; "info"; "-"\n' ctfile = f'T T11; T11; 0x02; "-"; "-"; "-"\n'
for track in self.get_tracks(): for track in self.get_tracks():
ctfile += track.get_ctfile(template=template, hidden=True) ctfile += track.get_ctfile(template=template, hidden=True)

View file

@ -1 +1,12 @@
from typing import TYPE_CHECKING
from source.translation import translate as _
if TYPE_CHECKING:
from pathlib import Path
Tag = str Tag = str
class PathOutsideAllowedRange(Exception):
def __init__(self, forbidden_path: "Path", allowed_range: "Path"):
super().__init__(_("ERROR_PATH_OUTSIDE_RANGE") % (forbidden_path, allowed_range))

View file

@ -5,7 +5,7 @@ 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__(_("CANNOT_FIND_COLOR", ' "', color_data, '"')) super().__init__(_("ERROR_CANNOT_FIND_COLOR") % color_data)
@dataclass(init=True, slots=True) @dataclass(init=True, slots=True)

View file

@ -4,7 +4,7 @@ from source.translation import translate as _
class SlotNotFound(Exception): class SlotNotFound(Exception):
def __init__(self, slot_data: any): 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) @dataclass(init=True, slots=True, frozen=True, repr=True)

View file

@ -6,7 +6,7 @@ from source.utils import restart_program
class OptionLoadingError(Exception): class OptionLoadingError(Exception):
def __init__(self): 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: class Option:

View file

@ -4,7 +4,7 @@ from source.translation import translate as _
class BetterSafeEvalError(Exception): class BetterSafeEvalError(Exception):
def __init__(self, template: str): 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): def better_safe_eval_error(func: Callable, template: str):

View file

@ -12,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__(_("INVALID_MACRO", ' : "', macro, '"')) super().__init__(_("ERROR_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

@ -60,7 +60,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(_("INVALID_AST_TYPE", ' : "', type(stmt).__name__, '"')) raise SafeEvalException(_("ERROR_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):
@ -70,20 +70,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(_("MAGIC_ATTRIBUTE_ARE_FORBIDDEN", ' : "', node.attr, '"')) raise SafeEvalException(_("ERROR_FORBIDDEN_MAGIC_ATTRIBUTE") % node.attr)
# ban modification to environment # ban modification to environment
if isinstance(node.ctx, ast.Store): 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 # 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(_("CANNOT_SET_ENVIRONMENT", ' : "', node.id, '"')) raise SafeEvalException(_("ERROR_CANNOT_SET_ENVIRONMENT") % node.id)
elif node.id in args: 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 ":=" # when assigning a value with ":="
case ast.NamedExpr: case ast.NamedExpr:
@ -96,13 +96,14 @@ def safe_eval(template: "TemplateSafeEval", env: "Env" = None, macros: dict[str,
case ast.Call: case ast.Call:
if isinstance(node.func, ast.Attribute): # if this is a method 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 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 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 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 # Forbidden type. Some of them can't be accessed with the eval mode, but just in case, still ban them
case ( 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 # comprehension are extremely dangerous since their can associate value
ast.ListComp | ast.SetComp | ast.DictComp | ast.GeneratorExp 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 # embed the whole expression into a lambda expression
stmt.value = ast.Lambda( stmt.value = ast.Lambda(

View file

@ -36,9 +36,9 @@ 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(_("MAGIC_METHOD_FORBIDDEN", ' : "', name, '"')) if "__" in name: raise Exception(_("ERROR_FORBIDDEN_MAGIC_METHOD") % name)
attr = getattr(obj, name, default) 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 return attr
@staticmethod @staticmethod
@ -63,5 +63,5 @@ class safe_function:
:param obj: the object to copy :param obj: the object to copy
:return: the copied object :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) return copy.deepcopy(obj)

View file

@ -19,17 +19,13 @@ def load_language(language: str):
self._language_data = json.loads(Path(f"./assets/language/{language}.json").read_text(encoding="utf8")) 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. Translate a text to the loaded language.
:param text: list of text to translate :param text: list of text to translate
:return: translated text :return: translated text
""" """
return "".join([ return self._language_data.get("translation", {}).get(text, text)
self._language_data.get("translation", {}).get(word, word) if isinstance(word, str)
else str(word)
for word in text
])
def translate_external(mod_config: "ModConfig", language: str, message_texts: dict[str, str], default: str = "") -> str: def translate_external(mod_config: "ModConfig", language: str, message_texts: dict[str, str], default: str = "") -> str:

View file

@ -21,15 +21,15 @@ class WTError(Exception):
check=True, check=True,
**subprocess_kwargs **subprocess_kwargs
).stdout.decode() ).stdout.decode()
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError:
error = _("- ", "CANNOT_GET_ERROR_MESSAGE", " -") 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): class MissingWTError(Exception):
def __init__(self, tool_name: str): 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 try: tools_szs_dir = next(tools_dir.glob("./szs*/")) / system

View file

@ -192,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(_("CANNOT_EXTRACT_A_DIRECTORY")) if self.is_dir(): raise ValueError(_("ERROR_CANNOT_EXTRACT_DIRECTORY"))
dest: Path = Path(dest) dest: Path = Path(dest)
if dest.is_dir(): dest /= self.basename() if dest.is_dir(): dest /= self.basename()

View file

@ -202,7 +202,7 @@ class WITSubPath:
if self.wit_path.extension == Extension.FST: if self.wit_path.extension == Extension.FST:
# if flat is used, extract the file / dir into the destination directory, without subdirectory # if flat is used, extract the file / dir into the destination directory, without subdirectory
if flat: 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 we are extracting a directory, we need to extract every file recursively
if self.is_dir(): if self.is_dir():
for file in (self._get_fst_path()).rglob("*"): for file in (self._get_fst_path()).rglob("*"):