diff --git a/Cargo.lock b/Cargo.lock index 64073ea..3fe4d51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -287,16 +287,6 @@ dependencies = [ "alloc-stdlib", ] -[[package]] -name = "bstr" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "bumpalo" version = "3.19.0" @@ -394,25 +384,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -521,81 +492,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "fluent-bundle" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe0a21ee80050c678013f82edf4b705fe2f26f1f9877593d13198612503f493" -dependencies = [ - "fluent-langneg", - "fluent-syntax", - "intl-memoizer", - "intl_pluralrules", - "rustc-hash 1.1.0", - "self_cell 0.10.3", - "smallvec", - "unic-langid", -] - -[[package]] -name = "fluent-langneg" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" -dependencies = [ - "unic-langid", -] - -[[package]] -name = "fluent-syntax" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d" -dependencies = [ - "thiserror", -] - -[[package]] -name = "fluent-template-macros" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ed02449601d0dacdc05cb5e13db5dab8f2b98d773aff5c53b62fad43a1b19a1" -dependencies = [ - "flume", - "ignore", - "proc-macro2", - "quote", - "syn", - "unic-langid", -] - -[[package]] -name = "fluent-templates" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69855a5fe87629495efca79aec72adfa97954f1006f928e3a2ec750cb3e85386" -dependencies = [ - "fluent-bundle", - "fluent-langneg", - "fluent-syntax", - "fluent-template-macros", - "flume", - "ignore", - "intl-memoizer", - "log", - "thiserror", - "unic-langid", -] - -[[package]] -name = "flume" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" -dependencies = [ - "spin", -] - [[package]] name = "fnv" version = "1.0.7" @@ -675,19 +571,6 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "globset" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" -dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", -] - [[package]] name = "h2" version = "0.3.26" @@ -843,22 +726,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "ignore" -version = "0.4.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" -dependencies = [ - "crossbeam-deque", - "globset", - "log", - "memchr", - "regex-automata 0.4.9", - "same-file", - "walkdir", - "winapi-util", -] - [[package]] name = "impl-more" version = "0.1.9" @@ -875,25 +742,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "intl-memoizer" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310da2e345f5eb861e7a07ee182262e94975051db9e4223e909ba90f392f163f" -dependencies = [ - "type-map", - "unic-langid", -] - -[[package]] -name = "intl_pluralrules" -version = "7.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972" -dependencies = [ - "unic-langid", -] - [[package]] name = "itoa" version = "1.0.15" @@ -1070,13 +918,12 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pagetop" -version = "0.0.5" +version = "0.0.4" dependencies = [ "actix-web", "colored", "config", "figlet-rs", - "fluent-templates", "itoa", "pagetop-macros", "serde", @@ -1086,7 +933,6 @@ dependencies = [ "tracing-actix-web", "tracing-appender", "tracing-subscriber", - "unic-langid", ] [[package]] @@ -1206,12 +1052,6 @@ dependencies = [ "toml_edit", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" version = "1.0.95" @@ -1342,18 +1182,6 @@ version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - [[package]] name = "rustix" version = "1.0.7" @@ -1379,36 +1207,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "self_cell" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d" -dependencies = [ - "self_cell 1.2.0", -] - -[[package]] -name = "self_cell" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" - [[package]] name = "serde" version = "1.0.219" @@ -1519,15 +1323,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -1809,64 +1604,12 @@ dependencies = [ "tracing-serde", ] -[[package]] -name = "type-map" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" -dependencies = [ - "rustc-hash 2.1.1", -] - [[package]] name = "typenum" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" -[[package]] -name = "unic-langid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28ba52c9b05311f4f6e62d5d9d46f094bd6e84cb8df7b3ef952748d752a7d05" -dependencies = [ - "unic-langid-impl", - "unic-langid-macros", -] - -[[package]] -name = "unic-langid-impl" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce1bf08044d4b7a94028c93786f8566047edc11110595914de93362559bc658" -dependencies = [ - "tinystr", -] - -[[package]] -name = "unic-langid-macros" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5957eb82e346d7add14182a3315a7e298f04e1ba4baac36f7f0dbfedba5fc25" -dependencies = [ - "proc-macro-hack", - "tinystr", - "unic-langid-impl", - "unic-langid-macros-impl", -] - -[[package]] -name = "unic-langid-macros-impl" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1249a628de3ad34b821ecb1001355bca3940bcb2f88558f1a8bd82e977f75b5" -dependencies = [ - "proc-macro-hack", - "quote", - "syn", - "unic-langid-impl", -] - [[package]] name = "unicode-ident" version = "1.0.18" @@ -1919,16 +1662,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -2018,15 +1751,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index bcf1fd5..b8b3f5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop" -version = "0.0.5" +version = "0.0.4" edition = "2021" description = """\ @@ -28,9 +28,6 @@ tracing-appender = "0.2.3" tracing-subscriber = { version = "0.3.19", features = ["json", "env-filter"] } tracing-actix-web = "0.7.18" -fluent-templates = "0.13.0" -unic-langid = { version = "0.9.6", features = ["macros"] } - actix-web = "4.11.0" pagetop-macros.workspace = true diff --git a/src/app.rs b/src/app.rs index b02b294..d21c08a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,7 +2,7 @@ mod figfont; -use crate::{global, locale, service, trace}; +use crate::{global, service, trace}; use substring::Substring; @@ -11,12 +11,6 @@ use std::sync::LazyLock; pub struct Application; -impl Default for Application { - fn default() -> Self { - Self::new() - } -} - impl Application { /// Crea una instancia de la aplicación. pub fn new() -> Self { @@ -26,9 +20,6 @@ impl Application { // Inicia gestión de trazas y registro de eventos (logging). LazyLock::force(&trace::TRACING); - // Valida el identificador de idioma por defecto. - LazyLock::force(&locale::DEFAULT_LANGID); - Self } diff --git a/src/config.rs b/src/config.rs index d532ba1..eae182f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -23,7 +23,7 @@ //! * Si `PAGETOP_RUN_MODE` no está definida, se asume el valor `default`, y `PageTop` intentará //! cargar *config/default.toml* si el archivo existe. //! -//! * Útil para definir configuraciones específicas por entorno, garantizando que cada uno (p.ej. +//! * Útil para definir configuraciones específicas por entorno, garantizando que cada uno (p.e. //! *dev*, *staging* o *production*) disponga de sus propias opciones, como claves de API, //! URLs o ajustes de rendimiento, sin afectar a los demás. //! @@ -126,7 +126,7 @@ const DEFAULT_RUN_MODE: &str = "default"; /// Valores originales cargados desde los archivos de configuración como pares `clave = valor`. pub static CONFIG_VALUES: LazyLock> = LazyLock::new(|| { // Determina el directorio de configuración: - // - Usa CONFIG_DIR si está definido en el entorno (p.ej.: CONFIG_DIR=/etc/myapp ./myapp). + // - Usa CONFIG_DIR si está definido en el entorno (p.e.: CONFIG_DIR=/etc/myapp ./myapp). // - Si no, intenta DEFAULT_CONFIG_DIR dentro del proyecto (en CARGO_MANIFEST_DIR). // - Si nada de esto aplica, entonces usa DEFAULT_CONFIG_DIR relativo al ejecutable. let config_dir: PathBuf = if let Ok(env_dir) = env::var("CONFIG_DIR") { @@ -143,15 +143,15 @@ pub static CONFIG_VALUES: LazyLock> = LazyLock::new( }; // Determina el modo de ejecución según la variable de entorno PAGETOP_RUN_MODE. Por defecto usa - // DEFAULT_RUN_MODE si no está definida (p.ej.: PAGETOP_RUN_MODE=production ./myapp). + // DEFAULT_RUN_MODE si no está definida (p.e.: PAGETOP_RUN_MODE=production ./myapp). let rm = env::var("PAGETOP_RUN_MODE").unwrap_or_else(|_| DEFAULT_RUN_MODE.into()); Config::builder() // 1. Configuración común para todos los entornos (common.toml). .add_source(File::from(config_dir.join("common.toml")).required(false)) - // 2. Configuración específica del entorno (p.ej.: default.toml, production.toml). + // 2. Configuración específica del entorno (p.e.: default.toml, production.toml). .add_source(File::from(config_dir.join(format!("{rm}.toml"))).required(false)) - // 3. Configuración local reservada para cada entorno (p.ej.: local.default.toml). + // 3. Configuración local reservada para cada entorno (p.e.: local.default.toml). .add_source(File::from(config_dir.join(format!("local.{rm}.toml"))).required(false)) // 4. Configuración local común (local.toml). .add_source(File::from(config_dir.join("local.toml")).required(false)) @@ -217,7 +217,7 @@ pub static CONFIG_VALUES: LazyLock> = LazyLock::new( /// * **Valores por defecto**. Declara un valor por defecto para cada clave obligatoria. Las claves /// opcionales pueden ser `Option`. /// -/// * **Secciones únicas**. Agrupa tus claves dentro de una sección exclusiva (p.ej. `[blog]`) para +/// * **Secciones únicas**. Agrupa tus claves dentro de una sección exclusiva (p.e. `[blog]`) para /// evitar colisiones con otras librerías. /// /// * **Solo lectura**. La variable generada es inmutable durante toda la vida del programa. Para @@ -236,6 +236,7 @@ pub static CONFIG_VALUES: LazyLock> = LazyLock::new( /// [dependencies] /// serde = { version = "1.0", features = ["derive"] } /// ``` + #[macro_export] macro_rules! include_config { ( $SETTINGS_NAME:ident : $Settings_Type:ty => [ $( $k:literal => $v:expr ),* $(,)? ] ) => { diff --git a/src/global.rs b/src/global.rs index a90aa86..8432032 100644 --- a/src/global.rs +++ b/src/global.rs @@ -8,7 +8,6 @@ include_config!(SETTINGS: Settings => [ // [app] "app.name" => "Sample", "app.description" => "Developed with the amazing PageTop framework.", - "app.language" => "en-US", "app.startup_banner" => "Slant", // [log] @@ -39,8 +38,6 @@ pub struct App { pub name: String, /// Breve descripción de la aplicación. pub description: String, - /// Idioma predeterminado (localización). - pub language: String, /// Banner ASCII mostrado al inicio: *"Off"* (desactivado), *"Slant"*, *"Small"*, *"Speed"* o /// *"Starwars"*. pub startup_banner: String, diff --git a/src/lib.rs b/src/lib.rs index 07b9934..b9b2a4b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,8 +36,6 @@ pub use pagetop_macros::{html, main, test, AutoDefault}; // API ********************************************************************************************* -// Funciones y macros útiles. -pub mod util; // Carga las opciones de configuración. pub mod config; // Opciones de configuración globales. @@ -46,8 +44,6 @@ pub mod global; pub mod trace; // HTML en código. pub mod html; -// Localización. -pub mod locale; // Gestión del servidor y servicios web. pub mod service; // Prepara y ejecuta la aplicación. diff --git a/src/locale.rs b/src/locale.rs deleted file mode 100644 index 064481f..0000000 --- a/src/locale.rs +++ /dev/null @@ -1,364 +0,0 @@ -//! Localización (L10n). -//! -//! `PageTop` utiliza las especificaciones de [Fluent](https://www.projectfluent.org/) para la -//! localización de aplicaciones, y aprovecha [fluent-templates](https://docs.rs/fluent-templates/) -//! para integrar los recursos de traducción directamente en el binario de la aplicación. -//! -//! # Sintaxis Fluent (FTL) -//! -//! El formato empleado para describir los recursos de traducción se denomina -//! [FTL](https://www.projectfluent.org/fluent/guide/). Está diseñado para ser legible y expresivo, -//! permitiendo representar construcciones complejas del lenguaje natural como el género, el plural -//! o las conjugaciones verbales. -//! -//! # Recursos Fluent -//! -//! Por defecto, las traducciones se organizan en el directorio *src/locale*, con subdirectorios -//! para cada [Identificador de Idioma Unicode](https://docs.rs/unic-langid/) válido. Podríamos -//! tener una estructura como esta: -//! -//! ```text -//! src/locale/ -//! ├── common.ftl -//! ├── en-US/ -//! │ ├── default.ftl -//! │ └── main.ftl -//! ├── es-ES/ -//! │ ├── default.ftl -//! │ └── main.ftl -//! ├── es-MX/ -//! │ ├── default.ftl -//! │ └── main.ftl -//! └── fr/ -//! ├── default.ftl -//! └── main.ftl -//! ``` -//! -//! Ejemplo de un archivo *src/locale/en-US/main.ftl*: -//! -//! ```text -//! hello-world = Hello world! -//! hello-user = Hello, {$userName}! -//! shared-photos = -//! {$userName} {$photoCount -> -//! [one] added a new photo -//! *[other] added {$photoCount} new photos -//! } of {$userGender -> -//! [male] him and his family -//! [female] her and her family -//! *[other] the family -//! }. -//! ``` -//! -//! Y su archivo equivalente para español *src/locale/es-ES/main.ftl*: -//! -//! ```text -//! hello-world = Hola mundo! -//! hello-user = ¡Hola, {$userName}! -//! shared-photos = -//! {$userName} {$photoCount -> -//! [one] ha añadido una nueva foto -//! *[other] ha añadido {$photoCount} nuevas fotos -//! } de {$userGender -> -//! [male] él y su familia -//! [female] ella y su familia -//! *[other] la familia -//! }. -//! ``` -//! -//! -//! # Cómo aplicar la localización en tu código -//! -//! Una vez creado el directorio con los recursos FTL, basta con utilizar la macro -//! [`include_locales!`](crate::include_locales) para integrarlos en la aplicación. -//! -//! Si los recursos se encuentran en el directorio `"src/locale"`, sólo hay que declarar: -//! -//! ```rust -//! use pagetop::prelude::*; -//! -//! include_locales!(LOCALES_SAMPLE); -//! ``` -//! -//! Pero si están ubicados en otro directorio, entonces se pueden incluir usando: -//! -//! ```rust,ignore -//! include_locales!(LOCALES_SAMPLE from "ruta/a/las/traducciones"); -//! ``` - -use crate::html::{Markup, PreEscaped}; -use crate::{global, hm, AutoDefault}; - -pub use fluent_templates; -pub use unic_langid::{CharacterDirection, LanguageIdentifier}; - -use unic_langid::langid; - -use fluent_templates::Loader; -use fluent_templates::StaticLoader as Locales; - -use std::borrow::Cow; -use std::collections::HashMap; -use std::sync::LazyLock; - -use std::fmt; - -// Asocia cada código de idioma (como "en-US") con su respectivo [`LanguageIdentifier`] y la clave -// en *locale/.../languages.ftl* para obtener el nombre del idioma según la localización. -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" ), - "es" => ( langid!("es-ES"), "spanish" ), - "es-ES" => ( langid!("es-ES"), "spanish_spain" ), - ] -}); - -// Identificador del idioma de **respaldo** (predefinido a `en-US`). -// -// Se usa cuando el valor del código 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")); - -// Identificador del idioma **por defecto** para la aplicación. -// -// Se resuelve a partir de [`global::SETTINGS.app.language`](global::SETTINGS). Si el código de -// idioma configurado 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)); - -/// Comprueba si el idioma está soportado por `PageTop`. -/// -/// Útil para transformar un código de idioma en un [`LanguageIdentifier`] válido para `PageTop`. -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum LangMatch { - /// Cuando el código del idioma es una cadena vacía. - Empty, - /// Si encuentra un [`LanguageIdentifier`] que coincide exactamente o retrocediendo al idioma - /// base. - Found(&'static LanguageIdentifier), - /// Si el código del idioma no está entre los soportados por `PageTop`. - Unknown(String), -} - -impl LangMatch { - /// Resuelve `language` y devuelve el [`LangMatch`] apropiado. - pub fn resolve(language: impl AsRef) -> Self { - let language = language.as_ref().trim(); - - // Rechaza cadenas vacías. - if language.is_empty() { - return Self::Empty; - } - - // Intenta aplicar coincidencia exacta con el código completo (p.ej. "es-MX"). - 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, _)) = language.split_once('-') { - if let Some(langid) = LANGUAGES.get(base_lang).map(|(langid, _)| langid) { - return Self::Found(langid); - } - } - - // Devuelve desconocido si el idioma no está soportado. - Self::Unknown(language.to_string()) - } - - /// Devuelve siempre un [`LanguageIdentifier`] válido. - /// - /// Si `language` está vacío o es desconocido, devuelve el idioma de respaldo ("en-US"). - #[inline] - pub fn langid_or_fallback(language: impl AsRef) -> &'static LanguageIdentifier { - match Self::resolve(language) { - Self::Found(l) => l, - _ => &FALLBACK_LANGID, - } - } - - /// Devuelve un [`LanguageIdentifier`] válido para la instancia. - /// - /// Si `language` está vacío o es desconocido, devuelve el idioma de respaldo ("en-US"). - #[inline] - pub fn as_langid(&self) -> &'static LanguageIdentifier { - match self { - LangMatch::Found(l) => l, - _ => &FALLBACK_LANGID, - } - } -} - -#[macro_export] -/// Define un conjunto de elementos de localización y textos de traducción local. -macro_rules! include_locales { - // Se eliminan las marcas de aislamiento Unicode en los argumentos para mejorar la legibilidad y - // la compatibilidad en ciertos contextos de renderizado. - ( $LOCALES:ident $(, $core_locales:literal)? ) => { - $crate::locale::fluent_templates::static_loader! { - static $LOCALES = { - locales: "src/locale", - $( core_locales: $core_locales, )? - fallback_language: "en-US", - // Elimina marcas de aislamiento Unicode en los argumentos. - customise: |bundle| bundle.set_use_isolating(false), - }; - } - }; - ( $LOCALES:ident from $dir_locales:literal $(, $core_locales:literal)? ) => { - $crate::locale::fluent_templates::static_loader! { - static $LOCALES = { - locales: $dir_locales, - $( core_locales: $core_locales, )? - fallback_language: "en-US", - // Elimina marcas de aislamiento Unicode en los argumentos. - customise: |bundle| bundle.set_use_isolating(false), - }; - } - }; -} - -include_locales!(LOCALES_PAGETOP); - -// Operación de localización a realizar. -// -// * `None` - No se aplica ninguna localización. -// * `Text` - Con una cadena literal que se devolverá tal cual. -// * `Translate` - Con la clave a resolver en el `Locales` indicado. -#[derive(AutoDefault)] -enum L10nOp { - #[default] - None, - Text(String), - Translate(String), -} - -/// Crea instancias para traducir textos localizados. -/// -/// 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 de un conjunto concreto de traducciones (`t()`). -/// -/// Los argumentos dinámicos se añaden mediante `with_arg()` o `with_args()`. -/// -/// ```rust -/// use pagetop::prelude::*; -/// -/// // Texto literal sin traducción. -/// let raw = L10n::n("© 2025 PageTop").get(); -/// -/// // Traducción simple con clave y argumentos. -/// let hello = L10n::l("greeting") -/// .with_arg("name", "Manuel") -/// .markup(); -/// ``` -/// -/// También para traducciones a idiomas concretos. -/// -/// ```rust,ignore -/// // Traducción con clave, conjunto de traducciones y código de idioma a usar. -/// let bye = L10n::t("goodbye", &LOCALES_CUSTOM).using(LangMatch::langid_or_fallback("it")); -/// ``` -#[derive(AutoDefault)] -pub struct L10n { - op: L10nOp, - #[default(&LOCALES_PAGETOP)] - locales: &'static Locales, - args: HashMap, -} - -impl L10n { - /// **n** = *“native”*. Crea una instancia con una cadena literal sin traducción. - pub fn n(text: impl Into) -> Self { - L10n { - op: L10nOp::Text(text.into()), - ..Default::default() - } - } - - /// **l** = *“lookup”*. Crea una instancia para traducir usando una clave de la tabla de - /// traducciones por defecto. - pub fn l(key: impl Into) -> Self { - L10n { - op: L10nOp::Translate(key.into()), - ..Default::default() - } - } - - /// **t** = *“translate”*. Crea una instancia para traducir usando una clave de una tabla de - /// traducciones específica. - pub fn t(key: impl Into, locales: &'static Locales) -> Self { - L10n { - op: L10nOp::Translate(key.into()), - locales, - ..Default::default() - } - } - - /// Añade un argumento `{$arg}` → `value` a la traducción. - pub fn with_arg(mut self, arg: impl Into, value: impl Into) -> Self { - self.args.insert(arg.into(), value.into()); - self - } - - /// Añade varios argumentos a la traducción de una sola vez (p.ej. usando la macro [`hm!`], - /// también vec![("k", "v")], incluso un array de duplas u otras colecciones). - pub fn with_args(mut self, args: I) -> Self - where - I: IntoIterator, - K: Into, - V: Into, - { - self.args - .extend(args.into_iter().map(|(k, v)| (k.into(), v.into()))); - 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. - pub fn get(&self) -> Option { - self.using(&DEFAULT_LANGID) - } - - /// 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 { - match &self.op { - L10nOp::None => None, - L10nOp::Text(text) => Some(text.to_owned()), - L10nOp::Translate(key) => self.locales.try_lookup_with_args( - langid, - key, - &self.args.iter().fold(HashMap::new(), |mut arg, (k, v)| { - arg.insert(Cow::Owned(k.clone()), v.to_owned().into()); - arg - }), - ), - } - } - - /// Traduce y escapa con el idioma por defecto, devolviendo [`Markup`]. - pub fn markup(&self) -> Markup { - PreEscaped(self.get().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 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/locale/en-US/languages.ftl b/src/locale/en-US/languages.ftl deleted file mode 100644 index 1e81660..0000000 --- a/src/locale/en-US/languages.ftl +++ /dev/null @@ -1,5 +0,0 @@ -english = English -english_british = English (British) -english_united_states = English (United States) -spanish = Spanish -spanish_spain = Spanish (Spain) diff --git a/src/locale/en-US/test.ftl b/src/locale/en-US/test.ftl deleted file mode 100644 index 3c317fc..0000000 --- a/src/locale/en-US/test.ftl +++ /dev/null @@ -1,11 +0,0 @@ -test-hello-world = Hello world! -test-hello-user = Hello, { $userName }! -test-shared-photos = - { $userName } { $photoCount -> - [one] added a new photo - *[other] added { $photoCount } new photos - } of { $userGender -> - [male] him and his family - [female] her and her family - *[other] their family - }. diff --git a/src/locale/es-ES/languages.ftl b/src/locale/es-ES/languages.ftl deleted file mode 100644 index ee74ec2..0000000 --- a/src/locale/es-ES/languages.ftl +++ /dev/null @@ -1,5 +0,0 @@ -english = Inglés -english_british = Inglés (Gran Bretaña) -english_united_states = Inglés (Estados Unidos) -spanish = Español -spanish_spain = Español (España) diff --git a/src/locale/es-ES/test.ftl b/src/locale/es-ES/test.ftl deleted file mode 100644 index 02cd22e..0000000 --- a/src/locale/es-ES/test.ftl +++ /dev/null @@ -1,11 +0,0 @@ -test-hello-world = ¡Hola mundo! -test-hello-user = ¡Hola, { $userName }! -test-shared-photos = - { $userName } { $photoCount -> - [one] ha añadido una nueva foto - *[other] ha añadido { $photoCount } nuevas fotos - } de { $userGender -> - [male] él y su familia - [female] ella y su familia - *[other] la familia - }. diff --git a/src/prelude.rs b/src/prelude.rs index af29d37..872447a 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -8,25 +8,17 @@ pub use crate::AutoDefault; // MACROS. -// crate::util -pub use crate::hm; // crate::config pub use crate::include_config; -// crate::locale -pub use crate::include_locales; // API. -pub use crate::util; - pub use crate::global; pub use crate::trace; pub use crate::html::*; -pub use crate::locale::*; - pub use crate::service; pub use crate::app::Application; diff --git a/src/trace.rs b/src/trace.rs index bd8ce56..93a8ff2 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -33,6 +33,7 @@ use std::sync::LazyLock; /// Dado que las trazas o eventos registrados poco antes de un fallo suelen ser cruciales para /// diagnosticar la causa, `Lazy` garantiza que todos los registros almacenados se /// envíen antes de finalizar la ejecución. + #[rustfmt::skip] pub(crate) static TRACING: LazyLock = LazyLock::new(|| { let env_filter = EnvFilter::try_new(&global::SETTINGS.log.tracing) diff --git a/src/util.rs b/src/util.rs deleted file mode 100644 index 07a6abd..0000000 --- a/src/util.rs +++ /dev/null @@ -1,26 +0,0 @@ -//! Funciones y macros útiles. - -// MACROS ÚTILES *********************************************************************************** - -#[macro_export] -/// Macro para construir una colección de pares clave-valor. -/// -/// ```rust -/// use pagetop::hm; -/// use std::collections::HashMap; -/// -/// let args:HashMap<&str, String> = hm![ -/// "userName" => "Roberto", -/// "photoCount" => "3", -/// "userGender" => "male", -/// ]; -/// ``` -macro_rules! hm { - ( $($key:expr => $value:expr),* $(,)? ) => {{ - let mut a = std::collections::HashMap::new(); - $( - a.insert($key.into(), $value.into()); - )* - a - }}; -} diff --git a/tests/locale.rs b/tests/locale.rs deleted file mode 100644 index c723cd4..0000000 --- a/tests/locale.rs +++ /dev/null @@ -1,58 +0,0 @@ -use pagetop::prelude::*; - -#[pagetop::test] -async fn literal_text() { - let _app = service::test::init_service(Application::new().test()).await; - - let l10n = L10n::n("© 2025 PageTop"); - assert_eq!(l10n.get(), Some("© 2025 PageTop".to_string())); -} - -#[pagetop::test] -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_fallback("es-ES")); - assert_eq!(translation, Some("¡Hola mundo!".to_string())); -} - -#[pagetop::test] -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_fallback("es-ES")); - assert_eq!(translation, Some("¡Hola, Manuel!".to_string())); -} - -#[pagetop::test] -async fn translation_with_plural_and_select() { - let _app = service::test::init_service(Application::new().test()).await; - - let l10n = L10n::l("test-shared-photos").with_args(vec![ - ("userName", "Roberto"), - ("photoCount", "3"), - ("userGender", "male"), - ]); - let translation = l10n.using(LangMatch::langid_or_fallback("es-ES")).unwrap(); - assert!(translation.contains("añadido 3 nuevas fotos de él")); -} - -#[pagetop::test] -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")); // Fallback a "en-US". - assert_eq!(translation, Some("Hello world!".to_string())); -} - -#[pagetop::test] -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_fallback("en-US")); - assert_eq!(translation, None); -}