🧑💻 Mejora y simplifica localización de módulos
This commit is contained in:
parent
d0add7c7ab
commit
520d3bb20b
21 changed files with 265 additions and 325 deletions
|
|
@ -17,6 +17,5 @@ pub(crate) use all::common_components;
|
|||
mod renderable;
|
||||
pub use renderable::{IsRenderable, Renderable};
|
||||
|
||||
mod basic;
|
||||
pub use basic::{Html, COMPONENT_HTML};
|
||||
pub use basic::{Text, COMPONENT_TEXT};
|
||||
mod l10n;
|
||||
pub use l10n::{L10n, COMPONENT_L10N};
|
||||
|
|
|
|||
|
|
@ -1,143 +0,0 @@
|
|||
use crate::core::component::{AnyComponent, ComponentTrait, RenderContext};
|
||||
use crate::html::{html, Markup, PreEscaped};
|
||||
use crate::locale::{translate, Locale, Locales};
|
||||
use crate::{define_handle, fn_builder, paste, Handle};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
macro_rules! basic_components {
|
||||
( $($COMPONENT_HANDLE:ident: $Component:ty => $TypeValue:ty),* ) => { $( paste! {
|
||||
|
||||
define_handle!($COMPONENT_HANDLE);
|
||||
|
||||
pub enum [< $Component Op >] {
|
||||
None,
|
||||
Value($TypeValue),
|
||||
Translated(&'static str, &'static Locales),
|
||||
Escaped(&'static str, &'static Locales),
|
||||
}
|
||||
|
||||
pub struct $Component {
|
||||
op: [< $Component Op >],
|
||||
args: HashMap<&'static str, String>,
|
||||
}
|
||||
|
||||
impl Default for $Component {
|
||||
fn default() -> Self {
|
||||
$Component {
|
||||
op: [< $Component Op >]::None,
|
||||
args: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentTrait for $Component {
|
||||
fn new() -> Self {
|
||||
$Component::default()
|
||||
}
|
||||
|
||||
fn handle(&self) -> Handle {
|
||||
$COMPONENT_HANDLE
|
||||
}
|
||||
|
||||
fn default_render(&self, rcx: &mut RenderContext) -> Markup {
|
||||
match self.op() {
|
||||
[< $Component Op >]::None => html! {},
|
||||
[< $Component Op >]::Value(value) => html! { (value) },
|
||||
[< $Component Op >]::Translated(key, locales) => html! {
|
||||
(translate(
|
||||
key,
|
||||
Locale::Using(
|
||||
rcx.langid(),
|
||||
locales,
|
||||
&self.args().iter().fold(HashMap::new(), |mut args, (key, value)| {
|
||||
args.insert(key.to_string(), value.to_owned().into());
|
||||
args
|
||||
})
|
||||
)
|
||||
))
|
||||
},
|
||||
[< $Component Op >]::Escaped(key, locales) => html! {
|
||||
(PreEscaped(translate(
|
||||
key,
|
||||
Locale::Using(
|
||||
rcx.langid(),
|
||||
locales,
|
||||
&self.args().iter().fold(HashMap::new(), |mut args, (key, value)| {
|
||||
args.insert(key.to_string(), value.to_owned().into());
|
||||
args
|
||||
})
|
||||
)
|
||||
)))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl $Component {
|
||||
pub fn n(value: $TypeValue) -> Self {
|
||||
$Component {
|
||||
op: [< $Component Op >]::Value(value),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn t(key: &'static str, locales: &'static Locales) -> Self {
|
||||
$Component {
|
||||
op: [< $Component Op >]::Translated(key, locales),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn e(key: &'static str, locales: &'static Locales) -> Self {
|
||||
$Component {
|
||||
op: [< $Component Op >]::Escaped(key, locales),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
// $Component BUILDER.
|
||||
|
||||
#[fn_builder]
|
||||
pub fn alter_op(&mut self, op: [< $Component Op >]) -> &mut Self {
|
||||
self.op = op;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn alter_arg(&mut self, arg: &'static str, value: String) -> &mut Self {
|
||||
self.args.insert(arg, value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn clear_args(&mut self) -> &mut Self {
|
||||
self.args.drain();
|
||||
self
|
||||
}
|
||||
|
||||
// $Component GETTERS.
|
||||
|
||||
pub fn op(&self) -> &[< $Component Op >] {
|
||||
&self.op
|
||||
}
|
||||
|
||||
pub fn args(&self) -> &HashMap<&str, String> {
|
||||
&self.args
|
||||
}
|
||||
}
|
||||
|
||||
} )* };
|
||||
}
|
||||
|
||||
basic_components!(
|
||||
COMPONENT_HTML: Html => Markup,
|
||||
COMPONENT_TEXT: Text => &'static str
|
||||
);
|
||||
141
pagetop/src/core/component/l10n.rs
Normal file
141
pagetop/src/core/component/l10n.rs
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
use crate::core::component::{AnyComponent, ComponentTrait, RenderContext};
|
||||
use crate::html::{html, Markup, PreEscaped};
|
||||
use crate::locale::Locales;
|
||||
use crate::{define_handle, fn_builder, Handle};
|
||||
|
||||
use fluent_templates::Loader;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
define_handle!(COMPONENT_L10N);
|
||||
|
||||
pub enum L10nOp {
|
||||
None,
|
||||
Value(Markup),
|
||||
Translated(&'static str, &'static Locales),
|
||||
Escaped(&'static str, &'static Locales),
|
||||
}
|
||||
|
||||
pub struct L10n {
|
||||
op: L10nOp,
|
||||
args: HashMap<&'static str, String>,
|
||||
}
|
||||
|
||||
impl Default for L10n {
|
||||
fn default() -> Self {
|
||||
L10n {
|
||||
op: L10nOp::None,
|
||||
args: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentTrait for L10n {
|
||||
fn new() -> Self {
|
||||
L10n::default()
|
||||
}
|
||||
|
||||
fn handle(&self) -> Handle {
|
||||
COMPONENT_L10N
|
||||
}
|
||||
|
||||
fn default_render(&self, rcx: &mut RenderContext) -> Markup {
|
||||
match self.op() {
|
||||
L10nOp::None => html! {},
|
||||
L10nOp::Value(value) => html! { (value) },
|
||||
L10nOp::Translated(key, locales) => html! {
|
||||
(locales
|
||||
.lookup_with_args(
|
||||
rcx.langid(),
|
||||
key,
|
||||
&self.args().iter().fold(HashMap::new(), |mut args, (key, value)| {
|
||||
args.insert(key.to_string(), value.to_owned().into());
|
||||
args
|
||||
})
|
||||
)
|
||||
.unwrap_or(key.to_string())
|
||||
)
|
||||
},
|
||||
L10nOp::Escaped(key, locales) => html! {
|
||||
(PreEscaped(locales
|
||||
.lookup_with_args(
|
||||
rcx.langid(),
|
||||
key,
|
||||
&self.args().iter().fold(HashMap::new(), |mut args, (key, value)| {
|
||||
args.insert(key.to_string(), value.to_owned().into());
|
||||
args
|
||||
})
|
||||
)
|
||||
.unwrap_or(key.to_string())
|
||||
))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl L10n {
|
||||
pub fn text(text: &'static str) -> Self {
|
||||
L10n {
|
||||
op: L10nOp::Value(html! { (text) }),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn html(html: Markup) -> Self {
|
||||
L10n {
|
||||
op: L10nOp::Value(html),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn t(key: &'static str, locales: &'static Locales) -> Self {
|
||||
L10n {
|
||||
op: L10nOp::Translated(key, locales),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn e(key: &'static str, locales: &'static Locales) -> Self {
|
||||
L10n {
|
||||
op: L10nOp::Escaped(key, locales),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
// L10n BUILDER.
|
||||
|
||||
#[fn_builder]
|
||||
pub fn alter_op(&mut self, op: L10nOp) -> &mut Self {
|
||||
self.op = op;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn alter_arg(&mut self, arg: &'static str, value: String) -> &mut Self {
|
||||
self.args.insert(arg, value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn clear_args(&mut self) -> &mut Self {
|
||||
self.args.drain();
|
||||
self
|
||||
}
|
||||
|
||||
// L10n GETTERS.
|
||||
|
||||
pub fn op(&self) -> &L10nOp {
|
||||
&self.op
|
||||
}
|
||||
|
||||
pub fn args(&self) -> &HashMap<&str, String> {
|
||||
&self.args
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
use super::ThemeStaticRef;
|
||||
|
||||
use crate::core::component::L10n;
|
||||
use crate::core::hook::HookAction;
|
||||
use crate::util::single_type_name;
|
||||
use crate::{server, Handle};
|
||||
|
|
@ -17,12 +18,12 @@ pub trait BaseModule {
|
|||
pub trait ModuleTrait: BaseModule + Send + Sync {
|
||||
fn handle(&self) -> Handle;
|
||||
|
||||
fn name(&self) -> String {
|
||||
self.single_name().to_owned()
|
||||
fn name(&self) -> L10n {
|
||||
L10n::text(self.single_name())
|
||||
}
|
||||
|
||||
fn description(&self) -> Option<String> {
|
||||
None
|
||||
fn description(&self) -> L10n {
|
||||
L10n::default()
|
||||
}
|
||||
|
||||
fn theme(&self) -> Option<ThemeStaticRef> {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
//! Localización (L10n).
|
||||
//!
|
||||
//! Proporciona soporte a [Fluent](https://www.projectfluent.org/), un conjunto de especificaciones
|
||||
//! para la localización de aplicaciones, así como implementaciones y buenas prácticas originalmente
|
||||
//! desarrolladas por Mozilla.
|
||||
//!
|
||||
//! PageTop usa el conjunto de especificaciones [Fluent](https://www.projectfluent.org/) para la
|
||||
//! localización de aplicaciones.
|
||||
//!
|
||||
//! # Sintaxis Fluent (FTL)
|
||||
//!
|
||||
|
|
@ -72,43 +70,23 @@
|
|||
//!
|
||||
//! # Cómo aplicar la localización en tu código
|
||||
//!
|
||||
//! 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 usa la macro
|
||||
//! [`define_locale!`](crate::define_locale) para integrarlos en tu módulo o aplicación.
|
||||
//!
|
||||
//! Y podrás usar las funciones globales [`_t()`] o [`_e()`] para traducir tus textos:
|
||||
//!
|
||||
//! ```
|
||||
//! use pagetop::prelude::*;
|
||||
//!
|
||||
//! define_locale!(LOCALE_SAMPLE, "static/locales");
|
||||
//!
|
||||
//! fn demo() {
|
||||
//! println!("* {}", _t("hello-world", Locale::From(&LOCALE_SAMPLE)));
|
||||
//! println!("* {}", _t("hello-user", Locale::With(&LOCALE_SAMPLE, &args!["userName" => "Julia"])));
|
||||
//!
|
||||
//! let args = args![
|
||||
//! "userName" => "Roberto",
|
||||
//! "photoCount" => 3,
|
||||
//! "userGender" => "male"
|
||||
//! ];
|
||||
//! println!("* {}\n", _t("shared-photos", Locale::With(&LOCALE_SAMPLE, &args)));
|
||||
//! }
|
||||
//! ```
|
||||
//! Aunque preferirás usar normalmente los componentes básicos [Text](crate::core::component::Text)
|
||||
//! y [Html](crate::core::component::Html) para incluir, en respuestas a las peticiones web, textos
|
||||
//! y contenidos opcionalmente traducibles según el contexto de renderizado.
|
||||
//! Y utiliza el componente [L10n](crate::core::component::L10n) para incluir, en respuestas a las
|
||||
//! peticiones web, textos y contenidos opcionalmente traducibles según el contexto de renderizado.
|
||||
|
||||
use crate::html::{Markup, PreEscaped};
|
||||
use crate::{args, config, trace, LazyStatic};
|
||||
|
||||
pub(crate) use unic_langid::{langid, LanguageIdentifier};
|
||||
|
||||
pub use fluent_templates;
|
||||
|
||||
pub(crate) use fluent_templates::StaticLoader as Locales;
|
||||
|
||||
use fluent_templates::fluent_bundle::FluentValue;
|
||||
use fluent_templates::Loader;
|
||||
pub(crate) use unic_langid::{langid, LanguageIdentifier};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
|
@ -127,7 +105,7 @@ static FALLBACK_LANGID: LazyStatic<LanguageIdentifier> = LazyStatic::new(|| lang
|
|||
/// Almacena el Identificador de Idioma Unicode
|
||||
/// ([Unicode Language Identifier](https://unicode.org/reports/tr35/tr35.html#Unicode_language_identifier))
|
||||
/// global para la aplicación a partir de `SETTINGS.app.language`.
|
||||
pub static DEFAULT_LANGID: LazyStatic<&LanguageIdentifier> =
|
||||
pub(crate) static DEFAULT_LANGID: LazyStatic<&LanguageIdentifier> =
|
||||
LazyStatic::new(|| langid_for(config::SETTINGS.app.language.as_str()));
|
||||
|
||||
pub fn langid_for(language: &str) -> &LanguageIdentifier {
|
||||
|
|
@ -144,31 +122,3 @@ pub fn langid_for(language: &str) -> &LanguageIdentifier {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Locale<'a> {
|
||||
From(&'a Locales),
|
||||
With(&'a Locales, &'a HashMap<String, FluentValue<'a>>),
|
||||
Using(
|
||||
&'a LanguageIdentifier,
|
||||
&'a Locales,
|
||||
&'a HashMap<String, FluentValue<'a>>,
|
||||
),
|
||||
}
|
||||
|
||||
pub fn _t(key: &str, locale: Locale) -> String {
|
||||
translate(key, locale)
|
||||
}
|
||||
|
||||
pub fn _e(key: &str, locale: Locale) -> Markup {
|
||||
PreEscaped(translate(key, locale))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn translate(key: &str, locale: Locale) -> String {
|
||||
match locale {
|
||||
Locale::From(locales) => locales.lookup(&DEFAULT_LANGID, key),
|
||||
Locale::With(locales, args) => locales.lookup_with_args(&DEFAULT_LANGID, key, args),
|
||||
Locale::Using(langid, locales, args) => locales.lookup_with_args(langid, key, args),
|
||||
}
|
||||
.unwrap_or(key.to_string())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ pub use error403::ERROR_403;
|
|||
mod error404;
|
||||
pub use error404::ERROR_404;
|
||||
|
||||
use crate::core::component::Text;
|
||||
use crate::core::component::L10n;
|
||||
use crate::response::{page::Page, ResponseError};
|
||||
use crate::server::http::{header::ContentType, StatusCode};
|
||||
use crate::server::{HttpRequest, HttpResponse};
|
||||
|
|
@ -32,7 +32,7 @@ impl fmt::Display for FatalError {
|
|||
FatalError::AccessDenied(request) => {
|
||||
let error_page = Page::new(request.clone());
|
||||
if let Ok(page) = error_page
|
||||
.with_title(Text::n("Error FORBIDDEN"))
|
||||
.with_title(L10n::text("Error FORBIDDEN"))
|
||||
.with_this_in("region-content", error403::Error403)
|
||||
.with_template("error")
|
||||
.render()
|
||||
|
|
@ -46,7 +46,7 @@ impl fmt::Display for FatalError {
|
|||
FatalError::NotFound(request) => {
|
||||
let error_page = Page::new(request.clone());
|
||||
if let Ok(page) = error_page
|
||||
.with_title(Text::n("Error RESOURCE NOT FOUND"))
|
||||
.with_title(L10n::text("Error RESOURCE NOT FOUND"))
|
||||
.with_this_in("region-content", error404::Error404)
|
||||
.with_template("error")
|
||||
.render()
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ use unic_langid::CharacterDirection;
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
type PageTitle = OneComponent<Text>;
|
||||
type PageDescription = OneComponent<Text>;
|
||||
type PageTitle = OneComponent<L10n>;
|
||||
type PageDescription = OneComponent<L10n>;
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub struct Page {
|
||||
|
|
@ -60,13 +60,13 @@ impl Page {
|
|||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn alter_title(&mut self, title: Text) -> &mut Self {
|
||||
pub fn alter_title(&mut self, title: L10n) -> &mut Self {
|
||||
self.title.set(title);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn alter_description(&mut self, description: Text) -> &mut Self {
|
||||
pub fn alter_description(&mut self, description: L10n) -> &mut Self {
|
||||
self.description.set(description);
|
||||
self
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue