🍻 Primera revista a las traducciones por contexto

This commit is contained in:
Manuel Cillero 2023-05-25 20:08:40 +02:00
parent 71b0b0889d
commit 0de26a4737
28 changed files with 307 additions and 187 deletions

View file

@ -54,7 +54,7 @@ tracing-unwrap = { version = "0.10.0", default-features = false }
tracing-actix-web = "0.7.4"
fluent-templates = "0.8.0"
unic-langid = "0.9.1"
unic-langid = { version = "0.9.1", features = ["macros"] }
actix-web = "4"
actix-session = { version = "0.7.2", features = ["cookie-session"] }

View file

@ -4,8 +4,8 @@ mod figfont;
use crate::core::{module, module::ModuleStaticRef};
use crate::html::Markup;
use crate::response::fatal_error::FatalError;
use crate::response::page::ResultPage;
use crate::response::FatalError;
use crate::{config, locale, server, trace, LazyStatic};
#[cfg(feature = "database")]

View file

@ -1,6 +1,12 @@
mod context;
pub use context::{ContextOp, RenderContext};
mod definition;
pub use definition::{component_mut, component_ref, AnyComponent, BaseComponent, ComponentTrait};
mod arc;
pub use arc::ComponentArc;
mod bundle;
pub use bundle::ComponentsBundle;

View file

@ -0,0 +1,32 @@
use crate::core::component::{ComponentTrait, RenderContext};
use crate::html::{html, Markup};
use std::sync::{Arc, RwLock};
#[derive(Clone, Default)]
pub struct ComponentArc(Option<Arc<RwLock<dyn ComponentTrait>>>);
impl ComponentArc {
pub fn new(component: impl ComponentTrait) -> Self {
ComponentArc(Some(Arc::new(RwLock::new(component))))
}
pub fn replace(&mut self, component: impl ComponentTrait) {
self.0 = Some(Arc::new(RwLock::new(component)));
}
pub fn weight(&self) -> isize {
match &self.0 {
Some(component) => component.read().unwrap().weight(),
_ => 0,
}
}
pub fn render(&self, rcx: &mut RenderContext) -> Markup {
html! {
@if let Some(component) = &self.0 {
(component.write().unwrap().render(rcx))
}
}
}
}

View file

