app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit bda3fa80b703901f6f29e2bcafabd4859d9a1fce
parent f2418cfb9fa221460780fd7dae9301bde494f756
Author: triesap <triesap@radroots.dev>
Date:   Mon, 19 Jan 2026 07:57:32 +0000

app-lib: add locale helper

- add locale resolution helper

- add wasm locale lookup fallback

- add unit tests for locale resolution

- re-export locale helpers from app lib

Diffstat:
Mcrates/app-lib/src/lib.rs | 2++
Acrates/app-lib/src/locale.rs | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 64 insertions(+), 0 deletions(-)

diff --git a/crates/app-lib/src/lib.rs b/crates/app-lib/src/lib.rs @@ -3,6 +3,7 @@ pub mod browser; pub mod fetch; pub mod geo; +pub mod locale; pub mod path; pub mod query; pub mod sleep; @@ -12,6 +13,7 @@ pub mod symbols; pub use browser::{browser_platform, BrowserPlatformInfo}; pub use fetch::{fetch_json, FetchJsonError, FetchJsonErrorKind, FetchJsonResult}; pub use geo::{geop_init, geop_is_valid, AppGeolocationPoint}; +pub use locale::{get_locale, resolve_locale}; pub use path::{normalize_path, sanitize_path, trim_slashes}; pub use query::{encode_query_params, encode_route}; pub use sleep::sleep; diff --git a/crates/app-lib/src/locale.rs b/crates/app-lib/src/locale.rs @@ -0,0 +1,62 @@ +#![forbid(unsafe_code)] + +const DEFAULT_LOCALE: &str = "en"; + +pub fn resolve_locale(locales: &[&str], navigator_locale: Option<&str>) -> String { + let fallback = locales.first().copied().unwrap_or(DEFAULT_LOCALE); + let fallback_lower = fallback.to_ascii_lowercase(); + let Some(nav_locale) = navigator_locale else { + return fallback_lower; + }; + let nav_lower = nav_locale.to_ascii_lowercase(); + if locales + .iter() + .any(|locale| locale.eq_ignore_ascii_case(&nav_lower)) + { + return nav_lower; + } + let prefix = nav_lower.chars().take(2).collect::<String>(); + if !prefix.is_empty() + && locales + .iter() + .any(|locale| locale.eq_ignore_ascii_case(&prefix)) + { + return prefix; + } + fallback_lower +} + +pub fn get_locale(locales: &[&str]) -> String { + #[cfg(target_arch = "wasm32")] + { + let navigator_locale = web_sys::window().map(|window| window.navigator().language()); + resolve_locale(locales, navigator_locale.as_deref()) + } + #[cfg(not(target_arch = "wasm32"))] + { + resolve_locale(locales, None) + } +} + +#[cfg(test)] +mod tests { + use super::resolve_locale; + + #[test] + fn resolve_locale_prefers_exact_match() { + let locales = ["en", "fr"]; + assert_eq!(resolve_locale(&locales, Some("fr")), "fr"); + } + + #[test] + fn resolve_locale_prefers_prefix_match() { + let locales = ["en", "fr"]; + assert_eq!(resolve_locale(&locales, Some("fr-CA")), "fr"); + } + + #[test] + fn resolve_locale_falls_back() { + let locales = ["en", "fr"]; + assert_eq!(resolve_locale(&locales, Some("es-ES")), "en"); + } +}