diff --git a/Cargo.toml b/Cargo.toml index 67ed8c44..0b8836ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,10 @@ doc-comment = "0.3.3" once_cell = "1.9.0" config_rs = { package = "config", version = "0.11.0", features = ["toml"] } + +fluent-templates = "0.6.1" +unic-langid = "0.9.0" + actix-web = "4.0.0-rc.3" sycamore = { version = "0.8.0-beta.2", features = ["ssr"] } diff --git a/config/settings.default.toml b/config/settings.default.toml index cef2d7cf..dbbf8355 100644 --- a/config/settings.default.toml +++ b/config/settings.default.toml @@ -1,6 +1,8 @@ [app] name = "PageTop Application" description = "Developed with the amazing PageTop framework." +# Idioma (localización) predeterminado. +language = "en-US" [webserver] # Configuración opcional del servidor web. diff --git a/src/base/mod.rs b/src/base/mod.rs new file mode 100644 index 00000000..fadf78f5 --- /dev/null +++ b/src/base/mod.rs @@ -0,0 +1,3 @@ +//! Temas, Módulos y Componentes base. + +pub mod module; diff --git a/src/base/module/homepage/locales/en-US/homepage.ftl b/src/base/module/homepage/locales/en-US/homepage.ftl new file mode 100644 index 00000000..380b29ee --- /dev/null +++ b/src/base/module/homepage/locales/en-US/homepage.ftl @@ -0,0 +1,4 @@ +module_name = Default homepage +module_desc = Displays a default homepage when none is configured. + +greeting = Hello { $name }! diff --git a/src/base/module/homepage/locales/es-ES/homepage.ftl b/src/base/module/homepage/locales/es-ES/homepage.ftl new file mode 100644 index 00000000..46571c42 --- /dev/null +++ b/src/base/module/homepage/locales/es-ES/homepage.ftl @@ -0,0 +1,4 @@ +module_name = Página de inicio predeterminada +module_desc = Muestra una página de inicio predeterminada cuando no hay ninguna configurada. + +greeting = Hola { $name }! diff --git a/src/base/module/homepage/mod.rs b/src/base/module/homepage/mod.rs new file mode 100644 index 00000000..637659cd --- /dev/null +++ b/src/base/module/homepage/mod.rs @@ -0,0 +1,39 @@ +use crate::prelude::*; + +localize!("en-US", "src/base/module/homepage/locales"); + +pub struct HomepageModule; + +impl Module for HomepageModule { + fn name(&self) -> String { + l("module_name") + } + + fn description(&self) -> String { + l("module_desc") + } + + fn configure_module(&self, cfg: &mut server::web::ServiceConfig) { + cfg.service( + server::web::resource("/") + .route(server::web::get().to(greet)) + ); + cfg.service( + server::web::resource("/{name}") + .route(server::web::get().to(greet_with_param)) + ); + } +} + +async fn greet() -> impl server::Responder { + t("greeting", &args!["name" => config_get!("app.name")]) +} + +async fn greet_with_param(req: server::HttpRequest) -> server::HttpResponse { + let name: String = req.match_info().get("name").unwrap_or("World").into(); + let args = args!["name" => name]; + server::HttpResponse::Ok() + .body(sycamore::render_to_string(|ctx| sycamore::view! { ctx, + p { (t("greeting", &args)) } + })) +} diff --git a/src/base/module/mod.rs b/src/base/module/mod.rs new file mode 100644 index 00000000..070e5b82 --- /dev/null +++ b/src/base/module/mod.rs @@ -0,0 +1 @@ +pub mod homepage; diff --git a/src/config/settings.rs b/src/config/settings.rs index ffb316f6..88c5805d 100644 --- a/src/config/settings.rs +++ b/src/config/settings.rs @@ -72,6 +72,7 @@ macro_rules! config_map { pub struct App { pub name : String, pub description : String, + pub language : String, pub run_mode : String, } @@ -88,7 +89,7 @@ pub struct Settings { } config_map!(r#" -Ajustes globales y valores por defecto para las secciones *\[app\]* y +Ajustes globales y valores predeterminados para las secciones *\[app\]* y *\[webserver\]* específicas de PageTop. "#, SETTINGS, Settings, @@ -96,6 +97,7 @@ Ajustes globales y valores por defecto para las secciones *\[app\]* y // [app] "app.name" => "PageTop Application", "app.description" => "Developed with the amazing PageTop framework.", + "app.language" => "en-US", // [webserver] "webserver.bind_address" => "localhost", diff --git a/src/core/server/main.rs b/src/core/server/main.rs index 3f5a7d90..49e78a65 100644 --- a/src/core/server/main.rs +++ b/src/core/server/main.rs @@ -1,12 +1,17 @@ +use crate::base; use crate::config::SETTINGS; -use crate::core::{Server, all, server}; +use crate::core::{Server, all, register_module, server}; pub fn run(bootstrap: Option) -> Result { - // Ejecuta la función de inicio específica para la aplicación. + // Ejecuta la función de arranque de la aplicación. if bootstrap != None { let _ = &(bootstrap.unwrap())(); } + // Registra la página de inicio de PageTop como último módulo. + // Así, la función de arranque de la aplicación podría sobrecargarlo. + register_module(&base::module::homepage::HomepageModule); + // Inicializa el servidor web. let server = server::HttpServer::new(|| { server::App::new() diff --git a/src/lib.rs b/src/lib.rs index 15be4ce1..87e80aac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,5 +7,10 @@ pub use once_cell::sync::Lazy; // APIs públicas. // ----------------------------------------------------------------------------- +pub mod macros; // Macros útiles. pub mod config; // Gestión de la configuración. +pub mod locale; // Localización. pub mod core; // Servidor web y sistemas para Temas, Módulos y Respuestas. +pub mod base; // Temas, Módulos y Componentes base. + +pub mod prelude; // Re-exporta recursos comunes. diff --git a/src/locale/mod.rs b/src/locale/mod.rs new file mode 100644 index 00000000..21fd7bd1 --- /dev/null +++ b/src/locale/mod.rs @@ -0,0 +1,47 @@ +use crate::Lazy; +use crate::config::SETTINGS; + +use unic_langid::LanguageIdentifier; + +pub use fluent_templates::{static_loader as static_locale, Loader as Locale}; +pub use fluent_templates; +pub use fluent_templates::fluent_bundle::FluentValue; + +/// Almacena el Identificador de Idioma Unicode ([Unicode Language Identifier] +/// (https://unicode.org/reports/tr35/tr35.html#Unicode_language_identifier)) de +/// la aplicación, obtenido de `SETTINGS.app.language`. +pub static LANGID: Lazy = Lazy::new(|| { + SETTINGS.app.language.parse().expect("Failed to parse.") +}); + +#[macro_export] +/// Permite integrar fácilmente localización en tus temas y módulos. +macro_rules! localize { + ( $DEF_LANGID:literal, $locales:literal $(, $core_locales:literal)? ) => { + use $crate::locale::*; + + static_locale! { + static LOCALES = { + locales: $locales, + $( core_locales: $core_locales, )? + fallback_language: $DEF_LANGID, + + // Elimina las marcas Unicode que delimitan los argumentos. + customise: |bundle| bundle.set_use_isolating(false), + }; + } + + #[allow(dead_code)] + pub fn l(key: &str) -> String { + LOCALES.lookup(&LANGID, key) + } + + #[allow(dead_code)] + pub fn t( + key: &str, + args: &std::collections::HashMap + ) -> String { + LOCALES.lookup_with_args(&LANGID, key, args) + } + }; +} diff --git a/src/macros/mod.rs b/src/macros/mod.rs new file mode 100644 index 00000000..ef3c05d5 --- /dev/null +++ b/src/macros/mod.rs @@ -0,0 +1,19 @@ +#[macro_export] +/// Macro para construir grupos de pares clave-valor. +/// +/// ``` +/// let args = args![ +/// "userName" => "Roberto", +/// "photoCount" => 3, +/// "userGender" => "male" +/// ]; +/// ``` +macro_rules! args { + ( $($key:expr => $value:expr),* ) => {{ + let mut a = std::collections::HashMap::new(); + $( + a.insert(String::from($key), $value.into()); + )* + a + }}; +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 4d3e46b9..96cee3df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,53 +1,4 @@ -use pagetop::config_get; -use pagetop::core::module::Module; -use pagetop::core::{register_module, server}; - -struct Greet; -impl Module for Greet { - fn name(&self) -> String { - "Hello".to_string() - } - - fn configure_module(&self, cfg: &mut server::web::ServiceConfig) { - cfg.service( - server::web::resource("/") - .route(server::web::get().to(greet)) - ); - } -} - -async fn greet() -> impl server::Responder { - format!("Hello from {}!", config_get!("app.name")) -} - -struct GreetWithParam; -impl Module for GreetWithParam { - fn name(&self) -> String { - "Hello World!".to_string() - } - - fn configure_module(&self, cfg: &mut server::web::ServiceConfig) { - cfg.service( - server::web::resource("/{name}") - .route(server::web::get().to(greet_with_param)) - ); - } -} - -async fn greet_with_param(req: server::HttpRequest) -> server::HttpResponse { - let name: String = req.match_info().get("name").unwrap_or("World").into(); - server::HttpResponse::Ok() - .body(sycamore::render_to_string(|ctx| sycamore::view! { ctx, - p { "Hello " (name) "!" } - })) -} - -fn bootstrap() { - register_module(&Greet); - register_module(&GreetWithParam); -} - #[tokio::main] async fn main() -> std::io::Result<()> { - server::run(Some(bootstrap))?.await + pagetop::core::server::run(None)?.await } diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 00000000..43370939 --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1,10 @@ +//! Re-exporta recursos comunes. + +pub use crate::args; +pub use crate::config_get; +pub use crate::localize; + +pub use crate::core::module::*; + +pub use crate::core::server; +pub use crate::core::register_module;