@ -1,10 +1,8 @@
use crate::core::component::ComponentTrait;
use crate::html::{html, Markup, RenderContext};
use std::sync::{Arc, RwLock};
use crate::core::component::{ComponentArc, ComponentTrait, RenderContext};
use crate::html::{html, Markup};
#[derive(Clone, Default)]
pub struct ComponentsBundle(Vec<Arc<RwLock<dyn ComponentTrait>>>);
pub struct ComponentsBundle(Vec<ComponentArc>);
impl ComponentsBundle {
pub fn new() -> Self {
@ -12,13 +10,13 @@ impl ComponentsBundle {
}
pub fn new_with(component: impl ComponentTrait) -> Self {
let mut container = ComponentsBundle::new();
container.add(component);
container
let mut bundle = ComponentsBundle::new();
bundle.add(component);
bundle
}
pub fn add(&mut self, component: impl ComponentTrait) {
self.0.push(Arc::new(RwLock::new(component)));
self.0.push(ComponentArc::new(component));
}
pub fn clear(&mut self) {
@ -27,10 +25,10 @@ impl ComponentsBundle {
pub fn render(&self, rcx: &mut RenderContext) -> Markup {
let mut components = self.0.clone();
components.sort_by_key(|c| c.read().unwrap().weight());
components.sort_by_key(|c| c.weight());
html! {
@for c in components.iter() {
(" ")(c.write().unwrap().render(rcx))(" ")
(" ")(c.render(rcx))(" ")
}
}
}

View file

@ -1,5 +1,6 @@
use crate::core::module::{all::theme_by_single_name, ThemeStaticRef};
use crate::html::{html, Assets, IdentifierValue, JavaScript, Markup, StyleSheet};
use crate::locale::{LanguageIdentifier, LANGID};
use crate::server::HttpRequest;
use crate::{concat_string, config, util, LazyStatic};
@ -23,6 +24,7 @@ pub enum ContextOp {
#[rustfmt::skip]
pub struct RenderContext {
language : &'static LanguageIdentifier,
theme : ThemeStaticRef,
request : Option<HttpRequest>,
stylesheets: Assets<StyleSheet>,
@ -35,6 +37,7 @@ impl Default for RenderContext {
#[rustfmt::skip]
fn default() -> Self {
RenderContext {
language : &LANGID,
theme : *DEFAULT_THEME,
request : None,
stylesheets: Assets::<StyleSheet>::new(),
@ -81,6 +84,10 @@ impl RenderContext {
/// Context GETTERS.
pub(crate) fn language(&self) -> &LanguageIdentifier {
self.language
}
pub(crate) fn theme(&self) -> ThemeStaticRef {
self.theme
}

View file

@ -1,9 +1,12 @@
use crate::html::{html, Markup, RenderContext};
use crate::core::component::RenderContext;
use crate::html::{html, Markup};
use crate::util::single_type_name;
use crate::Handle;
use crate::{define_handle, Handle};
pub use std::any::Any as AnyComponent;
define_handle!(COMPONENT_UNDEFINED);
pub trait BaseComponent {
fn render(&mut self, rcx: &mut RenderContext) -> Markup;
}
@ -13,7 +16,9 @@ pub trait ComponentTrait: AnyComponent + BaseComponent + Send + Sync {
where
Self: Sized;
fn handle(&self) -> Handle;
fn handle(&self) -> Handle {
COMPONENT_UNDEFINED
}
fn name(&self) -> String {
single_type_name::<Self>().to_owned()

View file

@ -1,5 +1,5 @@
use crate::core::component::{AnyComponent, ComponentTrait};
use crate::html::{html, Markup, RenderContext};
use crate::core::component::{AnyComponent, ComponentTrait, RenderContext};
use crate::html::{html, Markup};
use crate::{define_handle, Handle};
define_handle!(HTML_MARKUP);

View file

@ -1,4 +1,4 @@
use crate::html::RenderContext;
use crate::core::component::RenderContext;
pub type IsRenderable = fn(&RenderContext) -> bool;

View file

@ -1,7 +1,7 @@
use super::ModuleTrait;
use crate::core::component::{ComponentTrait, HtmlMarkup};
use crate::html::{html, Favicon, Markup, RenderContext};
use crate::core::component::{ComponentTrait, RenderContext};
use crate::html::{html, Favicon, Markup};
use crate::response::page::Page;
use crate::{concat_string, config};
@ -118,20 +118,4 @@ pub trait ThemeTrait: ModuleTrait + Send + Sync {
}
*/
}
fn error_404_not_found(&self) -> HtmlMarkup {
HtmlMarkup::new().with(html! {
div {
h1 { ("RESOURCE NOT FOUND") }
}
})
}
fn error_403_access_denied(&self) -> HtmlMarkup {
HtmlMarkup::new().with(html! {
div {
h1 { ("FORBIDDEN ACCESS") }
}
})
}
}

View file

@ -8,9 +8,6 @@ pub use assets::javascript::{JavaScript, ModeJS};
pub use assets::stylesheet::{StyleSheet, TargetMedia};
pub use assets::Assets;
mod context;
pub use context::{ContextOp, RenderContext};
mod favicon;
pub use favicon::Favicon;

View file

@ -85,59 +85,12 @@ pub mod server;
// Tipos de respuestas a peticiones web.
pub mod response;
// Funciones útiles.
// Funciones útiles y macros declarativas.
pub mod util;
// Prepara y ejecuta la aplicación.
pub mod app;
// *************************************************************************************************
// MACROS DECLARATIVAS.
// *************************************************************************************************
#[macro_export]
/// Macro para construir grupos de pares clave-valor.
///
/// ```rust#ignore
/// 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
}};
}
#[macro_export]
macro_rules! define_handle {
( $HANDLE:ident ) => {
pub const $HANDLE: $crate::Handle =
$crate::util::handle(module_path!(), file!(), line!(), column!());
};
}
#[macro_export]
macro_rules! serve_static_files {
( $cfg:ident, $dir:expr, $embed:ident ) => {{
let static_files = &$crate::config::SETTINGS.dev.static_files;
if static_files.is_empty() {
$cfg.service($crate::server::ResourceFiles::new($dir, $embed()));
} else {
$cfg.service(
$crate::server::ActixFiles::new($dir, $crate::concat_string!(static_files, $dir))
.show_files_listing(),
);
}
}};
}
// *************************************************************************************************
// RE-EXPORTA API ÚNICA.
// *************************************************************************************************

View file

@ -92,34 +92,49 @@
//! ```
use crate::html::{Markup, PreEscaped};
use crate::{config, trace, LazyStatic};
use crate::{args, config, trace, LazyStatic};
use unic_langid::LanguageIdentifier;
use unic_langid::langid;
pub use fluent_templates;
pub use fluent_templates::fluent_bundle::FluentValue;
pub use fluent_templates::{static_loader as static_locale, Loader, StaticLoader as Locales};
pub use unic_langid::LanguageIdentifier;
use std::collections::HashMap;
static LANGUAGES: LazyStatic<HashMap<String, (LanguageIdentifier, &str)>> = LazyStatic::new(|| {
args![
"en" => (langid!("en-US"), "English"),
"en-US" => (langid!("en-US"), "English (...)"),
"es" => (langid!("es-ES"), "Spanish"),
"es-ES" => (langid!("es-ES"), "Spanish (Spain)")
]
});
static DEFAULT_LANGID: LazyStatic<LanguageIdentifier> = LazyStatic::new(|| langid!("en-US"));
/// Almacena el Identificador de Idioma Unicode
/// ([Unicode Language Identifier](https://unicode.org/reports/tr35/tr35.html#Unicode_language_identifier))
/// para la aplicación, obtenido de `SETTINGS.app.language`.
pub static LANGID: LazyStatic<LanguageIdentifier> =
LazyStatic::new(|| match config::SETTINGS.app.language.parse() {
Ok(language) => language,
Err(_) => {
trace::warn!(
"{}, {} \"{}\"! {}, {}",
"Failed to parse language",
"unrecognized Unicode Language Identifier",
config::SETTINGS.app.language,
"Using \"en-US\"",
"check the settings file",
);
"en-US".parse().unwrap()
}
});
pub static LANGID: LazyStatic<&LanguageIdentifier> =
LazyStatic::new(
|| match LANGUAGES.get(config::SETTINGS.app.language.as_str()) {
Some((langid, _)) => langid,
_ => {
trace::warn!(
"{}, {} \"{}\"! {}, {}",
"Failed to parse language",
"unrecognized Unicode Language Identifier",
config::SETTINGS.app.language,
"Using \"en-US\"",
"check the settings file",
);
&*DEFAULT_LANGID
}
},
);
#[macro_export]
/// Define un conjunto de elementos de localización y funciones locales de traducción.
@ -128,7 +143,7 @@ macro_rules! define_locale {
use $crate::locale::*;
static_locale! {
static $LOCALES = {
pub static $LOCALES = {
locales: $dir_locales,
$( core_locales: $core_locales, )?
fallback_language: "en-US",
@ -151,7 +166,7 @@ pub enum Locale<'a> {
),
}
pub fn t(key: &str, locale: Locale) -> String {
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
@ -164,6 +179,6 @@ pub fn t(key: &str, locale: Locale) -> String {
}
}
pub fn e(key: &str, locale: Locale) -> Markup {
PreEscaped(t(key, locale))
pub fn _e(key: &str, locale: Locale) -> Markup {
PreEscaped(_t(key, locale))
}

View file

@ -30,6 +30,7 @@ pub use crate::{hook_action, hook_before_render_component};
pub use crate::server;
pub use crate::server::HttpMessage;
pub use crate::response::{page::*, FatalError, ResponseError};
pub use crate::response::fatal_error::*;
pub use crate::response::{page::*, ResponseError};
pub use crate::app::Application;

View file

@ -4,5 +4,4 @@ pub use actix_web::ResponseError;
pub mod page;
mod fatal_error;
pub use fatal_error::FatalError;
pub mod fatal_error;

View file

@ -1,3 +1,8 @@
mod error403;
pub use error403::ERROR_403;
mod error404;
pub use error404::ERROR_404;
use crate::response::{page::Page, ResponseError};
use crate::server::http::{header::ContentType, StatusCode};
use crate::server::{HttpRequest, HttpResponse};
@ -24,11 +29,10 @@ impl fmt::Display for FatalError {
FatalError::BadRequest(_) => write!(f, "Bad Client Data"),
// Error 403.
FatalError::AccessDenied(request) => {
let mut error_page = Page::new(request.clone());
let error_content = error_page.context().theme().error_403_access_denied();
let error_page = Page::new(request.clone());
if let Ok(page) = error_page
.with_title("Error FORBIDDEN")
.with_this_in("region-content", error_content)
.with_this_in("region-content", error403::Error403)
.with_template("error")
.render()
{
@ -39,11 +43,10 @@ impl fmt::Display for FatalError {
}
// Error 404.
FatalError::NotFound(request) => {
let mut error_page = Page::new(request.clone());
let error_content = error_page.context().theme().error_404_not_found();
let error_page = Page::new(request.clone());
if let Ok(page) = error_page
.with_title("Error RESOURCE NOT FOUND")
.with_this_in("region-content", error_content)
.with_this_in("region-content", error404::Error404)
.with_template("error")
.render()
{

View file

@ -0,0 +1,33 @@
use crate::core::component::{AnyComponent, ComponentTrait, RenderContext};
use crate::html::{html, Markup};
use crate::{define_handle, Handle};
define_handle!(ERROR_403);
pub struct Error403;
impl ComponentTrait for Error403 {
fn new() -> Self {
Self
}
fn handle(&self) -> Handle {
ERROR_403
}
fn default_render(&self, _rcx: &mut RenderContext) -> Markup {
html! {
div {
h1 { ("FORBIDDEN ACCESS") }
}
}
}
fn as_ref_any(&self) -> &dyn AnyComponent {
self
}
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
self
}
}

View file

@ -0,0 +1,33 @@
use crate::core::component::{AnyComponent, ComponentTrait, RenderContext};
use crate::html::{html, Markup};
use crate::{define_handle, Handle};
define_handle!(ERROR_404);
pub struct Error404;
impl ComponentTrait for Error404 {
fn new() -> Self {
Self
}
fn handle(&self) -> Handle {
ERROR_404
}
fn default_render(&self, _rcx: &mut RenderContext) -> Markup {
html! {
div {
h1 { ("RESOURCE NOT FOUND") }
}
}
}
fn as_ref_any(&self) -> &dyn AnyComponent {
self
}
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
self
}
}

View file

@ -2,10 +2,8 @@ use super::{BeforeRenderPageHook, ResultPage, HOOK_BEFORE_RENDER_PAGE};
use crate::core::component::*;
use crate::core::hook::{action_ref, run_actions};
use crate::html::{
html, AttributeValue, Classes, ClassesOp, ContextOp, Favicon, Markup, RenderContext, DOCTYPE,
};
use crate::response::FatalError;
use crate::html::{html, AttributeValue, Classes, ClassesOp, Favicon, Markup, DOCTYPE};
use crate::response::fatal_error::FatalError;
use crate::{config, fn_builder, locale, server, trace, LazyStatic};
use std::collections::HashMap;

View file

@ -1,7 +1,11 @@
//! Funciones útiles.
//! Funciones útiles y macros declarativas.
use crate::Handle;
// *************************************************************************************************
// FUNCIONES ÚTILES.
// *************************************************************************************************
// https://stackoverflow.com/a/71464396
pub const fn handle(
module_path: &'static str,
@ -50,3 +54,50 @@ pub fn partial_type_name(type_name: &'static str, last: usize) -> &'static str {
pub fn single_type_name<T: ?Sized>() -> &'static str {
partial_type_name(std::any::type_name::<T>(), 1)
}
// *************************************************************************************************
// MACROS DECLARATIVAS.
// *************************************************************************************************
#[macro_export]
/// Macro para construir grupos de pares clave-valor.
///
/// ```rust#ignore
/// 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
}};
}
#[macro_export]
macro_rules! define_handle {
( $HANDLE:ident ) => {
pub const $HANDLE: $crate::Handle =
$crate::util::handle(module_path!(), file!(), line!(), column!());
};
}
#[macro_export]
macro_rules! serve_static_files {
( $cfg:ident, $dir:expr, $embed:ident ) => {{
let static_files = &$crate::config::SETTINGS.dev.static_files;
if static_files.is_empty() {
$cfg.service($crate::server::ResourceFiles::new($dir, $embed()));
} else {
$cfg.service(
$crate::server::ActixFiles::new($dir, $crate::concat_string!(static_files, $dir))
.show_files_listing(),
);
}
}};
}