Archived
add el-style, el-layout, el-i18n, el-config, el-secrets: responsive by default, theme-driven, zero breakpoints
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
/// The t() translation function and LocaleContext.
|
||||
///
|
||||
/// Components call `ctx.t("key")` or `ctx.t_plural("key", count)`.
|
||||
/// The context flows down from the experience root and carries the active bundle.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use crate::bundle::LocaleBundle;
|
||||
use crate::locale::Locale;
|
||||
|
||||
/// The active localization context.
|
||||
///
|
||||
/// Holds the current locale and its translation bundle. Passed down
|
||||
/// through the component tree. When the locale changes, the context
|
||||
/// is updated and all components that used it re-render.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LocaleContext {
|
||||
pub locale: Locale,
|
||||
bundle: LocaleBundle,
|
||||
/// Fallback bundle (typically English) used when a key is missing.
|
||||
fallback: Option<LocaleBundle>,
|
||||
}
|
||||
|
||||
impl LocaleContext {
|
||||
/// Create a context with a locale and its bundle.
|
||||
pub fn new(locale: Locale, bundle: LocaleBundle) -> Self {
|
||||
Self {
|
||||
locale,
|
||||
bundle,
|
||||
fallback: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a fallback bundle for missing keys.
|
||||
pub fn with_fallback(mut self, fallback: LocaleBundle) -> Self {
|
||||
self.fallback = Some(fallback);
|
||||
self
|
||||
}
|
||||
|
||||
/// Translate a key with optional variable interpolation.
|
||||
///
|
||||
/// Returns the key itself if not found (never panics).
|
||||
pub fn t(&self, key: &str) -> String {
|
||||
self.t_vars(key, &HashMap::new())
|
||||
}
|
||||
|
||||
/// Translate a key with variables.
|
||||
pub fn t_vars(&self, key: &str, vars: &HashMap<&str, String>) -> String {
|
||||
// Try primary bundle
|
||||
if let Some(s) = self.bundle.translate(key, vars) {
|
||||
return s;
|
||||
}
|
||||
// Try fallback bundle
|
||||
if let Some(ref fb) = self.fallback {
|
||||
if let Some(s) = fb.translate(key, vars) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
// Return the key itself — visible but never panics
|
||||
key.to_string()
|
||||
}
|
||||
|
||||
/// Translate a plural key with a count.
|
||||
pub fn t_plural(&self, key: &str, count: i64) -> String {
|
||||
self.t_plural_vars(key, count, &HashMap::new())
|
||||
}
|
||||
|
||||
/// Translate a plural key with a count and extra variables.
|
||||
pub fn t_plural_vars(
|
||||
&self,
|
||||
key: &str,
|
||||
count: i64,
|
||||
vars: &HashMap<&str, String>,
|
||||
) -> String {
|
||||
if let Some(s) = self.bundle.translate_plural(key, count, vars) {
|
||||
return s;
|
||||
}
|
||||
if let Some(ref fb) = self.fallback {
|
||||
if let Some(s) = fb.translate_plural(key, count, vars) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
key.to_string()
|
||||
}
|
||||
|
||||
/// The current locale tag (e.g. "en-US").
|
||||
pub fn locale_tag(&self) -> String {
|
||||
self.locale.tag()
|
||||
}
|
||||
|
||||
/// Whether the current locale is RTL.
|
||||
pub fn is_rtl(&self) -> bool {
|
||||
self.locale.is_rtl()
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience macro for translation calls.
|
||||
///
|
||||
/// ```ignore
|
||||
/// // Simple
|
||||
/// let text = t!(ctx, "profile.follow");
|
||||
///
|
||||
/// // With variables
|
||||
/// let text = t!(ctx, "greeting", name => "Alice");
|
||||
///
|
||||
/// // Plural
|
||||
/// let text = t_n!(ctx, "profile.followers", count);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! t {
|
||||
($ctx:expr, $key:expr) => {
|
||||
$ctx.t($key)
|
||||
};
|
||||
($ctx:expr, $key:expr, $($var:ident => $val:expr),+) => {{
|
||||
let mut vars = std::collections::HashMap::new();
|
||||
$(vars.insert(stringify!($var), $val.to_string());)+
|
||||
$ctx.t_vars($key, &vars)
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! t_n {
|
||||
($ctx:expr, $key:expr, $count:expr) => {
|
||||
$ctx.t_plural($key, $count as i64)
|
||||
};
|
||||
($ctx:expr, $key:expr, $count:expr, $($var:ident => $val:expr),+) => {{
|
||||
let mut vars = std::collections::HashMap::new();
|
||||
$(vars.insert(stringify!($var), $val.to_string());)+
|
||||
$ctx.t_plural_vars($key, $count as i64, &vars)
|
||||
}};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::bundle::LocaleBundle;
|
||||
|
||||
fn make_ctx() -> LocaleContext {
|
||||
let mut bundle = LocaleBundle::new(Locale::en_us());
|
||||
bundle.insert("profile.follow", "Follow");
|
||||
bundle.insert("greeting", "Hello, {name}!");
|
||||
let mut forms = HashMap::new();
|
||||
forms.insert("one".to_string(), "{n} Follower".to_string());
|
||||
forms.insert("other".to_string(), "{n} Followers".to_string());
|
||||
bundle.insert_plural("profile.followers", forms);
|
||||
LocaleContext::new(Locale::en_us(), bundle)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn t_simple() {
|
||||
let ctx = make_ctx();
|
||||
assert_eq!(ctx.t("profile.follow"), "Follow");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn t_missing_returns_key() {
|
||||
let ctx = make_ctx();
|
||||
assert_eq!(ctx.t("no.such.key"), "no.such.key");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn t_vars_interpolation() {
|
||||
let ctx = make_ctx();
|
||||
let mut vars = HashMap::new();
|
||||
vars.insert("name", "Bob".to_string());
|
||||
assert_eq!(ctx.t_vars("greeting", &vars), "Hello, Bob!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn t_plural_one() {
|
||||
let ctx = make_ctx();
|
||||
assert_eq!(ctx.t_plural("profile.followers", 1), "1 Follower");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn t_plural_many() {
|
||||
let ctx = make_ctx();
|
||||
assert_eq!(ctx.t_plural("profile.followers", 42), "42 Followers");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_rtl_english() {
|
||||
let ctx = make_ctx();
|
||||
assert!(!ctx.is_rtl());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_rtl_arabic() {
|
||||
let bundle = LocaleBundle::new(Locale::ar());
|
||||
let ctx = LocaleContext::new(Locale::ar(), bundle);
|
||||
assert!(ctx.is_rtl());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fallback_bundle_used() {
|
||||
let primary_bundle = LocaleBundle::new(Locale::new("fr"));
|
||||
// Key not in French bundle
|
||||
let mut fallback = LocaleBundle::new(Locale::en_us());
|
||||
fallback.insert("app.name", "My App");
|
||||
let ctx = LocaleContext::new(Locale::new("fr"), primary_bundle)
|
||||
.with_fallback(fallback);
|
||||
assert_eq!(ctx.t("app.name"), "My App");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn locale_tag() {
|
||||
let ctx = make_ctx();
|
||||
assert_eq!(ctx.locale_tag(), "en-US");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user