mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-27 11:37:13 -06:00
@@ -667,11 +667,40 @@ impl eframe::App for WebApp {
|
||||
}
|
||||
}
|
||||
|
||||
/// "Auto" follows the browser's languages (issue #2085): the first of `navigator.languages` (else
|
||||
/// `navigator.language`) that LightCraft ships becomes the UI's default. Every step is fallible
|
||||
/// (as in [`crate::safety::acquire_tab_lock`]): without a usable navigator Auto stays English,
|
||||
/// as before.
|
||||
fn install_browser_language() {
|
||||
let Ok(navigator) = js_sys::Reflect::get(&js_sys::global(), &"navigator".into()) else { return };
|
||||
let mut tags: Vec<String> = Vec::new();
|
||||
if let Ok(languages) = js_sys::Reflect::get(&navigator, &"languages".into())
|
||||
&& let Some(languages) = languages.dyn_ref::<js_sys::Array>()
|
||||
{
|
||||
for tag in languages.iter() {
|
||||
if let Some(tag) = tag.as_string() {
|
||||
tags.push(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
if tags.is_empty()
|
||||
&& let Ok(language) = js_sys::Reflect::get(&navigator, &"language".into())
|
||||
&& let Some(language) = language.as_string()
|
||||
{
|
||||
tags.push(language);
|
||||
}
|
||||
if let Some(language) = lightcraft_ui_egui::i18n::locale_from_tags(&tags) {
|
||||
lightcraft_ui_egui::i18n::set_default_language(language);
|
||||
log::info!("lightcraft: default language {language:?} from the browser's languages");
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the app with the separately fetched Simplified Chinese font (empty in font-free builds).
|
||||
#[wasm_bindgen]
|
||||
pub fn start(chinese_font: Vec<u8>) {
|
||||
eframe::WebLogger::init(log::LevelFilter::Info).ok();
|
||||
safety::install_panic_hook();
|
||||
install_browser_language();
|
||||
log::info!("lightcraft: wasm instantiated at {:.0} ms", perf_now());
|
||||
if chinese_font.is_empty() {
|
||||
log::warn!("lightcraft: no Simplified Chinese font supplied; Chinese-only glyphs will show as boxes");
|
||||
|
||||
@@ -149,11 +149,33 @@ impl<'de> Deserialize<'de> for Locale {
|
||||
|
||||
thread_local! {
|
||||
static LOCALE: std::cell::Cell<Locale> = const { std::cell::Cell::new(Locale::En) };
|
||||
/// The host's answer for the "Auto" language ([`set_default_language`]).
|
||||
static DEFAULT_LANGUAGE: std::cell::Cell<Option<Locale>> = const { std::cell::Cell::new(None) };
|
||||
}
|
||||
|
||||
/// The UI language from the environment (`LIGHTCRAFT_LANGUAGE=zh-hans`), for headless runs.
|
||||
/// Install the language the "Auto" setting resolves to when nothing is stored. The browser host
|
||||
/// calls this once at startup with the browser's own languages ([`locale_from_tags`]); the
|
||||
/// desktop host doesn't: there Auto keeps following the environment. Must run before the UI state
|
||||
/// is first constructed.
|
||||
pub fn set_default_language(language: Locale) {
|
||||
DEFAULT_LANGUAGE.with(|slot| slot.set(Some(language)));
|
||||
}
|
||||
|
||||
/// The first of `tags` (BCP-47, most preferred first, as `navigator.languages` lists them) that
|
||||
/// names a shipped language; None when none does.
|
||||
pub fn locale_from_tags(tags: &[String]) -> Option<Locale> {
|
||||
tags.iter().find_map(|tag| Locale::parse_tag(tag))
|
||||
}
|
||||
|
||||
/// The UI language "Auto" resolves to: the environment's (`LIGHTCRAFT_LANGUAGE=zh-hans`, for
|
||||
/// headless runs), or the host-installed default ([`set_default_language`], the browser's languages
|
||||
/// on the web), else English.
|
||||
pub fn default_language() -> Locale {
|
||||
std::env::var("LIGHTCRAFT_LANGUAGE").ok().and_then(|value| Locale::parse_tag(&value)).unwrap_or(Locale::En)
|
||||
std::env::var("LIGHTCRAFT_LANGUAGE")
|
||||
.ok()
|
||||
.and_then(|value| Locale::parse_tag(&value))
|
||||
.or_else(|| DEFAULT_LANGUAGE.with(std::cell::Cell::get))
|
||||
.unwrap_or(Locale::En)
|
||||
}
|
||||
|
||||
pub fn set_language(language: Locale) {
|
||||
@@ -636,6 +658,20 @@ mod tests {
|
||||
assert_eq!(Locale::parse_tag("xx"), None);
|
||||
}
|
||||
|
||||
/// The web host's "Auto" (issue #2085): the browser's `navigator.languages` in order, first
|
||||
/// shipped language wins, unsupported tags are skipped, an empty or foreign list stays English
|
||||
/// ([`default_language`] fallback).
|
||||
#[test]
|
||||
fn locale_from_tags_takes_the_first_shipped_language() {
|
||||
assert_eq!(locale_from_tags(&[]), None);
|
||||
assert_eq!(locale_from_tags(&["xx-XX".into()]), None);
|
||||
assert_eq!(locale_from_tags(&["it-IT".into()]), Some(Locale::It));
|
||||
assert_eq!(locale_from_tags(&["xx".into(), "de-DE".into(), "fr".into()]), Some(Locale::De));
|
||||
assert_eq!(locale_from_tags(&["zh-TW".into(), "en".into()]), Some(Locale::ZhHant));
|
||||
assert_eq!(locale_from_tags(&["ja_JP.UTF-8".into(), "en".into()]), Some(Locale::Ja));
|
||||
assert_eq!(locale_from_tags(&["pt".into(), "pt-BR".into()]), Some(Locale::PtBr));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn switching_language_translates_and_restores() {
|
||||
set_language(Locale::ZhHans);
|
||||
|
||||
Reference in New Issue
Block a user