diff --git a/src/base/extension/welcome.rs b/src/base/extension/welcome.rs index d8b1259..39edfd3 100644 --- a/src/base/extension/welcome.rs +++ b/src/base/extension/welcome.rs @@ -28,12 +28,15 @@ async fn homepage(request: HttpRequest) -> ResultPage { .with_title(L10n::l("welcome_page")) .with_theme("Basic") .with_assets(AssetsOp::AddStyleSheet(StyleSheet::from("/css/welcome.css"))) - .with_component(Html::with(move |_| html! { + .with_component(Html::with(move |cx| html! { div id="main-header" { header { - h1 id="header-title" aria-label=(L10n::l("welcome_aria").with_arg("app", app)) { - span { (L10n::l("welcome_title")) } - (L10n::l("welcome_intro").with_arg("app", app)) + h1 + id="header-title" + aria-label=(L10n::l("welcome_aria").with_arg("app", app).to_markup(cx)) + { + span { (L10n::l("welcome_title").to_markup(cx)) } + (L10n::l("welcome_intro").with_arg("app", app).to_markup(cx)) } } aside id="header-image" aria-hidden="true" { @@ -59,20 +62,25 @@ async fn homepage(request: HttpRequest) -> ResultPage { main id="main-content" { section class="content-body" { div id="poweredby-button" { - a id="poweredby-link" href="https://pagetop.cillero.es" target="_blank" rel="noreferrer" { + a + id="poweredby-link" + href="https://pagetop.cillero.es" + target="_blank" + rel="noreferrer" + { span {} span {} span {} - div id="poweredby-text" { (L10n::l("welcome_powered")) } + div id="poweredby-text" { (L10n::l("welcome_powered").to_markup(cx)) } } } div class="content-text" { - p { (L10n::l("welcome_text1")) } - p { (L10n::l("welcome_text2")) } + p { (L10n::l("welcome_text1").to_markup(cx)) } + p { (L10n::l("welcome_text2").to_markup(cx)) } div class="subcontent" { - h1 { span { (L10n::l("welcome_about")) } } - p { (L10n::l("welcome_pagetop")) } - p { (L10n::l("welcome_issues1")) } - p { (L10n::l("welcome_issues2").with_arg("app", app)) } + h1 { span { (L10n::l("welcome_about").to_markup(cx)) } } + p { (L10n::l("welcome_pagetop").to_markup(cx)) } + p { (L10n::l("welcome_issues1").to_markup(cx)) } + p { (L10n::l("welcome_issues2").with_arg("app", app).to_markup(cx)) } } } } @@ -85,7 +93,7 @@ async fn homepage(request: HttpRequest) -> ResultPage { viewBox="0 0 1614 1614" xmlns="http://www.w3.org/2000/svg" role="img" - aria-label=(L10n::l("pagetop_logo")) + aria-label=[L10n::l("pagetop_logo").using(cx)] preserveAspectRatio="xMidYMid slice" focusable="false" { @@ -97,8 +105,8 @@ async fn homepage(request: HttpRequest) -> ResultPage { div class="footer-links" { a href="https://crates.io/crates/pagetop" target="_blank" rel="noreferrer" { ("Crates.io") } a href="https://docs.rs/pagetop" target="_blank" rel="noreferrer" { ("Docs.rs") } - a href="https://git.cillero.es/manuelcillero/pagetop" target="_blank" rel="noreferrer" { (L10n::l("welcome_code")) } - em { (L10n::l("welcome_have_fun")) } + a href="https://git.cillero.es/manuelcillero/pagetop" target="_blank" rel="noreferrer" { (L10n::l("welcome_code").to_markup(cx)) } + em { (L10n::l("welcome_have_fun").to_markup(cx)) } } } } diff --git a/src/global.rs b/src/global.rs index f150ca6..25a1781 100644 --- a/src/global.rs +++ b/src/global.rs @@ -9,7 +9,7 @@ include_config!(SETTINGS: Settings => [ "app.name" => "PageTop App", "app.description" => "Developed with the amazing PageTop framework.", "app.theme" => "Basic", - "app.language" => "en-US", + "app.language" => "", "app.startup_banner" => "Slant", // [dev] @@ -48,7 +48,14 @@ pub struct App { pub description: String, /// Tema predeterminado. pub theme: String, - /// Idioma predeterminado (localización). + /// Idioma por defecto para la aplicación. + /// + /// Si no se especifica un valor válido, normalmente se usará el idioma devuelto por la + /// implementación de [`LangId`](crate::locale::LangId) para [`Context`](crate::html::Context), + /// en el siguiente orden: primero, el idioma establecido explícitamente con + /// [`Context::with_langid`](crate::html::Context::with_langid); si no se ha definido, se usará + /// el indicado en la cabecera `Accept-Language` del navegador; y, si ninguno aplica, se + /// empleará el idioma de respaldo ("en-US"). pub language: String, /// Banner ASCII mostrado al inicio: *"Off"* (desactivado), *"Slant"*, *"Small"*, *"Speed"* o /// *"Starwars"*. diff --git a/src/html/context.rs b/src/html/context.rs index 456ca7a..8aaeb4a 100644 --- a/src/html/context.rs +++ b/src/html/context.rs @@ -3,7 +3,7 @@ use crate::core::theme::ThemeRef; use crate::core::TypeInfo; use crate::html::{html, Markup}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; -use crate::locale::{LanguageIdentifier, DEFAULT_LANGID}; +use crate::locale::{LangId, LangMatch, LanguageIdentifier, DEFAULT_LANGID, FALLBACK_LANGID}; use crate::service::HttpRequest; use crate::{builder_fn, join}; @@ -69,9 +69,9 @@ impl Error for ErrorParam {} /// use pagetop::prelude::*; /// /// fn new_context(request: HttpRequest) -> Context { -/// Context::new(request) +/// Context::new(Some(request)) /// // Establece el idioma del documento a español. -/// .with_langid(LangMatch::langid_or_default("es-ES")) +/// .with_langid(&LangMatch::resolve("es-ES")) /// // Selecciona un tema (por su nombre corto). /// .with_theme("aliner") /// // Asigna un favicon. @@ -125,9 +125,23 @@ impl Context { /// cargados. #[rustfmt::skip] pub fn new(request: Option) -> Self { + // Se intenta DEFAULT_LANGID. + let langid = DEFAULT_LANGID + // Si es None evalúa la cadena de extracción desde la cabecera HTTP. + .or_else(|| { + request + // Se usa `as_ref()` sobre `Option` para no mover el valor. + .as_ref() + .and_then(|req| req.headers().get("Accept-Language")) + .and_then(|value| value.to_str().ok()) + .and_then(|language| LangMatch::resolve(language).as_option()) + }) + // Si todo falla, se recurre a &FALLBACK_LANGID. + .unwrap_or(&FALLBACK_LANGID); + Context { request, - langid : &DEFAULT_LANGID, + langid, theme : *DEFAULT_THEME, layout : "default", favicon : None, @@ -140,10 +154,10 @@ impl Context { // Context BUILDER ***************************************************************************** - /// Modifica el identificador de idioma del documento. + /// Modifica la fuente de idioma del documento. #[builder_fn] - pub fn with_langid(mut self, langid: &'static LanguageIdentifier) -> Self { - self.langid = langid; + pub fn with_langid(mut self, language: &impl LangId) -> Self { + self.langid = language.langid(); self } @@ -202,11 +216,6 @@ impl Context { self.request.as_ref() } - /// Devuelve el identificador de idioma asociado al documento. - pub fn langid(&self) -> &LanguageIdentifier { - self.langid - } - /// Devuelve el tema que se usará para renderizar el documento. pub fn theme(&self) -> ThemeRef { self.theme @@ -281,3 +290,21 @@ impl Context { } } } + +/// Permite a [`Context`](crate::html::Context) actuar como proveedor de idioma. +/// +/// Devuelve un [`LanguageIdentifier`] siguiendo este orden de prioridad: +/// +/// 1. Un idioma válido establecido explícitamente con [`Context::with_langid`]. +/// 2. El idioma por defecto configurado para la aplicación. +/// 3. Un idioma válido extraído de la cabecera `Accept-Language` del navegador. +/// 4. Y si ninguna de las opciones anteriores aplica, se usa el idioma de respaldo (`"en-US"`). +/// +/// Resulta útil para usar un contexto ([`Context`]) como fuente de traducción en +/// [`L10n::using`](crate::locale::L10n::using) o +/// [`L10n::to_markup`](crate::locale::L10n::to_markup). +impl LangId for Context { + fn langid(&self) -> &'static LanguageIdentifier { + self.langid + } +} diff --git a/src/html/opt_translated.rs b/src/html/opt_translated.rs index ba60a0f..b15ea18 100644 --- a/src/html/opt_translated.rs +++ b/src/html/opt_translated.rs @@ -1,5 +1,5 @@ use crate::html::Markup; -use crate::locale::{L10n, LanguageIdentifier}; +use crate::locale::{L10n, LangId}; use crate::{builder_fn, AutoDefault}; /// Cadena para traducir al renderizar ([`locale`](crate::locale)). @@ -16,18 +16,18 @@ use crate::{builder_fn, AutoDefault}; /// /// // Español disponible. /// assert_eq!( -/// hello.using(LangMatch::langid_or_default("es-ES")), +/// hello.using(&LangMatch::resolve("es-ES")), /// Some(String::from("¡Hola mundo!")) /// ); /// /// // Japonés no disponible, traduce al idioma de respaldo ("en-US"). /// assert_eq!( -/// hello.using(LangMatch::langid_or_fallback("ja-JP")), +/// hello.using(&LangMatch::resolve("ja-JP")), /// Some(String::from("Hello world!")) /// ); /// /// // Para incrustar en HTML escapado: -/// let markup = hello.escaped(LangMatch::langid_or_default("es-ES")); +/// let markup = hello.to_markup(&LangMatch::resolve("es-ES")); /// assert_eq!(markup.into_string(), "¡Hola mundo!"); /// ``` #[derive(AutoDefault, Clone, Debug)] @@ -50,16 +50,16 @@ impl OptionTranslated { // OptionTranslated GETTERS ******************************************************************** - /// Devuelve la traducción para `langid`, si existe. - pub fn using(&self, langid: &LanguageIdentifier) -> Option { - self.0.using(langid) + /// Devuelve la traducción para `language`, si existe. + pub fn using(&self, language: &impl LangId) -> Option { + self.0.using(language) } - /// Devuelve la traducción *escapada* como [`Markup`] para `langid`, si existe. + /// Devuelve la traducción *escapada* como [`Markup`] para `language`, si existe. /// /// Útil para incrustar el texto directamente en plantillas HTML sin riesgo de inyección de /// contenido. - pub fn escaped(&self, langid: &LanguageIdentifier) -> Markup { - self.0.escaped(langid) + pub fn to_markup(&self, language: &impl LangId) -> Markup { + self.0.to_markup(language) } } diff --git a/src/locale.rs b/src/locale.rs index 2a08ba6..d81243e 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -90,7 +90,7 @@ //! Y *voilà*, sólo queda operar con los idiomas soportados por `PageTop` usando [`LangMatch`] y //! traducir textos con [`L10n`]. -use crate::html::{Markup, PreEscaped, Render}; +use crate::html::{Markup, PreEscaped}; use crate::{global, hm, AutoDefault}; pub use fluent_templates; @@ -112,10 +112,10 @@ use std::fmt; static LANGUAGES: LazyLock> = LazyLock::new(|| { hm![ "en" => ( langid!("en-US"), "english" ), - "en-GB" => ( langid!("en-GB"), "english_british" ), - "en-US" => ( langid!("en-US"), "english_united_states" ), + "en-gb" => ( langid!("en-GB"), "english_british" ), + "en-us" => ( langid!("en-US"), "english_united_states" ), "es" => ( langid!("es-ES"), "spanish" ), - "es-ES" => ( langid!("es-ES"), "spanish_spain" ), + "es-es" => ( langid!("es-ES"), "spanish_spain" ), ] }); @@ -123,14 +123,23 @@ static LANGUAGES: LazyLock> = LazyLock // // Se usa cuando el valor del identificador de idioma en las traducciones no corresponde con ningún // idioma soportado por la aplicación. -static FALLBACK_LANGID: LazyLock = LazyLock::new(|| langid!("en-US")); +pub(crate) static FALLBACK_LANGID: LazyLock = + LazyLock::new(|| langid!("en-US")); // Identificador de idioma **por defecto** para la aplicación. // // Se resuelve a partir de [`global::SETTINGS.app.language`](global::SETTINGS). Si el identificador // de idioma no es válido o no está disponible entonces resuelve como [`FALLBACK_LANGID`]. -pub(crate) static DEFAULT_LANGID: LazyLock<&LanguageIdentifier> = - LazyLock::new(|| LangMatch::langid_or_fallback(&global::SETTINGS.app.language)); +pub(crate) static DEFAULT_LANGID: LazyLock> = + LazyLock::new(|| LangMatch::resolve(&global::SETTINGS.app.language).as_option()); + +/// Representa la fuente de idioma (`LanguageIdentifier`) asociada a un recurso. +/// +/// Este *trait* permite que distintas estructuras expongan su fuente de idioma de forma uniforme. +pub trait LangId { + /// Devuelve el identificador de idioma asociado al recurso. + fn langid(&self) -> &'static LanguageIdentifier; +} /// Operaciones con los idiomas soportados por `PageTop`. /// @@ -144,11 +153,11 @@ pub(crate) static DEFAULT_LANGID: LazyLock<&LanguageIdentifier> = /// /// // Coincidencia exacta. /// let lang = LangMatch::resolve("es-ES"); -/// assert_eq!(lang.as_langid().to_string(), "es-ES"); +/// assert_eq!(lang.langid().to_string(), "es-ES"); /// /// // Coincidencia parcial (con el idioma base). /// let lang = LangMatch::resolve("es-EC"); -/// assert_eq!(lang.as_langid().to_string(), "es-ES"); // Porque "es-EC" no está soportado. +/// assert_eq!(lang.langid().to_string(), "es-ES"); // Porque "es-EC" no está soportado. /// /// // Idioma no especificado. /// let lang = LangMatch::resolve(""); @@ -159,21 +168,16 @@ pub(crate) static DEFAULT_LANGID: LazyLock<&LanguageIdentifier> = /// assert_eq!(lang, LangMatch::Unsupported(String::from("ja-JP"))); /// ``` /// -/// Las siguientes líneas devuelven siempre un [`LanguageIdentifier`] válido, ya sea porque -/// resuelven un idioma soportado o porque aplican el idioma por defecto o de respaldo: +/// Con la siguiente instrucción siempre se obtiene un [`LanguageIdentifier`] válido, ya sea porque +/// resuelve un idioma soportado o porque se aplica el idioma por defecto o, en último caso, el de +/// respaldo ("en-US"): /// /// ```rust /// use pagetop::prelude::*; /// -/// // Idioma por defecto si no resuelve. +/// // Idioma por defecto o de respaldo si no resuelve. /// let lang = LangMatch::resolve("it-IT"); -/// let langid = lang.as_langid(); -/// -/// // Idioma por defecto si no se encuentra. -/// let langid = LangMatch::langid_or_default("es-MX"); -/// -/// // Idioma de respaldo ("en-US") si no se encuentra. -/// let langid = LangMatch::langid_or_fallback("es-MX"); +/// let langid = lang.langid(); /// ``` #[derive(Clone, Debug, Eq, PartialEq)] pub enum LangMatch { @@ -187,6 +191,13 @@ pub enum LangMatch { Unsupported(String), } +impl Default for LangMatch { + /// Resuelve al idioma por defecto y, si no está disponible, al idioma de respaldo ("en-US"). + fn default() -> Self { + LangMatch::Found(DEFAULT_LANGID.unwrap_or(&FALLBACK_LANGID)) + } +} + impl LangMatch { /// Resuelve `language` y devuelve la variante [`LangMatch`] apropiada. pub fn resolve(language: impl AsRef) -> Self { @@ -198,12 +209,13 @@ impl LangMatch { } // Intenta aplicar coincidencia exacta con el código completo (p.ej. "es-MX"). - if let Some(langid) = LANGUAGES.get(language).map(|(langid, _)| langid) { + let lang = language.to_ascii_lowercase(); + if let Some(langid) = LANGUAGES.get(lang.as_str()).map(|(langid, _)| langid) { return Self::Found(langid); } // Si la variante regional no existe, retrocede al idioma base (p.ej. "es"). - if let Some((base_lang, _)) = language.split_once('-') { + if let Some((base_lang, _)) = lang.split_once('-') { if let Some(langid) = LANGUAGES.get(base_lang).map(|(langid, _)| langid) { return Self::Found(langid); } @@ -213,37 +225,47 @@ impl LangMatch { Self::Unsupported(String::from(language)) } - /// Devuelve el idioma de la variante de la instancia, o el idioma por defecto si no está - /// soportado. + /// Devuelve el [`LanguageIdentifier`] si el idioma fue reconocido. /// - /// Siempre devuelve un [`LanguageIdentifier`] válido. + /// Solo retorna `Some` si la variante es [`LangMatch::Found`]. En cualquier otro caso (por + /// ejemplo, si el identificador es vacío o no está soportado), devuelve `None`. + /// + /// Este método es útil cuando se desea acceder directamente al idioma reconocido sin aplicar el + /// idioma por defecto ni el de respaldo. + /// + /// # Ejemplo + /// + /// ```rust + /// use pagetop::prelude::*; + /// + /// let lang = LangMatch::resolve("es-ES").as_option(); + /// assert_eq!(lang.unwrap().to_string(), "es-ES"); + /// + /// let lang = LangMatch::resolve("jp-JP").as_option(); + /// assert!(lang.is_none()); + /// ``` #[inline] - pub fn as_langid(&self) -> &'static LanguageIdentifier { + pub fn as_option(&self) -> Option<&'static LanguageIdentifier> { + match self { + LangMatch::Found(l) => Some(l), + _ => None, + } + } +} + +/// Permite a [`LangMatch`] actuar como proveedor de idioma. +/// +/// Devuelve el [`LanguageIdentifier`] si la variante es [`LangMatch::Found`]; en caso contrario, +/// devuelve el idioma por defecto de la aplicación y, si tampoco está disponible, el idioma de +/// respaldo ("en-US"). +/// +/// Resulta útil para usar un valor de [`LangMatch`] como fuente de traducción en [`L10n::using`] o +/// [`L10n::to_markup`]. +impl LangId for LangMatch { + fn langid(&self) -> &'static LanguageIdentifier { match self { LangMatch::Found(l) => l, - _ => &DEFAULT_LANGID, - } - } - - /// Si `language` está vacío o no está soportado, devuelve el idioma por defecto. - /// - /// Siempre devuelve un [`LanguageIdentifier`] válido. - #[inline] - pub fn langid_or_default(language: impl AsRef) -> &'static LanguageIdentifier { - match Self::resolve(language) { - Self::Found(l) => l, - _ => &DEFAULT_LANGID, - } - } - - /// Si `language` está vacío o no está soportado, devuelve el idioma de respaldo ("en-US"). - /// - /// Siempre devuelve un [`LanguageIdentifier`] válido. - #[inline] - pub fn langid_or_fallback(language: impl AsRef) -> &'static LanguageIdentifier { - match Self::resolve(language) { - Self::Found(l) => l, - _ => &FALLBACK_LANGID, + _ => DEFAULT_LANGID.unwrap_or(&FALLBACK_LANGID), } } } @@ -297,7 +319,7 @@ enum L10nOp { /// Cada instancia puede representar: /// /// - Un texto puro (`n()`) que no requiere traducción. -/// - Una clave para traducir un texto de las traducciones por defecto de `PageTop` (`l()`). +/// - Una clave para traducir un texto de las traducciones predefinidas de `PageTop` (`l()`). /// - Una clave para traducir de un conjunto concreto de traducciones (`t()`). /// /// # Ejemplo @@ -319,8 +341,8 @@ enum L10nOp { /// También para traducciones a idiomas concretos. /// /// ```rust,ignore -/// // Traducción con clave, conjunto de traducciones e identificador de idioma a usar. -/// let bye = L10n::t("goodbye", &LOCALES_CUSTOM).using(LangMatch::langid_or_default("it")); +/// // Traducción con clave, conjunto de traducciones y fuente de idioma. +/// let bye = L10n::t("goodbye", &LOCALES_CUSTOM).using(&LangMatch::resolve("it")); /// ``` #[derive(AutoDefault, Clone)] pub struct L10n { @@ -339,8 +361,8 @@ impl L10n { } } - /// **l** = *“lookup”*. Crea una instancia para traducir usando una clave de la tabla de - /// traducciones por defecto. + /// **l** = *“lookup”*. Crea una instancia para traducir usando una clave del conjunto de + /// traducciones predefinidas. pub fn l(key: impl Into) -> Self { L10n { op: L10nOp::Translate(key.into()), @@ -348,8 +370,8 @@ impl L10n { } } - /// **t** = *“translate”*. Crea una instancia para traducir usando una clave de una tabla de - /// traducciones específica. + /// **t** = *“translate”*. Crea una instancia para traducir usando una clave de un conjunto de + /// traducciones específico. pub fn t(key: impl Into, locales: &'static Locales) -> Self { L10n { op: L10nOp::Translate(key.into()), @@ -377,20 +399,47 @@ impl L10n { self } - /// Resuelve la traducción usando el idioma por defecto de la aplicación. Devuelve `None` si no - /// aplica o no encuentra una traducción. + /// Resuelve la traducción usando el idioma por defecto o de respaldo de la aplicación. + /// + /// Devuelve `None` si no aplica o no encuentra una traducción válida. + /// + /// # Ejemplo + /// + /// ```rust + /// use pagetop::prelude::*; + /// + /// let text = L10n::l("greeting").with_arg("name", "Manuel").get(); + /// ``` pub fn get(&self) -> Option { - self.using(&DEFAULT_LANGID) + self.using(&LangMatch::default()) } - /// Resuelve la traducción usando el [`LanguageIdentifier`] indicado. Devuelve `None` si no - /// aplica o no encuentra una traducción. - pub fn using(&self, langid: &LanguageIdentifier) -> Option { + /// Resuelve la traducción usando la fuente de idioma proporcionada. + /// + /// Devuelve `None` si no aplica o no encuentra una traducción válida. + /// + /// # Ejemplo + /// + /// ```rust + /// use pagetop::prelude::*; + /// + /// struct ResourceLang; + /// + /// impl LangId for ResourceLang { + /// fn langid(&self) -> &'static LanguageIdentifier { + /// LangMatch::resolve("es-MX").langid() + /// } + /// } + /// + /// let r = ResourceLang; + /// let text = L10n::l("greeting").with_arg("name", "Usuario").using(&r); + /// ``` + pub fn using(&self, language: &impl LangId) -> Option { match &self.op { L10nOp::None => None, L10nOp::Text(text) => Some(text.to_owned()), L10nOp::Translate(key) => self.locales.try_lookup_with_args( - langid, + language.langid(), key, &self.args.iter().fold(HashMap::new(), |mut arg, (k, v)| { arg.insert(Cow::Owned(k.clone()), v.to_owned().into()); @@ -400,16 +449,19 @@ impl L10n { } } - /// Traduce y escapa con el [`LanguageIdentifier`] indicado, devolviendo [`Markup`]. - pub fn escaped(&self, langid: &LanguageIdentifier) -> Markup { - PreEscaped(self.using(langid).unwrap_or_default()) - } -} - -impl Render for L10n { - /// Traduce y escapa con el idioma por defecto, devolviendo [`Markup`]. - fn render(&self) -> Markup { - PreEscaped(self.get().unwrap_or_default()) + /// Traduce el texto y lo devuelve como [`Markup`] usando la fuente de idioma proporcionada. + /// + /// Si no se encuentra una traducción válida, devuelve una cadena vacía. + /// + /// # Ejemplo + /// + /// ```rust + /// use pagetop::prelude::*; + /// + /// let html = L10n::l("welcome.message").to_markup(&LangMatch::resolve("es")); + /// ``` + pub fn to_markup(&self, language: &impl LangId) -> Markup { + PreEscaped(self.using(language).unwrap_or_default()) } } @@ -423,14 +475,3 @@ impl fmt::Debug for L10n { .finish() } } - -impl fmt::Display for L10n { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let content = match &self.op { - L10nOp::None => String::new(), - L10nOp::Text(text) => text.clone(), - L10nOp::Translate(key) => self.get().unwrap_or_else(|| format!("??<{}>", key)), - }; - write!(f, "{content}") - } -} diff --git a/src/response/page.rs b/src/response/page.rs index 0964509..c1815d0 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -9,7 +9,7 @@ use crate::core::component::{Child, ChildOp, ComponentTrait}; use crate::core::theme::{ChildrenInRegions, ThemeRef, CONTENT_REGION_NAME}; use crate::html::{html, AssetsOp, Context, Markup, DOCTYPE}; use crate::html::{ClassesOp, OptionClasses, OptionId, OptionTranslated}; -use crate::locale::{CharacterDirection, L10n, LanguageIdentifier}; +use crate::locale::{CharacterDirection, L10n, LangId, LanguageIdentifier}; use crate::service::HttpRequest; /// Representa una página HTML completa lista para renderizar. @@ -78,10 +78,10 @@ impl Page { self } - /// Modifica el identificador de idioma de la página ([`Context::with_langid`]). + /// Modifica la fuente de idioma de la página ([`Context::with_langid`]). #[builder_fn] - pub fn with_langid(mut self, langid: &'static LanguageIdentifier) -> Self { - self.context.alter_langid(langid); + pub fn with_langid(mut self, language: &impl LangId) -> Self { + self.context.alter_langid(language); self } @@ -147,14 +147,14 @@ impl Page { // Page GETTERS ******************************************************************************** - /// Devuelve el título traducido para el idioma activo, si existe. + /// Devuelve el título traducido para el idioma de la página, si existe. pub fn title(&mut self) -> Option { - self.title.using(self.context.langid()) + self.title.using(&self.context) } - /// Devuelve la descripción traducida para el idioma activo, si existe. + /// Devuelve la descripción traducida para el idioma de la página, si existe. pub fn description(&mut self) -> Option { - self.description.using(self.context.langid()) + self.description.using(&self.context) } /// Devuelve la lista de metadatos ``. diff --git a/tests/locale.rs b/tests/locale.rs index 420914d..ef875d7 100644 --- a/tests/locale.rs +++ b/tests/locale.rs @@ -13,7 +13,7 @@ async fn translation_without_args() { let _app = service::test::init_service(Application::new().test()).await; let l10n = L10n::l("test-hello-world"); - let translation = l10n.using(LangMatch::langid_or_default("es-ES")); + let translation = l10n.using(&LangMatch::resolve("es-ES")); assert_eq!(translation, Some("¡Hola mundo!".to_string())); } @@ -22,7 +22,7 @@ async fn translation_with_args() { let _app = service::test::init_service(Application::new().test()).await; let l10n = L10n::l("test-hello-user").with_arg("userName", "Manuel"); - let translation = l10n.using(LangMatch::langid_or_default("es-ES")); + let translation = l10n.using(&LangMatch::resolve("es-ES")); assert_eq!(translation, Some("¡Hola, Manuel!".to_string())); } @@ -35,7 +35,7 @@ async fn translation_with_plural_and_select() { ("photoCount", "3"), ("userGender", "male"), ]); - let translation = l10n.using(LangMatch::langid_or_default("es-ES")).unwrap(); + let translation = l10n.using(&LangMatch::resolve("es-ES")).unwrap(); assert!(translation.contains("añadido 3 nuevas fotos de él")); } @@ -44,7 +44,7 @@ async fn check_fallback_language() { let _app = service::test::init_service(Application::new().test()).await; let l10n = L10n::l("test-hello-world"); - let translation = l10n.using(LangMatch::langid_or_fallback("xx-YY")); // Retrocede a "en-US". + let translation = l10n.using(&LangMatch::resolve("xx-YY")); // Retrocede a "en-US". assert_eq!(translation, Some("Hello world!".to_string())); } @@ -53,6 +53,6 @@ async fn check_unknown_key() { let _app = service::test::init_service(Application::new().test()).await; let l10n = L10n::l("non-existent-key"); - let translation = l10n.using(LangMatch::langid_or_default("en-US")); + let translation = l10n.using(&LangMatch::resolve("en-US")); assert_eq!(translation, None); }