🚚 Renombra LangMatch por Locale

This commit is contained in:
Manuel Cillero 2025-12-10 15:18:07 +01:00
parent a46cf35fee
commit caa4cf6096
5 changed files with 51 additions and 51 deletions

View file

@ -28,7 +28,7 @@ impl Extension for Welcome {
} }
async fn home_page(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { async fn home_page(request: HttpRequest) -> ResultPage<Markup, ErrorPage> {
let language = LangMatch::from_request(Some(&request)); let language = Locale::from_request(Some(&request));
home(request, &language) home(request, &language)
} }
@ -36,9 +36,9 @@ async fn home_lang(
request: HttpRequest, request: HttpRequest,
path: service::web::Path<String>, path: service::web::Path<String>,
) -> ResultPage<Markup, ErrorPage> { ) -> ResultPage<Markup, ErrorPage> {
let language = LangMatch::resolve(path.into_inner()); let language = Locale::resolve(path.into_inner());
match language { match language {
LangMatch::Found(_) => home(request, &language), Locale::Found(_) => home(request, &language),
_ => Err(ErrorPage::NotFound(request)), _ => Err(ErrorPage::NotFound(request)),
} }
} }

View file

@ -4,7 +4,7 @@ use crate::core::theme::{ChildrenInRegions, RegionRef, TemplateRef, ThemeRef};
use crate::core::TypeInfo; use crate::core::TypeInfo;
use crate::html::{html, Markup}; use crate::html::{html, Markup};
use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet};
use crate::locale::{LangId, LangMatch, LanguageIdentifier}; use crate::locale::{LangId, LanguageIdentifier, Locale};
use crate::service::HttpRequest; use crate::service::HttpRequest;
use crate::{builder_fn, util}; use crate::{builder_fn, util};
@ -65,7 +65,7 @@ pub enum ContextError {
/// # use pagetop::prelude::*; /// # use pagetop::prelude::*;
/// # use pagetop_aliner::Aliner; /// # use pagetop_aliner::Aliner;
/// fn prepare_context<C: Contextual>(cx: C) -> C { /// fn prepare_context<C: Contextual>(cx: C) -> C {
/// cx.with_langid(&LangMatch::resolve("es-ES")) /// cx.with_langid(&Locale::resolve("es-ES"))
/// .with_theme(&Aliner) /// .with_theme(&Aliner)
/// .with_template(&DefaultTemplate::Standard) /// .with_template(&DefaultTemplate::Standard)
/// .with_assets(ContextOp::SetFavicon(Some(Favicon::new().with_icon("/favicon.ico")))) /// .with_assets(ContextOp::SetFavicon(Some(Favicon::new().with_icon("/favicon.ico"))))
@ -169,7 +169,7 @@ pub trait Contextual: LangId {
/// fn new_context(request: HttpRequest) -> Context { /// fn new_context(request: HttpRequest) -> Context {
/// Context::new(Some(request)) /// Context::new(Some(request))
/// // Establece el idioma del documento a español. /// // Establece el idioma del documento a español.
/// .with_langid(&LangMatch::resolve("es-ES")) /// .with_langid(&Locale::resolve("es-ES"))
/// // Establece el tema para renderizar. /// // Establece el tema para renderizar.
/// .with_theme(&Aliner) /// .with_theme(&Aliner)
/// // Asigna un favicon. /// // Asigna un favicon.
@ -229,7 +229,7 @@ impl Context {
/// recursos cargados. /// recursos cargados.
#[rustfmt::skip] #[rustfmt::skip]
pub fn new(request: Option<HttpRequest>) -> Self { pub fn new(request: Option<HttpRequest>) -> Self {
let langid = LangMatch::from_request(request.as_ref()).langid(); let langid = Locale::from_request(request.as_ref()).langid();
Context { Context {
request, request,
langid, langid,

View file

@ -14,18 +14,18 @@ use crate::{builder_fn, AutoDefault};
/// ///
/// // Español disponible. /// // Español disponible.
/// assert_eq!( /// assert_eq!(
/// hello.lookup(&LangMatch::resolve("es-ES")), /// hello.lookup(&Locale::resolve("es-ES")),
/// Some("¡Hola mundo!".to_string()) /// Some("¡Hola mundo!".to_string())
/// ); /// );
/// ///
/// // Japonés no disponible, traduce al idioma de respaldo (`"en-US"`). /// // Japonés no disponible, traduce al idioma de respaldo (`"en-US"`).
/// assert_eq!( /// assert_eq!(
/// hello.lookup(&LangMatch::resolve("ja-JP")), /// hello.lookup(&Locale::resolve("ja-JP")),
/// Some("Hello world!".to_string()) /// Some("Hello world!".to_string())
/// ); /// );
/// ///
/// // Uso típico en un atributo: /// // Uso típico en un atributo:
/// let title = hello.value(&LangMatch::resolve("es-ES")); /// let title = hello.value(&Locale::resolve("es-ES"));
/// // Ejemplo: html! { a title=(title) { "Link" } } /// // Ejemplo: html! { a title=(title) { "Link" } }
/// ``` /// ```
#[derive(AutoDefault, Clone, Debug)] #[derive(AutoDefault, Clone, Debug)]

View file

@ -86,8 +86,8 @@
//! include_locales!(LOCALES_SAMPLE from "ruta/a/las/traducciones"); //! include_locales!(LOCALES_SAMPLE from "ruta/a/las/traducciones");
//! ``` //! ```
//! //!
//! Y *voilà*, sólo queda operar con los idiomas soportados por PageTop usando [`LangMatch`] y //! Y *voilà*, sólo queda operar con los idiomas soportados por PageTop usando [`Locale`] y traducir
//! traducir textos con [`L10n`]. //! textos con [`L10n`].
use crate::html::{Markup, PreEscaped}; use crate::html::{Markup, PreEscaped};
use crate::service::HttpRequest; use crate::service::HttpRequest;
@ -129,9 +129,9 @@ static FALLBACK_LANGID: LazyLock<LanguageIdentifier> = LazyLock::new(|| langid!(
// //
// Se resuelve a partir de [`global::SETTINGS.app.language`](global::SETTINGS). Si el identificador // Se resuelve a partir de [`global::SETTINGS.app.language`](global::SETTINGS). Si el identificador
// de idioma no es válido o no está disponible, se deja sin definir (`None`) y se delega en // de idioma no es válido o no está disponible, se deja sin definir (`None`) y se delega en
// [`LangMatch::default()`] o [`LangId::langid()`] la aplicación del idioma de respaldo. // [`Locale::default()`] o [`LangId::langid()`] la aplicación del idioma de respaldo.
pub(crate) static DEFAULT_LANGID: LazyLock<Option<&LanguageIdentifier>> = LazyLock::new(|| { pub(crate) static DEFAULT_LANGID: LazyLock<Option<&LanguageIdentifier>> = LazyLock::new(|| {
LangMatch::resolve(global::SETTINGS.app.language.as_deref().unwrap_or("")).as_option() Locale::resolve(global::SETTINGS.app.language.as_deref().unwrap_or("")).as_option()
}); });
/// Representa la fuente de idioma (`LanguageIdentifier`) asociada a un recurso. /// Representa la fuente de idioma (`LanguageIdentifier`) asociada a un recurso.
@ -144,7 +144,7 @@ pub trait LangId {
/// Operaciones con los idiomas soportados por PageTop. /// Operaciones con los idiomas soportados por PageTop.
/// ///
/// Utiliza [`LangMatch`] para transformar un identificador de idioma en un [`LanguageIdentifier`] /// Utiliza [`Locale`] para transformar un identificador de idioma en un [`LanguageIdentifier`]
/// soportado por PageTop. /// soportado por PageTop.
/// ///
/// # Ejemplos /// # Ejemplos
@ -152,20 +152,20 @@ pub trait LangId {
/// ```rust /// ```rust
/// # use pagetop::prelude::*; /// # use pagetop::prelude::*;
/// // Coincidencia exacta. /// // Coincidencia exacta.
/// let lang = LangMatch::resolve("es-ES"); /// let lang = Locale::resolve("es-ES");
/// assert_eq!(lang.langid().to_string(), "es-ES"); /// assert_eq!(lang.langid().to_string(), "es-ES");
/// ///
/// // Coincidencia parcial (retrocede al idioma base si no hay variante regional). /// // Coincidencia parcial (retrocede al idioma base si no hay variante regional).
/// let lang = LangMatch::resolve("es-EC"); /// let lang = Locale::resolve("es-EC");
/// assert_eq!(lang.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. /// // Idioma no especificado.
/// let lang = LangMatch::resolve(""); /// let lang = Locale::resolve("");
/// assert_eq!(lang, LangMatch::Unspecified); /// assert_eq!(lang, Locale::Unspecified);
/// ///
/// // Idioma no soportado. /// // Idioma no soportado.
/// let lang = LangMatch::resolve("ja-JP"); /// let lang = Locale::resolve("ja-JP");
/// assert_eq!(lang, LangMatch::Unsupported("ja-JP".to_string())); /// assert_eq!(lang, Locale::Unsupported("ja-JP".to_string()));
/// ``` /// ```
/// ///
/// Con la siguiente instrucción siempre se obtiene un [`LanguageIdentifier`] válido, ya sea porque /// Con la siguiente instrucción siempre se obtiene un [`LanguageIdentifier`] válido, ya sea porque
@ -175,11 +175,11 @@ pub trait LangId {
/// ```rust /// ```rust
/// # use pagetop::prelude::*; /// # use pagetop::prelude::*;
/// // Idioma por defecto o de respaldo si no resuelve. /// // Idioma por defecto o de respaldo si no resuelve.
/// let lang = LangMatch::resolve("it-IT"); /// let lang = Locale::resolve("it-IT");
/// let langid = lang.langid(); /// let langid = lang.langid();
/// ``` /// ```
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum LangMatch { pub enum Locale {
/// Cuando el identificador de idioma es una cadena vacía. /// Cuando el identificador de idioma es una cadena vacía.
Unspecified, Unspecified,
/// Si encuentra un [`LanguageIdentifier`] en la lista de idiomas soportados por PageTop que /// Si encuentra un [`LanguageIdentifier`] en la lista de idiomas soportados por PageTop que
@ -190,15 +190,15 @@ pub enum LangMatch {
Unsupported(String), Unsupported(String),
} }
impl Default for LangMatch { impl Default for Locale {
/// Resuelve al idioma por defecto y, si no está disponible, al idioma de respaldo (`"en-US"`). /// Resuelve al idioma por defecto y, si no está disponible, al idioma de respaldo (`"en-US"`).
fn default() -> Self { fn default() -> Self {
LangMatch::Found(DEFAULT_LANGID.unwrap_or(&FALLBACK_LANGID)) Locale::Found(DEFAULT_LANGID.unwrap_or(&FALLBACK_LANGID))
} }
} }
impl LangMatch { impl Locale {
/// Resuelve `language` y devuelve la variante [`LangMatch`] apropiada. /// Resuelve `language` y devuelve la variante [`Locale`] apropiada.
pub fn resolve(language: impl AsRef<str>) -> Self { pub fn resolve(language: impl AsRef<str>) -> Self {
let language = language.as_ref().trim(); let language = language.as_ref().trim();
@ -224,15 +224,15 @@ impl LangMatch {
Self::Unsupported(language.to_string()) Self::Unsupported(language.to_string())
} }
/// Crea un [`LangMatch`] a partir de una petición HTTP. /// Crea un [`Locale`] a partir de una petición HTTP.
/// ///
/// El orden de resolución del idioma es el siguiente: /// El orden de resolución del idioma es el siguiente:
/// ///
/// 1. Idioma por defecto de la aplicación, si se ha definido en la configuración global /// 1. Idioma por defecto de la aplicación, si se ha definido en la configuración global
/// ([`global::SETTINGS.app.language`]). /// ([`global::SETTINGS.app.language`]).
/// 2. Si no hay idioma por defecto válido, se intenta extraer el idioma de la cabecera HTTP /// 2. Si no hay idioma por defecto válido, se intenta extraer el idioma de la cabecera HTTP
/// `Accept-Language` usando [`LangMatch::resolve`]. /// `Accept-Language` usando [`Locale::resolve`].
/// 3. Si no hay cabecera o el valor no es legible, se devuelve [`LangMatch::Unspecified`]. /// 3. Si no hay cabecera o el valor no es legible, se devuelve [`Locale::Unspecified`].
/// ///
/// Este método **no aplica** idioma de respaldo. Para obtener siempre un [`LanguageIdentifier`] /// Este método **no aplica** idioma de respaldo. Para obtener siempre un [`LanguageIdentifier`]
/// válido (aplicando idioma por defecto y, en último término, el de respaldo), utiliza /// válido (aplicando idioma por defecto y, en último término, el de respaldo), utiliza
@ -240,21 +240,21 @@ impl LangMatch {
pub fn from_request(request: Option<&HttpRequest>) -> Self { pub fn from_request(request: Option<&HttpRequest>) -> Self {
// 1) Se usa `DEFAULT_LANGID` si la aplicación tiene un idioma por defecto válido. // 1) Se usa `DEFAULT_LANGID` si la aplicación tiene un idioma por defecto válido.
if let Some(default) = *DEFAULT_LANGID { if let Some(default) = *DEFAULT_LANGID {
return LangMatch::Found(default); return Locale::Found(default);
} }
// 2) Sin idioma por defecto, se evalúa la cabecera `Accept-Language` de la petición HTTP. // 2) Sin idioma por defecto, se evalúa la cabecera `Accept-Language` de la petición HTTP.
request request
.and_then(|req| req.headers().get("Accept-Language")) .and_then(|req| req.headers().get("Accept-Language"))
.and_then(|value| value.to_str().ok()) .and_then(|value| value.to_str().ok())
// Aplica `resolve()` para devolver `Found`, `Unspecified` o `Unsupported`. // Aplica `resolve()` para devolver `Found`, `Unspecified` o `Unsupported`.
.map(LangMatch::resolve) .map(Locale::resolve)
// 3) Si no hay cabecera o no puede leerse, se considera no especificado. // 3) Si no hay cabecera o no puede leerse, se considera no especificado.
.unwrap_or(LangMatch::Unspecified) .unwrap_or(Locale::Unspecified)
} }
/// Devuelve el [`LanguageIdentifier`] si el idioma fue reconocido. /// Devuelve el [`LanguageIdentifier`] si el idioma fue reconocido.
/// ///
/// Solo retorna `Some` si la variante es [`LangMatch::Found`]. En cualquier otro caso (por /// Solo retorna `Some` si la variante es [`Locale::Found`]. En cualquier otro caso (por
/// ejemplo, si el identificador es vacío o no está soportado), devuelve `None`. /// 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 /// Este método es útil cuando se desea acceder directamente al idioma reconocido sin aplicar el
@ -264,33 +264,33 @@ impl LangMatch {
/// ///
/// ```rust /// ```rust
/// # use pagetop::prelude::*; /// # use pagetop::prelude::*;
/// let lang = LangMatch::resolve("es-ES").as_option(); /// let lang = Locale::resolve("es-ES").as_option();
/// assert_eq!(lang.unwrap().to_string(), "es-ES"); /// assert_eq!(lang.unwrap().to_string(), "es-ES");
/// ///
/// let lang = LangMatch::resolve("ja-JP").as_option(); /// let lang = Locale::resolve("ja-JP").as_option();
/// assert!(lang.is_none()); /// assert!(lang.is_none());
/// ``` /// ```
#[inline] #[inline]
pub fn as_option(&self) -> Option<&'static LanguageIdentifier> { pub fn as_option(&self) -> Option<&'static LanguageIdentifier> {
match self { match self {
LangMatch::Found(l) => Some(l), Locale::Found(l) => Some(l),
_ => None, _ => None,
} }
} }
} }
/// Permite a [`LangMatch`] actuar como proveedor de idioma. /// Permite a [`Locale`] actuar como proveedor de idioma.
/// ///
/// Devuelve el [`LanguageIdentifier`] si la variante es [`LangMatch::Found`]; en caso contrario, /// Devuelve el [`LanguageIdentifier`] si la variante es [`Locale::Found`]; en caso contrario,
/// devuelve el idioma por defecto de la aplicación y, si tampoco está disponible, el idioma de /// devuelve el idioma por defecto de la aplicación y, si tampoco está disponible, el idioma de
/// respaldo ("en-US"). /// respaldo ("en-US").
/// ///
/// Resulta útil para usar un valor de [`LangMatch`] como fuente de traducción en [`L10n::lookup()`] /// Resulta útil para usar un valor de [`Locale`] como fuente de traducción en [`L10n::lookup()`]
/// o [`L10n::using()`]. /// o [`L10n::using()`].
impl LangId for LangMatch { impl LangId for Locale {
fn langid(&self) -> &'static LanguageIdentifier { fn langid(&self) -> &'static LanguageIdentifier {
match self { match self {
LangMatch::Found(l) => l, Locale::Found(l) => l,
_ => DEFAULT_LANGID.unwrap_or(&FALLBACK_LANGID), _ => DEFAULT_LANGID.unwrap_or(&FALLBACK_LANGID),
} }
} }
@ -367,7 +367,7 @@ enum L10nOp {
/// ///
/// ```rust,ignore /// ```rust,ignore
/// // Traducción con clave, conjunto de traducciones y fuente de idioma. /// // Traducción con clave, conjunto de traducciones y fuente de idioma.
/// let bye = L10n::t("goodbye", &LOCALES_CUSTOM).lookup(&LangMatch::resolve("it")); /// let bye = L10n::t("goodbye", &LOCALES_CUSTOM).lookup(&Locale::resolve("it"));
/// ``` /// ```
#[derive(AutoDefault, Clone)] #[derive(AutoDefault, Clone)]
pub struct L10n { pub struct L10n {
@ -436,7 +436,7 @@ impl L10n {
/// let text = L10n::l("greeting").with_arg("name", "Manuel").get(); /// let text = L10n::l("greeting").with_arg("name", "Manuel").get();
/// ``` /// ```
pub fn get(&self) -> Option<String> { pub fn get(&self) -> Option<String> {
self.lookup(&LangMatch::default()) self.lookup(&Locale::default())
} }
/// Resuelve la traducción usando la fuente de idioma proporcionada. /// Resuelve la traducción usando la fuente de idioma proporcionada.
@ -451,7 +451,7 @@ impl L10n {
/// ///
/// impl LangId for ResourceLang { /// impl LangId for ResourceLang {
/// fn langid(&self) -> &'static LanguageIdentifier { /// fn langid(&self) -> &'static LanguageIdentifier {
/// LangMatch::resolve("es-MX").langid() /// Locale::resolve("es-MX").langid()
/// } /// }
/// } /// }
/// ///
@ -488,7 +488,7 @@ impl L10n {
/// ///
/// ```rust /// ```rust
/// # use pagetop::prelude::*; /// # use pagetop::prelude::*;
/// let html = L10n::l("welcome.message").using(&LangMatch::resolve("es")); /// let html = L10n::l("welcome.message").using(&Locale::resolve("es"));
/// ``` /// ```
pub fn using(&self, language: &impl LangId) -> Markup { pub fn using(&self, language: &impl LangId) -> Markup {
PreEscaped(self.lookup(language).unwrap_or_default()) PreEscaped(self.lookup(language).unwrap_or_default())

View file

@ -13,7 +13,7 @@ async fn translation_without_args() {
let _app = service::test::init_service(Application::new().test()).await; let _app = service::test::init_service(Application::new().test()).await;
let l10n = L10n::l("test_hello_world"); let l10n = L10n::l("test_hello_world");
let translation = l10n.lookup(&LangMatch::resolve("es-ES")); let translation = l10n.lookup(&Locale::resolve("es-ES"));
assert_eq!(translation, Some("¡Hola mundo!".to_string())); 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 _app = service::test::init_service(Application::new().test()).await;
let l10n = L10n::l("test_hello_user").with_arg("userName", "Manuel"); let l10n = L10n::l("test_hello_user").with_arg("userName", "Manuel");
let translation = l10n.lookup(&LangMatch::resolve("es-ES")); let translation = l10n.lookup(&Locale::resolve("es-ES"));
assert_eq!(translation, Some("¡Hola, Manuel!".to_string())); assert_eq!(translation, Some("¡Hola, Manuel!".to_string()));
} }
@ -35,7 +35,7 @@ async fn translation_with_plural_and_select() {
("photoCount", "3"), ("photoCount", "3"),
("userGender", "male"), ("userGender", "male"),
]); ]);
let translation = l10n.lookup(&LangMatch::resolve("es-ES")).unwrap(); let translation = l10n.lookup(&Locale::resolve("es-ES")).unwrap();
assert!(translation.contains("añadido 3 nuevas fotos de él")); 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 _app = service::test::init_service(Application::new().test()).await;
let l10n = L10n::l("test_hello_world"); let l10n = L10n::l("test_hello_world");
let translation = l10n.lookup(&LangMatch::resolve("xx-YY")); // Retrocede a "en-US". let translation = l10n.lookup(&Locale::resolve("xx-YY")); // Retrocede a "en-US".
assert_eq!(translation, Some("Hello world!".to_string())); 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 _app = service::test::init_service(Application::new().test()).await;
let l10n = L10n::l("non-existent-key"); let l10n = L10n::l("non-existent-key");
let translation = l10n.lookup(&LangMatch::resolve("en-US")); let translation = l10n.lookup(&Locale::resolve("en-US"));
assert_eq!(translation, None); assert_eq!(translation, None);
} }