mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-31 22:06:49 -06:00
M7: localization layer (#128)
UI strings come from per-language TOML catalogs compiled in (English text as the key and fallback, named placeholders, one/other plurals, context suffixes); command labels are translated at display time while ids, CLI, MCP and control output stay English; accessibility names follow. Preferences > Language (System / English / Español, live; the system language from the OS or the browser), pref.language for scripts. A key check test guards the catalogs. The Spanish catalog is a machine draft awaiting native review; docs/translating.md explains adding a language.
This commit is contained in:
@@ -25,4 +25,4 @@ log = { workspace = true }
|
||||
eframe = { workspace = true, features = ["default_fonts", "wgpu", "accesskit"] }
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
web-sys = { version = "0.3", features = ["Window", "Document", "Element", "HtmlCanvasElement", "Location", "MediaQueryList"] }
|
||||
web-sys = { version = "0.3", features = ["Window", "Document", "Element", "HtmlCanvasElement", "Location", "MediaQueryList", "Navigator"] }
|
||||
|
||||
@@ -86,4 +86,8 @@ impl Services for WebServices {
|
||||
fn reduce_motion(&self) -> bool {
|
||||
self.reduce_motion.as_ref().is_some_and(web_sys::MediaQueryList::matches)
|
||||
}
|
||||
/// The browser's language (`navigator.language`).
|
||||
fn system_language(&self) -> Option<String> {
|
||||
web_sys::window().and_then(|w| w.navigator().language())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,10 @@ webbrowser = { workspace = true }
|
||||
image = { workspace = true }
|
||||
env_logger = { version = "0.11", default-features = false, features = ["auto-color", "humantime"] }
|
||||
|
||||
# The system's reduce-motion setting (UISettings.AnimationsEnabled).
|
||||
# The system's reduce-motion setting (UISettings.AnimationsEnabled) and interface language
|
||||
# (GlobalizationPreferences.Languages).
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows = { version = "0.62", default-features = false, features = ["UI_ViewManagement"] }
|
||||
windows = { version = "0.62", default-features = false, features = ["UI_ViewManagement", "System_UserProfile", "Foundation_Collections"] }
|
||||
|
||||
[build-dependencies]
|
||||
winresource = "0.1"
|
||||
|
||||
@@ -13,6 +13,8 @@ pub struct Desktop {
|
||||
pub prefs: Option<Prefs>,
|
||||
/// The system's reduce-motion setting (read in the background at startup).
|
||||
reduce_motion: Arc<AtomicBool>,
|
||||
/// The system's interface language (read at startup).
|
||||
language: Option<String>,
|
||||
}
|
||||
|
||||
fn config_dir() -> Option<PathBuf> {
|
||||
@@ -44,7 +46,34 @@ impl Desktop {
|
||||
let reduce_motion = Arc::new(AtomicBool::new(false));
|
||||
let flag = reduce_motion.clone();
|
||||
std::thread::spawn(move || flag.store(system_reduce_motion(), Ordering::Relaxed));
|
||||
Self { clipboard: arboard::Clipboard::new().ok(), prefs, reduce_motion }
|
||||
Self { clipboard: arboard::Clipboard::new().ok(), prefs, reduce_motion, language: system_language() }
|
||||
}
|
||||
}
|
||||
|
||||
/// The system's preferred interface language as a tag ("es-ES", "es_ES.UTF-8"): Windows' language
|
||||
/// list, macOS's AppleLanguages, the locale variables elsewhere.
|
||||
fn system_language() -> Option<String> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
windows::System::UserProfile::GlobalizationPreferences::Languages()
|
||||
.and_then(|l| l.GetAt(0))
|
||||
.map(|s| s.to_string())
|
||||
.inspect_err(|e| log::info!("language: can't read the system languages: {e}"))
|
||||
.ok()
|
||||
}
|
||||
// `defaults` prints a list: ( "es-ES", en ).
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let out = std::process::Command::new("defaults").args(["read", "-g", "AppleLanguages"]).output().ok()?;
|
||||
String::from_utf8_lossy(&out.stdout).split(['(', ',', ')']).map(|s| s.trim().trim_matches('"').to_string()).find(|s| !s.is_empty())
|
||||
}
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
{
|
||||
["LC_ALL", "LC_MESSAGES", "LANG"].iter().filter_map(|k| std::env::var(k).ok()).find(|v| !v.is_empty() && v != "C" && v != "POSIX")
|
||||
}
|
||||
#[cfg(not(any(windows, unix)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +182,9 @@ impl Services for Desktop {
|
||||
fn reduce_motion(&self) -> bool {
|
||||
self.reduce_motion.load(Ordering::Relaxed)
|
||||
}
|
||||
fn system_language(&self) -> Option<String> {
|
||||
self.language.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// The window icon from the bundled app icon (if present).
|
||||
|
||||
Reference in New Issue
Block a user