212 lines
8.2 KiB
Rust
212 lines
8.2 KiB
Rust
use crate::{global, trace, util};
|
|
|
|
use super::languages::LANGUAGES;
|
|
use super::{LanguageIdentifier, langid};
|
|
|
|
use std::sync::LazyLock;
|
|
|
|
// Identificador del idioma configurado para la aplicación, si es válido.
|
|
static CONFIG_LANGID: LazyLock<Option<&'static LanguageIdentifier>> = LazyLock::new(|| {
|
|
Locale::resolve(global::SETTINGS.app.language.as_deref().unwrap_or("")).as_option()
|
|
});
|
|
|
|
// Identificador del idioma de respaldo (predefinido a `"en-US"`).
|
|
static FALLBACK_LANGID: LazyLock<LanguageIdentifier> = LazyLock::new(|| langid!("en-US"));
|
|
|
|
/// Representa el identificador de idioma [`LanguageIdentifier`] asociado a un recurso.
|
|
///
|
|
/// Este *trait* permite que distintas estructuras expongan su idioma de forma uniforme. Las
|
|
/// implementaciones deben garantizar que siempre se devuelve un identificador de idioma válido. Si
|
|
/// el recurso no tiene uno asignado, se puede devolver, si procede, el identificador de idioma por
|
|
/// defecto de la aplicación ([`Locale::default_langid()`]).
|
|
pub trait LangId {
|
|
/// Devuelve el identificador de idioma asociado al recurso.
|
|
fn langid(&self) -> &'static LanguageIdentifier;
|
|
}
|
|
|
|
/// Resultado de resolver un identificador de idioma.
|
|
///
|
|
/// Utiliza [`Locale::resolve()`] para transformar una cadena de idioma en un [`LanguageIdentifier`]
|
|
/// soportado por PageTop.
|
|
///
|
|
/// # Ejemplos
|
|
///
|
|
/// ```rust
|
|
/// # use pagetop::prelude::*;
|
|
/// // Coincidencia exacta.
|
|
/// let lang = Locale::resolve("es-ES");
|
|
/// assert_eq!(lang.langid().to_string(), "es-ES");
|
|
///
|
|
/// // Coincidencia parcial (retrocede al idioma base si no hay variante regional).
|
|
/// let lang = Locale::resolve("es-EC");
|
|
/// assert_eq!(lang.langid().to_string(), "es-ES"); // Porque "es-EC" no está soportado.
|
|
///
|
|
/// // Idioma no especificado.
|
|
/// let lang = Locale::resolve("");
|
|
/// assert_eq!(lang, Locale::Unspecified);
|
|
///
|
|
/// // Idioma no soportado.
|
|
/// let lang = Locale::resolve("ja-JP");
|
|
/// assert_eq!(lang, Locale::Unsupported("ja-JP".to_string()));
|
|
/// ```
|
|
///
|
|
/// 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 término, el
|
|
/// de respaldo (`"en-US"`):
|
|
///
|
|
/// ```rust,no_run
|
|
/// # use pagetop::prelude::*;
|
|
/// // Idioma por defecto si no resuelve.
|
|
/// let lang = Locale::resolve("it-IT");
|
|
/// let langid = lang.langid();
|
|
/// ```
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub enum Locale {
|
|
/// No se ha especificado ningún identificador de idioma.
|
|
///
|
|
/// Se usa cuando la cadena de idioma está vacía o no se puede obtener un idioma válido de la
|
|
/// petición HTTP.
|
|
Unspecified,
|
|
/// El identificador se ha resuelto a un idioma soportado por PageTop.
|
|
///
|
|
/// Se utiliza cuando se encuentra un [`LanguageIdentifier`] en la lista de idiomas soportados
|
|
/// por PageTop que coincide exactamente con el identificador de idioma (p. ej. `"es-ES"`) o
|
|
/// con el identificador del idioma base (p. ej. `"es"`).
|
|
Resolved(&'static LanguageIdentifier),
|
|
/// El identificador de idioma no está soportado por PageTop.
|
|
Unsupported(String),
|
|
}
|
|
|
|
impl Default for Locale {
|
|
/// Resuelve al idioma por defecto y, si no está disponible, al idioma de respaldo (`"en-US"`).
|
|
fn default() -> Self {
|
|
Locale::Resolved(Locale::default_langid())
|
|
}
|
|
}
|
|
|
|
impl Locale {
|
|
/// Resuelve `language` y devuelve la variante [`Locale`] apropiada.
|
|
///
|
|
/// - Si la cadena está vacía o contiene solo espacios, devuelve [`Locale::Unspecified`].
|
|
/// - Si el idioma se reconoce (ya sea como código completo o como idioma base), devuelve
|
|
/// [`Locale::Resolved`].
|
|
/// - En caso contrario, devuelve [`Locale::Unsupported`] con la cadena original.
|
|
pub fn resolve(language: impl AsRef<str>) -> Self {
|
|
let Some(language) = util::non_blank(language.as_ref()) else {
|
|
return Self::Unspecified;
|
|
};
|
|
|
|
// 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) {
|
|
return Self::Resolved(langid);
|
|
}
|
|
|
|
// Si la variante regional no existe, retrocede al idioma base (p. ej. "es").
|
|
if let Some((base_lang, _)) = lang.split_once('-')
|
|
&& let Some(langid) = LANGUAGES.get(base_lang).map(|(langid, _)| langid)
|
|
{
|
|
return Self::Resolved(langid);
|
|
}
|
|
|
|
// En caso contrario, indica que el idioma no está soportado.
|
|
Self::Unsupported(language.to_string())
|
|
}
|
|
|
|
/// Devuelve el [`LanguageIdentifier`] si el idioma fue reconocido.
|
|
///
|
|
/// Solo retorna `Some` si la variante es [`Locale::Resolved`]. 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 = Locale::resolve("es-ES").as_option();
|
|
/// assert_eq!(lang.unwrap().to_string(), "es-ES");
|
|
///
|
|
/// let lang = Locale::resolve("ja-JP").as_option();
|
|
/// assert!(lang.is_none());
|
|
/// ```
|
|
#[inline]
|
|
pub fn as_option(&self) -> Option<&'static LanguageIdentifier> {
|
|
match self {
|
|
Locale::Resolved(l) => Some(l),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
// **< Locale HELPERS >*************************************************************************
|
|
|
|
/// Inicializa el idioma por defecto que utilizará la aplicación.
|
|
///
|
|
/// Debe llamarse durante la inicialización para indicar si el idioma por defecto procede de la
|
|
/// configuración, de una configuración no válida o del idioma de respaldo.
|
|
pub(crate) fn init() {
|
|
match global::SETTINGS
|
|
.app
|
|
.language
|
|
.as_deref()
|
|
.and_then(util::non_blank)
|
|
{
|
|
Some(raw) => {
|
|
if let Some(langid) = *CONFIG_LANGID {
|
|
trace::debug!("Default language \"{langid}\" (from config: \"{raw}\")");
|
|
} else {
|
|
trace::debug!(
|
|
"Default language \"{}\" (fallback, invalid config: \"{raw}\")",
|
|
*FALLBACK_LANGID
|
|
);
|
|
}
|
|
}
|
|
_ => trace::debug!(
|
|
"Default language \"{}\" (fallback, no config)",
|
|
*FALLBACK_LANGID
|
|
),
|
|
}
|
|
}
|
|
|
|
/// Devuelve el identificador de idioma configurado explícitamente, si es válido.
|
|
///
|
|
/// Si no se ha configurado un idioma por defecto o el valor no es válido, devuelve `None`.
|
|
pub fn try_langid() -> Option<&'static LanguageIdentifier> {
|
|
*CONFIG_LANGID
|
|
}
|
|
|
|
/// Devuelve siempre el identificador de idioma de respaldo (`"en-US"`).
|
|
///
|
|
/// Es el idioma garantizado incluso cuando no haya configuración de la aplicación o cuando
|
|
/// el valor configurado no sea válido.
|
|
pub fn fallback_langid() -> &'static LanguageIdentifier {
|
|
&FALLBACK_LANGID
|
|
}
|
|
|
|
/// Devuelve el identificador de idioma configurado o, en su defecto, el de respaldo.
|
|
///
|
|
/// Este es el idioma que utiliza internamente [`Locale::default()`] y resulta útil como idioma
|
|
/// base cuando no se dispone de un contexto más específico.
|
|
pub fn default_langid() -> &'static LanguageIdentifier {
|
|
(*CONFIG_LANGID).unwrap_or(&FALLBACK_LANGID)
|
|
}
|
|
}
|
|
|
|
/// Permite a [`Locale`] actuar como proveedor de idioma.
|
|
///
|
|
/// Devuelve el [`LanguageIdentifier`] si la variante es [`Locale::Resolved`]; 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 [`Locale`] como fuente de traducción en
|
|
/// [`L10n::lookup()`](crate::locale::L10n::lookup) o [`L10n::using()`](crate::locale::L10n::using).
|
|
impl LangId for Locale {
|
|
#[inline]
|
|
fn langid(&self) -> &'static LanguageIdentifier {
|
|
match self {
|
|
Locale::Resolved(l) => l,
|
|
_ => Locale::default_langid(),
|
|
}
|
|
}
|
|
}
|