Compare commits
No commits in common. "208ad83bea2ecc2f7402a00abb0b25ea44bf3ad7" and "3bb2355b4f269eecd9e533826bd172675de8d072" have entirely different histories.
208ad83bea
...
3bb2355b4f
15 changed files with 11 additions and 792 deletions
278
Cargo.lock
generated
278
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
11
src/app.rs
11
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
|
||||
}
|
||||
|
||||
|
|
|
@ -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<ConfigBuilder<DefaultState>> = 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<ConfigBuilder<DefaultState>> = 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<ConfigBuilder<DefaultState>> = LazyLock::new(
|
|||
/// * **Valores por defecto**. Declara un valor por defecto para cada clave obligatoria. Las claves
|
||||
/// opcionales pueden ser `Option<T>`.
|
||||
///
|
||||
/// * **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<ConfigBuilder<DefaultState>> = 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 ),* $(,)? ] ) => {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
364
src/locale.rs
364
src/locale.rs
|
@ -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<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" ),
|
||||
"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<LanguageIdentifier> = 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<str>) -> 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<str>) -> &'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<String, String>,
|
||||
}
|
||||
|
||||
impl L10n {
|
||||
/// **n** = *“native”*. Crea una instancia con una cadena literal sin traducción.
|
||||
pub fn n(text: impl Into<String>) -> 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<String>) -> 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<String>, 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<String>, value: impl Into<String>) -> 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<I, K, V>(mut self, args: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = (K, V)>,
|
||||
K: Into<String>,
|
||||
V: Into<String>,
|
||||
{
|
||||
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<String> {
|
||||
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<String> {
|
||||
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}")
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
english = English
|
||||
english_british = English (British)
|
||||
english_united_states = English (United States)
|
||||
spanish = Spanish
|
||||
spanish_spain = Spanish (Spain)
|
|
@ -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
|
||||
}.
|
|
@ -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)
|
|
@ -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
|
||||
}.
|
|
@ -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;
|
||||
|
|
|
@ -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<WorkerGuard>` garantiza que todos los registros almacenados se
|
||||
/// envíen antes de finalizar la ejecución.
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub(crate) static TRACING: LazyLock<WorkerGuard> = LazyLock::new(|| {
|
||||
let env_filter = EnvFilter::try_new(&global::SETTINGS.log.tracing)
|
||||
|
|
26
src/util.rs
26
src/util.rs
|
@ -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
|
||||
}};
|
||||
}
|
|
@ -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);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue