fix(locale): stabilize web options

Embed debug locales and expand the WebAssembly stack so modal construction remains responsive. Keep the Options scrollbar in its own lane and refine Turkish CAD terminology.
This commit is contained in:
Hakan Seven committed 2026-08-02 23:14:48 +03:00
1 parent 3e6693b93f
commit 40b41f9814
6 files changed
+141 -91

No files matched your search

+36 -8
View File
@@ -7,8 +7,8 @@ use i18n_embed::DesktopLanguageRequester;
#[cfg(target_arch = "wasm32")]
use i18n_embed::WebLanguageRequester;
use rust_embed::RustEmbed;
use std::borrow::Cow;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::sync::OnceLock;
#[path = "locale_catalog.rs"]
@@ -69,11 +69,9 @@ impl Language {
Language::ZhCn => vec!["zh-CN".parse().expect("valid locale")],
}
}
}
impl std::fmt::Display for Language {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let label = match self {
pub fn label(self) -> String {
match self {
Language::System => crate::tr!("language-system"),
Language::EnUs => crate::tr!("language-english"),
Language::TrTr => crate::tr!("language-turkish"),
@@ -83,16 +81,36 @@ impl std::fmt::Display for Language {
Language::HiIn => crate::tr!("language-hindi"),
Language::RuRu => crate::tr!("language-russian"),
Language::ZhCn => crate::tr!("language-chinese-simplified"),
};
}
}
}
impl std::fmt::Display for Language {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let label = self.label();
f.write_str(&label)
}
}
fn system_languages() -> Vec<i18n_embed::unic_langid::LanguageIdentifier> {
#[cfg(target_arch = "wasm32")]
let requested = WebLanguageRequester::requested_languages();
let mut requested = WebLanguageRequester::requested_languages();
#[cfg(not(target_arch = "wasm32"))]
let requested = DesktopLanguageRequester::requested_languages();
// `navigator.languages` may be empty in privacy-restricted browser
// contexts. The singular preference is still exposed by mainstream
// browsers, so keep it as the first fallback before English.
#[cfg(target_arch = "wasm32")]
if requested.is_empty() {
if let Some(language) = web_sys::window()
.and_then(|window| window.navigator().language())
.and_then(|language| language.parse().ok())
{
requested.push(language);
}
}
requested
}
@@ -104,7 +122,17 @@ fn load_language(
if requested.is_empty() {
requested.push(loader.fallback_language().clone());
}
i18n_embed::select(loader, &Localizations, &requested).map(|_| ())
let selected = i18n_embed::select(loader, &Localizations, &requested)?;
#[cfg(target_arch = "wasm32")]
if let Some(language) = selected.first() {
if let Some(root) = web_sys::window()
.and_then(|window| window.document())
.and_then(|document| document.document_element())
{
let _ = root.set_attribute("lang", &language.to_string());
}
}
Ok(())
}
pub fn loader() -> &'static FluentLanguageLoader {
+1 -10
View File
@@ -322,16 +322,7 @@ pub fn modal<'a>(
// The first stack layer dictates its intrinsic size. Its top spacer reserves
// room for the title, while the actual title bar and resize grip overlay it
// without influencing the modal dimensions.
// Iced draws vertical scrollbars inside a scrollable's bounds. Reserve a
// shared end gutter at the modal boundary so a scrollbar cannot cover the
// last control column in Options or any other dialog.
let measured_content = container(sensor(content).on_resize(Message::ModalContentResized))
.padding(Padding {
top: 0.0,
right: 12.0,
bottom: 0.0,
left: 0.0,
});
let measured_content = sensor(content).on_resize(Message::ModalContentResized);
let body_base = column![
Space::new().height(Length::Fixed(24.0)),
measured_content,
+40 -18
View File
@@ -7,17 +7,14 @@ use iced::{Background, Border, Element, Theme};
use std::fmt;
#[derive(Clone, Debug, PartialEq, Eq)]
struct ThemeChoice(String);
struct Labelled<T> {
value: T,
label: String,
}
impl fmt::Display for ThemeChoice {
impl<T> fmt::Display for Labelled<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let label = match self.0.as_str() {
"Light" => crate::t!("Light"),
"Dark" => crate::t!("Dark"),
"Custom" => crate::t!("Custom"),
_ => std::borrow::Cow::Borrowed(self.0.as_str()),
};
f.write_str(label.as_ref())
f.write_str(&self.label)
}
}
@@ -37,9 +34,32 @@ pub fn view_window<'a>(
.iter()
.map(ToString::to_string)
.chain(std::iter::once("Custom".to_string()))
.map(ThemeChoice)
.map(|value| Labelled {
label: match value.as_str() {
"Light" => crate::t!("Light").into_owned(),
"Dark" => crate::t!("Dark").into_owned(),
"Custom" => crate::t!("Custom").into_owned(),
_ => value.clone(),
},
value,
})
.collect::<Vec<_>>();
let selected_theme = Some(ThemeChoice(ui_theme.name.clone()));
let selected_theme = theme_options
.iter()
.find(|choice| choice.value == ui_theme.name)
.cloned();
let language_options = crate::i18n::Language::ALL
.into_iter()
.map(|value| Labelled {
label: value.label(),
value,
})
.collect::<Vec<_>>();
let selected_language = language_options
.iter()
.find(|choice| choice.value == language)
.cloned();
let palette = ui_theme.palette.to_iced();
let colors = [
@@ -89,11 +109,11 @@ pub fn view_window<'a>(
row![
text(crate::tr!("options-language-label")).size(12).width(150),
iced::widget::pick_list(
Some(language),
crate::i18n::Language::ALL,
|value| value.to_string(),
selected_language,
language_options,
|choice| choice.label.clone(),
)
.on_select(Message::LanguageChanged)
.on_select(|choice| Message::LanguageChanged(choice.value))
.width(sizing.width),
]
.spacing(12)
@@ -125,9 +145,9 @@ pub fn view_window<'a>(
iced::widget::pick_list(
selected_theme,
theme_options,
|value| value.to_string(),
|choice| choice.label.clone(),
)
.on_select(|choice| Message::OptionsThemeChanged(choice.0))
.on_select(|choice| Message::OptionsThemeChanged(choice.value))
.width(sizing.width),
]
.spacing(12)
@@ -143,7 +163,9 @@ pub fn view_window<'a>(
.width(sizing.width);
let body = column![
scrollable(content).height(sizing.height),
// Keep the scrollbar in its own lane instead of floating over the
// controls at the trailing edge of the Options content.
scrollable(content).spacing(8).height(sizing.height),
Space::new().height(12),
row![Space::new().width(sizing.width), close],
]