Añade traducción de textos con plantillas Fluent
This commit is contained in:
parent
96884cbbc0
commit
0e3300dc90
14 changed files with 149 additions and 53 deletions
|
|
@ -28,6 +28,10 @@ doc-comment = "0.3.3"
|
||||||
once_cell = "1.9.0"
|
once_cell = "1.9.0"
|
||||||
|
|
||||||
config_rs = { package = "config", version = "0.11.0", features = ["toml"] }
|
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"
|
actix-web = "4.0.0-rc.3"
|
||||||
sycamore = { version = "0.8.0-beta.2", features = ["ssr"] }
|
sycamore = { version = "0.8.0-beta.2", features = ["ssr"] }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
[app]
|
[app]
|
||||||
name = "PageTop Application"
|
name = "PageTop Application"
|
||||||
description = "Developed with the amazing PageTop framework."
|
description = "Developed with the amazing PageTop framework."
|
||||||
|
# Idioma (localización) predeterminado.
|
||||||
|
language = "en-US"
|
||||||
|
|
||||||
[webserver]
|
[webserver]
|
||||||
# Configuración opcional del servidor web.
|
# Configuración opcional del servidor web.
|
||||||
|
|
|
||||||
3
src/base/mod.rs
Normal file
3
src/base/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
//! Temas, Módulos y Componentes base.
|
||||||
|
|
||||||
|
pub mod module;
|
||||||
4
src/base/module/homepage/locales/en-US/homepage.ftl
Normal file
4
src/base/module/homepage/locales/en-US/homepage.ftl
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
module_name = Default homepage
|
||||||
|
module_desc = Displays a default homepage when none is configured.
|
||||||
|
|
||||||
|
greeting = Hello { $name }!
|
||||||
4
src/base/module/homepage/locales/es-ES/homepage.ftl
Normal file
4
src/base/module/homepage/locales/es-ES/homepage.ftl
Normal 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 }!
|
||||||
39
src/base/module/homepage/mod.rs
Normal file
39
src/base/module/homepage/mod.rs
Normal 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
1
src/base/module/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod homepage;
|
||||||
|
|
@ -72,6 +72,7 @@ macro_rules! config_map {
|
||||||
pub struct App {
|
pub struct App {
|
||||||
pub name : String,
|
pub name : String,
|
||||||
pub description : String,
|
pub description : String,
|
||||||
|
pub language : String,
|
||||||
pub run_mode : String,
|
pub run_mode : String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,7 +89,7 @@ pub struct Settings {
|
||||||
}
|
}
|
||||||
|
|
||||||
config_map!(r#"
|
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.
|
*\[webserver\]* específicas de PageTop.
|
||||||
"#,
|
"#,
|
||||||
SETTINGS, Settings,
|
SETTINGS, Settings,
|
||||||
|
|
@ -96,6 +97,7 @@ Ajustes globales y valores por defecto para las secciones *\[app\]* y
|
||||||
// [app]
|
// [app]
|
||||||
"app.name" => "PageTop Application",
|
"app.name" => "PageTop Application",
|
||||||
"app.description" => "Developed with the amazing PageTop framework.",
|
"app.description" => "Developed with the amazing PageTop framework.",
|
||||||
|
"app.language" => "en-US",
|
||||||
|
|
||||||
// [webserver]
|
// [webserver]
|
||||||
"webserver.bind_address" => "localhost",
|
"webserver.bind_address" => "localhost",
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
|
use crate::base;
|
||||||
use crate::config::SETTINGS;
|
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> {
|
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 {
|
if bootstrap != None {
|
||||||
let _ = &(bootstrap.unwrap())();
|
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.
|
// Inicializa el servidor web.
|
||||||
let server = server::HttpServer::new(|| {
|
let server = server::HttpServer::new(|| {
|
||||||
server::App::new()
|
server::App::new()
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,10 @@ pub use once_cell::sync::Lazy;
|
||||||
// APIs públicas.
|
// APIs públicas.
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub mod macros; // Macros útiles.
|
||||||
pub mod config; // Gestión de la configuración.
|
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 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
47
src/locale/mod.rs
Normal 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
19
src/macros/mod.rs
Normal 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
|
||||||
|
}};
|
||||||
|
}
|
||||||
51
src/main.rs
51
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]
|
#[tokio::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
server::run(Some(bootstrap))?.await
|
pagetop::core::server::run(None)?.await
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
src/prelude.rs
Normal file
10
src/prelude.rs
Normal 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;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue