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 @@
+
+
+ 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(