diff --git a/src/backend/models/samples.py b/src/backend/models/samples.py index dd414ac2af1..168c6991476 100644 --- a/src/backend/models/samples.py +++ b/src/backend/models/samples.py @@ -63,7 +63,8 @@ class Samples: "DLL_Overrides": {}, "External_Programs": {}, "Uninstallers": {}, - "Latest_Executables": [] + "Latest_Executables": [], + "Language": "sys" } environments = { diff --git a/src/backend/utils/manager.py b/src/backend/utils/manager.py index b096c6c8a2b..ac331a7b686 100644 --- a/src/backend/utils/manager.py +++ b/src/backend/utils/manager.py @@ -18,6 +18,7 @@ import shutil import gi import os +import locale import subprocess from glob import glob from typing import NewType, Union @@ -299,3 +300,93 @@ def browse_wineprefix(wineprefix: dict): path_type="custom", custom_path=wineprefix.get("Path") ) + + @staticmethod + def get_languages(from_name=None, from_locale=None, from_index=None, get_index=False, get_locales=False): + locales = [ + 'sys', + 'bg_BG', + 'cs_CZ', + 'da_DK', + 'de_DE', + 'el_GR', + 'en_US', + 'es_ES', + 'et_EE', + 'fi_FI', + 'fr_FR', + 'hr_HR', + 'hu_HU', + 'it_IT', + 'lt_LT', + 'lv_LV', + 'nl_NL', + 'no_NO', + 'pl_PL', + 'pt_PT', + 'ro_RO', + 'ru_RU', + 'sk_SK', + 'sl_SI', + 'sv_SE', + 'tr_TR', + 'zh_CN' + ] + names = [ + _('System'), + _('Bulgarian'), + _('Czech'), + _('Danish'), + _('German'), + _('Greek'), + _('English'), + _('Spanish'), + _('Estonian'), + _('Finnish'), + _('French'), + _('Croatian'), + _('Hungarian'), + _('Italian'), + _('Lithuanian'), + _('Latvian'), + _('Dutch'), + _('Norwegian'), + _('Polish'), + _('Portuguese'), + _('Romanian'), + _('Russian'), + _('Slovak'), + _('Slovenian'), + _('Swedish'), + _('Turkish'), + _('Chinese'), + ] + + if from_name and from_locale: + raise ValueError("Cannot pass both from_name, from_locale and from_index.") + + if from_name: + if from_name not in names: + raise ValueError("Given name not in list.") + i = names.index(from_name) + if get_index: + return i + return from_name, locales[i] + + if from_locale: + if from_locale not in locales: + raise ValueError("Given locale not in list.") + i = locales.index(from_locale) + if get_index: + return i + return from_locale, names[i] + + if isinstance(from_index, int): + if from_index not in range(0, len(locales)): + raise ValueError("Given index not in range.") + return locales[from_index], names[from_index] + + if get_locales: + return locales + + return names diff --git a/src/backend/wine/winecommand.py b/src/backend/wine/winecommand.py index 4249b77a932..8d4a297f1f9 100644 --- a/src/backend/wine/winecommand.py +++ b/src/backend/wine/winecommand.py @@ -150,7 +150,7 @@ def get_env(self, environment, return_steam_env: bool = False) -> dict: for var in config.get("Environment_Variables").items(): env.add(var[0], var[1], override=True) - # Environment variables from argument + # Environment variables from argument if environment: if environment.get("WINEDLLOVERRIDES"): dll_overrides.append(environment["WINEDLLOVERRIDES"]) @@ -159,6 +159,10 @@ def get_env(self, environment, return_steam_env: bool = False) -> dict: for e in environment: env.add(e, environment[e], override=True) + # Language + if config["Language"] != "sys": + env.add("LC_ALL", config["Language"]) + # Bottle DLL_Overrides if config["DLL_Overrides"]: for dll in config.get("DLL_Overrides").items(): diff --git a/src/ui/details-preferences.ui b/src/ui/details-preferences.ui index 23e725c8dfc..f5220fd5860 100644 --- a/src/ui/details-preferences.ui +++ b/src/ui/details-preferences.ui @@ -452,7 +452,7 @@ combo_windows - Windows version + Windows Version Updating Windows version, please wait… @@ -481,6 +481,16 @@ + + + Language + Choose the language to use with programs. + + + + + + switch_runtime diff --git a/src/views/bottle_preferences.py b/src/views/bottle_preferences.py index 555d8d4f84c..0a9f81bdcd5 100644 --- a/src/views/bottle_preferences.py +++ b/src/views/bottle_preferences.py @@ -23,7 +23,8 @@ from bottles.utils.threading import RunAsync # pyright: reportMissingImports=false from bottles.utils.gtk import GtkUtils -from bottles.backend.runner import Runner, gamemode_available, gamescope_available, mangohud_available, obs_vkc_available +from bottles.backend.runner import Runner, gamemode_available, gamescope_available, mangohud_available, \ + obs_vkc_available from bottles.backend.managers.runtime import RuntimeManager from bottles.backend.utils.manager import ManagerUtils @@ -99,6 +100,7 @@ class PreferencesView(Adw.PreferencesPage): combo_latencyflex = Gtk.Template.Child() combo_windows = Gtk.Template.Child() combo_renderer = Gtk.Template.Child() + combo_language = Gtk.Template.Child() spinner_dxvk = Gtk.Template.Child() spinner_dxvkbool = Gtk.Template.Child() spinner_vkd3d = Gtk.Template.Child() @@ -112,6 +114,7 @@ class PreferencesView(Adw.PreferencesPage): box_sync = Gtk.Template.Child() group_details = Gtk.Template.Child() exp_components = Gtk.Template.Child() + str_list_languages = Gtk.Template.Child() ev_controller = Gtk.EventControllerKey.new() # endregion @@ -167,6 +170,7 @@ def __init__(self, window, config, **kwargs): self.combo_latencyflex.connect('changed', self.__set_latencyflex) self.combo_windows.connect('changed', self.__set_windows) self.combo_renderer.connect('changed', self.__set_renderer) + self.combo_language.connect('notify::selected-item', self.__set_language) self.ev_controller.connect("key-released", self.__check_entry_name) self.entry_name.connect("apply", self.__save_name) # endregion @@ -214,6 +218,7 @@ def __save_name(self, *args): def choose_cwd(self, widget, reset=False): """Change the default current working directory for the bottle""" + def set_path(_dialog, response, _file_dialog): if response == Gtk.ResponseType.OK: _file = _file_dialog.get_file() @@ -259,12 +264,14 @@ def update_combo_components(self): self.combo_vkd3d.handler_block_by_func(self.__set_vkd3d) self.combo_nvapi.handler_block_by_func(self.__set_nvapi) self.combo_latencyflex.handler_block_by_func(self.__set_latencyflex) + self.combo_language.handler_block_by_func(self.__set_language) self.combo_runner.remove_all() self.combo_dxvk.remove_all() self.combo_vkd3d.remove_all() self.combo_nvapi.remove_all() self.combo_latencyflex.remove_all() + self.str_list_languages.splice(0, self.str_list_languages.get_n_items()) for runner in self.manager.runners_available: self.combo_runner.append(runner, runner) @@ -281,11 +288,15 @@ def update_combo_components(self): for latencyflex in self.manager.latencyflex_available: self.combo_latencyflex.append(latencyflex, latencyflex) + for l in ManagerUtils.get_languages(): + self.str_list_languages.append(l) + self.combo_runner.handler_unblock_by_func(self.__set_runner) self.combo_dxvk.handler_unblock_by_func(self.__set_dxvk) self.combo_vkd3d.handler_unblock_by_func(self.__set_vkd3d) self.combo_nvapi.handler_unblock_by_func(self.__set_nvapi) self.combo_latencyflex.handler_unblock_by_func(self.__set_latencyflex) + self.combo_language.handler_unblock_by_func(self.__set_language) def set_config(self, config): self.config = config @@ -323,6 +334,7 @@ def set_config(self, config): self.combo_latencyflex.handler_block_by_func(self.__set_latencyflex) self.combo_windows.handler_block_by_func(self.__set_windows) self.combo_renderer.handler_block_by_func(self.__set_renderer) + self.combo_language.handler_block_by_func(self.__set_language) self.combo_dpi.handler_block_by_func(self.__set_custom_dpi) self.toggle_sync.handler_block_by_func(self.__set_wine_sync) self.toggle_esync.handler_block_by_func(self.__set_esync) @@ -388,6 +400,10 @@ def set_config(self, config): self.combo_windows.append("win95", "Windows 95") self.combo_windows.set_active_id(self.config.get("Windows")) + self.combo_language.set_selected(ManagerUtils.get_languages( + from_locale=self.config.get("Language"), + get_index=True + )) # unlock functions connected to the widgets self.switch_dxvk.handler_unblock_by_func(self.__toggle_dxvk) @@ -421,6 +437,7 @@ def set_config(self, config): self.combo_latencyflex.handler_unblock_by_func(self.__set_latencyflex) self.combo_windows.handler_unblock_by_func(self.__set_windows) self.combo_renderer.handler_unblock_by_func(self.__set_renderer) + self.combo_language.handler_unblock_by_func(self.__set_language) self.combo_dpi.handler_unblock_by_func(self.__set_custom_dpi) self.toggle_sync.handler_unblock_by_func(self.__set_wine_sync) self.toggle_esync.handler_unblock_by_func(self.__set_esync) @@ -457,6 +474,7 @@ def __set_sync_type(self, sync): Set the sync type (wine, esync, fsync, futext2) Don't use this directly, use dedicated wrappers instead (e.g. __set_wine_sync) """ + def update(result, error=False): self.config = result.data["config"] toggles = [ @@ -873,6 +891,7 @@ def update(result, error=False): def __set_renderer(self, widget): """Set the renderer to use for the bottle""" + def update(result, error=False): self.config = self.manager.update_config( config=self.config, @@ -892,6 +911,16 @@ def update(result, error=False): value=renderer ) + def __set_language(self, *args): + """Set the language to use for the bottle""" + index = self.combo_language.get_selected() + language = ManagerUtils.get_languages(from_index=index) + self.config = self.manager.update_config( + config=self.config, + key="Language", + value=language[0], + ).data["config"] + def __toggle_pulse_latency(self, widget, state): """Set the pulse latency to use for the bottle""" self.config = self.manager.update_config(