Merge pull request #438 from wlx-team/feat-language-selector
Settings: Language selector, "Requires restart" info
This commit is contained in:
@@ -10,7 +10,6 @@
|
||||
"APP_SETTINGS": {
|
||||
"HIDE_USERNAME": "Benutzernamen ausblenden",
|
||||
"OPAQUE_BACKGROUND": "Undurchsichtiger Hintergrund",
|
||||
"WLX": {},
|
||||
"LOOK_AND_FEEL": "Aussehen und Verhalten",
|
||||
"HIDE_GRAB_HELP": "Greif-Hilfe ausblenden",
|
||||
"ANIMATION_SPEED": "UI-Animationsgeschwindigkeit",
|
||||
@@ -89,7 +88,9 @@
|
||||
"RESET_PLAYSPACE": "Spielbereich zurücksetzen",
|
||||
"RESET_PLAYSPACE_HELP": "Den Abstand des Spielbereichs zurücksetzen.",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION": "Posen beim Interagieren mit der Tastatur blockieren",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Verhindert, dass das Spiel Posen empfängt, wenn die Tastatur angefahren wird und „Spieleingabe blockieren“ aktiviert ist"
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Verhindert, dass das Spiel Posen empfängt, wenn die Tastatur angefahren wird und „Spieleingabe blockieren“ aktiviert ist",
|
||||
"LANGUAGE": "Sprache",
|
||||
"REQUIRES_RESTART": "Erfordert Neustart"
|
||||
},
|
||||
"HELLO": "Hallo!",
|
||||
"AUDIO": {
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
"KEYBOARD_MIDDLE_CLICK": "Keyboard middle click",
|
||||
"KEYBOARD_MIDDLE_CLICK_HELP": "Modifier to use when typing\nwith purple laser",
|
||||
"KEYBOARD_SOUND_ENABLED": "Keyboard sounds",
|
||||
"LANGUAGE": "Language",
|
||||
"LEFT_HANDED_MOUSE": "Left-handed mouse",
|
||||
"LEFT_HANDED_MOUSE_HELP": "Use this if mouse buttons are swapped",
|
||||
"LONG_PRESS_DURATION": "Long press duration",
|
||||
@@ -74,10 +75,10 @@
|
||||
"OPTION": {
|
||||
"AUTO": "Automatic",
|
||||
"AUTO_HELP": "ScreenCopy GPU if supported,\notherwise PipeWire GPU.",
|
||||
"EYE_PINCH": "Eye + pinch",
|
||||
"HMD_PINCH": "HMD + pinch",
|
||||
"EYE_ONLY": "Eye only",
|
||||
"EYE_PINCH": "Eye + pinch",
|
||||
"HMD_ONLY": "HMD only",
|
||||
"HMD_PINCH": "HMD + pinch",
|
||||
"NONE": "None",
|
||||
"PIPEWIRE_HELP": "Fast GPU capture,\nstandard on all desktops.",
|
||||
"PW_FALLBACK_HELP": "Slow method with high CPU usage.\nTry in case PipeWire GPU doesn't work",
|
||||
@@ -85,6 +86,7 @@
|
||||
"SCREENCOPY_HELP": "Slow, no screen share popups.\nWorks on: Hyprland, Niri, River, Sway"
|
||||
},
|
||||
"POINTER_LERP_FACTOR": "Pointer smoothing",
|
||||
"REQUIRES_RESTART": "Requires restart",
|
||||
"RESET_PLAYSPACE": "Reset playspace",
|
||||
"RESET_PLAYSPACE_HELP": "Clear the stage space offset.",
|
||||
"RESTART_SOFTWARE": "Restart software",
|
||||
@@ -148,8 +150,8 @@
|
||||
"RESOLUTION": "Resolution"
|
||||
},
|
||||
"PROCESS": {
|
||||
"STOP": "Stop",
|
||||
"FORCE_KILL": "Force-kill"
|
||||
"FORCE_KILL": "Force-kill",
|
||||
"STOP": "Stop"
|
||||
},
|
||||
"PROCESS_LIST": "Process list",
|
||||
"REFRESH": "Refresh",
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
"APP_SETTINGS": {
|
||||
"HIDE_USERNAME": "Ocultar nombre de usuario",
|
||||
"OPAQUE_BACKGROUND": "Fondo opaco",
|
||||
"WLX": {},
|
||||
"LOOK_AND_FEEL": "Apariencia y estilo",
|
||||
"HIDE_GRAB_HELP": "Ocultar ayuda para agarrar",
|
||||
"ANIMATION_SPEED": "Velocidad de animación de la IU",
|
||||
@@ -89,7 +88,9 @@
|
||||
"RESET_PLAYSPACE": "Restablecer espacio de juego",
|
||||
"RESET_PLAYSPACE_HELP": "Borrar el desplazamiento del espacio de juego.",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION": "Bloquear poses al interactuar con el teclado",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Bloquea que el juego reciba poses cuando el teclado está sobre él y 'Bloquear entrada del juego' está habilitado"
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Bloquea que el juego reciba poses cuando el teclado está sobre él y 'Bloquear entrada del juego' está habilitado",
|
||||
"LANGUAGE": "Idioma",
|
||||
"REQUIRES_RESTART": "Requiere reinicio"
|
||||
},
|
||||
"HELLO": "¡Hola!",
|
||||
"AUDIO": {
|
||||
|
||||
@@ -109,7 +109,9 @@
|
||||
"RESET_PLAYSPACE": "Ripristina playspace",
|
||||
"RESET_PLAYSPACE_HELP": "Cancella l'offset dello spazio di gioco.",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION": "Blocca le pose durante l'interazione con la tastiera",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Impedisce al gioco di ricevere pose quando la tastiera è evidenziata e 'Blocca input di gioco' è abilitato"
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Impedisce al gioco di ricevere pose quando la tastiera è evidenziata e 'Blocca input di gioco' è abilitato",
|
||||
"LANGUAGE": "Lingua",
|
||||
"REQUIRES_RESTART": "Richiede riavvio"
|
||||
},
|
||||
"APPLICATION_LAUNCHER": "Lanciatore applicazioni",
|
||||
"APPLICATION_STARTED": "Applicazione avviata",
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
"APP_SETTINGS": {
|
||||
"HIDE_USERNAME": "ユーザー名を非表示",
|
||||
"OPAQUE_BACKGROUND": "不透明な背景",
|
||||
"WLX": {},
|
||||
"LOOK_AND_FEEL": "外観",
|
||||
"HIDE_GRAB_HELP": "グリップ動作中にのヘルプを非表示",
|
||||
"ANIMATION_SPEED": "UIアニメーション速度",
|
||||
@@ -89,7 +88,9 @@
|
||||
"RESET_PLAYSPACE": "プレイエリアをリセット",
|
||||
"RESET_PLAYSPACE_HELP": "プレイエリアのオフセットをクリアします。",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION": "キーボード操作時のポーズをブロック",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "キーボードがホバーされ、「ゲーム入力をブロック」が有効になっている場合、ゲームがポーズを受信することをブロックします"
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "キーボードがホバーされ、「ゲーム入力をブロック」が有効になっている場合、ゲームがポーズを受信することをブロックします",
|
||||
"LANGUAGE": "言語",
|
||||
"REQUIRES_RESTART": "再起動が必要です"
|
||||
},
|
||||
"HELLO": "こんにちは!",
|
||||
"AUDIO": {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"APP_SETTINGS": {
|
||||
"HIDE_USERNAME": "Ukryj nazwę użytkownika",
|
||||
"OPAQUE_BACKGROUND": "Nieprzezroczyste tło",
|
||||
"WLX": {},
|
||||
"LOOK_AND_FEEL": "Wygląd i działanie",
|
||||
"HIDE_GRAB_HELP": "Ukryj pomoc dotyczącą chwytania",
|
||||
"ANIMATION_SPEED": "Prędkość animacji UI",
|
||||
@@ -34,7 +33,6 @@
|
||||
"XR_CLICK_SENSITIVITY": "Czułość kliknięć XR",
|
||||
"XR_CLICK_SENSITIVITY_RELEASE": "Czułość zwalniania XR",
|
||||
"CLICK_FREEZE_TIME_MS": "Czas zamrożenia po kliknięciu (ms)",
|
||||
"MISC": "Różne",
|
||||
"XWAYLAND_BY_DEFAULT": "Uruchamiaj aplikacje domyślnie w trybie kompatybilności",
|
||||
"UPRIGHT_SCREEN_FIX": "Naprawa pozycji ekranu",
|
||||
"DOUBLE_CURSOR_FIX": "Naprawa podwójnego kursora",
|
||||
@@ -55,7 +53,7 @@
|
||||
"CLEAR_SAVED_STATE": "Wyczyść zapisany stan",
|
||||
"CLEAR_PIPEWIRE_TOKENS": "Wyczyść tokeny PipeWire",
|
||||
"DELETE_ALL_CONFIGS": "Wyczyść konfigurację",
|
||||
"RESTART_SOFTWARE": "Uruchom ponownie oprogramowanie",
|
||||
"RESTART_SOFTWARE": "Restartuj WayVR",
|
||||
"CLEAR_SAVED_STATE_HELP": "Zresetuj zestawy i pozycje nakładek",
|
||||
"CLEAR_PIPEWIRE_TOKENS_HELP": "Zapytaj o wybór ekranu przy następnym uruchomieniu",
|
||||
"DELETE_ALL_CONFIGS_HELP": "Usuń wszystkie pliki konfiguracyjne z katalogu conf.d",
|
||||
@@ -72,19 +70,22 @@
|
||||
"SCREENCOPY_GPU_HELP": "Szybkie działanie, brak wyskakujących okien z informacją o udostępnianiu ekranu.\nDziała na: Hyprland, Niri, River, Sway",
|
||||
"SCREENCOPY_HELP": "Wolne, bez wyskakujących okienek udostępniania ekranu.\nDziała na: Hyprland, Niri, River, Sway",
|
||||
"NONE": "Brak",
|
||||
"HMD_PINCH": "HMD + szczyknięcie",
|
||||
"HMD_PINCH": "HMD + ściśnięcie placami",
|
||||
"EYE_PINCH": "Ściśnięcie palcami + oko",
|
||||
"EYE_ONLY": "Tylko oko",
|
||||
"HMD_ONLY": "Tylko HMD"
|
||||
},
|
||||
"AUTOSTART_APPS": "Aplikacje do uruchomienia przy starcie",
|
||||
"AUTOSTART_APPS": "Aplikacje auto-start",
|
||||
"HANDSFREE_POINTER": "Tryb bez użycia rąk",
|
||||
"HANDSFREE_POINTER_HELP": "Wejście do użycia, gdy kontrolery ruchu\nsą niedostępne. Lewy szczyptak to chwyt,\nprawy to kliknięcie.",
|
||||
"UI_GRADIENT_INTENSITY": "Intensywność gradientu UI",
|
||||
"RESET_PLAYSPACE": "Zresetuj przestrzeń gry",
|
||||
"RESET_PLAYSPACE_HELP": "Wyczyść przesunięcie przestrzeni gry.",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION": "Blokuj pozy podczas interakcji z klawiaturą",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Blokuje odbieranie póz przez grę, gdy kursor myszy znajduje się nad klawiaturą i włączona jest opcja 'Blokuj dane wejściowe z gry'"
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Blokuje odbieranie póz przez grę, gdy kursor myszy znajduje się nad klawiaturą i włączona jest opcja 'Blokuj dane wejściowe z gry'",
|
||||
"LANGUAGE": "Język",
|
||||
"REQUIRES_RESTART": "Wymaga restartu",
|
||||
"MISC": "Różne"
|
||||
},
|
||||
"APPLICATION_LAUNCHER": "Uruchamiacz aplikacji",
|
||||
"APPLICATIONS": "Aplikacje",
|
||||
|
||||
@@ -109,7 +109,9 @@
|
||||
"RESET_PLAYSPACE": "重置游戏空间",
|
||||
"RESET_PLAYSPACE_HELP": "清除舞台空间偏移。",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION": "与键盘交互时阻止姿势",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "当键盘悬停且启用“阻止游戏输入”时,阻止游戏接收姿势"
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "当键盘悬停且启用“阻止游戏输入”时,阻止游戏接收姿势",
|
||||
"LANGUAGE": "语言",
|
||||
"REQUIRES_RESTART": "需要重启"
|
||||
},
|
||||
"APPLICATION_LAUNCHER": "应用启动器",
|
||||
"APPLICATION_STARTED": "应用已启动",
|
||||
|
||||
@@ -18,6 +18,7 @@ use wgui::{
|
||||
use wlx_common::{
|
||||
audio,
|
||||
dash_interface::{BoxDashInterface, RecenterMode},
|
||||
locale::WayVRLangProvider,
|
||||
timestep::{self, Timestep},
|
||||
};
|
||||
|
||||
@@ -81,8 +82,9 @@ pub struct FrontendUpdateResult {
|
||||
pub sounds_to_play: Vec<SoundType>,
|
||||
}
|
||||
|
||||
pub struct InitParams<T> {
|
||||
pub struct InitParams<'a, T> {
|
||||
pub interface: BoxDashInterface<T>,
|
||||
pub lang_provider: &'a WayVRLangProvider,
|
||||
pub has_monado: bool,
|
||||
}
|
||||
|
||||
@@ -118,6 +120,7 @@ impl<T: 'static> Frontend<T> {
|
||||
|
||||
let globals = WguiGlobals::new(
|
||||
assets,
|
||||
params.lang_provider,
|
||||
wgui::globals::Defaults::default(),
|
||||
&WguiFontConfig {
|
||||
binaries: vec![&font_binary_regular, &font_binary_bold, &font_binary_light],
|
||||
|
||||
@@ -10,14 +10,20 @@ use wgui::{
|
||||
slider::ComponentSlider,
|
||||
tabs::ComponentTabs,
|
||||
},
|
||||
drawing,
|
||||
event::{CallbackDataCommon, EventAlterables},
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID},
|
||||
log::LogErr,
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||
renderer_vk::text::{FontWeight, TextStyle},
|
||||
taffy::{self, prelude::length},
|
||||
task::Tasks,
|
||||
widget::label::WidgetLabel,
|
||||
widget::{
|
||||
div::WidgetDiv,
|
||||
label::{WidgetLabel, WidgetLabelParams},
|
||||
},
|
||||
windowing::context_menu::{self, Blueprint, ContextMenu, TickResult},
|
||||
};
|
||||
use wlx_common::{config::GeneralConfig, config_io::ConfigRoot, dash_interface::RecenterMode};
|
||||
@@ -202,46 +208,48 @@ impl<T> Tab<T> for TabSettings<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// Sorted alphabetically
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
#[derive(Clone, Copy, AsRefStr, EnumString)]
|
||||
enum SettingType {
|
||||
UiAnimationSpeed,
|
||||
UiGradientIntensity,
|
||||
UiRoundMultiplier,
|
||||
InvertScrollDirectionX,
|
||||
InvertScrollDirectionY,
|
||||
ScrollSpeed,
|
||||
LongPressDuration,
|
||||
NotificationsEnabled,
|
||||
NotificationsSoundEnabled,
|
||||
KeyboardSoundEnabled,
|
||||
UprightScreenFix,
|
||||
DoubleCursorFix,
|
||||
SetsOnWatch,
|
||||
HideGrabHelp,
|
||||
XrClickSensitivity,
|
||||
XrClickSensitivityRelease,
|
||||
AllowSliding,
|
||||
ClickFreezeTimeMs,
|
||||
FocusFollowsMouseMode,
|
||||
LeftHandedMouse,
|
||||
BlockGameInput,
|
||||
BlockGameInputIgnoreWatch,
|
||||
BlockPosesOnKbdInteraction,
|
||||
SpaceDragMultiplier,
|
||||
UseSkybox,
|
||||
UsePassthrough,
|
||||
ScreenRenderDown,
|
||||
CaptureMethod,
|
||||
ClickFreezeTimeMs,
|
||||
Clock12h,
|
||||
DoubleCursorFix,
|
||||
FocusFollowsMouseMode,
|
||||
HandsfreePointer,
|
||||
HideGrabHelp,
|
||||
HideUsername,
|
||||
InvertScrollDirectionX,
|
||||
InvertScrollDirectionY,
|
||||
KeyboardMiddleClick,
|
||||
KeyboardSoundEnabled,
|
||||
Language,
|
||||
LeftHandedMouse,
|
||||
LongPressDuration,
|
||||
NotificationsEnabled,
|
||||
NotificationsSoundEnabled,
|
||||
OpaqueBackground,
|
||||
PointerLerpFactor,
|
||||
ScreenRenderDown,
|
||||
ScrollSpeed,
|
||||
SetsOnWatch,
|
||||
SpaceDragMultiplier,
|
||||
SpaceDragUnlocked,
|
||||
SpaceRotateUnlocked,
|
||||
Clock12h,
|
||||
HideUsername,
|
||||
OpaqueBackground,
|
||||
UiAnimationSpeed,
|
||||
UiGradientIntensity,
|
||||
UiRoundMultiplier,
|
||||
UprightScreenFix,
|
||||
UsePassthrough,
|
||||
UseSkybox,
|
||||
XrClickSensitivity,
|
||||
XrClickSensitivityRelease,
|
||||
XwaylandByDefault,
|
||||
CaptureMethod,
|
||||
KeyboardMiddleClick,
|
||||
HandsfreePointer,
|
||||
}
|
||||
|
||||
impl SettingType {
|
||||
@@ -309,6 +317,9 @@ impl SettingType {
|
||||
Self::HandsfreePointer => {
|
||||
config.handsfree_pointer = wlx_common::config::HandsfreePointer::from_str(value).expect("Invalid enum value!")
|
||||
}
|
||||
Self::Language => {
|
||||
config.language = Some(wlx_common::locale::Language::from_str(value).expect("Invalid enum value!"))
|
||||
}
|
||||
_ => panic!("Requested enum for non-enum SettingType"),
|
||||
}
|
||||
}
|
||||
@@ -318,6 +329,10 @@ impl SettingType {
|
||||
Self::CaptureMethod => Self::get_enum_title_inner(config.capture_method),
|
||||
Self::KeyboardMiddleClick => Self::get_enum_title_inner(config.keyboard_middle_click_mode),
|
||||
Self::HandsfreePointer => Self::get_enum_title_inner(config.handsfree_pointer),
|
||||
Self::Language => match &config.language {
|
||||
Some(lang) => Self::get_enum_title_inner(*lang),
|
||||
None => Translation::from_translation_key("APP_SETTINGS.OPTION.AUTO"),
|
||||
},
|
||||
_ => panic!("Requested enum for non-enum SettingType"),
|
||||
}
|
||||
}
|
||||
@@ -341,70 +356,71 @@ impl SettingType {
|
||||
}
|
||||
|
||||
/// Ok is translation, Err is raw text
|
||||
/// `match` sorted alphabetically
|
||||
fn get_translation(self) -> Result<&'static str, &'static str> {
|
||||
match self {
|
||||
Self::UiAnimationSpeed => Ok("APP_SETTINGS.ANIMATION_SPEED"),
|
||||
Self::UiGradientIntensity => Ok("APP_SETTINGS.UI_GRADIENT_INTENSITY"),
|
||||
Self::UiRoundMultiplier => Ok("APP_SETTINGS.ROUND_MULTIPLIER"),
|
||||
Self::InvertScrollDirectionX => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_X"),
|
||||
Self::InvertScrollDirectionY => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_Y"),
|
||||
Self::ScrollSpeed => Ok("APP_SETTINGS.SCROLL_SPEED"),
|
||||
Self::LongPressDuration => Ok("APP_SETTINGS.LONG_PRESS_DURATION"),
|
||||
Self::NotificationsEnabled => Ok("APP_SETTINGS.NOTIFICATIONS_ENABLED"),
|
||||
Self::NotificationsSoundEnabled => Ok("APP_SETTINGS.NOTIFICATIONS_SOUND_ENABLED"),
|
||||
Self::KeyboardSoundEnabled => Ok("APP_SETTINGS.KEYBOARD_SOUND_ENABLED"),
|
||||
Self::UprightScreenFix => Ok("APP_SETTINGS.UPRIGHT_SCREEN_FIX"),
|
||||
Self::DoubleCursorFix => Ok("APP_SETTINGS.DOUBLE_CURSOR_FIX"),
|
||||
Self::SetsOnWatch => Ok("APP_SETTINGS.SETS_ON_WATCH"),
|
||||
Self::HideGrabHelp => Ok("APP_SETTINGS.HIDE_GRAB_HELP"),
|
||||
Self::XrClickSensitivity => Ok("APP_SETTINGS.XR_CLICK_SENSITIVITY"),
|
||||
Self::XrClickSensitivityRelease => Ok("APP_SETTINGS.XR_CLICK_SENSITIVITY_RELEASE"),
|
||||
Self::AllowSliding => Ok("APP_SETTINGS.ALLOW_SLIDING"),
|
||||
Self::ClickFreezeTimeMs => Ok("APP_SETTINGS.CLICK_FREEZE_TIME_MS"),
|
||||
Self::FocusFollowsMouseMode => Ok("APP_SETTINGS.FOCUS_FOLLOWS_MOUSE_MODE"),
|
||||
Self::LeftHandedMouse => Ok("APP_SETTINGS.LEFT_HANDED_MOUSE"),
|
||||
Self::BlockGameInput => Ok("APP_SETTINGS.BLOCK_GAME_INPUT"),
|
||||
Self::BlockGameInputIgnoreWatch => Ok("APP_SETTINGS.BLOCK_GAME_INPUT_IGNORE_WATCH"),
|
||||
Self::BlockPosesOnKbdInteraction => Ok("APP_SETTINGS.BLOCK_POSES_ON_KBD_INTERACTION"),
|
||||
Self::SpaceDragMultiplier => Ok("APP_SETTINGS.SPACE_DRAG_MULTIPLIER"),
|
||||
Self::UseSkybox => Ok("APP_SETTINGS.USE_SKYBOX"),
|
||||
Self::UsePassthrough => Ok("APP_SETTINGS.USE_PASSTHROUGH"),
|
||||
Self::ScreenRenderDown => Ok("APP_SETTINGS.SCREEN_RENDER_DOWN"),
|
||||
Self::CaptureMethod => Ok("APP_SETTINGS.CAPTURE_METHOD"),
|
||||
Self::ClickFreezeTimeMs => Ok("APP_SETTINGS.CLICK_FREEZE_TIME_MS"),
|
||||
Self::Clock12h => Ok("APP_SETTINGS.CLOCK_12H"),
|
||||
Self::DoubleCursorFix => Ok("APP_SETTINGS.DOUBLE_CURSOR_FIX"),
|
||||
Self::FocusFollowsMouseMode => Ok("APP_SETTINGS.FOCUS_FOLLOWS_MOUSE_MODE"),
|
||||
Self::HandsfreePointer => Ok("APP_SETTINGS.HANDSFREE_POINTER"),
|
||||
Self::HideGrabHelp => Ok("APP_SETTINGS.HIDE_GRAB_HELP"),
|
||||
Self::HideUsername => Ok("APP_SETTINGS.HIDE_USERNAME"),
|
||||
Self::InvertScrollDirectionX => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_X"),
|
||||
Self::InvertScrollDirectionY => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_Y"),
|
||||
Self::KeyboardMiddleClick => Ok("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK"),
|
||||
Self::KeyboardSoundEnabled => Ok("APP_SETTINGS.KEYBOARD_SOUND_ENABLED"),
|
||||
Self::Language => Ok("APP_SETTINGS.LANGUAGE"),
|
||||
Self::LeftHandedMouse => Ok("APP_SETTINGS.LEFT_HANDED_MOUSE"),
|
||||
Self::LongPressDuration => Ok("APP_SETTINGS.LONG_PRESS_DURATION"),
|
||||
Self::NotificationsEnabled => Ok("APP_SETTINGS.NOTIFICATIONS_ENABLED"),
|
||||
Self::NotificationsSoundEnabled => Ok("APP_SETTINGS.NOTIFICATIONS_SOUND_ENABLED"),
|
||||
Self::OpaqueBackground => Ok("APP_SETTINGS.OPAQUE_BACKGROUND"),
|
||||
Self::PointerLerpFactor => Ok("APP_SETTINGS.POINTER_LERP_FACTOR"),
|
||||
Self::ScreenRenderDown => Ok("APP_SETTINGS.SCREEN_RENDER_DOWN"),
|
||||
Self::ScrollSpeed => Ok("APP_SETTINGS.SCROLL_SPEED"),
|
||||
Self::SetsOnWatch => Ok("APP_SETTINGS.SETS_ON_WATCH"),
|
||||
Self::SpaceDragMultiplier => Ok("APP_SETTINGS.SPACE_DRAG_MULTIPLIER"),
|
||||
Self::SpaceDragUnlocked => Ok("APP_SETTINGS.SPACE_DRAG_UNLOCKED"),
|
||||
Self::SpaceRotateUnlocked => Ok("APP_SETTINGS.SPACE_ROTATE_UNLOCKED"),
|
||||
Self::Clock12h => Ok("APP_SETTINGS.CLOCK_12H"),
|
||||
Self::HideUsername => Ok("APP_SETTINGS.HIDE_USERNAME"),
|
||||
Self::OpaqueBackground => Ok("APP_SETTINGS.OPAQUE_BACKGROUND"),
|
||||
Self::UiAnimationSpeed => Ok("APP_SETTINGS.ANIMATION_SPEED"),
|
||||
Self::UiGradientIntensity => Ok("APP_SETTINGS.UI_GRADIENT_INTENSITY"),
|
||||
Self::UiRoundMultiplier => Ok("APP_SETTINGS.ROUND_MULTIPLIER"),
|
||||
Self::UprightScreenFix => Ok("APP_SETTINGS.UPRIGHT_SCREEN_FIX"),
|
||||
Self::UsePassthrough => Ok("APP_SETTINGS.USE_PASSTHROUGH"),
|
||||
Self::UseSkybox => Ok("APP_SETTINGS.USE_SKYBOX"),
|
||||
Self::XrClickSensitivity => Ok("APP_SETTINGS.XR_CLICK_SENSITIVITY"),
|
||||
Self::XrClickSensitivityRelease => Ok("APP_SETTINGS.XR_CLICK_SENSITIVITY_RELEASE"),
|
||||
Self::XwaylandByDefault => Ok("APP_SETTINGS.XWAYLAND_BY_DEFAULT"),
|
||||
Self::CaptureMethod => Ok("APP_SETTINGS.CAPTURE_METHOD"),
|
||||
Self::KeyboardMiddleClick => Ok("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK"),
|
||||
Self::HandsfreePointer => Ok("APP_SETTINGS.HANDSFREE_POINTER"),
|
||||
}
|
||||
}
|
||||
|
||||
/// `match` sorted alphabetically
|
||||
fn get_tooltip(self) -> Option<&'static str> {
|
||||
match self {
|
||||
Self::UprightScreenFix => Some("APP_SETTINGS.UPRIGHT_SCREEN_FIX_HELP"),
|
||||
Self::DoubleCursorFix => Some("APP_SETTINGS.DOUBLE_CURSOR_FIX_HELP"),
|
||||
Self::XrClickSensitivity => Some("APP_SETTINGS.XR_CLICK_SENSITIVITY_HELP"),
|
||||
Self::XrClickSensitivityRelease => Some("APP_SETTINGS.XR_CLICK_SENSITIVITY_RELEASE_HELP"),
|
||||
Self::FocusFollowsMouseMode => Some("APP_SETTINGS.FOCUS_FOLLOWS_MOUSE_MODE_HELP"),
|
||||
Self::LeftHandedMouse => Some("APP_SETTINGS.LEFT_HANDED_MOUSE_HELP"),
|
||||
Self::BlockGameInput => Some("APP_SETTINGS.BLOCK_GAME_INPUT_HELP"),
|
||||
Self::BlockGameInputIgnoreWatch => Some("APP_SETTINGS.BLOCK_GAME_INPUT_IGNORE_WATCH_HELP"),
|
||||
Self::BlockPosesOnKbdInteraction => Some("APP_SETTINGS.BLOCK_POSES_ON_KBD_INTERACTION_HELP"),
|
||||
Self::UseSkybox => Some("APP_SETTINGS.USE_SKYBOX_HELP"),
|
||||
Self::UsePassthrough => Some("APP_SETTINGS.USE_PASSTHROUGH_HELP"),
|
||||
Self::ScreenRenderDown => Some("APP_SETTINGS.SCREEN_RENDER_DOWN_HELP"),
|
||||
Self::CaptureMethod => Some("APP_SETTINGS.CAPTURE_METHOD_HELP"),
|
||||
Self::KeyboardMiddleClick => Some("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK_HELP"),
|
||||
Self::DoubleCursorFix => Some("APP_SETTINGS.DOUBLE_CURSOR_FIX_HELP"),
|
||||
Self::HandsfreePointer => Some("APP_SETTINGS.HANDSFREE_POINTER_HELP"),
|
||||
Self::KeyboardMiddleClick => Some("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK_HELP"),
|
||||
Self::LeftHandedMouse => Some("APP_SETTINGS.LEFT_HANDED_MOUSE_HELP"),
|
||||
Self::ScreenRenderDown => Some("APP_SETTINGS.SCREEN_RENDER_DOWN_HELP"),
|
||||
Self::UprightScreenFix => Some("APP_SETTINGS.UPRIGHT_SCREEN_FIX_HELP"),
|
||||
Self::UsePassthrough => Some("APP_SETTINGS.USE_PASSTHROUGH_HELP"),
|
||||
Self::UseSkybox => Some("APP_SETTINGS.USE_SKYBOX_HELP"),
|
||||
Self::XrClickSensitivity => Some("APP_SETTINGS.XR_CLICK_SENSITIVITY_HELP"),
|
||||
Self::XrClickSensitivityRelease => Some("APP_SETTINGS.XR_CLICK_SENSITIVITY_RELEASE_HELP"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: incorporate this
|
||||
fn requires_restart(self) -> bool {
|
||||
match self {
|
||||
Self::UiAnimationSpeed
|
||||
@@ -412,6 +428,7 @@ impl SettingType {
|
||||
| Self::UprightScreenFix
|
||||
| Self::DoubleCursorFix
|
||||
| Self::ScreenRenderDown
|
||||
| Self::Language
|
||||
| Self::CaptureMethod => true,
|
||||
_ => false,
|
||||
}
|
||||
@@ -426,6 +443,42 @@ impl SettingType {
|
||||
}
|
||||
}
|
||||
|
||||
// creates a simple div with horizontal, centered flow
|
||||
fn horiz_cell(layout: &mut Layout, parent: WidgetID) -> anyhow::Result<WidgetID> {
|
||||
let (pair, _) = layout.add_child(
|
||||
parent,
|
||||
WidgetDiv::create(),
|
||||
taffy::Style {
|
||||
flex_direction: taffy::FlexDirection::Row,
|
||||
align_items: Some(taffy::AlignItems::Center),
|
||||
gap: length(8.0),
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(pair.id)
|
||||
}
|
||||
|
||||
fn mount_requires_restart(layout: &mut Layout, parent: WidgetID) -> anyhow::Result<()> {
|
||||
let content = Translation::from_translation_key("APP_SETTINGS.REQUIRES_RESTART");
|
||||
let label = WidgetLabel::create(
|
||||
&mut layout.state.globals.get(),
|
||||
WidgetLabelParams {
|
||||
content,
|
||||
style: TextStyle {
|
||||
wrap: false,
|
||||
color: Some(drawing::Color::new(1.0, 0.5, 0.5, 1.0)),
|
||||
weight: Some(FontWeight::Bold),
|
||||
size: Some(10.0),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
layout.add_child(parent, label, Default::default())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
macro_rules! category {
|
||||
($pe:expr, $root:expr, $translation:expr, $icon:expr) => {{
|
||||
let id = $pe.idx.to_string();
|
||||
@@ -464,9 +517,15 @@ macro_rules! checkbox {
|
||||
let checked = if *$setting.mut_bool($mp.config) { "1" } else { "0" };
|
||||
params.insert(Rc::from("checked"), Rc::from(checked));
|
||||
|
||||
let id_cell = horiz_cell($mp.layout, $root)?;
|
||||
|
||||
$mp
|
||||
.parser_state
|
||||
.instantiate_template($mp.doc_params, "CheckBoxSetting", $mp.layout, $root, params)?;
|
||||
.instantiate_template($mp.doc_params, "CheckBoxSetting", $mp.layout, id_cell, params)?;
|
||||
|
||||
if $setting.requires_restart() {
|
||||
mount_requires_restart($mp.layout, id_cell)?;
|
||||
}
|
||||
|
||||
let checkbox = $mp.parser_state.fetch_component_as::<ComponentCheckbox>(&id)?;
|
||||
checkbox.on_toggle(Box::new({
|
||||
@@ -502,9 +561,15 @@ macro_rules! slider_f32 {
|
||||
params.insert(Rc::from("max"), Rc::from($max.to_string()));
|
||||
params.insert(Rc::from("step"), Rc::from($step.to_string()));
|
||||
|
||||
let id_cell = horiz_cell($mp.layout, $root)?;
|
||||
|
||||
$mp
|
||||
.parser_state
|
||||
.instantiate_template($mp.doc_params, "SliderSetting", $mp.layout, $root, params)?;
|
||||
.instantiate_template($mp.doc_params, "SliderSetting", $mp.layout, id_cell, params)?;
|
||||
|
||||
if $setting.requires_restart() {
|
||||
mount_requires_restart($mp.layout, id_cell)?;
|
||||
}
|
||||
|
||||
let slider = $mp.parser_state.fetch_component_as::<ComponentSlider>(&id)?;
|
||||
slider.on_value_changed(Box::new({
|
||||
@@ -534,6 +599,8 @@ macro_rules! slider_i32 {
|
||||
params.insert(Rc::from("tooltip"), Rc::from(tooltip));
|
||||
}
|
||||
|
||||
let id_cell = horiz_cell($mp.layout, $root)?;
|
||||
|
||||
let value = $setting.mut_i32($mp.config).to_string();
|
||||
params.insert(Rc::from("value"), Rc::from(value));
|
||||
params.insert(Rc::from("min"), Rc::from($min.to_string()));
|
||||
@@ -542,7 +609,11 @@ macro_rules! slider_i32 {
|
||||
|
||||
$mp
|
||||
.parser_state
|
||||
.instantiate_template($mp.doc_params, "SliderSetting", $mp.layout, $root, params)?;
|
||||
.instantiate_template($mp.doc_params, "SliderSetting", $mp.layout, id_cell, params)?;
|
||||
|
||||
if $setting.requires_restart() {
|
||||
mount_requires_restart($mp.layout, id_cell)?;
|
||||
}
|
||||
|
||||
let slider = $mp.parser_state.fetch_component_as::<ComponentSlider>(&id)?;
|
||||
slider.on_value_changed(Box::new({
|
||||
@@ -556,7 +627,7 @@ macro_rules! slider_i32 {
|
||||
}
|
||||
|
||||
macro_rules! dropdown {
|
||||
($mp:expr, $root:expr, $setting:expr, $options:expr) => {
|
||||
($mp:expr /* `MacroParams` struct */, $root:expr, $setting:expr, $options:expr) => {
|
||||
let id = $mp.idx.to_string();
|
||||
$mp.idx += 1;
|
||||
|
||||
@@ -572,9 +643,15 @@ macro_rules! dropdown {
|
||||
params.insert(Rc::from("tooltip"), Rc::from(tooltip));
|
||||
}
|
||||
|
||||
let id_cell = horiz_cell($mp.layout, $root)?;
|
||||
|
||||
$mp
|
||||
.parser_state
|
||||
.instantiate_template($mp.doc_params, "DropdownButton", $mp.layout, $root, params)?;
|
||||
.instantiate_template($mp.doc_params, "DropdownButton", $mp.layout, id_cell, params)?;
|
||||
|
||||
if $setting.requires_restart() {
|
||||
mount_requires_restart($mp.layout, id_cell)?;
|
||||
}
|
||||
|
||||
let setting_str = $setting.as_ref();
|
||||
let title = $setting.get_enum_title($mp.config);
|
||||
@@ -709,6 +786,7 @@ impl<T> TabSettings<T> {
|
||||
match name {
|
||||
TabNameEnum::LookAndFeel => {
|
||||
let c = category!(mp, root, "APP_SETTINGS.LOOK_AND_FEEL", "dashboard/palette.svg")?;
|
||||
dropdown!(mp, c, SettingType::Language, wlx_common::locale::Language::VARIANTS);
|
||||
checkbox!(mp, c, SettingType::OpaqueBackground);
|
||||
checkbox!(mp, c, SettingType::HideUsername);
|
||||
checkbox!(mp, c, SettingType::HideGrabHelp);
|
||||
|
||||
@@ -12,6 +12,7 @@ use wgui::{
|
||||
layout::{Layout, LayoutParams, LayoutUpdateParams},
|
||||
parser::{ParseDocumentParams, ParserState},
|
||||
};
|
||||
use wlx_common::locale::WayVRLangProvider;
|
||||
|
||||
pub struct TestbedAny {
|
||||
pub layout: Layout,
|
||||
@@ -28,8 +29,11 @@ impl TestbedAny {
|
||||
AssetPath::BuiltIn(&format!("gui/{name}.xml"))
|
||||
};
|
||||
|
||||
let lang_provider = WayVRLangProvider::default();
|
||||
|
||||
let globals = WguiGlobals::new(
|
||||
assets,
|
||||
&lang_provider,
|
||||
wgui::globals::Defaults::default(),
|
||||
&WguiFontConfig::default(),
|
||||
PathBuf::new(), // cwd
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::testbed::{Testbed, TestbedUpdateParams};
|
||||
use dash_frontend::frontend::{self, FrontendUpdateParams};
|
||||
use wgui::layout::Layout;
|
||||
use wlx_common::dash_interface_emulated::DashInterfaceEmulated;
|
||||
use wlx_common::{dash_interface_emulated::DashInterfaceEmulated, locale::WayVRLangProvider};
|
||||
|
||||
pub struct TestbedDashboard {
|
||||
frontend: frontend::Frontend<()>,
|
||||
@@ -10,11 +10,13 @@ pub struct TestbedDashboard {
|
||||
impl TestbedDashboard {
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
let interface = DashInterfaceEmulated::new();
|
||||
let lang_provider = WayVRLangProvider::default();
|
||||
|
||||
let frontend = frontend::Frontend::new(
|
||||
frontend::InitParams {
|
||||
interface: Box::new(interface),
|
||||
has_monado: true,
|
||||
lang_provider: &lang_provider,
|
||||
},
|
||||
&mut (),
|
||||
)?;
|
||||
|
||||
@@ -27,6 +27,7 @@ use wgui::{
|
||||
window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra},
|
||||
},
|
||||
};
|
||||
use wlx_common::locale::WayVRLangProvider;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum TestbedTask {
|
||||
@@ -85,8 +86,11 @@ impl TestbedGeneric {
|
||||
}
|
||||
|
||||
pub fn new(assets: Box<assets::Asset>) -> anyhow::Result<Self> {
|
||||
let lang_provider = WayVRLangProvider::default();
|
||||
|
||||
let globals = WguiGlobals::new(
|
||||
assets,
|
||||
&lang_provider,
|
||||
wgui::globals::Defaults::default(),
|
||||
&WguiFontConfig::default(),
|
||||
PathBuf::new(), // cwd
|
||||
|
||||
@@ -10,6 +10,7 @@ use wlx_common::{
|
||||
SerializedWindowStates,
|
||||
},
|
||||
config_io,
|
||||
locale::Language,
|
||||
overlays::BackendAttribValue,
|
||||
};
|
||||
|
||||
@@ -143,6 +144,7 @@ pub struct AutoSettings {
|
||||
pub keyboard_middle_click_mode: AltModifier,
|
||||
pub autostart_apps: Vec<WvrProcessLaunchParams>,
|
||||
pub handsfree_pointer: HandsfreePointer,
|
||||
pub language: Option<Language>,
|
||||
}
|
||||
|
||||
fn get_settings_path() -> PathBuf {
|
||||
@@ -192,6 +194,7 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> {
|
||||
keyboard_middle_click_mode: config.keyboard_middle_click_mode,
|
||||
autostart_apps: config.autostart_apps.clone(),
|
||||
handsfree_pointer: config.handsfree_pointer,
|
||||
language: config.language,
|
||||
};
|
||||
|
||||
let json = serde_json::to_string_pretty(&conf).unwrap(); // want panic
|
||||
|
||||
@@ -17,6 +17,7 @@ use wgui::{
|
||||
};
|
||||
use wlx_common::{
|
||||
dash_interface::{self, DashInterface, RecenterMode},
|
||||
locale::WayVRLangProvider,
|
||||
overlays::{BackendAttrib, BackendAttribValue},
|
||||
};
|
||||
use wlx_common::{
|
||||
@@ -79,6 +80,7 @@ impl DashFrontend {
|
||||
let frontend = frontend::Frontend::new(
|
||||
frontend::InitParams {
|
||||
interface: Box::new(interface),
|
||||
lang_provider: &WayVRLangProvider::from_config(&app.session.config),
|
||||
has_monado: matches!(app.xr_backend, XrBackend::OpenXR),
|
||||
},
|
||||
app,
|
||||
|
||||
@@ -7,6 +7,7 @@ use wgui::{
|
||||
drawing, font_config::WguiFontConfig, gfx::WGfx, globals::WguiGlobals, parser::parse_color_hex,
|
||||
renderer_vk::context::SharedContext as WSharedContext,
|
||||
};
|
||||
use wlx_common::locale::WayVRLangProvider;
|
||||
use wlx_common::{
|
||||
audio,
|
||||
config::GeneralConfig,
|
||||
@@ -138,6 +139,8 @@ impl AppState {
|
||||
let mut desktop_finder = DesktopFinder::new();
|
||||
desktop_finder.refresh();
|
||||
|
||||
let lang_provider = WayVRLangProvider::from_config(&session.config);
|
||||
|
||||
Ok(Self {
|
||||
session,
|
||||
tasks,
|
||||
@@ -153,6 +156,7 @@ impl AppState {
|
||||
anchor_grabbed: false,
|
||||
wgui_globals: WguiGlobals::new(
|
||||
assets,
|
||||
&lang_provider,
|
||||
defaults,
|
||||
&WguiFontConfig::default(),
|
||||
get_config_file_path(&theme),
|
||||
|
||||
@@ -3,6 +3,8 @@ use std::ffi::OsStr;
|
||||
use std::io::Read;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
use crate::i18n::LangsList;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum AssetPath<'a> {
|
||||
WguiInternal(&'a str), // tied to internal wgui AssetProvider. Used internally
|
||||
@@ -83,6 +85,11 @@ impl Default for AssetPathOwned {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LangProvider {
|
||||
fn langs_list(&self) -> &dyn LangsList;
|
||||
fn forced_lang(&self) -> Option<&str>;
|
||||
}
|
||||
|
||||
pub trait AssetProvider {
|
||||
fn load_from_path(&mut self, path: &str) -> anyhow::Result<Vec<u8>>;
|
||||
fn load_from_path_gzip(&mut self, path: &str) -> anyhow::Result<Vec<u8>> {
|
||||
|
||||
@@ -10,7 +10,7 @@ use anyhow::Context;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::{
|
||||
assets::{AssetPath, AssetProvider},
|
||||
assets::{AssetPath, AssetProvider, LangProvider},
|
||||
assets_internal, drawing,
|
||||
font_config::{WguiFontConfig, WguiFontSystem},
|
||||
i18n::I18n,
|
||||
@@ -66,11 +66,12 @@ pub struct WguiGlobals(Rc<RefCell<Globals>>);
|
||||
impl WguiGlobals {
|
||||
pub fn new(
|
||||
mut assets_builtin: Box<dyn AssetProvider>,
|
||||
lang_provider: &dyn LangProvider,
|
||||
defaults: Defaults,
|
||||
font_config: &WguiFontConfig,
|
||||
asset_folder: PathBuf,
|
||||
) -> anyhow::Result<Self> {
|
||||
let i18n_builtin = I18n::new(&mut assets_builtin)?;
|
||||
let i18n_builtin = I18n::new(assets_builtin.as_mut(), lang_provider)?;
|
||||
let assets_internal = Box::new(assets_internal::AssetInternal {});
|
||||
|
||||
Ok(Self(Rc::new(RefCell::new(Globals {
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{fmt::Display, rc::Rc, str};
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use crate::assets::AssetProvider;
|
||||
use crate::assets::{AssetProvider, LangProvider};
|
||||
|
||||
// a string which optionally has translation key in it
|
||||
// it will hopefully support dynamic language changing soon
|
||||
@@ -63,50 +63,76 @@ pub struct Locale {
|
||||
matched: String,
|
||||
}
|
||||
|
||||
pub trait LangsList {
|
||||
fn all_locale(&self) -> &'static [&'static str]; // "en", "de", "es" (...)
|
||||
fn default_lang(&self) -> &'static str; // "en"
|
||||
}
|
||||
|
||||
impl Locale {
|
||||
pub fn all_locale() -> &'static [&'static str] {
|
||||
&["de", "en", "es", "ja", "it", "pl", "zh_CN"]
|
||||
}
|
||||
pub fn default_lang() -> &'static str {
|
||||
"en"
|
||||
}
|
||||
fn match_locale<'o>(lang: &str, region: Option<&str>, all_locales: &[&'o str]) -> &'o str {
|
||||
fn match_locale<'o>(
|
||||
default_lang: &'static str,
|
||||
lang: &str,
|
||||
region: Option<&str>,
|
||||
all_locales: &[&'o str],
|
||||
) -> &'o str {
|
||||
if let Some(region) = region {
|
||||
let locale_str = format!("{lang}_{region}");
|
||||
if let Some(locale) = all_locales.iter().find(|&&l| l == locale_str) {
|
||||
return locale;
|
||||
}
|
||||
log::warn!("Unsupported locale \"{locale_str}\", trying \"{lang}\".");
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(locale) = all_locales.iter().find(|&&l| l == lang) {
|
||||
return locale;
|
||||
}
|
||||
|
||||
|
||||
let prefix = format!("{lang}_");
|
||||
if let Some(locale) = all_locales.iter().find(|&&l| l.starts_with(&prefix)) {
|
||||
return locale;
|
||||
}
|
||||
|
||||
let locale = Self::default_lang();
|
||||
log::warn!("Unsupported language \"{lang}\", defaulting to \"{locale}\".");
|
||||
locale
|
||||
log::warn!("Unsupported language \"{lang}\", defaulting to \"{default_lang}\".");
|
||||
default_lang
|
||||
}
|
||||
pub fn new(lang: String, region: Option<String>) -> Self {
|
||||
let matched = Self::match_locale(&lang, region.as_deref(), Self::all_locale()).to_string();
|
||||
|
||||
pub fn new(langs_list: &dyn LangsList, lang: String, region: Option<String>) -> Self {
|
||||
let matched = Self::match_locale(
|
||||
langs_list.default_lang(),
|
||||
&lang,
|
||||
region.as_deref(),
|
||||
langs_list.all_locale(),
|
||||
)
|
||||
.to_string();
|
||||
Self { lang, region, matched }
|
||||
}
|
||||
pub fn parse_str(locale: &str) -> Self {
|
||||
let base = locale.split(|c| c == '.' || c == '@').next().unwrap_or(locale);
|
||||
let parts: Vec<&str> = base.split(|c| c == '_' || c == '-').collect();
|
||||
|
||||
pub fn parse_str(langs_list: &dyn LangsList, locale: &str) -> Self {
|
||||
let base = locale.split(['.', '@']).next().unwrap_or(locale);
|
||||
let parts: Vec<&str> = base.split(['_', '-']).collect();
|
||||
// Ensures the format is lang_REGION
|
||||
match parts.as_slice() {
|
||||
[lang, region, ..] => Self::new(lang.to_lowercase(), Some(region.to_uppercase())),
|
||||
[lang] if !lang.is_empty() => Self::new(lang.to_lowercase(), None),
|
||||
_ => Self::new("en".to_string(), None),
|
||||
[lang, region, ..] => Self::new(langs_list, lang.to_lowercase(), Some(region.to_uppercase())),
|
||||
[lang] if !lang.is_empty() => Self::new(langs_list, lang.to_lowercase(), None),
|
||||
_ => Self::new(langs_list, langs_list.default_lang().to_string(), None),
|
||||
}
|
||||
}
|
||||
pub fn from_env() -> Self {
|
||||
|
||||
pub fn from_env(lang_provider: &dyn LangProvider) -> Self {
|
||||
let default_lang = lang_provider.langs_list().default_lang();
|
||||
|
||||
// check if forced language is set
|
||||
if let Some(forced_lang) = lang_provider.forced_lang() {
|
||||
let matched =
|
||||
Self::match_locale(default_lang, forced_lang, None, lang_provider.langs_list().all_locale()).to_string();
|
||||
return Self {
|
||||
lang: forced_lang.to_string(),
|
||||
region: None,
|
||||
matched,
|
||||
};
|
||||
}
|
||||
|
||||
// fallback to environment variables
|
||||
use std::env;
|
||||
let vars = ["LC_ALL", "LC_MESSAGES", "LANG"];
|
||||
let full_locale = vars
|
||||
@@ -114,13 +140,10 @@ impl Locale {
|
||||
.find_map(|&v| env::var(v).ok())
|
||||
.filter(|v| !v.is_empty() && v != "C" && v != "POSIX")
|
||||
.unwrap_or_else(|| {
|
||||
log::warn!(
|
||||
"LC_ALL/LC_MESSAGES/LANG is not set, defaulting to \"{}\"",
|
||||
Self::default_lang()
|
||||
);
|
||||
Self::default_lang().to_string()
|
||||
log::warn!("LC_ALL/LC_MESSAGES/LANG is not set, defaulting to \"{default_lang}\"");
|
||||
default_lang.to_string()
|
||||
});
|
||||
Self::parse_str(&full_locale)
|
||||
Self::parse_str(lang_provider.langs_list(), &full_locale)
|
||||
}
|
||||
pub fn get_matched(&self) -> &str {
|
||||
&self.matched
|
||||
@@ -155,13 +178,13 @@ fn find_translation<'a>(translation: &str, mut val: &'a serde_json::Value) -> Op
|
||||
}
|
||||
|
||||
impl I18n {
|
||||
pub fn new(provider: &mut Box<dyn AssetProvider>) -> anyhow::Result<Self> {
|
||||
let locale = Locale::from_env();
|
||||
pub fn new(asset_provider: &mut dyn AssetProvider, lang_provider: &dyn LangProvider) -> anyhow::Result<Self> {
|
||||
let locale = Locale::from_env(lang_provider);
|
||||
log::info!("Guessed system language: {locale}");
|
||||
|
||||
let data_english = provider.load_from_path("lang/en.json")?;
|
||||
let data_english = asset_provider.load_from_path("lang/en.json")?;
|
||||
let path = format!("lang/{}.json", locale.get_matched());
|
||||
let data_translated = provider
|
||||
let data_translated = asset_provider
|
||||
.load_from_path(&path)
|
||||
.with_context(|| path.clone())
|
||||
.context("Could not load translation file")?;
|
||||
@@ -189,7 +212,7 @@ impl I18n {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_locale(&self) -> &Locale {
|
||||
pub const fn get_locale(&self) -> &Locale {
|
||||
&self.locale
|
||||
}
|
||||
|
||||
@@ -202,7 +225,7 @@ impl I18n {
|
||||
}
|
||||
|
||||
if let Some(translated_fallback) = find_translation(translation_key, &self.json_root_fallback) {
|
||||
log::warn!("missing translation for key \"{translation_key}\", using \"en\" instead");
|
||||
log::warn!("missing translation for key \"{translation_key}\", using fallback instead");
|
||||
return Rc::from(format_translated(translated_fallback, sections));
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,7 @@ use strum::{AsRefStr, EnumProperty, EnumString, VariantArray};
|
||||
use wayvr_ipc::packet_client::WvrProcessLaunchParams;
|
||||
|
||||
use crate::{
|
||||
astr_containers::{AStrMap, AStrSet},
|
||||
overlays::{BackendAttribValue, ToastDisplayMethod, ToastTopic},
|
||||
windowing::OverlayWindowState,
|
||||
astr_containers::{AStrMap, AStrSet}, locale::{self}, overlays::{BackendAttribValue, ToastDisplayMethod, ToastTopic}, windowing::OverlayWindowState
|
||||
};
|
||||
|
||||
pub type PwTokenMap = AStrMap<String>;
|
||||
@@ -140,6 +138,8 @@ const fn def_max_height() -> u16 {
|
||||
1440
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct GeneralConfig {
|
||||
#[serde(default = "def_theme_path")]
|
||||
@@ -151,6 +151,8 @@ pub struct GeneralConfig {
|
||||
pub color_faded: Option<String>,
|
||||
pub color_background: Option<String>,
|
||||
|
||||
pub language: Option<locale::Language>, // auto-detected at runtime if unset
|
||||
|
||||
#[serde(default = "def_one")]
|
||||
#[serde(alias = "ui_animation_speed", alias = "animation_speed" /* old name */)]
|
||||
pub ui_animation_speed: f32,
|
||||
|
||||
@@ -8,6 +8,7 @@ pub mod dash_interface;
|
||||
pub mod dash_interface_emulated;
|
||||
pub mod desktop_finder;
|
||||
mod handle;
|
||||
pub mod locale;
|
||||
pub mod overlays;
|
||||
pub mod timestep;
|
||||
pub mod windowing;
|
||||
|
||||
88
wlx-common/src/locale.rs
Normal file
88
wlx-common/src/locale.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{AsRefStr, EnumProperty, EnumString, VariantArray};
|
||||
|
||||
use crate::config::GeneralConfig;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, AsRefStr, EnumString, EnumProperty, VariantArray)]
|
||||
pub enum Language {
|
||||
#[strum(props(Text = "English"))]
|
||||
English,
|
||||
#[strum(props(Text = "Polski"))]
|
||||
Polish,
|
||||
#[strum(props(Text = "日本語"))]
|
||||
Japanese,
|
||||
#[strum(props(Text = "German"))]
|
||||
German,
|
||||
#[strum(props(Text = "Italiano"))]
|
||||
Italian,
|
||||
#[strum(props(Text = "简体中文"))]
|
||||
ChineseSimplified,
|
||||
#[strum(props(Text = "Español"))]
|
||||
Spanish,
|
||||
}
|
||||
|
||||
impl Language {
|
||||
pub const fn code(&self) -> &'static str {
|
||||
match self {
|
||||
Language::English => "en",
|
||||
Language::Polish => "pl",
|
||||
Language::Japanese => "ja",
|
||||
Language::German => "de",
|
||||
Language::Italian => "it",
|
||||
Language::ChineseSimplified => "zh_CN",
|
||||
Language::Spanish => "es",
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn get_default() -> Self {
|
||||
Self::English
|
||||
}
|
||||
|
||||
pub const fn all_codes() -> &'static [&'static str] {
|
||||
&["en", "pl", "ja", "de", "it", "zh_CN", "es"]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WayVRLangsList {}
|
||||
|
||||
impl wgui::i18n::LangsList for WayVRLangsList {
|
||||
fn all_locale(&self) -> &'static [&'static str] {
|
||||
Language::all_codes()
|
||||
}
|
||||
|
||||
fn default_lang(&self) -> &'static str {
|
||||
Language::get_default().code()
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
const G_LANGS_LIST: WayVRLangsList = WayVRLangsList {};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WayVRLangProvider {
|
||||
forced_lang: Option<Rc<str>>,
|
||||
}
|
||||
|
||||
impl wgui::assets::LangProvider for WayVRLangProvider {
|
||||
fn langs_list(&self) -> &dyn wgui::i18n::LangsList {
|
||||
&G_LANGS_LIST
|
||||
}
|
||||
|
||||
fn forced_lang(&self) -> Option<&str> {
|
||||
self.forced_lang.as_ref().map(|lang| lang.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl WayVRLangProvider {
|
||||
pub fn from_config(config: &GeneralConfig) -> Self {
|
||||
if let Some(lang) = &config.language {
|
||||
return Self {
|
||||
forced_lang: Some(lang.code().into()),
|
||||
};
|
||||
}
|
||||
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user