diff --git a/selfdrive/ui/layouts/home.py b/selfdrive/ui/layouts/home.py index 519e0e020a..34a7558cdc 100644 --- a/selfdrive/ui/layouts/home.py +++ b/selfdrive/ui/layouts/home.py @@ -9,6 +9,7 @@ from openpilot.selfdrive.ui.widgets.prime import PrimeWidget from openpilot.selfdrive.ui.widgets.setup import SetupWidget from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos +from openpilot.system.ui.lib.multilang import tr, trn from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.widgets import Widget @@ -151,7 +152,7 @@ class HomeLayout(Widget): highlight_color = rl.Color(75, 95, 255, 255) if self.current_state == HomeLayoutState.UPDATE else rl.Color(54, 77, 239, 255) rl.draw_rectangle_rounded(self.update_notif_rect, 0.3, 10, highlight_color) - text = "UPDATE" + text = tr("UPDATE") text_size = measure_text_cached(font, text, HEAD_BUTTON_FONT_SIZE) text_x = self.update_notif_rect.x + (self.update_notif_rect.width - text_size.x) // 2 text_y = self.update_notif_rect.y + (self.update_notif_rect.height - text_size.y) // 2 @@ -165,7 +166,7 @@ class HomeLayout(Widget): highlight_color = rl.Color(255, 70, 70, 255) if self.current_state == HomeLayoutState.ALERTS else rl.Color(226, 44, 44, 255) rl.draw_rectangle_rounded(self.alert_notif_rect, 0.3, 10, highlight_color) - alert_text = f"{self.alert_count} ALERT{'S' if self.alert_count > 1 else ''}" + alert_text = trn("{} ALERT", "{} ALERTS", self.alert_count).format(self.alert_count) text_size = measure_text_cached(font, alert_text, HEAD_BUTTON_FONT_SIZE) text_x = self.alert_notif_rect.x + (self.alert_notif_rect.width - text_size.x) // 2 text_y = self.alert_notif_rect.y + (self.alert_notif_rect.height - text_size.y) // 2 diff --git a/selfdrive/ui/layouts/onboarding.py b/selfdrive/ui/layouts/onboarding.py index a817fc53ad..df259a8fb5 100644 --- a/selfdrive/ui/layouts/onboarding.py +++ b/selfdrive/ui/layouts/onboarding.py @@ -6,6 +6,7 @@ from enum import IntEnum import pyray as rl from openpilot.common.basedir import BASEDIR from openpilot.system.ui.lib.application import FontWeight, gui_app +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.label import Label @@ -107,12 +108,12 @@ class TermsPage(Widget): self._on_accept = on_accept self._on_decline = on_decline - self._title = Label("Welcome to openpilot", font_size=90, font_weight=FontWeight.BOLD, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) - self._desc = Label("You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing.", + self._title = Label(tr("Welcome to openpilot"), font_size=90, font_weight=FontWeight.BOLD, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) + self._desc = Label(tr("You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing."), font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) - self._decline_btn = Button("Decline", click_callback=on_decline) - self._accept_btn = Button("Agree", button_style=ButtonStyle.PRIMARY, click_callback=on_accept) + self._decline_btn = Button(tr("Decline"), click_callback=on_decline) + self._accept_btn = Button(tr("Agree"), button_style=ButtonStyle.PRIMARY, click_callback=on_accept) def _render(self, _): welcome_x = self._rect.x + 165 @@ -141,10 +142,10 @@ class TermsPage(Widget): class DeclinePage(Widget): def __init__(self, back_callback=None): super().__init__() - self._text = Label("You must accept the Terms and Conditions in order to use openpilot.", + self._text = Label(tr("You must accept the Terms and Conditions in order to use openpilot."), font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) - self._back_btn = Button("Back", click_callback=back_callback) - self._uninstall_btn = Button("Decline, uninstall openpilot", button_style=ButtonStyle.DANGER, + self._back_btn = Button(tr("Back"), click_callback=back_callback) + self._uninstall_btn = Button(tr("Decline, uninstall openpilot"), button_style=ButtonStyle.DANGER, click_callback=self._on_uninstall_clicked) def _on_uninstall_clicked(self): diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index 21eb44efe7..91268960cb 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -6,19 +6,20 @@ from openpilot.system.ui.widgets.list_view import toggle_item from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import DialogResult # Description constants DESCRIPTIONS = { - 'enable_adb': ( + 'enable_adb': tr( "ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. " + "See https://docs.comma.ai/how-to/connect-to-comma for more info." ), - 'ssh_key': ( + 'ssh_key': tr( "Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username " + "other than your own. A comma employee will NEVER ask you to add their GitHub username." ), - 'alpha_longitudinal': ( + 'alpha_longitudinal': tr( "WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).

" + "On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. " + "Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha." @@ -34,7 +35,7 @@ class DeveloperLayout(Widget): # Build items and keep references for callbacks/state updates self._adb_toggle = toggle_item( - "Enable ADB", + tr("Enable ADB"), description=DESCRIPTIONS["enable_adb"], initial_state=self._params.get_bool("AdbEnabled"), callback=self._on_enable_adb, @@ -43,7 +44,7 @@ class DeveloperLayout(Widget): # SSH enable toggle + SSH key management self._ssh_toggle = toggle_item( - "Enable SSH", + tr("Enable SSH"), description="", initial_state=self._params.get_bool("SshEnabled"), callback=self._on_enable_ssh, @@ -51,7 +52,7 @@ class DeveloperLayout(Widget): self._ssh_keys = ssh_key_item("SSH Keys", description=DESCRIPTIONS["ssh_key"]) self._joystick_toggle = toggle_item( - "Joystick Debug Mode", + tr("Joystick Debug Mode"), description="", initial_state=self._params.get_bool("JoystickDebugMode"), callback=self._on_joystick_debug_mode, @@ -59,14 +60,14 @@ class DeveloperLayout(Widget): ) self._long_maneuver_toggle = toggle_item( - "Longitudinal Maneuver Mode", + tr("Longitudinal Maneuver Mode"), description="", initial_state=self._params.get_bool("LongitudinalManeuverMode"), callback=self._on_long_maneuver_mode, ) self._alpha_long_toggle = toggle_item( - "openpilot Longitudinal Control (Alpha)", + tr("openpilot Longitudinal Control (Alpha)"), description=DESCRIPTIONS["alpha_longitudinal"], initial_state=self._params.get_bool("AlphaLongitudinalEnabled"), callback=self._on_alpha_long_enabled, @@ -163,7 +164,7 @@ class DeveloperLayout(Widget): content = (f"

{self._alpha_long_toggle.title}


" + f"

{self._alpha_long_toggle.description}

