Compare commits

..

No commits in common. "ac0889cb8c1a117e3b86c3f61378c900c59937b1" and "ef8d16f41fae0fee78f3e97f389009f8a2d75192" have entirely different histories.

9 changed files with 138 additions and 221 deletions

View file

@ -28,15 +28,12 @@ async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> {
.with_title(L10n::l("welcome_page"))
.with_theme("Basic")
.with_assets(AssetsOp::AddStyleSheet(StyleSheet::from("/css/welcome.css")))
.with_component(Html::with(move |cx| html! {
.with_component(Html::with(move |_| html! {
div id="main-header" {
header {
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))
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))
}
}
aside id="header-image" aria-hidden="true" {
@ -62,25 +59,20 @@ async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> {
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").to_markup(cx)) }
div id="poweredby-text" { (L10n::l("welcome_powered")) }
}
}
div class="content-text" {
p { (L10n::l("welcome_text1").to_markup(cx)) }
p { (L10n::l("welcome_text2").to_markup(cx)) }
p { (L10n::l("welcome_text1")) }
p { (L10n::l("welcome_text2")) }
div class="subcontent" {
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)) }
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)) }
}
}
}
@ -93,7 +85,7 @@ async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> {
viewBox="0 0 1614 1614"
xmlns="http://www.w3.org/2000/svg"
role="img"
aria-label=[L10n::l("pagetop_logo").using(cx)]
aria-label=(L10n::l("pagetop_logo"))
preserveAspectRatio="xMidYMid slice"
focusable="false"
{
@ -105,8 +97,8 @@ async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> {
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").to_markup(cx)) }
em { (L10n::l("welcome_have_fun").to_markup(cx)) }
a href="https://git.cillero.es/manuelcillero/pagetop" target="_blank" rel="noreferrer" { (L10n::l("welcome_code")) }
em { (L10n::l("welcome_have_fun")) }
}
}
}

View file

