Añade traducción de textos con plantillas Fluent

This commit is contained in:
Manuel Cillero 2022-02-13 09:12:12 +01:00
parent 96884cbbc0
commit 0e3300dc90
14 changed files with 149 additions and 53 deletions

View file

@ -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"] }

View file

@ -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.

3
src/base/mod.rs Normal file
View file

@ -0,0 +1,3 @@
//! Temas, Módulos y Componentes base.
pub mod module;

View file

@ -0,0 +1,4 @@
module_name = Default homepage
module_desc = Displays a default homepage when none is configured.
greeting = Hello { $name }!

View file

@ -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 }!

View file

@ -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)) }
}))
}

1
src/base/module/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod homepage;

View file

@ -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",

View file

@ -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<fn()>) -> Result<Server, std::io::Error> {
// 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()

View file

@ -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.

47
src/locale/mod.rs Normal file
View file

@ -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<LanguageIdentifier> = 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, FluentValue>
) -> String {
LOCALES.lookup_with_args(&LANGID, key, args)
}
};
}

19
src/macros/mod.rs Normal file
View file

@ -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
}};
}

View file

@ -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
}

10
src/prelude.rs Normal file
View file

@ -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;