") - dlg = ConfirmDialog(content, "Enable", rich=True) + dlg = ConfirmDialog(content, tr("Enable"), rich=True) gui_app.set_modal_overlay(dlg, callback=confirm_callback) else: diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index 66341db37b..e9bdf59e26 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -12,6 +12,7 @@ from openpilot.selfdrive.ui.layouts.onboarding import TrainingGuide from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog from openpilot.system.hardware import TICI from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog, alert_dialog from openpilot.system.ui.widgets.html_render import HtmlModal @@ -21,10 +22,10 @@ from openpilot.system.ui.widgets.scroller import Scroller # Description constants DESCRIPTIONS = { - 'pair_device': "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.", - 'driver_camera': "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)", - 'reset_calibration': "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down.", - 'review_guide': "Review the rules, features, and limitations of openpilot", + '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"), } @@ -45,27 +46,27 @@ class DeviceLayout(Widget): ui_state.add_offroad_transition_callback(self._offroad_transition) def _initialize_items(self): - dongle_id = self._params.get("DongleId") or "N/A" - serial = self._params.get("HardwareSerial") or "N/A" + dongle_id = self._params.get("DongleId") or tr("N/A") + serial = self._params.get("HardwareSerial") or tr("N/A") - self._pair_device_btn = button_item("Pair Device", "PAIR", DESCRIPTIONS['pair_device'], callback=self._pair_device) + self._pair_device_btn = button_item(tr("Pair Device"), tr("PAIR"), DESCRIPTIONS['pair_device'], callback=self._pair_device) self._pair_device_btn.set_visible(lambda: not ui_state.prime_state.is_paired()) - self._reset_calib_btn = button_item("Reset Calibration", "RESET", DESCRIPTIONS['reset_calibration'], callback=self._reset_calibration_prompt) + self._reset_calib_btn = button_item(tr("Reset Calibration"), tr("RESET"), DESCRIPTIONS['reset_calibration'], callback=self._reset_calibration_prompt) self._reset_calib_btn.set_description_opened_callback(self._update_calib_description) - self._power_off_btn = dual_button_item("Reboot", "Power Off", left_callback=self._reboot_prompt, right_callback=self._power_off_prompt) + self._power_off_btn = dual_button_item(tr("Reboot"), tr("Power Off"), left_callback=self._reboot_prompt, right_callback=self._power_off_prompt) items = [ - text_item("Dongle ID", dongle_id), - text_item("Serial", serial), + text_item(tr("Dongle ID"), dongle_id), + text_item(tr("Serial"), serial), self._pair_device_btn, - button_item("Driver Camera", "PREVIEW", DESCRIPTIONS['driver_camera'], callback=self._show_driver_camera, enabled=ui_state.is_offroad), + button_item(tr("Driver Camera"), tr("PREVIEW"), DESCRIPTIONS['driver_camera'], callback=self._show_driver_camera, enabled=ui_state.is_offroad), self._reset_calib_btn, - button_item("Review Training Guide", "REVIEW", DESCRIPTIONS['review_guide'], self._on_review_training_guide, enabled=ui_state.is_offroad), - regulatory_btn := button_item("Regulatory", "VIEW", callback=self._on_regulatory, enabled=ui_state.is_offroad), + button_item(tr("Review Training Guide"), tr("REVIEW"), DESCRIPTIONS['review_guide'], self._on_review_training_guide, enabled=ui_state.is_offroad), + regulatory_btn := button_item(tr("Regulatory"), tr("VIEW"), callback=self._on_regulatory, enabled=ui_state.is_offroad), # TODO: implement multilang - # button_item("Change Language", "CHANGE", callback=self._show_language_selection, enabled=ui_state.is_offroad), + # button_item(tr("Change Language"), tr("CHANGE"), callback=self._show_language_selection, enabled=ui_state.is_offroad), self._power_off_btn, ] regulatory_btn.set_visible(TICI) @@ -106,7 +107,7 @@ class DeviceLayout(Widget): def _reset_calibration_prompt(self): if ui_state.engaged: - gui_app.set_modal_overlay(alert_dialog("Disengage to Reset Calibration")) + gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Reset Calibration"))) return def reset_calibration(result: int): @@ -122,7 +123,7 @@ class DeviceLayout(Widget): self._params.put_bool("OnroadCycleRequested", True) self._update_calib_description() - dialog = ConfirmDialog("Are you sure you want to reset calibration?", "Reset") + dialog = ConfirmDialog(tr("Are you sure you want to reset calibration?"), tr("Reset")) gui_app.set_modal_overlay(dialog, callback=reset_calibration) def _update_calib_description(self): @@ -136,7 +137,8 @@ class DeviceLayout(Widget): if calib.calStatus != log.LiveCalibrationData.Status.uncalibrated: pitch = math.degrees(calib.rpyCalib[1]) yaw = math.degrees(calib.rpyCalib[2]) - desc += f" Your device is pointed {abs(pitch):.1f}° {'down' if pitch > 0 else 'up'} and {abs(yaw):.1f}° {'left' if yaw > 0 else 'right'}." + desc += tr(" Your device is pointed {:.1f}° {} and {:.1f}° {}.").format(abs(pitch), tr("down") if pitch > 0 else tr("up"), + abs(yaw), tr("left") if yaw > 0 else tr("right")) except Exception: cloudlog.exception("invalid CalibrationParams") @@ -148,9 +150,9 @@ class DeviceLayout(Widget): except Exception: cloudlog.exception("invalid LiveDelay") if lag_perc < 100: - desc += f"

Steering lag calibration is {lag_perc}% complete." + desc += tr("

Steering lag calibration is {}% complete.").format(lag_perc) else: - desc += "

Steering lag calibration is complete." + desc += tr("

Steering lag calibration is complete.") torque_bytes = self._params.get("LiveTorqueParameters") if torque_bytes: @@ -160,24 +162,24 @@ class DeviceLayout(Widget): if torque.useParams: torque_perc = torque.calPerc if torque_perc < 100: - desc += f" Steering torque response calibration is {torque_perc}% complete." + desc += tr(" Steering torque response calibration is {}% complete.").format(torque_perc) else: - desc += " Steering torque response calibration is complete." + desc += tr(" Steering torque response calibration is complete.") except Exception: cloudlog.exception("invalid LiveTorqueParameters") desc += "

" - desc += ("openpilot is continuously calibrating, resetting is rarely required. " + - "Resetting calibration will restart openpilot if the car is powered on.") + desc += tr("openpilot is continuously calibrating, resetting is rarely required. " + + "Resetting calibration will restart openpilot if the car is powered on.") self._reset_calib_btn.set_description(desc) def _reboot_prompt(self): if ui_state.engaged: - gui_app.set_modal_overlay(alert_dialog("Disengage to Reboot")) + gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Reboot"))) return - dialog = ConfirmDialog("Are you sure you want to reboot?", "Reboot") + dialog = ConfirmDialog(tr("Are you sure you want to reboot?"), tr("Reboot")) gui_app.set_modal_overlay(dialog, callback=self._perform_reboot) def _perform_reboot(self, result: int): @@ -186,10 +188,10 @@ class DeviceLayout(Widget): def _power_off_prompt(self): if ui_state.engaged: - gui_app.set_modal_overlay(alert_dialog("Disengage to Power Off")) + gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Power Off"))) return - dialog = ConfirmDialog("Are you sure you want to power off?", "Power Off") + dialog = ConfirmDialog(tr("Are you sure you want to power off?"), tr("Power Off")) gui_app.set_modal_overlay(dialog, callback=self._perform_power_off) def _perform_power_off(self, result: int): @@ -210,5 +212,6 @@ class DeviceLayout(Widget): if not self._training_guide: def completed_callback(): gui_app.set_modal_overlay(None) + self._training_guide = TrainingGuide(completed_callback=completed_callback) gui_app.set_modal_overlay(self._training_guide) diff --git a/selfdrive/ui/layouts/settings/firehose.py b/selfdrive/ui/layouts/settings/firehose.py index e8eaaa44f2..f4d70eaa47 100644 --- a/selfdrive/ui/layouts/settings/firehose.py +++ b/selfdrive/ui/layouts/settings/firehose.py @@ -8,19 +8,20 @@ from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE +from openpilot.system.ui.lib.multilang import tr, trn from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget from openpilot.selfdrive.ui.lib.api_helpers import get_token -TITLE = "Firehose Mode" -DESCRIPTION = ( +TITLE = tr("Firehose Mode") +DESCRIPTION = tr( "openpilot learns to drive by watching humans, like you, drive.\n\n" + "Firehose Mode allows you to maximize your training data uploads to improve " + "openpilot's driving models. More data means bigger models, which means better Experimental Mode." ) -INSTRUCTIONS = ( +INSTRUCTIONS = tr( "For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.\n\n" + "Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.\n\n\n" + "Frequently Asked Questions\n\n" @@ -106,7 +107,8 @@ class FirehoseLayout(Widget): # Contribution count (if available) if self.segment_count > 0: - contrib_text = f"{self.segment_count} segment(s) of your driving is in the training dataset so far." + contrib_text = trn("{} segment of your driving is in the training dataset so far.", + "{} segments of your driving is in the training dataset so far.", self.segment_count).format(self.segment_count) y = self._draw_wrapped_text(x, y, w, contrib_text, gui_app.font(FontWeight.BOLD), 52, rl.WHITE) y += 20 + 20 @@ -132,9 +134,9 @@ class FirehoseLayout(Widget): network_metered = ui_state.sm["deviceState"].networkMetered if not network_metered and network_type != 0: # Not metered and connected - return "ACTIVE", self.GREEN + return tr("ACTIVE"), self.GREEN else: - return "INACTIVE: connect to an unmetered network", self.RED + return tr("INACTIVE: connect to an unmetered network"), self.RED def _fetch_firehose_stats(self): try: diff --git a/selfdrive/ui/layouts/settings/settings.py b/selfdrive/ui/layouts/settings/settings.py index d43382f199..7f431d3a77 100644 --- a/selfdrive/ui/layouts/settings/settings.py +++ b/selfdrive/ui/layouts/settings/settings.py @@ -8,6 +8,7 @@ from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wifi_manager import WifiManager from openpilot.system.ui.widgets import Widget @@ -58,12 +59,12 @@ class SettingsLayout(Widget): wifi_manager.set_active(False) self._panels = { - PanelType.DEVICE: PanelInfo("Device", DeviceLayout()), - PanelType.NETWORK: PanelInfo("Network", NetworkUI(wifi_manager)), - PanelType.TOGGLES: PanelInfo("Toggles", TogglesLayout()), - PanelType.SOFTWARE: PanelInfo("Software", SoftwareLayout()), - PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayout()), - PanelType.DEVELOPER: PanelInfo("Developer", DeveloperLayout()), + PanelType.DEVICE: PanelInfo(tr("Device"), DeviceLayout()), + PanelType.NETWORK: PanelInfo(tr("Network"), NetworkUI(wifi_manager)), + PanelType.TOGGLES: PanelInfo(tr("Toggles"), TogglesLayout()), + PanelType.SOFTWARE: PanelInfo(tr("Software"), SoftwareLayout()), + PanelType.FIREHOSE: PanelInfo(tr("Firehose"), FirehoseLayout()), + PanelType.DEVELOPER: PanelInfo(tr("Developer"), DeveloperLayout()), } self._font_medium = gui_app.font(FontWeight.MEDIUM) diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index 0c17a54fbe..8e0cfdbc3c 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -4,6 +4,7 @@ import datetime from openpilot.common.time_helpers import system_time_valid from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.multilang import tr, trn from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.widgets.list_view import button_item, text_item, ListItem @@ -15,7 +16,7 @@ UPDATED_TIMEOUT = 10 # seconds to wait for updated to respond def time_ago(date: datetime.datetime | None) -> str: if not date: - return "never" + return tr("never") if not system_time_valid(): return date.strftime("%a %b %d %Y") @@ -26,16 +27,16 @@ def time_ago(date: datetime.datetime | None) -> str: diff_seconds = int((now - date).total_seconds()) if diff_seconds < 60: - return "now" + return tr("now") if diff_seconds < 3600: m = diff_seconds // 60 - return f"{m} minute{'s' if m != 1 else ''} ago" + return trn("{} minute ago", "{} minutes ago", m).format(m) if diff_seconds < 86400: h = diff_seconds // 3600 - return f"{h} hour{'s' if h != 1 else ''} ago" + return trn("{} hour ago", "{} hours ago", h).format(h) if diff_seconds < 604800: d = diff_seconds // 86400 - return f"{d} day{'s' if d != 1 else ''} ago" + return trn("{} day ago", "{} days ago", d).format(d) return date.strftime("%a %b %d %Y") @@ -43,12 +44,12 @@ class SoftwareLayout(Widget): def __init__(self): super().__init__() - self._onroad_label = ListItem(title="Updates are only downloaded while the car is off.") - self._version_item = text_item("Current Version", ui_state.params.get("UpdaterCurrentDescription") or "") - self._download_btn = button_item("Download", "CHECK", callback=self._on_download_update) + self._onroad_label = ListItem(title=tr("Updates are only downloaded while the car is off.")) + self._version_item = text_item(tr("Current Version"), ui_state.params.get("UpdaterCurrentDescription") or "") + self._download_btn = button_item(tr("Download"), tr("CHECK"), callback=self._on_download_update) # Install button is initially hidden - self._install_btn = button_item("Install Update", "INSTALL", callback=self._on_install_update) + self._install_btn = button_item(tr("Install Update"), tr("INSTALL"), callback=self._on_install_update) self._install_btn.set_visible(False) # Track waiting-for-updater transition to avoid brief re-enable while still idle @@ -66,7 +67,7 @@ class SoftwareLayout(Widget): self._install_btn, # TODO: implement branch switching # button_item("Target Branch", "SELECT", callback=self._on_select_branch), - button_item("Uninstall", "UNINSTALL", callback=self._on_uninstall), + button_item("Uninstall", tr("UNINSTALL"), callback=self._on_uninstall), ] return items @@ -101,19 +102,19 @@ class SoftwareLayout(Widget): self._download_btn.action_item.set_value(updater_state) else: if failed_count > 0: - self._download_btn.action_item.set_value("failed to check for update") - self._download_btn.action_item.set_text("CHECK") + self._download_btn.action_item.set_value(tr("failed to check for update")) + self._download_btn.action_item.set_text(tr("CHECK")) elif fetch_available: - self._download_btn.action_item.set_value("update available") - self._download_btn.action_item.set_text("DOWNLOAD") + self._download_btn.action_item.set_value(tr("update available")) + self._download_btn.action_item.set_text(tr("DOWNLOAD")) else: last_update = ui_state.params.get("LastUpdateTime") if last_update: formatted = time_ago(last_update) - self._download_btn.action_item.set_value(f"up to date, last checked {formatted}") + self._download_btn.action_item.set_value(tr("up to date, last checked {}").format(formatted)) else: - self._download_btn.action_item.set_value("up to date, last checked never") - self._download_btn.action_item.set_text("CHECK") + self._download_btn.action_item.set_value(tr("up to date, last checked never")) + self._download_btn.action_item.set_text(tr("CHECK")) # If we've been waiting too long without a state change, reset state if self._waiting_for_updater and (time.monotonic() - self._waiting_start_ts > UPDATED_TIMEOUT): @@ -127,7 +128,7 @@ class SoftwareLayout(Widget): if update_available: new_desc = ui_state.params.get("UpdaterNewDescription") or "" new_release_notes = (ui_state.params.get("UpdaterNewReleaseNotes") or b"").decode("utf-8", "replace") - self._install_btn.action_item.set_text("INSTALL") + self._install_btn.action_item.set_text(tr("INSTALL")) self._install_btn.action_item.set_value(new_desc) self._install_btn.set_description(new_release_notes) # Enable install button for testing (like Qt showEvent) @@ -138,7 +139,7 @@ class SoftwareLayout(Widget): def _on_download_update(self): # Check if we should start checking or start downloading self._download_btn.action_item.set_enabled(False) - if self._download_btn.action_item.text == "CHECK": + if self._download_btn.action_item.text == tr("CHECK"): # Start checking for updates self._waiting_for_updater = True self._waiting_start_ts = time.monotonic() @@ -154,7 +155,7 @@ class SoftwareLayout(Widget): if result == DialogResult.CONFIRM: ui_state.params.put_bool("DoUninstall", True) - dialog = ConfirmDialog("Are you sure you want to uninstall?", "Uninstall") + dialog = ConfirmDialog(tr("Are you sure you want to uninstall?"), tr("Uninstall")) gui_app.set_modal_overlay(dialog, callback=handle_uninstall_confirmation) def _on_install_update(self): diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index 01195142e3..1e4d5c6c85 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -5,6 +5,7 @@ from openpilot.system.ui.widgets.list_view import multiple_button_item, toggle_i from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import DialogResult from openpilot.selfdrive.ui.ui_state import ui_state @@ -12,24 +13,24 @@ PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants # Description constants DESCRIPTIONS = { - "OpenpilotEnabledToggle": ( + "OpenpilotEnabledToggle": tr( "Use the openpilot system for adaptive cruise control and lane keep driver assistance. " + "Your attention is required at all times to use this feature." ), - "DisengageOnAccelerator": "When enabled, pressing the accelerator pedal will disengage openpilot.", - "LongitudinalPersonality": ( + "DisengageOnAccelerator": tr("When enabled, pressing the accelerator pedal will disengage openpilot."), + "LongitudinalPersonality": tr( "Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. " + "In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " + "your steering wheel distance button." ), - "IsLdwEnabled": ( + "IsLdwEnabled": tr( "Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line " + "without a turn signal activated while driving over 31 mph (50 km/h)." ), - "AlwaysOnDM": "Enable driver monitoring even when openpilot is not engaged.", - 'RecordFront': "Upload data from the driver facing camera and help improve the driver monitoring algorithm.", - "IsMetric": "Display speed in km/h instead of mph.", - "RecordAudio": "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect.", + "AlwaysOnDM": tr("Enable driver monitoring even when openpilot is not engaged."), + 'RecordFront': tr("Upload data from the driver facing camera and help improve the driver monitoring algorithm."), + "IsMetric": tr("Display speed in km/h instead of mph."), + "RecordAudio": tr("Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect."), } @@ -42,49 +43,49 @@ class TogglesLayout(Widget): # param, title, desc, icon, needs_restart self._toggle_defs = { "OpenpilotEnabledToggle": ( - "Enable openpilot", + tr("Enable openpilot"), DESCRIPTIONS["OpenpilotEnabledToggle"], "chffr_wheel.png", True, ), "ExperimentalMode": ( - "Experimental Mode", + tr("Experimental Mode"), "", "experimental_white.png", False, ), "DisengageOnAccelerator": ( - "Disengage on Accelerator Pedal", + tr("Disengage on Accelerator Pedal"), DESCRIPTIONS["DisengageOnAccelerator"], "disengage_on_accelerator.png", False, ), "IsLdwEnabled": ( - "Enable Lane Departure Warnings", + tr("Enable Lane Departure Warnings"), DESCRIPTIONS["IsLdwEnabled"], "warning.png", False, ), "AlwaysOnDM": ( - "Always-On Driver Monitoring", + tr("Always-On Driver Monitoring"), DESCRIPTIONS["AlwaysOnDM"], "monitoring.png", False, ), "RecordFront": ( - "Record and Upload Driver Camera", + tr("Record and Upload Driver Camera"), DESCRIPTIONS["RecordFront"], "monitoring.png", True, ), "RecordAudio": ( - "Record and Upload Microphone Audio", + tr("Record and Upload Microphone Audio"), DESCRIPTIONS["RecordAudio"], "microphone.png", True, ), "IsMetric": ( - "Use Metric System", + tr("Use Metric System"), DESCRIPTIONS["IsMetric"], "metric.png", False, @@ -92,9 +93,9 @@ class TogglesLayout(Widget): } self._long_personality_setting = multiple_button_item( - "Driving Personality", + tr("Driving Personality"), DESCRIPTIONS["LongitudinalPersonality"], - buttons=["Aggressive", "Standard", "Relaxed"], + buttons=[tr("Aggressive"), tr("Standard"), tr("Relaxed")], button_width=255, callback=self._set_longitudinal_personality, selected_index=self._params.get("LongitudinalPersonality", return_default=True), @@ -119,7 +120,7 @@ class TogglesLayout(Widget): toggle.action_item.set_enabled(not locked) if needs_restart and not locked: - toggle.set_description(toggle.description + " Changing this setting will restart openpilot if the car is powered on.") + toggle.set_description(toggle.description + tr(" Changing this setting will restart openpilot if the car is powered on.")) # track for engaged state updates if locked: @@ -150,7 +151,7 @@ class TogglesLayout(Widget): def _update_toggles(self): ui_state.update_params() - e2e_description = ( + e2e_description = tr( "openpilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. " + "Experimental features are listed below:
" + "