@ -240,7 +240,7 @@ pub static CONFIG_VALUES: LazyLock<ConfigBuilder<DefaultState>> = LazyLock::new(
macro_rules! include_config {
( $SETTINGS_NAME:ident : $Settings_Type:ty => [ $( $k:literal => $v:expr ),* $(,)? ] ) => {
#[doc = concat!(
"Referencia y valores por defecto de los ajustes de configuración para [`",
"Referencia a los ajustes de configuración deserializados de [`",
stringify!($Settings_Type),
"`]."
)]

View file

@ -74,12 +74,12 @@ impl InRegion {
/// use pagetop::prelude::*;
///
/// // Banner global, en la región por defecto de cualquier página.
/// InRegion::Content.add(Child::with(Html::with(|_|
/// InRegion::Content.add(Child::with(Html::with(
/// html! { ("🎉 ¡Bienvenido!") }
/// )));
///
/// // Texto en la región "sidebar".
/// InRegion::Named("sidebar").add(Child::with(Html::with(|_|
/// InRegion::Named("sidebar").add(Child::with(Html::with(
/// html! { ("Publicidad") }
/// )));
/// ```

View file

@ -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" => "",
"app.language" => "en-US",
"app.startup_banner" => "Slant",
// [dev]
@ -48,14 +48,7 @@ pub struct App {
pub description: String,
/// Tema predeterminado.
pub theme: String,
/// 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").
/// Idioma predeterminado (localización).
pub language: String,
/// Banner ASCII mostrado al inicio: *"Off"* (desactivado), *"Slant"*, *"Small"*, *"Speed"* o
/// *"Starwars"*.

View file

@ -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::{LangId, LangMatch, LanguageIdentifier, DEFAULT_LANGID, FALLBACK_LANGID};
use crate::locale::{LanguageIdentifier, DEFAULT_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(Some(request))
/// Context::new(request)
/// // Establece el idioma del documento a español.
/// .with_langid(&LangMatch::resolve("es-ES"))
/// .with_langid(LangMatch::langid_or_default("es-ES"))
/// // Selecciona un tema (por su nombre corto).
/// .with_theme("aliner")
/// // Asigna un favicon.
@ -125,23 +125,9 @@ impl Context {
/// cargados.
#[rustfmt::skip]
pub fn new(request: Option<HttpRequest>) -> 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<HttpRequest>` 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,
langid : &DEFAULT_LANGID,
theme : *DEFAULT_THEME,
layout : "default",
favicon : None,
@ -154,10 +140,10 @@ impl Context {
// Context BUILDER *****************************************************************************
/// Modifica la fuente de idioma del documento.
/// Modifica el identificador de idioma del documento.
#[builder_fn]
pub fn with_langid(mut self, language: &impl LangId) -> Self {
self.langid = language.langid();
pub fn with_langid(mut self, langid: &'static LanguageIdentifier) -> Self {
self.langid = langid;
self
}
@ -216,6 +202,11 @@ 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
@ -290,21 +281,3 @@ 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
}
}

View file

@ -1,5 +1,5 @@
use crate::html::Markup;
use crate::locale::{L10n, LangId};
use crate::locale::{L10n, LanguageIdentifier};
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::resolve("es-ES")),
/// hello.using(LangMatch::langid_or_default("es-ES")),
/// Some(String::from("¡Hola mundo!"))
/// );
///
/// // Japonés no disponible, traduce al idioma de respaldo ("en-US").
/// assert_eq!(
/// hello.using(&LangMatch::resolve("ja-JP")),
/// hello.using(LangMatch::langid_or_fallback("ja-JP")),
/// Some(String::from("Hello world!"))
/// );
///
/// // Para incrustar en HTML escapado:
/// let markup = hello.to_markup(&LangMatch::resolve("es-ES"));
/// let markup = hello.escaped(LangMatch::langid_or_default("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 `language`, si existe.
pub fn using(&self, language: &impl LangId) -> Option<String> {
self.0.using(language)
/// Devuelve la traducción para `langid`, si existe.
pub fn using(&self, langid: &LanguageIdentifier) -> Option<String> {
self.0.using(langid)
}
/// Devuelve la traducción *escapada* como [`Markup`] para `language`, si existe.
/// Devuelve la traducción *escapada* como [`Markup`] para `langid`, si existe.
///
/// Útil para incrustar el texto directamente en plantillas HTML sin riesgo de inyección de
/// contenido.
pub fn to_markup(&self, language: &impl LangId) -> Markup {
self.0.to_markup(language)
pub fn escaped(&self, langid: &LanguageIdentifier) -> Markup {
self.0.escaped(langid)
}
}

View file

@ -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};
use crate::html::{Markup, PreEscaped, Render};
use crate::{global, hm, AutoDefault};
pub use fluent_templates;
@ -112,10 +112,10 @@ use std::fmt;
static LANGUAGES: LazyLock<HashMap<&str, (LanguageIdentifier, &str)>> = 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,23 +123,14 @@ static LANGUAGES: LazyLock<HashMap<&str, (LanguageIdentifier, &str)>> = LazyLock
//
// Se usa cuando el valor del identificador de idioma en las traducciones no corresponde con ningún
// idioma soportado por la aplicación.
pub(crate) static FALLBACK_LANGID: LazyLock<LanguageIdentifier> =
LazyLock::new(|| langid!("en-US"));
static FALLBACK_LANGID: LazyLock<LanguageIdentifier> = 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<Option<&LanguageIdentifier>> =
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;
}
pub(crate) static DEFAULT_LANGID: LazyLock<&LanguageIdentifier> =
LazyLock::new(|| LangMatch::langid_or_fallback(&global::SETTINGS.app.language));
/// Operaciones con los idiomas soportados por `PageTop`.
///
@ -153,11 +144,11 @@ pub trait LangId {
///
/// // Coincidencia exacta.
/// let lang = LangMatch::resolve("es-ES");
/// assert_eq!(lang.langid().to_string(), "es-ES");
/// assert_eq!(lang.as_langid().to_string(), "es-ES");
///
/// // Coincidencia parcial (con el idioma base).
/// let lang = LangMatch::resolve("es-EC");
/// assert_eq!(lang.langid().to_string(), "es-ES"); // Porque "es-EC" no está soportado.
/// assert_eq!(lang.as_langid().to_string(), "es-ES"); // Porque "es-EC" no está soportado.
///
/// // Idioma no especificado.
/// let lang = LangMatch::resolve("");
@ -168,16 +159,21 @@ pub trait LangId {
/// assert_eq!(lang, LangMatch::Unsupported(String::from("ja-JP")));
/// ```
///
/// 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"):
/// 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:
///
/// ```rust
/// use pagetop::prelude::*;
///
/// // Idioma por defecto o de respaldo si no resuelve.
/// // Idioma por defecto si no resuelve.
/// let lang = LangMatch::resolve("it-IT");
/// let langid = lang.langid();
/// 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");
/// ```
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum LangMatch {
@ -191,13 +187,6 @@ 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<str>) -> Self {
@ -209,13 +198,12 @@ impl LangMatch {
}
// Intenta aplicar coincidencia exacta con el código completo (p.ej. "es-MX").
let lang = language.to_ascii_lowercase();
if let Some(langid) = LANGUAGES.get(lang.as_str()).map(|(langid, _)| langid) {
if let Some(langid) = LANGUAGES.get(language).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, _)) = lang.split_once('-') {
if let Some((base_lang, _)) = language.split_once('-') {
if let Some(langid) = LANGUAGES.get(base_lang).map(|(langid, _)| langid) {
return Self::Found(langid);
}
@ -225,47 +213,37 @@ impl LangMatch {
Self::Unsupported(String::from(language))
}
/// Devuelve el [`LanguageIdentifier`] si el idioma fue reconocido.
/// Devuelve el idioma de la variante de la instancia, o el idioma por defecto si no está
/// soportado.
///
/// 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());
/// ```
/// Siempre devuelve un [`LanguageIdentifier`] válido.
#[inline]
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 {
pub fn as_langid(&self) -> &'static LanguageIdentifier {
match self {
LangMatch::Found(l) => l,
_ => DEFAULT_LANGID.unwrap_or(&FALLBACK_LANGID),
_ => &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<str>) -> &'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<str>) -> &'static LanguageIdentifier {
match Self::resolve(language) {
Self::Found(l) => l,
_ => &FALLBACK_LANGID,
}
}
}
@ -319,7 +297,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 predefinidas de `PageTop` (`l()`).
/// - Una clave para traducir un texto de las traducciones por defecto de `PageTop` (`l()`).
/// - Una clave para traducir de un conjunto concreto de traducciones (`t()`).
///
/// # Ejemplo
@ -341,8 +319,8 @@ enum L10nOp {
/// También para traducciones a idiomas concretos.
///
/// ```rust,ignore
/// // Traducción con clave, conjunto de traducciones y fuente de idioma.
/// let bye = L10n::t("goodbye", &LOCALES_CUSTOM).using(&LangMatch::resolve("it"));
/// // 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"));
/// ```
#[derive(AutoDefault, Clone)]
pub struct L10n {
@ -361,8 +339,8 @@ impl L10n {
}
}
/// **l** = *“lookup”*. Crea una instancia para traducir usando una clave del conjunto de
/// traducciones predefinidas.
/// **l** = *“lookup”*. Crea una instancia para traducir usando una clave de la tabla de
/// traducciones por defecto.
pub fn l(key: impl Into<String>) -> Self {
L10n {
op: L10nOp::Translate(key.into()),
@ -370,8 +348,8 @@ impl L10n {
}
}
/// **t** = *“translate”*. Crea una instancia para traducir usando una clave de un conjunto de
/// traducciones específico.
/// **t** = *“translate”*. Crea una instancia para traducir usando una clave de una tabla de
/// traducciones específica.
pub fn t(key: impl Into<String>, locales: &'static Locales) -> Self {
L10n {
op: L10nOp::Translate(key.into()),
@ -399,47 +377,20 @@ impl L10n {
self
}
/// 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();
/// ```
/// Resuelve la traducción usando el idioma por defecto de la aplicación. Devuelve `None` si no
/// aplica o no encuentra una traducción.
pub fn get(&self) -> Option<String> {
self.using(&LangMatch::default())
self.using(&DEFAULT_LANGID)
}
/// 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<String> {
/// 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<String> {
match &self.op {
L10nOp::None => None,
L10nOp::Text(text) => Some(text.to_owned()),
L10nOp::Translate(key) => self.locales.try_lookup_with_args(
language.langid(),
langid,
key,
&self.args.iter().fold(HashMap::new(), |mut arg, (k, v)| {
arg.insert(Cow::Owned(k.clone()), v.to_owned().into());
@ -449,19 +400,16 @@ impl L10n {
}
}
/// 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())
/// 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())
}
}
@ -475,3 +423,14 @@ 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}")
}
}

View file

@ -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, LangId, LanguageIdentifier};
use crate::locale::{CharacterDirection, L10n, LanguageIdentifier};
use crate::service::HttpRequest;
/// Representa una página HTML completa lista para renderizar.
@ -78,10 +78,10 @@ impl Page {
self
}
/// Modifica la fuente de idioma de la página ([`Context::with_langid`]).
/// Modifica el identificador de idioma de la página ([`Context::with_langid`]).
#[builder_fn]
pub fn with_langid(mut self, language: &impl LangId) -> Self {
self.context.alter_langid(language);
pub fn with_langid(mut self, langid: &'static LanguageIdentifier) -> Self {
self.context.alter_langid(langid);
self
}
@ -147,14 +147,14 @@ impl Page {
// Page GETTERS ********************************************************************************
/// Devuelve el título traducido para el idioma de la página, si existe.
/// Devuelve el título traducido para el idioma activo, si existe.
pub fn title(&mut self) -> Option<String> {
self.title.using(&self.context)
self.title.using(self.context.langid())
}
/// Devuelve la descripción traducida para el idioma de la página, si existe.
/// Devuelve la descripción traducida para el idioma activo, si existe.
pub fn description(&mut self) -> Option<String> {
self.description.using(&self.context)
self.description.using(self.context.langid())
}
/// Devuelve la lista de metadatos `<meta name=...>`.

View file

@ -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::resolve("es-ES"));
let translation = l10n.using(LangMatch::langid_or_default("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::resolve("es-ES"));
let translation = l10n.using(LangMatch::langid_or_default("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::resolve("es-ES")).unwrap();
let translation = l10n.using(LangMatch::langid_or_default("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::resolve("xx-YY")); // Retrocede a "en-US".
let translation = l10n.using(LangMatch::langid_or_fallback("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::resolve("en-US"));
let translation = l10n.using(LangMatch::langid_or_default("en-US"));
assert_eq!(translation, None);
}