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",
"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": "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 <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 ?", "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" "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 <installer-path>' 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"
} }
} }

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", "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\"",
"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": "Attention",
"WARNING_DESTINATION_GAME_NOT_WRITABLE": "Le chemin de destination n'est pas modifiable", "WARNING_EMPTY_CACHE": "En vidant le cache, vous allez gagner %.2fGB, mais reinstaller un mod prendra beaucoup plus de temps. Continuer ?",
"INSTALL": "Installer", "WARNING_DESTINATION_NOT_WRITABLE": "Le dossier de destination du jeu ne peut pas être modifié. Continuer quand même ?",
"ERROR_INVALID_DESTINATION_GAME": "Chemin invalide pour la destination du jeu", "WARNING_LOW_SPACE_CONTINUE": "Espace faible sur le disque %s (%.2fGB). Continuer quand même ?",
"WARNING_LOW_SPACE_CONTINUE": "L'espace restant sur le disque dur est faible. Continuer ?", "WARNING_NOT_ROOT": "Permissions administrateur requise. Vous devriez lancer l'application avec 'sudo'. Continuer quand même ?",
"INSTALLATION_COMPLETED": "Installation Complété", "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 ?",
"INSTALLATION_FINISHED_WITH_SUCCESS": "L'installation s'est terminé avec succès !",
"OPEN_MYSTUFF_SETTINGS": "Ouvrir les paramètres MyStuff", "ERROR": "Erreur",
"THREADS_USAGE": "Utilisation des Threads", "ERROR_SEE_LOGS": "Voir le fichier error.log pour plus d'information",
"USE": "Utiliser", "ERROR_INVALID_SOURCE_GAME": "Jeu source invalide : \"%s\"",
"THREADS": "threads", "ERROR_INVALID_GAME_DESTINATION": "Destination de jeu invalide : \"%s\"",
"MESSAGE_FROM_MOD_AUTHOR": "Message de l'auteur", "ERROR_MYSTUFF_PROFILE_ALREADY_EXIST": "Le profil MyStuff \"%s\" existe déjà !",
"GLOBAL_MOD_SETTINGS": "Paramètre global de mod", "ERROR_MYSTUFF_PROFILE_FORBIDDEN_NAME": "Le nom de profil MyStuff \"%s\" est interdit !",
"SPECIFIC_MOD_SETTINGS": "Paramètre spécifique de mod", "ERROR_NOT_MKW_GAME": "Ce jeu n'est pas un jeu Mario Kart Wii : \"%s\"",
"CONFIGURE_MYSTUFF_PATCH": "Configurer les patchs MyStuff", "ERROR_GAME_ALREADY_MODDED": "Ce jeu est déjà moddé : \"%s\"",
"DISABLED": "Désactiver", "ERROR_PATH_OUTSIDE_RANGE": "Le chemin \"%s\" est en dehors de la portée autorisée : \"%s\"",
"ENABLED": "Activer", "ERROR_PREVIEW_WINDOW_NOT_FOUND": "Type de fenêtre de prévisualisation \"%s\" introuvable",
"NEW_PROFILE": "Nouveau profil", "ERROR_CANNOT_FIND_COLOR": "Impossible de trouver la couleur : \"%s\"",
"DELETE_PROFILE": "Retirer un Profil", "ERROR_CANNOT_FIND_SLOT": "Impossible de trouver le slot : \"%s\"",
"ADD_MYSTUFF": "Ajouter un patch MyStuff", "ERROR_MOD_SETTINGS_NOT_FOUND": "Type de paramètre de mod \"%s\" introuvable",
"REMOVE_MYSTUFF": "Retirer un patch MyStuff", "ERROR_FORBIDDEN_TRACK_ATTRIBUTE": "Attribut de course interdit : \"%s\"",
"MYSTUFF_PROFILE_ALREADY_EXIST": "Ce profil MyStuff existe déjà !", "ERROR_FORBIDDEN_ARENA_ATTRIBUTE": "Attribut d'arène interdit : \"%s\"",
"MYSTUFF_PROFILE_FORBIDDEN_NAME": "Ce nom de profil MyStuff ne peut pas être utilisé !", "ERROR_FORBIDDEN_TRACKGROUP_ATTRIBUTE": "Attribut de groupe de course interdit : \"%s\"",
"SELECT_MYSTUFF": "Sélectionner un patch MyStuff", "ERROR_SAFEEVAL": "Erreur de safe eval (modèle utilisé : \"%s\")",
"TYPE_PREVIEW_WINDOW": "Type de fenêtre de prévisualisation", "ERROR_INVALID_MACRO": "Macro invalide : \"%s\"",
"NOT_FOUND": "introuvable", "ERROR_INVALID_AST_TYPE": "Type d'AST invalide : \"%s\"",
"TYPE_MOD_SETTINGS": "Type de paramètre de mod", "ERROR_FORBIDDEN_MAGIC_ATTRIBUTE": "Les attributs magiques sont interdit : \"%s\"",
"BMG_LAYER_MODE": "mode de couche bmg", "ERROR_FORBIDDEN_MAGIC_METHOD": "Les méthodes magiques sont interdites : \"%s\"",
"IS_NOT_IMPLEMENTED": "n'est pas implémenté", "ERROR_GETTING_METHOD_FORBIDDEN": "Récupérer une méthode est interdit : \"%s\"",
"IMAGE_LAYER_TYPE": "type de couche d'image", "ERROR_CANNOT_SET_ATTRIBUTE": "Changer un attribut est interdit : \"%s\"",
"OPERATION": "Opération", "ERROR_CANNOT_SET_ENVIRONMENT": "Changer une variable d'environnement est interdit : \"%s\"",
"PATH": "Chemin", "ERROR_CANNOT_SET_ARGUMENT": "Changer une variable d'argument est interdit : \"%s\"",
"MODE": "Mode", "ERROR_CAN_ONLY_CALL_CONSTANT_METHOD": "Seulement appeler la méthode d'une constante est autorisé",
"SOURCE": "Source", "ERROR_CAN_ONLY_CALL_ENV_FUNCTION": "Seulement appeler une fonction de l'environnement est autorisé",
"OUTSIDE_ALLOWED_RANGE": "En dehors de la portée autorisée", "ERROR_FORBIDDEN_SYNTAX": "Syntaxe interdite : \"%s\"",
"IN_PATCH": "dans le patch", "ERROR_FUNCTION_COPY_FORBIDDEN": "Copier une fonction est interdit",
"INSTALLING_PATCH": "Installation du patch", "ERROR_CANNOT_GET_ERROR_MESSAGE": "Impossible de récupérer le message d'erreur",
"PATCH_TITLE": "Patch", "ERROR_WT": "%s à généré l'erreur %i :\n%s",
"PRE-PATCH_TITLE": "Pre-patch", "ERROR_CANNOT_FIND_TOOL": "Impossible de trouver l'outil \"%s\" dans le dossier tools",
"PATCHING": "Patch de", "ERROR_CANNOT_EXTRACT_DIRECTORY": "Un dossier ne peut pas être extrait",
"FORBIDDEN_TRACK_ATTRIBUTE": "Attribut de course interdit", "ERROR_PATCH_MODE_NOT_IMPLEMENTED": "Le mode de Patch \"%s\" n'est pas implémenté (du patch : \"%s\")",
"FORBIDDEN_ARENA_ATTRIBUTE": "Attribut d'arène interdit", "ERROR_SOURCE_NOT_IMPLEMENTED": "La source \"%s\" n'est pas implémenté (du patch : \"%s\")",
"EXTRACTING_AUTOADD_FILES": "Extraction des fichiers autoadd...", "ERROR_OPERATION_NOT_IMPLEMENTED": "L'opération \"%s\" n'est pas implémenté",
"EXTRACTING_ORIGINAL_TRACKS": "Extraction des courses originelles...", "ERROR_BMG_LAYER_MODE_NOT_IMPLEMENTED": "Le mode de couche bmg \"%s\" n'est pas implémenté",
"INSTALLING_MYSTUFF": "Installation de MyStuff", "ERROR_IMAGE_LAYER_TYPE_NOT_IMPLEMENTED": "Le type de couche d'image \"%s\" n'est pas implémenté"
"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 <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,11 +167,15 @@ 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):
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) 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("*"):