diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index 427fcb8da7..ed3bc72f50 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -1,5 +1,5 @@ import os -import json +import sys import math from cereal import messaging, log @@ -84,8 +84,11 @@ class DeviceLayout(Widget): def handle_language_selection(result: int): if result == 1 and self._select_language_dialog: selected_language = multilang.languages[self._select_language_dialog.selection] - self._params.put("LanguageSetting", selected_language) + multilang.change_language(selected_language) print("Selected language:", selected_language) + # Rebuild UI with new translations + self._rebuild_for_language_change() + gui_app.set_modal_overlay(None) self._select_language_dialog = None @@ -95,6 +98,21 @@ class DeviceLayout(Widget): except FileNotFoundError: pass + def _rebuild_for_language_change(self): + # Recreate translated strings and UI items + global DESCRIPTIONS + DESCRIPTIONS = { + 'pair_device': tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."), + 'driver_camera': tr("Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)"), + 'reset_calibration': tr("openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down."), + 'review_guide': tr("Review the rules, features, and limitations of openpilot"), + } + + items = self._initialize_items() + self._scroller = Scroller(items, line_separator=True, spacing=0) + self._offroad_transition() + self._scroller.show_event() + def _show_driver_camera(self): if not self._driver_camera: self._driver_camera = DriverCameraDialog() diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 5c49872bef..47fe3b759e 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -43,15 +43,15 @@ FONT_DIR = ASSETS_DIR.joinpath("fonts") class FontWeight(StrEnum): - THIN = "OpenpilotSans-Regular.ttf" - EXTRA_LIGHT = "OpenpilotSans-Regular.ttf" - LIGHT = "OpenpilotSans-Regular.ttf" - NORMAL = "OpenpilotSans-Regular.ttf" - MEDIUM = "OpenpilotSans-Regular.ttf" - SEMI_BOLD = "OpenpilotSans-Regular.ttf" - BOLD = "OpenpilotSans-Regular.ttf" - EXTRA_BOLD = "OpenpilotSans-Regular.ttf" - BLACK = "OpenpilotSans-Regular.ttf" + THIN = "Inter-Thin.ttf" + EXTRA_LIGHT = "Inter-ExtraLight.ttf" + LIGHT = "Inter-Light.ttf" + NORMAL = "Inter-Regular.ttf" + MEDIUM = "Inter-Medium.ttf" + SEMI_BOLD = "Inter-SemiBold.ttf" + BOLD = "Inter-Bold.ttf" + EXTRA_BOLD = "Inter-ExtraBold.ttf" + BLACK = "Inter-Black.ttf" @dataclass @@ -347,7 +347,6 @@ class GuiApplication: def height(self): return self._height - def _load_fonts(self): # Create a character set from our keyboard layouts from openpilot.system.ui.widgets.keyboard import KEYBOARD_LAYOUTS @@ -360,20 +359,7 @@ class GuiApplication: for cp in range(0x00A0, 0x0180): all_chars.add(chr(cp)) all_chars |= set("–✓×°§•") - - with open(LANGUAGES_FILE, "r") as f: - l = f.read() - languages = json.loads(l) - all_chars |= set(l) - - # read each translation file now - for lang in languages: - lang_file = os.path.join(TRANSLATIONS_DIR, f"app_{lang}.po") - with open(lang_file, "r", encoding="utf-8") as f: - all_chars |= set(f.read()) - all_chars = "".join(all_chars) - print(all_chars) codepoint_count = rl.ffi.new("int *", 1) codepoints = rl.load_codepoints(all_chars, codepoint_count) diff --git a/system/ui/lib/multilang.py b/system/ui/lib/multilang.py index 45cc0586e0..ef9c144931 100644 --- a/system/ui/lib/multilang.py +++ b/system/ui/lib/multilang.py @@ -14,6 +14,7 @@ class Multilang: self._params = Params() self.languages = {} self.codes = {} + self._translation: gettext.NullTranslations | gettext.GNUTranslations | None = None self._load_languages() @property @@ -26,16 +27,31 @@ class Multilang: with open(os.path.join(TRANSLATIONS_DIR, f'app_{language}.mo'), 'rb') as fh: translation = gettext.GNUTranslations(fh) translation.install() - tr = translation.gettext - trn = translation.ngettext + self._translation = translation print(f"Loaded translations for language: {language}") except FileNotFoundError: print(f"No translation file found for language: {language}, using default.") gettext.install('app') - tr = gettext.gettext - trn = gettext.ngettext + self._translation = gettext.NullTranslations() + return None - return tr, trn + def change_language(self, language_code: str) -> None: + # Persist selection + if self._params.get("LanguageSetting") != language_code: + self._params.put("LanguageSetting", language_code) + + # Reinstall gettext with the selected language + self.setup() + + def tr(self, text: str) -> str: + if self._translation is None: + self.setup() + return self._translation.gettext(text) + + def trn(self, singular: str, plural: str, n: int) -> str: + if self._translation is None: + self.setup() + return self._translation.ngettext(singular, plural, n) def _load_languages(self): with open(LANGUAGES_FILE, encoding='utf-8') as f: @@ -44,4 +60,10 @@ class Multilang: multilang = Multilang() -tr, trn = multilang.setup() +multilang.setup() + +def tr(text: str) -> str: + return multilang.tr(text) + +def trn(singular: str, plural: str, n: int) -> str: + return multilang.trn(singular, plural, n)