🎨 Nueov enfoque para las funciones de traducción

This commit is contained in:
Manuel Cillero 2023-05-21 21:59:47 +02:00
parent 7691bf4b2f
commit 71b0b0889d
11 changed files with 110 additions and 93 deletions

View file

@ -2,7 +2,7 @@ use pagetop::prelude::*;
define_handle!(MODULE_ADMIN); define_handle!(MODULE_ADMIN);
define_locale!("src/locales"); define_locale!(LOCALE_ADMIN, "src/locales");
mod summary; mod summary;
@ -14,11 +14,11 @@ impl ModuleTrait for Admin {
} }
fn name(&self) -> String { fn name(&self) -> String {
l("module_name") t("module_name", Locale::From(&LOCALE_ADMIN))
} }
fn description(&self) -> Option<String> { fn description(&self) -> Option<String> {
Some(l("module_description")) Some(t("module_description", Locale::From(&LOCALE_ADMIN)))
} }
#[rustfmt::skip] #[rustfmt::skip]

View file

@ -1,11 +1,13 @@
use super::l; use super::LOCALE_ADMIN;
use pagetop::prelude::*; use pagetop::prelude::*;
use pagetop_megamenu::component::{MegaMenu, MegaMenuItem}; use pagetop_megamenu::component::{MegaMenu, MegaMenuItem};
use pagetop_minimal::component::*; use pagetop_minimal::component::*;
pub async fn summary(request: server::HttpRequest) -> ResultPage<Markup, FatalError> { pub async fn summary(request: server::HttpRequest) -> ResultPage<Markup, FatalError> {
let top_menu = MegaMenu::new() let top_menu = MegaMenu::new()
.with_item(MegaMenuItem::label(l("module_name").as_str())) .with_item(MegaMenuItem::label(
t("module_name", Locale::From(&LOCALE_ADMIN)).as_str(),
))
.with_item(MegaMenuItem::link("Opción 2", "https://www.google.es")) .with_item(MegaMenuItem::link("Opción 2", "https://www.google.es"))
.with_item(MegaMenuItem::link_blank( .with_item(MegaMenuItem::link_blank(
"Opción 3", "Opción 3",

View file

@ -2,7 +2,7 @@ use pagetop::prelude::*;
define_handle!(THEME_BOOTSIER); define_handle!(THEME_BOOTSIER);
define_locale!("src/locales"); define_locale!(LOCALE_BOOTSIER, "src/locales");
include!(concat!(env!("OUT_DIR"), "/bootsier.rs")); include!(concat!(env!("OUT_DIR"), "/bootsier.rs"));
@ -53,15 +53,15 @@ impl ThemeTrait for Bootsier {
alt="Caution!"; alt="Caution!";
div class="media-body" { div class="media-body" {
h1 class="display-4" { ("RESOURCE NOT FOUND") } h1 class="display-4" { ("RESOURCE NOT FOUND") }
p class="lead" { (l("e404-description")) } p class="lead" { (t("e404-description", Locale::From(&LOCALE_BOOTSIER))) }
hr class="my-4"; hr class="my-4";
p { (l("e404-description")) } p { (t("e404-description", Locale::From(&LOCALE_BOOTSIER))) }
a a
class="btn btn-primary btn-lg" class="btn btn-primary btn-lg"
href="/" href="/"
role="button" role="button"
{ {
(l("back-homepage")) (t("back-homepage", Locale::From(&LOCALE_BOOTSIER)))
} }
} }
} }

View file

@ -3,7 +3,7 @@ use pagetop_minimal::component::*;
define_handle!(MODULE_DEMOHOME); define_handle!(MODULE_DEMOHOME);
define_locale!("src/locales"); define_locale!(LOCALE_DEMOHOME, "src/locales");
include!(concat!(env!("OUT_DIR"), "/homedemo.rs")); include!(concat!(env!("OUT_DIR"), "/homedemo.rs"));
@ -15,11 +15,11 @@ impl ModuleTrait for HomeDemo {
} }
fn name(&self) -> String { fn name(&self) -> String {
l("module_name") t("module_name", Locale::From(&LOCALE_DEMOHOME))
} }
fn description(&self) -> Option<String> { fn description(&self) -> Option<String> {
Some(l("module_description")) Some(t("module_description", Locale::From(&LOCALE_DEMOHOME)))
} }
fn dependencies(&self) -> Vec<ModuleStaticRef> { fn dependencies(&self) -> Vec<ModuleStaticRef> {
@ -34,7 +34,7 @@ impl ModuleTrait for HomeDemo {
async fn demo(request: server::HttpRequest) -> ResultPage<Markup, FatalError> { async fn demo(request: server::HttpRequest) -> ResultPage<Markup, FatalError> {
Page::new(request) Page::new(request)
.with_title(l("page_title").as_str()) .with_title(t("page_title", Locale::From(&LOCALE_DEMOHOME)).as_str())
.with_context(ContextOp::AddStyleSheet(StyleSheet::located( .with_context(ContextOp::AddStyleSheet(StyleSheet::located(
"/homedemo/css/styles.css", "/homedemo/css/styles.css",
))) )))
@ -56,43 +56,46 @@ fn hello_world() -> Container {
.with_size(grid::ColumnSize::Is5of12) .with_size(grid::ColumnSize::Is5of12)
.with_component( .with_component(
Heading::h1(html! { Heading::h1(html! {
(l("page_title")) (t("page_title", Locale::From(&LOCALE_DEMOHOME)))
}) })
.with_display(HeadingDisplay::Medium), .with_display(HeadingDisplay::Medium),
) )
.with_component( .with_component(
Paragraph::with(html! { Paragraph::with(html! {
(e("hello_intro", &args![ (e("hello_intro", Locale::With(&LOCALE_DEMOHOME, &args![
"app" => format!( "app" => format!(
"<span class=\"app-name\">{}</span>", "<span class=\"app-name\">{}</span>",
&config::SETTINGS.app.name, &config::SETTINGS.app.name,
) )
])) ])))
}) })
.with_display(ParagraphDisplay::Small), .with_display(ParagraphDisplay::Small),
) )
.with_component(Paragraph::with(html! { .with_component(Paragraph::with(html! {
(e("hello_powered", &args![ (e("hello_powered", Locale::With(&LOCALE_DEMOHOME, &args![
"pagetop" => format!( "pagetop" => format!(
"<a href=\"{}\" target=\"_blank\">{}</a>", "<a href=\"{}\" target=\"_blank\">{}</a>",
"https://pagetop.cillero.es", "https://pagetop.cillero.es",
"PageTop", "PageTop",
) )
])) ])))
})) }))
.with_component( .with_component(
Anchor::button( Anchor::button(
"https://github.com/manuelcillero/pagetop", "https://github.com/manuelcillero/pagetop",
html! { (l("hello_code")) }, html! { (t("hello_code", Locale::From(&LOCALE_DEMOHOME))) },
) )
.with_target(AnchorTarget::Blank) .with_target(AnchorTarget::Blank)
.with_left_icon(Icon::with("git")) .with_left_icon(Icon::with("git"))
.with_classes(ClassesOp::Add, "code-link"), .with_classes(ClassesOp::Add, "code-link"),
) )
.with_component( .with_component(
Anchor::link("#welcome", html! { (l("hello_welcome")) }) Anchor::link(
.with_left_icon(Icon::with("arrow-down-circle-fill")) "#welcome",
.with_classes(ClassesOp::Add, "welcome-link"), html! { (t("hello_welcome", Locale::From(&LOCALE_DEMOHOME))) },
)
.with_left_icon(Icon::with("arrow-down-circle-fill"))
.with_classes(ClassesOp::Add, "welcome-link"),
), ),
) )
.with_column( .with_column(
@ -108,26 +111,28 @@ fn welcome() -> Container {
.with_id("welcome") .with_id("welcome")
.with_classes(ClassesOp::Add, "welcome-col-text") .with_classes(ClassesOp::Add, "welcome-col-text")
.with_component(Heading::h2(html! { .with_component(Heading::h2(html! {
(l("welcome_page")) (t("welcome_page", Locale::From(&LOCALE_DEMOHOME)))
})) }))
.with_component( .with_component(
Heading::h3(html! { Heading::h3(html! {
(e("welcome_subtitle", &args![ (e("welcome_subtitle", Locale::With(&LOCALE_DEMOHOME, &args![
"app" => format!( "app" => format!(
"<span class=\"app-name\">{}</span>", "<span class=\"app-name\">{}</span>",
&config::SETTINGS.app.name &config::SETTINGS.app.name
) )
])) ])))
}) })
.with_display(HeadingDisplay::Subtitle), .with_display(HeadingDisplay::Subtitle),
) )
.with_component( .with_component(
Paragraph::with(html! { Paragraph::with(html! {
(l("welcome_text1")) (t("welcome_text1", Locale::From(&LOCALE_DEMOHOME)))
}) })
.with_display(ParagraphDisplay::Small), .with_display(ParagraphDisplay::Small),
) )
.with_component(Paragraph::with(html! { (l("welcome_text2")) })) .with_component(Paragraph::with(
html! { (t("welcome_text2", Locale::From(&LOCALE_DEMOHOME))) },
))
} }
fn about_pagetop() -> Container { fn about_pagetop() -> Container {
@ -143,25 +148,25 @@ fn about_pagetop() -> Container {
grid::Column::new() grid::Column::new()
.with_classes(ClassesOp::Add, "pagetop-col-text") .with_classes(ClassesOp::Add, "pagetop-col-text")
.with_component(Heading::h2(html! { .with_component(Heading::h2(html! {
(l("pagetop_title")) (t("pagetop_title", Locale::From(&LOCALE_DEMOHOME)))
})) }))
.with_component( .with_component(
Paragraph::with(html! { Paragraph::with(html! {
(l("pagetop_text1")) (t("pagetop_text1", Locale::From(&LOCALE_DEMOHOME)))
}) })
.with_display(ParagraphDisplay::Small), .with_display(ParagraphDisplay::Small),
) )
.with_component(Paragraph::with(html! { .with_component(Paragraph::with(html! {
(l("pagetop_text2")) (t("pagetop_text2", Locale::From(&LOCALE_DEMOHOME)))
})) }))
.with_component(Paragraph::with(html! { .with_component(Paragraph::with(html! {
(e("pagetop_text3", &args![ (e("pagetop_text3", Locale::With(&LOCALE_DEMOHOME, &args![
"pagetop_website" => format!( "pagetop_website" => format!(
"<a href=\"{}\" target=\"_blank\">{}</a>", "<a href=\"{}\" target=\"_blank\">{}</a>",
"https://docs.rs/pagetop/latest/pagetop", "https://docs.rs/pagetop/latest/pagetop",
l("pagetop_website"), t("pagetop_website", Locale::From(&LOCALE_DEMOHOME)),
) )
])) ])))
})), })),
), ),
) )
@ -174,17 +179,17 @@ fn promo_pagetop() -> Container {
grid::Column::new() grid::Column::new()
.with_classes(ClassesOp::Add, "promo-col-text") .with_classes(ClassesOp::Add, "promo-col-text")
.with_component(Heading::h2(html! { .with_component(Heading::h2(html! {
(l("pagetop_promo_title")) (t("pagetop_promo_title", Locale::From(&LOCALE_DEMOHOME)))
})) }))
.with_component( .with_component(
Paragraph::with(html! { Paragraph::with(html! {
(e("pagetop_promo_text1", &args![ (e("pagetop_promo_text1", Locale::With(&LOCALE_DEMOHOME, &args![
"pagetop" => format!( "pagetop" => format!(
"<a href=\"{}\" target=\"_blank\">{}</a>", "<a href=\"{}\" target=\"_blank\">{}</a>",
"https://crates.io/crates/pagetop", "https://crates.io/crates/pagetop",
"PageTop", "PageTop",
) )
])) ])))
}) })
.with_display(ParagraphDisplay::Small), .with_display(ParagraphDisplay::Small),
), ),
@ -211,16 +216,16 @@ fn reporting_issues() -> Container {
.with_classes(ClassesOp::Add, "reporting-col-text") .with_classes(ClassesOp::Add, "reporting-col-text")
.with_size(grid::ColumnSize::Is6of12) .with_size(grid::ColumnSize::Is6of12)
.with_component(Heading::h2(html! { .with_component(Heading::h2(html! {
(l("report_problems_title")) (t("report_problems_title", Locale::From(&LOCALE_DEMOHOME)))
})) }))
.with_component( .with_component(
Paragraph::with(html! { Paragraph::with(html! {
(l("report_problems_text1")) (t("report_problems_text1", Locale::From(&LOCALE_DEMOHOME)))
}) })
.with_display(ParagraphDisplay::Small), .with_display(ParagraphDisplay::Small),
) )
.with_component(Paragraph::with(html! { .with_component(Paragraph::with(html! {
(l("report_problems_text2")) (t("report_problems_text2", Locale::From(&LOCALE_DEMOHOME)))
})), })),
), ),
) )

View file

@ -8,8 +8,8 @@ mod escape;
mod generate; mod generate;
mod parse; mod parse;
use proc_macro_crate::{crate_name, FoundCrate};
use proc_macro2::{Ident, Span, TokenStream, TokenTree}; use proc_macro2::{Ident, Span, TokenStream, TokenTree};
use proc_macro_crate::{crate_name, FoundCrate};
use quote::quote; use quote::quote;
pub fn expand(input: TokenStream) -> TokenStream { pub fn expand(input: TokenStream) -> TokenStream {
@ -27,7 +27,7 @@ pub fn expand(input: TokenStream) -> TokenStream {
), ),
_ => quote!( _ => quote!(
pagetop::html::PreEscaped(#output_ident) pagetop::html::PreEscaped(#output_ident)
) ),
}; };
quote!({ quote!({

View file

@ -113,7 +113,7 @@ impl Generator {
), ),
_ => quote!( _ => quote!(
pagetop::html::html_private::render_to!(&#expr, &mut #output_ident); pagetop::html::html_private::render_to!(&#expr, &mut #output_ident);
) ),
}); });
} }

View file

@ -2,7 +2,7 @@ use pagetop::prelude::*;
define_handle!(MODULE_MENU); define_handle!(MODULE_MENU);
define_locale!("src/module/menu/locales"); define_locale!(LOCALE_MENU, "src/module/menu/locales");
pub struct Menu; pub struct Menu;
@ -12,10 +12,10 @@ impl ModuleTrait for Menu {
} }
fn name(&self) -> String { fn name(&self) -> String {
l("module_name") t("module_name", Locale::From(&LOCALE_MENU))
} }
fn description(&self) -> Option<String> { fn description(&self) -> Option<String> {
Some(l("module_description")) Some(t("module_description", Locale::From(&LOCALE_MENU)))
} }
} }

View file

@ -2,7 +2,7 @@ use pagetop::prelude::*;
define_handle!(MODULE_NODE); define_handle!(MODULE_NODE);
define_locale!("src/locales"); define_locale!(LOCALE_NODE, "src/locales");
//mod entity; //mod entity;
mod migration; mod migration;
@ -15,11 +15,11 @@ impl ModuleTrait for Node {
} }
fn name(&self) -> String { fn name(&self) -> String {
l("module_name") t("module_name", Locale::From(&LOCALE_NODE))
} }
fn description(&self) -> Option<String> { fn description(&self) -> Option<String> {
Some(l("module_description")) Some(t("module_description", Locale::From(&LOCALE_NODE)))
} }
fn configure_service(&self, cfg: &mut server::web::ServiceConfig) { fn configure_service(&self, cfg: &mut server::web::ServiceConfig) {

View file

@ -3,7 +3,7 @@ use pagetop_minimal::component::*;
define_handle!(MODULE_USER); define_handle!(MODULE_USER);
define_locale!("src/locales"); define_locale!(LOCALE_USER, "src/locales");
mod migration; mod migration;
@ -15,11 +15,11 @@ impl ModuleTrait for User {
} }
fn name(&self) -> String { fn name(&self) -> String {
l("module_name") t("module_name", Locale::From(&LOCALE_USER))
} }
fn description(&self) -> Option<String> { fn description(&self) -> Option<String> {
Some(l("module_description")) Some(t("module_description", Locale::From(&LOCALE_USER)))
} }
fn dependencies(&self) -> Vec<ModuleStaticRef> { fn dependencies(&self) -> Vec<ModuleStaticRef> {
@ -58,13 +58,14 @@ fn form_login() -> Form {
.with_element( .with_element(
form_element::Input::textfield() form_element::Input::textfield()
.with_name("name") .with_name("name")
.with_label(l("username").as_str()) .with_label(t("username", Locale::From(&LOCALE_USER)).as_str())
.with_help_text( .with_help_text(
t( t(
"username_help", "username_help",
&args![ Locale::With(
"app" => config::SETTINGS.app.name.to_owned() &LOCALE_USER,
], &args!["app" => config::SETTINGS.app.name.to_owned()],
),
) )
.as_str(), .as_str(),
) )
@ -73,8 +74,10 @@ fn form_login() -> Form {
.with_element( .with_element(
form_element::Input::password() form_element::Input::password()
.with_name("pass") .with_name("pass")
.with_label(l("password").as_str()) .with_label(t("password", Locale::From(&LOCALE_USER)).as_str())
.with_help_text(l("password_help").as_str()), .with_help_text(t("password_help", Locale::From(&LOCALE_USER)).as_str()),
) )
.with_element(form_element::Button::submit(l("login").as_str())) .with_element(form_element::Button::submit(
t("login", Locale::From(&LOCALE_USER)).as_str(),
))
} }

View file

@ -71,36 +71,36 @@
//! Una vez hayas creado tu directorio de recursos FTL, sólo tienes que usar la poderosa macro //! Una vez hayas creado tu directorio de recursos FTL, sólo tienes que usar la poderosa macro
//! [`define_locale!`](crate::define_locale) para integrar fácilmente tus recursos de localización. //! [`define_locale!`](crate::define_locale) para integrar fácilmente tus recursos de localización.
//! //!
//! Esta macro crea dos funciones para el ámbito donde se ejecuta. Por un lado la función `l()` para //! Luego sólo tendrás que usar la función `t()` para realizar tus traducciones:
//! traducciones directas de etiquetas. Y por otro la función `t()` para traducciones que requieren
//! argumentos:
//! //!
//! ``` //! ```
//! use pagetop::{args, define_locale}; //! use pagetop::{args, define_locale, t};
//! //!
//! define_locale!("en-US"); //! define_locale!(LOCALE_SAMPLE, "src/locales");
//! //!
//! fn demo() { //! fn demo() {
//! println!("* {}", l("hello-world")); //! println!("* {}", l("hello-world", Locale::From(&LOCALE_SAMPLE)));
//! println!("* {}", t("hello-world", &args![])); //! println!("* {}", t("hello-user", Locale::With(&LOCALE_SAMPLE, &args!["userName" => "Julia"])));
//! println!("* {}", t("hello-user", &args!["userName" => "Julia"]));
//! //!
//! let args = args![ //! let args = args![
//! "userName" => "Roberto", //! "userName" => "Roberto",
//! "photoCount" => 3, //! "photoCount" => 3,
//! "userGender" => "male" //! "userGender" => "male"
//! ]; //! ];
//! println!("* {}\n", t("shared-photos", &args)); //! println!("* {}\n", t("shared-photos", Locale::With(&LOCALE_SAMPLE, &args)));
//! } //! }
//! ``` //! ```
use crate::html::{Markup, PreEscaped};
use crate::{config, trace, LazyStatic}; use crate::{config, trace, LazyStatic};
use unic_langid::LanguageIdentifier; use unic_langid::LanguageIdentifier;
pub use fluent_templates; pub use fluent_templates;
pub use fluent_templates::fluent_bundle::FluentValue; pub use fluent_templates::fluent_bundle::FluentValue;
pub use fluent_templates::{static_loader as static_locale, Loader as Locale}; pub use fluent_templates::{static_loader as static_locale, Loader, StaticLoader as Locales};
use std::collections::HashMap;
/// Almacena el Identificador de Idioma Unicode /// Almacena el Identificador de Idioma Unicode
/// ([Unicode Language Identifier](https://unicode.org/reports/tr35/tr35.html#Unicode_language_identifier)) /// ([Unicode Language Identifier](https://unicode.org/reports/tr35/tr35.html#Unicode_language_identifier))
@ -124,11 +124,11 @@ pub static LANGID: LazyStatic<LanguageIdentifier> =
#[macro_export] #[macro_export]
/// Define un conjunto de elementos de localización y funciones locales de traducción. /// Define un conjunto de elementos de localización y funciones locales de traducción.
macro_rules! define_locale { macro_rules! define_locale {
( $dir_locales:literal $(, $core_locales:literal)? ) => { ( $LOCALES:ident, $dir_locales:literal $(, $core_locales:literal)? ) => {
use $crate::locale::*; use $crate::locale::*;
static_locale! { static_locale! {
static LOCALES = { static $LOCALES = {
locales: $dir_locales, locales: $dir_locales,
$( core_locales: $core_locales, )? $( core_locales: $core_locales, )?
fallback_language: "en-US", fallback_language: "en-US",
@ -137,28 +137,33 @@ macro_rules! define_locale {
customise: |bundle| bundle.set_use_isolating(false), customise: |bundle| bundle.set_use_isolating(false),
}; };
} }
#[allow(dead_code)]
fn l(key: &str) -> String {
LOCALES.lookup(&LANGID, key).unwrap_or(key.to_string())
}
#[allow(dead_code)]
fn t(
key: &str,
args: &std::collections::HashMap<String, FluentValue>
) -> String {
LOCALES.lookup_with_args(&LANGID, key, args).unwrap_or(key.to_string())
}
#[allow(dead_code)]
fn e(
key: &str,
args: &std::collections::HashMap<String, FluentValue>
) -> $crate::html::PreEscaped<String> {
$crate::html::PreEscaped(
LOCALES.lookup_with_args(&LANGID, key, args).unwrap_or(key.to_string())
)
}
}; };
} }
pub enum Locale<'a> {
From(&'a Locales),
With(&'a Locales, &'a HashMap<String, FluentValue<'a>>),
Lang(&'a Locales, &'a LanguageIdentifier),
Using(
&'a Locales,
&'a LanguageIdentifier,
&'a HashMap<String, FluentValue<'a>>,
),
}
pub fn t(key: &str, locale: Locale) -> String {
match locale {
Locale::From(locales) => locales.lookup(&LANGID, key).unwrap_or(key.to_string()),
Locale::With(locales, args) => locales
.lookup_with_args(&LANGID, key, args)
.unwrap_or(key.to_string()),
Locale::Lang(locales, langid) => locales.lookup(langid, key).unwrap_or(key.to_string()),
Locale::Using(locales, langid, args) => locales
.lookup_with_args(langid, key, args)
.unwrap_or(key.to_string()),
}
}
pub fn e(key: &str, locale: Locale) -> Markup {
PreEscaped(t(key, locale))
}

View file

@ -16,6 +16,8 @@ pub use crate::config;
pub use crate::trace; pub use crate::trace;
pub use crate::locale::*;
pub use crate::html::*; pub use crate::html::*;
#[cfg(feature = "database")] #[cfg(feature = "database")]