End-to-End Longitudinal Control


" + @@ -174,15 +175,15 @@ class TogglesLayout(Widget): self._long_personality_setting.action_item.set_enabled(False) self._params.remove("ExperimentalMode") - unavailable = "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." + unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.") - long_desc = unavailable + " openpilot longitudinal control may come in a future update." + long_desc = unavailable + " " + tr("openpilot longitudinal control may come in a future update.") if ui_state.CP.alphaLongitudinalAvailable: if self._is_release: - long_desc = unavailable + " " + ("An alpha version of openpilot longitudinal control can be tested, along with " + - "Experimental mode, on non-release branches.") + long_desc = unavailable + " " + tr("An alpha version of openpilot longitudinal control can be tested, along with " + + "Experimental mode, on non-release branches.") else: - long_desc = "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." + long_desc = tr("Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode.") self._toggles["ExperimentalMode"].set_description("" + long_desc + "

" + e2e_description) else: @@ -221,7 +222,7 @@ class TogglesLayout(Widget): # show confirmation dialog content = (f"

{self._toggles['ExperimentalMode'].title}


" + f"

{self._toggles['ExperimentalMode'].description}

") - dlg = ConfirmDialog(content, "Enable", rich=True) + dlg = ConfirmDialog(content, tr("Enable"), rich=True) gui_app.set_modal_overlay(dlg, callback=confirm_callback) else: self._update_experimental_mode_icon() diff --git a/selfdrive/ui/layouts/sidebar.py b/selfdrive/ui/layouts/sidebar.py index 9337b3c239..48b577ea59 100644 --- a/selfdrive/ui/layouts/sidebar.py +++ b/selfdrive/ui/layouts/sidebar.py @@ -5,6 +5,7 @@ from collections.abc import Callable from cereal import log from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, FONT_SCALE +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import Widget @@ -39,13 +40,13 @@ class Colors: NETWORK_TYPES = { - NetworkType.none: "--", - NetworkType.wifi: "Wi-Fi", - NetworkType.ethernet: "ETH", - NetworkType.cell2G: "2G", - NetworkType.cell3G: "3G", - NetworkType.cell4G: "LTE", - NetworkType.cell5G: "5G", + NetworkType.none: tr("--"), + NetworkType.wifi: tr("Wi-Fi"), + NetworkType.ethernet: tr("ETH"), + NetworkType.cell2G: tr("2G"), + NetworkType.cell3G: tr("3G"), + NetworkType.cell4G: tr("LTE"), + NetworkType.cell5G: tr("5G"), } @@ -67,9 +68,9 @@ class Sidebar(Widget): self._net_type = NETWORK_TYPES.get(NetworkType.none) self._net_strength = 0 - self._temp_status = MetricData("TEMP", "GOOD", Colors.GOOD) - self._panda_status = MetricData("VEHICLE", "ONLINE", Colors.GOOD) - self._connect_status = MetricData("CONNECT", "OFFLINE", Colors.WARNING) + self._temp_status = MetricData(tr("TEMP"), tr("GOOD"), Colors.GOOD) + self._panda_status = MetricData(tr("VEHICLE"), tr("ONLINE"), Colors.GOOD) + self._connect_status = MetricData(tr("CONNECT"), tr("OFFLINE"), Colors.WARNING) self._recording_audio = False self._home_img = gui_app.texture("images/button_home.png", HOME_BTN.width, HOME_BTN.height) @@ -113,7 +114,7 @@ class Sidebar(Widget): self._update_panda_status() def _update_network_status(self, device_state): - self._net_type = NETWORK_TYPES.get(device_state.networkType.raw, "Unknown") + self._net_type = NETWORK_TYPES.get(device_state.networkType.raw, tr("Unknown")) strength = device_state.networkStrength self._net_strength = max(0, min(5, strength.raw + 1)) if strength > 0 else 0 @@ -121,26 +122,26 @@ class Sidebar(Widget): thermal_status = device_state.thermalStatus if thermal_status == ThermalStatus.green: - self._temp_status.update("TEMP", "GOOD", Colors.GOOD) + self._temp_status.update(tr("TEMP"), tr("GOOD"), Colors.GOOD) elif thermal_status == ThermalStatus.yellow: - self._temp_status.update("TEMP", "OK", Colors.WARNING) + self._temp_status.update(tr("TEMP"), tr("OK"), Colors.WARNING) else: - self._temp_status.update("TEMP", "HIGH", Colors.DANGER) + self._temp_status.update(tr("TEMP"), tr("HIGH"), Colors.DANGER) def _update_connection_status(self, device_state): last_ping = device_state.lastAthenaPingTime if last_ping == 0: - self._connect_status.update("CONNECT", "OFFLINE", Colors.WARNING) + self._connect_status.update(tr("CONNECT"), tr("OFFLINE"), Colors.WARNING) elif time.monotonic_ns() - last_ping < 80_000_000_000: # 80 seconds in nanoseconds - self._connect_status.update("CONNECT", "ONLINE", Colors.GOOD) + self._connect_status.update(tr("CONNECT"), tr("ONLINE"), Colors.GOOD) else: - self._connect_status.update("CONNECT", "ERROR", Colors.DANGER) + self._connect_status.update(tr("CONNECT"), tr("ERROR"), Colors.DANGER) def _update_panda_status(self): if ui_state.panda_type == log.PandaState.PandaType.unknown: - self._panda_status.update("NO", "PANDA", Colors.DANGER) + self._panda_status.update(tr("NO"), tr("PANDA"), Colors.DANGER) else: - self._panda_status.update("VEHICLE", "ONLINE", Colors.GOOD) + self._panda_status.update(tr("VEHICLE"), tr("ONLINE"), Colors.GOOD) def _handle_mouse_release(self, mouse_pos: MousePos): if rl.check_collision_point_rec(mouse_pos, SETTINGS_BTN): diff --git a/selfdrive/ui/onroad/alert_renderer.py b/selfdrive/ui/onroad/alert_renderer.py index 0f944bac52..a81fbfc440 100644 --- a/selfdrive/ui/onroad/alert_renderer.py +++ b/selfdrive/ui/onroad/alert_renderer.py @@ -5,6 +5,7 @@ from cereal import messaging, log from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.hardware import TICI from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.label import Label @@ -47,22 +48,22 @@ class Alert: # Pre-defined alert instances ALERT_STARTUP_PENDING = Alert( - text1="openpilot Unavailable", - text2="Waiting to start", + text1=tr("openpilot Unavailable"), + text2=tr("Waiting to start"), size=AlertSize.mid, status=AlertStatus.normal, ) ALERT_CRITICAL_TIMEOUT = Alert( - text1="TAKE CONTROL IMMEDIATELY", - text2="System Unresponsive", + text1=tr("TAKE CONTROL IMMEDIATELY"), + text2=tr("System Unresponsive"), size=AlertSize.full, status=AlertStatus.critical, ) ALERT_CRITICAL_REBOOT = Alert( - text1="System Unresponsive", - text2="Reboot Device", + text1=tr("System Unresponsive"), + text2=tr("Reboot Device"), size=AlertSize.mid, status=AlertStatus.normal, ) diff --git a/selfdrive/ui/onroad/driver_camera_dialog.py b/selfdrive/ui/onroad/driver_camera_dialog.py index a3bd2e23e0..543ea35e81 100644 --- a/selfdrive/ui/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/onroad/driver_camera_dialog.py @@ -5,6 +5,7 @@ from openpilot.selfdrive.ui.onroad.cameraview import CameraView from openpilot.selfdrive.ui.onroad.driver_state import DriverStateRenderer from openpilot.selfdrive.ui.ui_state import ui_state, device from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets.label import gui_label @@ -30,7 +31,7 @@ class DriverCameraDialog(CameraView): if not self.frame: gui_label( rect, - "camera starting", + tr("camera starting"), font_size=100, font_weight=FontWeight.BOLD, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, diff --git a/selfdrive/ui/onroad/hud_renderer.py b/selfdrive/ui/onroad/hud_renderer.py index 98c3d686fa..a2459c27e2 100644 --- a/selfdrive/ui/onroad/hud_renderer.py +++ b/selfdrive/ui/onroad/hud_renderer.py @@ -4,6 +4,7 @@ from openpilot.common.constants import CV from openpilot.selfdrive.ui.onroad.exp_button import ExpButton from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import Widget @@ -144,7 +145,7 @@ class HudRenderer(Widget): elif ui_state.status == UIStatus.OVERRIDE: max_color = COLORS.override - max_text = "MAX" + max_text = tr("MAX") max_text_width = measure_text_cached(self._font_semi_bold, max_text, FONT_SIZES.max_speed).x rl.draw_text_ex( self._font_semi_bold, @@ -173,7 +174,7 @@ class HudRenderer(Widget): speed_pos = rl.Vector2(rect.x + rect.width / 2 - speed_text_size.x / 2, 180 - speed_text_size.y / 2) rl.draw_text_ex(self._font_bold, speed_text, speed_pos, FONT_SIZES.current_speed, 0, COLORS.white) - unit_text = "km/h" if ui_state.is_metric else "mph" + unit_text = tr("km/h") if ui_state.is_metric else tr("mph") unit_text_size = measure_text_cached(self._font_medium, unit_text, FONT_SIZES.speed_unit) unit_pos = rl.Vector2(rect.x + rect.width / 2 - unit_text_size.x / 2, 290 - unit_text_size.y / 2) rl.draw_text_ex(self._font_medium, unit_text, unit_pos, FONT_SIZES.speed_unit, 0, COLORS.white_translucent) diff --git a/selfdrive/ui/update_translations.py b/selfdrive/ui/update_translations.py index 424b78851c..643d246012 100755 --- a/selfdrive/ui/update_translations.py +++ b/selfdrive/ui/update_translations.py @@ -4,10 +4,8 @@ import json import os from openpilot.common.basedir import BASEDIR +from openpilot.system.ui.lib.multilang import UI_DIR, TRANSLATIONS_DIR, LANGUAGES_FILE -UI_DIR = os.path.join(BASEDIR, "selfdrive", "ui") -TRANSLATIONS_DIR = os.path.join(UI_DIR, "translations") -LANGUAGES_FILE = os.path.join(TRANSLATIONS_DIR, "languages.json") TRANSLATIONS_INCLUDE_FILE = os.path.join(TRANSLATIONS_DIR, "alerts_generated.h") PLURAL_ONLY = ["en"] # base language, only create entries for strings with plural forms diff --git a/selfdrive/ui/widgets/exp_mode_button.py b/selfdrive/ui/widgets/exp_mode_button.py index 23031be306..faa3bf877f 100644 --- a/selfdrive/ui/widgets/exp_mode_button.py +++ b/selfdrive/ui/widgets/exp_mode_button.py @@ -1,6 +1,7 @@ import pyray as rl from openpilot.common.params import Params from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget @@ -46,7 +47,7 @@ class ExperimentalModeButton(Widget): rl.draw_line_ex(rl.Vector2(line_x, rect.y), rl.Vector2(line_x, rect.y + rect.height), 3, separator_color) # Draw text label (left aligned) - text = "EXPERIMENTAL MODE ON" if self.experimental_mode else "CHILL MODE ON" + text = tr("EXPERIMENTAL MODE ON") if self.experimental_mode else tr("CHILL MODE ON") text_x = rect.x + self.horizontal_padding text_y = rect.y + rect.height / 2 - 45 * FONT_SCALE // 2 # Center vertically diff --git a/selfdrive/ui/widgets/offroad_alerts.py b/selfdrive/ui/widgets/offroad_alerts.py index 0b3ca8176f..0bd5b161b9 100644 --- a/selfdrive/ui/widgets/offroad_alerts.py +++ b/selfdrive/ui/widgets/offroad_alerts.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from openpilot.common.params import Params from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wrap_text import wrap_text @@ -14,7 +15,7 @@ from openpilot.system.ui.widgets.html_render import HtmlRenderer from openpilot.selfdrive.selfdrived.alertmanager import OFFROAD_ALERTS -NO_RELEASE_NOTES = "

No release notes available.

" +NO_RELEASE_NOTES = tr("

No release notes available.

") class AlertColors: @@ -101,15 +102,15 @@ class AbstractAlert(Widget, ABC): if self.dismiss_callback: self.dismiss_callback() - self.dismiss_btn = ActionButton("Close") + self.dismiss_btn = ActionButton(tr("Close")) - self.snooze_btn = ActionButton("Snooze Update", style=ButtonStyle.DARK) + self.snooze_btn = ActionButton(tr("Snooze Update"), style=ButtonStyle.DARK) self.snooze_btn.set_click_callback(snooze_callback) - self.excessive_actuation_btn = ActionButton("Acknowledge Excessive Actuation", style=ButtonStyle.DARK, min_width=800) + self.excessive_actuation_btn = ActionButton(tr("Acknowledge Excessive Actuation"), style=ButtonStyle.DARK, min_width=800) self.excessive_actuation_btn.set_click_callback(excessive_actuation_callback) - self.reboot_btn = ActionButton("Reboot and Update", min_width=600) + self.reboot_btn = ActionButton(tr("Reboot and Update"), min_width=600) self.reboot_btn.set_click_callback(lambda: HARDWARE.reboot()) # TODO: just use a Scroller? diff --git a/selfdrive/ui/widgets/pairing_dialog.py b/selfdrive/ui/widgets/pairing_dialog.py index 3778586f89..85b42d1a7a 100644 --- a/selfdrive/ui/widgets/pairing_dialog.py +++ b/selfdrive/ui/widgets/pairing_dialog.py @@ -8,6 +8,7 @@ from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params from openpilot.system.ui.widgets import Widget from openpilot.system.ui.lib.application import FontWeight, gui_app +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.selfdrive.ui.ui_state import ui_state @@ -99,7 +100,7 @@ class PairingDialog(Widget): y += close_size + 40 # Title - title = "Pair your device to your comma account" + title = tr("Pair your device to your comma account") title_font = gui_app.font(FontWeight.NORMAL) left_width = int(content_rect.width * 0.5 - 15) @@ -124,9 +125,9 @@ class PairingDialog(Widget): def _render_instructions(self, rect: rl.Rectangle) -> None: instructions = [ - "Go to https://connect.comma.ai on your phone", - "Click \"add new device\" and scan the QR code on the right", - "Bookmark connect.comma.ai to your home screen to use it like an app", + tr("Go to https://connect.comma.ai on your phone"), + tr("Click \"add new device\" and scan the QR code on the right"), + tr("Bookmark connect.comma.ai to your home screen to use it like an app"), ] font = gui_app.font(FontWeight.BOLD) @@ -157,7 +158,7 @@ class PairingDialog(Widget): rl.draw_rectangle_rounded(rect, 0.1, 20, rl.Color(240, 240, 240, 255)) error_font = gui_app.font(FontWeight.BOLD) rl.draw_text_ex( - error_font, "QR Code Error", rl.Vector2(rect.x + 20, rect.y + rect.height // 2 - 15), 30, 0.0, rl.RED + error_font, tr("QR Code Error"), rl.Vector2(rect.x + 20, rect.y + rect.height // 2 - 15), 30, 0.0, rl.RED ) return diff --git a/selfdrive/ui/widgets/prime.py b/selfdrive/ui/widgets/prime.py index bbbd52a8cd..49a0e56cdc 100644 --- a/selfdrive/ui/widgets/prime.py +++ b/selfdrive/ui/widgets/prime.py @@ -2,6 +2,7 @@ import pyray as rl from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget @@ -29,21 +30,21 @@ class PrimeWidget(Widget): w = rect.width - 160 # Title - gui_label(rl.Rectangle(x, y, w, 90), "Upgrade Now", 75, font_weight=FontWeight.BOLD) + gui_label(rl.Rectangle(x, y, w, 90), tr("Upgrade Now"), 75, font_weight=FontWeight.BOLD) # Description with wrapping desc_y = y + 140 font = gui_app.font(FontWeight.LIGHT) - wrapped_text = "\n".join(wrap_text(font, "Become a comma prime member at connect.comma.ai", 56, int(w))) + wrapped_text = "\n".join(wrap_text(font, tr("Become a comma prime member at connect.comma.ai"), 56, int(w))) text_size = measure_text_cached(font, wrapped_text, 56) rl.draw_text_ex(font, wrapped_text, rl.Vector2(x, desc_y), 56, 0, rl.WHITE) # Features section features_y = desc_y + text_size.y + 50 - gui_label(rl.Rectangle(x, features_y, w, 50), "PRIME FEATURES:", 41, font_weight=FontWeight.BOLD) + gui_label(rl.Rectangle(x, features_y, w, 50), tr("PRIME FEATURES:"), 41, font_weight=FontWeight.BOLD) # Feature list - features = ["Remote access", "24/7 LTE connectivity", "1 year of drive storage", "Remote snapshots"] + features = [tr("Remote access"), tr("24/7 LTE connectivity"), tr("1 year of drive storage"), tr("Remote snapshots")] for i, feature in enumerate(features): item_y = features_y + 80 + i * 65 gui_label(rl.Rectangle(x, item_y, 100, 60), "✓", 50, color=rl.Color(70, 91, 234, 255)) @@ -58,5 +59,5 @@ class PrimeWidget(Widget): y = rect.y + 40 font = gui_app.font(FontWeight.BOLD) - rl.draw_text_ex(font, "✓ SUBSCRIBED", rl.Vector2(x, y), 41, 0, rl.Color(134, 255, 78, 255)) - rl.draw_text_ex(font, "comma prime", rl.Vector2(x, y + 61), 75, 0, rl.WHITE) + rl.draw_text_ex(font, tr("✓ SUBSCRIBED"), rl.Vector2(x, y), 41, 0, rl.Color(134, 255, 78, 255)) + rl.draw_text_ex(font, tr("comma prime"), rl.Vector2(x, y + 61), 75, 0, rl.WHITE) diff --git a/selfdrive/ui/widgets/setup.py b/selfdrive/ui/widgets/setup.py index 0538570e4a..ea88180ef8 100644 --- a/selfdrive/ui/widgets/setup.py +++ b/selfdrive/ui/widgets/setup.py @@ -3,6 +3,7 @@ from openpilot.common.time_helpers import system_time_valid from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.confirm_dialog import alert_dialog @@ -15,10 +16,10 @@ class SetupWidget(Widget): super().__init__() self._open_settings_callback = None self._pairing_dialog: PairingDialog | None = None - self._pair_device_btn = Button("Pair device", self._show_pairing, button_style=ButtonStyle.PRIMARY) - self._open_settings_btn = Button("Open", lambda: self._open_settings_callback() if self._open_settings_callback else None, + self._pair_device_btn = Button(tr("Pair device"), self._show_pairing, button_style=ButtonStyle.PRIMARY) + self._open_settings_btn = Button(tr("Open"), lambda: self._open_settings_callback() if self._open_settings_callback else None, button_style=ButtonStyle.PRIMARY) - self._firehose_label = Label("🔥 Firehose Mode 🔥", font_weight=FontWeight.MEDIUM, font_size=64) + self._firehose_label = Label(tr("🔥 Firehose Mode 🔥"), font_weight=FontWeight.MEDIUM, font_size=64) def set_open_settings_callback(self, callback): self._open_settings_callback = callback @@ -40,11 +41,11 @@ class SetupWidget(Widget): # Title font = gui_app.font(FontWeight.BOLD) - rl.draw_text_ex(font, "Finish Setup", rl.Vector2(x, y), 75, 0, rl.WHITE) + rl.draw_text_ex(font, tr("Finish Setup"), rl.Vector2(x, y), 75, 0, rl.WHITE) y += 113 # 75 + 38 spacing # Description - desc = "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." + desc = tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.") light_font = gui_app.font(FontWeight.LIGHT) wrapped = wrap_text(light_font, desc, 50, int(w)) for line in wrapped: @@ -71,7 +72,7 @@ class SetupWidget(Widget): # Description desc_font = gui_app.font(FontWeight.NORMAL) - desc_text = "Maximize your training data uploads to improve openpilot's driving models." + desc_text = tr("Maximize your training data uploads to improve openpilot's driving models.") wrapped_desc = wrap_text(desc_font, desc_text, 40, int(w)) for line in wrapped_desc: @@ -87,7 +88,7 @@ class SetupWidget(Widget): def _show_pairing(self): if not system_time_valid(): - dlg = alert_dialog("Please connect to Wi-Fi to complete initial pairing") + dlg = alert_dialog(tr("Please connect to Wi-Fi to complete initial pairing")) gui_app.set_modal_overlay(dlg) return diff --git a/selfdrive/ui/widgets/ssh_key.py b/selfdrive/ui/widgets/ssh_key.py index dc3c5a4a76..e44c4aad5d 100644 --- a/selfdrive/ui/widgets/ssh_key.py +++ b/selfdrive/ui/widgets/ssh_key.py @@ -7,6 +7,7 @@ from enum import Enum from openpilot.common.params import Params from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import DialogResult from openpilot.system.ui.widgets.button import Button, ButtonStyle @@ -25,9 +26,9 @@ VALUE_FONT_SIZE = 48 class SshKeyActionState(Enum): - LOADING = "LOADING" - ADD = "ADD" - REMOVE = "REMOVE" + LOADING = tr("LOADING") + ADD = tr("ADD") + REMOVE = tr("REMOVE") class SshKeyAction(ItemAction): @@ -85,7 +86,7 @@ class SshKeyAction(ItemAction): def _handle_button_click(self): if self._state == SshKeyActionState.ADD: self._keyboard.reset() - self._keyboard.set_title("Enter your GitHub username") + self._keyboard.set_title(tr("Enter your GitHub username")) gui_app.set_modal_overlay(self._keyboard, callback=self._on_username_submit) elif self._state == SshKeyActionState.REMOVE: self._params.remove("GithubUsername") @@ -110,7 +111,7 @@ class SshKeyAction(ItemAction): response.raise_for_status() keys = response.text.strip() if not keys: - raise requests.exceptions.HTTPError("No SSH keys found") + raise requests.exceptions.HTTPError(tr("No SSH keys found")) # Success - save keys self._params.put("GithubUsername", username) @@ -119,10 +120,10 @@ class SshKeyAction(ItemAction): self._username = username except requests.exceptions.Timeout: - self._error_message = "Request timed out" + self._error_message = tr("Request timed out") self._state = SshKeyActionState.ADD except Exception: - self._error_message = f"No SSH keys found for user '{username}'" + self._error_message = tr("No SSH keys found for user '{}'").format(username) self._state = SshKeyActionState.ADD diff --git a/system/ui/lib/multilang.py b/system/ui/lib/multilang.py new file mode 100644 index 0000000000..c020b33d5e --- /dev/null +++ b/system/ui/lib/multilang.py @@ -0,0 +1,10 @@ +import os +import gettext +from openpilot.common.basedir import BASEDIR + +UI_DIR = os.path.join(BASEDIR, "selfdrive", "ui") +TRANSLATIONS_DIR = os.path.join(UI_DIR, "translations") +LANGUAGES_FILE = os.path.join(TRANSLATIONS_DIR, "languages.json") + +tr = gettext.gettext +trn = gettext.ngettext diff --git a/system/ui/widgets/confirm_dialog.py b/system/ui/widgets/confirm_dialog.py index 274307395c..8c5ae0aa01 100644 --- a/system/ui/widgets/confirm_dialog.py +++ b/system/ui/widgets/confirm_dialog.py @@ -1,5 +1,6 @@ import pyray as rl from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import DialogResult from openpilot.system.ui.widgets.button import ButtonStyle, Button from openpilot.system.ui.widgets.label import Label @@ -16,8 +17,10 @@ BACKGROUND_COLOR = rl.Color(27, 27, 27, 255) class ConfirmDialog(Widget): - def __init__(self, text: str, confirm_text: str, cancel_text: str = "Cancel", rich: bool = False): + def __init__(self, text: str, confirm_text: str, cancel_text: str | None = None, rich: bool = False): super().__init__() + if cancel_text is None: + cancel_text = tr("Cancel") self._label = Label(text, 70, FontWeight.BOLD, text_color=rl.Color(201, 201, 201, 255)) self._html_renderer = HtmlRenderer(text=text, text_size={ElementType.P: 50}, center_text=True) self._cancel_button = Button(cancel_text, self._cancel_button_callback) @@ -85,5 +88,7 @@ class ConfirmDialog(Widget): return self._dialog_result -def alert_dialog(message: str, button_text: str = "Ok"): +def alert_dialog(message: str, button_text: str | None = None): + if button_text is None: + button_text = tr("OK") return ConfirmDialog(message, button_text, cancel_text="") diff --git a/system/ui/widgets/html_render.py b/system/ui/widgets/html_render.py index f90f78fe1b..368d02cdfc 100644 --- a/system/ui/widgets/html_render.py +++ b/system/ui/widgets/html_render.py @@ -4,6 +4,7 @@ from dataclasses import dataclass from enum import Enum from typing import Any from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.widgets import Widget @@ -243,7 +244,7 @@ class HtmlModal(Widget): super().__init__() self._content = HtmlRenderer(file_path=file_path, text=text) self._scroll_panel = GuiScrollPanel() - self._ok_button = Button("OK", click_callback=lambda: gui_app.set_modal_overlay(None), button_style=ButtonStyle.PRIMARY) + self._ok_button = Button(tr("OK"), click_callback=lambda: gui_app.set_modal_overlay(None), button_style=ButtonStyle.PRIMARY) def _render(self, rect: rl.Rectangle): margin = 50 diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index 6663c4e0ee..ac006c2545 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -5,6 +5,7 @@ from typing import Literal import pyray as rl from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import ButtonStyle, Button from openpilot.system.ui.widgets.inputbox import InputBox @@ -77,7 +78,7 @@ class Keyboard(Widget): self._backspace_last_repeat: float = 0.0 self._render_return_status = -1 - self._cancel_button = Button("Cancel", self._cancel_button_callback) + self._cancel_button = Button(tr("Cancel"), self._cancel_button_callback) self._eye_button = Button("", self._eye_button_callback, button_style=ButtonStyle.TRANSPARENT) @@ -98,7 +99,7 @@ class Keyboard(Widget): if key in self._key_icons: texture = self._key_icons[key] self._all_keys[key] = Button("", partial(self._key_callback, key), icon=texture, - button_style=ButtonStyle.PRIMARY if key == ENTER_KEY else ButtonStyle.KEYBOARD, multi_touch=True) + button_style=ButtonStyle.PRIMARY if key == ENTER_KEY else ButtonStyle.KEYBOARD, multi_touch=True) else: self._all_keys[key] = Button(key, partial(self._key_callback, key), button_style=ButtonStyle.KEYBOARD, font_size=85, multi_touch=True) self._all_keys[CAPS_LOCK_KEY] = Button("", partial(self._key_callback, CAPS_LOCK_KEY), icon=self._key_icons[CAPS_LOCK_KEY], diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index 1f4f69a6bf..55abe02fe1 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -3,6 +3,7 @@ import pyray as rl from collections.abc import Callable from abc import ABC from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import Button, ButtonStyle @@ -111,7 +112,7 @@ class ButtonAction(ItemAction): @property def text(self): - return _resolve_value(self._text_source, "Error") + return _resolve_value(self._text_source, tr("Error")) @property def value(self): @@ -149,7 +150,7 @@ class TextAction(ItemAction): @property def text(self): - return _resolve_value(self._text_source, "Error") + return _resolve_value(self._text_source, tr("Error")) def get_width_hint(self) -> float: text_width = measure_text_cached(self._font, self.text, ITEM_TEXT_FONT_SIZE).x diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 3bbf2e6402..a59030363b 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -4,6 +4,7 @@ from typing import cast import pyray as rl from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wifi_manager import WifiManager, SecurityType, Network, MeteredType from openpilot.system.ui.widgets import Widget @@ -70,7 +71,7 @@ class NetworkUI(Widget): self._current_panel: PanelType = PanelType.WIFI self._wifi_panel = WifiManagerUI(wifi_manager) self._advanced_panel = AdvancedNetworkSettings(wifi_manager) - self._nav_button = NavButton("Advanced") + self._nav_button = NavButton(tr("Advanced")) self._nav_button.set_click_callback(self._cycle_panel) def show_event(self): @@ -91,11 +92,11 @@ class NetworkUI(Widget): content_rect = rl.Rectangle(self._rect.x, self._rect.y + self._nav_button.rect.height + 40, self._rect.width, self._rect.height - self._nav_button.rect.height - 40) if self._current_panel == PanelType.WIFI: - self._nav_button.text = "Advanced" + self._nav_button.text = tr("Advanced") self._nav_button.set_position(self._rect.x + self._rect.width - self._nav_button.rect.width, self._rect.y + 20) self._wifi_panel.render(content_rect) else: - self._nav_button.text = "Back" + self._nav_button.text = tr("Back") self._nav_button.set_position(self._rect.x, self._rect.y + 20) self._advanced_panel.render(content_rect) @@ -116,40 +117,40 @@ class AdvancedNetworkSettings(Widget): # Tethering self._tethering_action = ToggleAction(initial_state=False) - tethering_btn = ListItem(title="Enable Tethering", action_item=self._tethering_action, callback=self._toggle_tethering) + tethering_btn = ListItem(title=tr("Enable Tethering"), action_item=self._tethering_action, callback=self._toggle_tethering) # Edit tethering password - self._tethering_password_action = ButtonAction(text="EDIT") - tethering_password_btn = ListItem(title="Tethering Password", action_item=self._tethering_password_action, callback=self._edit_tethering_password) + self._tethering_password_action = ButtonAction(text=tr("EDIT")) + tethering_password_btn = ListItem(title=tr("Tethering Password"), action_item=self._tethering_password_action, callback=self._edit_tethering_password) # Roaming toggle roaming_enabled = self._params.get_bool("GsmRoaming") self._roaming_action = ToggleAction(initial_state=roaming_enabled) - self._roaming_btn = ListItem(title="Enable Roaming", action_item=self._roaming_action, callback=self._toggle_roaming) + self._roaming_btn = ListItem(title=tr("Enable Roaming"), action_item=self._roaming_action, callback=self._toggle_roaming) # Cellular metered toggle cellular_metered = self._params.get_bool("GsmMetered") self._cellular_metered_action = ToggleAction(initial_state=cellular_metered) - self._cellular_metered_btn = ListItem(title="Cellular Metered", description="Prevent large data uploads when on a metered cellular connection", + self._cellular_metered_btn = ListItem(title=tr("Cellular Metered"), description=tr("Prevent large data uploads when on a metered cellular connection"), action_item=self._cellular_metered_action, callback=self._toggle_cellular_metered) # APN setting - self._apn_btn = button_item("APN Setting", "EDIT", callback=self._edit_apn) + self._apn_btn = button_item(tr("APN Setting"), tr("EDIT"), callback=self._edit_apn) # Wi-Fi metered toggle - self._wifi_metered_action = MultipleButtonAction(["default", "metered", "unmetered"], 255, 0, callback=self._toggle_wifi_metered) - wifi_metered_btn = ListItem(title="Wi-Fi Network Metered", description="Prevent large data uploads when on a metered Wi-Fi connection", + self._wifi_metered_action = MultipleButtonAction([tr("default"), tr("metered"), tr("unmetered")], 255, 0, callback=self._toggle_wifi_metered) + wifi_metered_btn = ListItem(title=tr("Wi-Fi Network Metered"), description=tr("Prevent large data uploads when on a metered Wi-Fi connection"), action_item=self._wifi_metered_action) items: list[Widget] = [ tethering_btn, tethering_password_btn, - text_item("IP Address", lambda: self._wifi_manager.ipv4_address), + text_item(tr("IP Address"), lambda: self._wifi_manager.ipv4_address), self._roaming_btn, self._apn_btn, self._cellular_metered_btn, wifi_metered_btn, - button_item("Hidden Network", "CONNECT", callback=self._connect_to_hidden_network), + button_item(tr("Hidden Network"), tr("CONNECT"), callback=self._connect_to_hidden_network), ] self._scroller = Scroller(items, line_separator=True, spacing=0) @@ -198,7 +199,7 @@ class AdvancedNetworkSettings(Widget): current_apn = self._params.get("GsmApn") or "" self._keyboard.reset(min_text_size=0) - self._keyboard.set_title("Enter APN", "leave blank for automatic configuration") + self._keyboard.set_title(tr("Enter APN"), tr("leave blank for automatic configuration")) self._keyboard.set_text(current_apn) gui_app.set_modal_overlay(self._keyboard, update_apn) @@ -231,11 +232,11 @@ class AdvancedNetworkSettings(Widget): self._wifi_manager.connect_to_network(ssid, password, hidden=True) self._keyboard.reset(min_text_size=0) - self._keyboard.set_title("Enter password", f"for \"{ssid}\"") + self._keyboard.set_title(tr("Enter password"), tr("for \"{}\"").format(ssid)) gui_app.set_modal_overlay(self._keyboard, enter_password) self._keyboard.reset(min_text_size=1) - self._keyboard.set_title("Enter SSID", "") + self._keyboard.set_title(tr("Enter SSID"), "") gui_app.set_modal_overlay(self._keyboard, connect_hidden) def _edit_tethering_password(self): @@ -248,7 +249,7 @@ class AdvancedNetworkSettings(Widget): self._tethering_password_action.set_enabled(False) self._keyboard.reset(min_text_size=MIN_PASSWORD_LENGTH) - self._keyboard.set_title("Enter new tethering password", "") + self._keyboard.set_title(tr("Enter new tethering password"), "") self._keyboard.set_text(self._wifi_manager.tethering_password) gui_app.set_modal_overlay(self._keyboard, update_password) @@ -281,7 +282,7 @@ class WifiManagerUI(Widget): self._networks: list[Network] = [] self._networks_buttons: dict[str, Button] = {} self._forget_networks_buttons: dict[str, Button] = {} - self._confirm_dialog = ConfirmDialog("", "Forget", "Cancel") + self._confirm_dialog = ConfirmDialog("", tr("Forget"), tr("Cancel")) self._wifi_manager.set_callbacks(need_auth=self._on_need_auth, activated=self._on_activated, @@ -305,15 +306,15 @@ class WifiManagerUI(Widget): def _render(self, rect: rl.Rectangle): if not self._networks: - gui_label(rect, "Scanning Wi-Fi networks...", 72, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) + gui_label(rect, tr("Scanning Wi-Fi networks..."), 72, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) return if self.state == UIState.NEEDS_AUTH and self._state_network: - self.keyboard.set_title("Wrong password" if self._password_retry else "Enter password", f"for {self._state_network.ssid}") + self.keyboard.set_title(tr("Wrong password") if self._password_retry else tr("Enter password"), tr("for \"{}\"").format(self._state_network.ssid)) self.keyboard.reset(min_text_size=MIN_PASSWORD_LENGTH) gui_app.set_modal_overlay(self.keyboard, lambda result: self._on_password_entered(cast(Network, self._state_network), result)) elif self.state == UIState.SHOW_FORGET_CONFIRM and self._state_network: - self._confirm_dialog.set_text(f'Forget Wi-Fi Network "{self._state_network.ssid}"?') + self._confirm_dialog.set_text(tr("Forget Wi-Fi Network \"{}\"?").format(self._state_network.ssid)) self._confirm_dialog.reset() gui_app.set_modal_overlay(self._confirm_dialog, callback=lambda result: self.on_forgot_confirm_finished(self._state_network, result)) else: @@ -363,11 +364,11 @@ class WifiManagerUI(Widget): if self.state == UIState.CONNECTING and self._state_network: if self._state_network.ssid == network.ssid: self._networks_buttons[network.ssid].set_enabled(False) - status_text = "CONNECTING..." + status_text = tr("CONNECTING...") elif self.state == UIState.FORGETTING and self._state_network: if self._state_network.ssid == network.ssid: self._networks_buttons[network.ssid].set_enabled(False) - status_text = "FORGETTING..." + status_text = tr("FORGETTING...") elif network.security_type == SecurityType.UNSUPPORTED: self._networks_buttons[network.ssid].set_enabled(False) else: @@ -445,7 +446,7 @@ class WifiManagerUI(Widget): self._networks_buttons[n.ssid] = Button(n.ssid, partial(self._networks_buttons_callback, n), font_size=55, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, button_style=ButtonStyle.TRANSPARENT_WHITE_TEXT) self._networks_buttons[n.ssid].set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid()) - self._forget_networks_buttons[n.ssid] = Button("Forget", partial(self._forget_networks_buttons_callback, n), button_style=ButtonStyle.FORGET_WIFI, + self._forget_networks_buttons[n.ssid] = Button(tr("Forget"), partial(self._forget_networks_buttons_callback, n), button_style=ButtonStyle.FORGET_WIFI, font_size=45) self._forget_networks_buttons[n.ssid].set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid()) diff --git a/system/ui/widgets/option_dialog.py b/system/ui/widgets/option_dialog.py index 593a371fd9..8c63ca3f9f 100644 --- a/system/ui/widgets/option_dialog.py +++ b/system/ui/widgets/option_dialog.py @@ -1,5 +1,6 @@ import pyray as rl from openpilot.system.ui.lib.application import FontWeight +from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.label import gui_label @@ -30,8 +31,8 @@ class MultiOptionDialog(Widget): text_padding=50) for option in options] self.scroller = Scroller(self.option_buttons, spacing=LIST_ITEM_SPACING) - self.cancel_button = Button("Cancel", click_callback=lambda: self._set_result(DialogResult.CANCEL)) - self.select_button = Button("Select", click_callback=lambda: self._set_result(DialogResult.CONFIRM), button_style=ButtonStyle.PRIMARY) + self.cancel_button = Button(tr("Cancel"), click_callback=lambda: self._set_result(DialogResult.CANCEL)) + self.select_button = Button(tr("Select"), click_callback=lambda: self._set_result(DialogResult.CONFIRM), button_style=ButtonStyle.PRIMARY) def _set_result(self, result: DialogResult): self._result = result