diff --git a/.gitignore b/.gitignore index 57f1cb2..fbe3fdc 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -/.idea/ \ No newline at end of file +/.idea/ +/option.json diff --git a/main.pyw b/main.pyw index b37acc9..46447d5 100644 --- a/main.pyw +++ b/main.pyw @@ -1,8 +1,15 @@ -from source.gui import install import sys +from source.gui import install +from source.option import Option +from source.translation import load_language + + # this allows every variable to be accessible from other files, useful for the plugins self = sys.modules[__name__] -self.window = install.Window() +options = Option.from_file("./option.json") +translater = load_language(options["language"]) + +self.window = install.Window(options) self.window.run() diff --git a/source/__init__.py b/source/__init__.py index fca8e91..606939f 100644 --- a/source/__init__.py +++ b/source/__init__.py @@ -1,3 +1,5 @@ +import os +import sys from threading import Thread from typing import Callable @@ -28,3 +30,10 @@ def threaded(func: Callable) -> Callable: Thread(target=func, args=args, kwargs=kwargs, daemon=True).start() return wrapper + + +def restart_program(): + """ + Restart the program + """ + os.execl(sys.executable, sys.executable, *sys.argv) diff --git a/source/gui/__init__.py b/source/gui/__init__.py index 86092cb..32be3cf 100644 --- a/source/gui/__init__.py +++ b/source/gui/__init__.py @@ -1,7 +1,6 @@ import traceback from tkinter import messagebox from typing import Callable - from source.translation import translate as _ @@ -12,6 +11,6 @@ def better_gui_error(func: Callable) -> Callable: def wrapper(*args, **kwargs): try: return func(*args, **kwargs) - except: messagebox.showerror(_("ERROR"), traceback.format_exc()) + except: messagebox.showerror(_("Error"), traceback.format_exc()) - return wrapper \ No newline at end of file + return wrapper diff --git a/source/gui/install.py b/source/gui/install.py index f24baff..8ca41c8 100644 --- a/source/gui/install.py +++ b/source/gui/install.py @@ -12,6 +12,7 @@ from typing import Generator from source.gui import better_gui_error from source.mkw.Game import Game from source.mkw.ModConfig import ModConfig +from source.option import Option from source.translation import translate as _ from source import event from source import * @@ -35,9 +36,11 @@ class InstallerState(enum.Enum): # Main window for the installer class Window(tkinter.Tk): - def __init__(self): + def __init__(self, options: Option): super().__init__() + self.options: Option = options + self.title(_("INSTALLER_TITLE")) self.resizable(False, False) self.iconbitmap("./assets/icon.ico") @@ -141,8 +144,15 @@ class Menu(tkinter.Menu): master.add_cascade(label=_("LANGUAGE_SELECTION"), menu=self) + def callback(file: Path): + def func(): self.master.master.options["language"] = file.stem + return func + for file in Path("./assets/language/").iterdir(): - self.add_command(label=json.loads(file.read_text(encoding="utf8"))["name"]) + self.add_command( + label=json.loads(file.read_text(encoding="utf8"))["name"], + command=callback(file) + ) # Track configuration menu class TrackConfiguration(tkinter.Menu): diff --git a/source/mkw/Track.py b/source/mkw/Track.py index 6241900..c0b8cf7 100644 --- a/source/mkw/Track.py +++ b/source/mkw/Track.py @@ -37,6 +37,8 @@ class Track: # others not mandatory attributes for key, value in kwargs.items(): + # if the attribute start with __, this is a magic attribute, and it should not be modified + if key.startswith("__"): continue setattr(self, key, value) @classmethod diff --git a/source/option.py b/source/option.py new file mode 100644 index 0000000..a93c3b6 --- /dev/null +++ b/source/option.py @@ -0,0 +1,77 @@ +import json +from pathlib import Path + +from source import restart_program + + +class Option: + __slots__ = ("_path", "_options") + + reboot_on_change: list[any] = ["language"] + default_options: dict[str, any] = { + "language": "en" + } + + def __init__(self, language=None): + self._path: Path | None = None + self._options: dict[str, any] = self.default_options.copy() + if language is not None: self._options["language"] = language + + def __getitem__(self, key: str) -> any: + """ + get an options value from its key + :param key: the option name + :return: the value of the option + """ + return self._options[key] + + def __setitem__(self, key: str, value: any) -> None: + """ + change the value of an options for a key, if the options have been loaded from a file, save it inside + if the option is in the reboot_on_change list, reboot the program + :param key: the name of the option to edit + :param value: the value of the option + :return: + """ + self._options[key] = value + if self._path: self.save() + if key in self.reboot_on_change: restart_program() + + def save(self, option_file: Path | str = None) -> None: + """ + save the options to the file + :return: None + """ + if option_file is None: option_file = self._path + if isinstance(option_file, str): option_file = Path(option_file) + + with option_file.open("w") as file: + json.dump(self._options, file, indent=4, ensure_ascii=False) + + @classmethod + def from_dict(cls, option_dict: dict) -> "Option": + """ + Create a Option from a dict if the parameters are in the default_options + :param option_dict: dict containing the configuration + :return: Option + """ + kwargs = {} + for key in cls.default_options.keys(): + if "key" in option_dict: kwargs[key] = option_dict[key] + + return cls(**kwargs) + + @classmethod + def from_file(cls, option_file: str | Path) -> "Option": + """ + Loads the option from a file. If the option file does not exist, only load default configuration + :param option_file: the option file + :return: Option + """ + if isinstance(option_file, str): option_file = Path(option_file) + + if not option_file.exists(): obj = cls() + else: obj = cls.from_dict(json.loads(option_file.read_text(encoding="utf8"))) + + obj._path = option_file + return obj diff --git a/source/translation.py b/source/translation.py index 4e24f9f..a503fc7 100644 --- a/source/translation.py +++ b/source/translation.py @@ -1,8 +1,19 @@ import json +import sys from pathlib import Path -language_data = json.loads(Path("./assets/language/en.json").read_text(encoding="utf8")) +self = sys.modules[__name__] +self._language_data = {} + + +def load_language(language: str): + """ + Load a language file. + :param language: language code to load + :return: + """ + self._language_data = json.loads(Path(f"./assets/language/{language}.json").read_text(encoding="utf8")) def translate(*text) -> str: @@ -11,4 +22,8 @@ def translate(*text) -> str: :param text: list of text to translate :return: translated text """ - return "".join([language_data["translation"].get(word, word) for word in text]) + return "".join([self._language_data["translation"].get(word, word) for word in text]) + + + +