63 lines
2.5 KiB
Rust
63 lines
2.5 KiB
Rust
use async_trait::async_trait;
|
|
use axum::{Extension, Router as AxumRouter};
|
|
use fluent_templates::{ArcLoader, FluentLoader};
|
|
use loco_rs::{
|
|
app::{AppContext, Initializer},
|
|
controller::views::{engines, ViewEngine},
|
|
Error, Result,
|
|
};
|
|
use std::collections::HashMap;
|
|
use tracing::info;
|
|
|
|
const I18N_DIR: &str = "assets/i18n";
|
|
// Kept outside `I18N_DIR`: fluent-templates >=0.13 scans top-level *.ftl files
|
|
// in that dir as locales, and "shared" parses as a langid, so a shared.ftl
|
|
// living there would be loaded twice and fail with a duplicate-resource error.
|
|
const I18N_SHARED: &str = "assets/i18n_shared/shared.ftl";
|
|
#[allow(clippy::module_name_repetitions)]
|
|
pub struct ViewEngineInitializer;
|
|
|
|
#[async_trait]
|
|
impl Initializer for ViewEngineInitializer {
|
|
fn name(&self) -> String {
|
|
"view-engine".to_string()
|
|
}
|
|
|
|
async fn after_routes(&self, router: AxumRouter, _ctx: &AppContext) -> Result<AxumRouter> {
|
|
// Load locales only if present; `t` is registered conditionally below so
|
|
// the single post-process closure covers both cases.
|
|
let locales = if std::path::Path::new(I18N_DIR).exists() {
|
|
let arc = std::sync::Arc::new(
|
|
ArcLoader::builder(&I18N_DIR, unic_langid::langid!("sk"))
|
|
.shared_resources(Some(&[I18N_SHARED.into()]))
|
|
.customize(|bundle| bundle.set_use_isolating(false))
|
|
.build()
|
|
.map_err(|e| Error::string(&e.to_string()))?,
|
|
);
|
|
info!("locales loaded");
|
|
Some(arc)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let tera_engine = engines::TeraView::build()?.post_process(move |tera| {
|
|
if let Some(arc) = &locales {
|
|
tera.register_function("t", FluentLoader::new(arc.clone()));
|
|
}
|
|
// `csrf_token()`: the in-flight request's CSRF token (bound by
|
|
// `shared::csrf::protect`), rendered into `<body hx-headers>` and
|
|
// `ui::csrf_field()`. Inlined so its `tera::Error` return is inferred
|
|
// from `register_function` — we never name a `tera` type, keeping it
|
|
// off our direct deps and pinned to loco's.
|
|
tera.register_function("csrf_token", |_args: &HashMap<String, serde_json::Value>| {
|
|
Ok(serde_json::Value::String(
|
|
crate::shared::csrf::current_token().unwrap_or_default(),
|
|
))
|
|
});
|
|
Ok(())
|
|
})?;
|
|
|
|
Ok(router.layer(Extension(ViewEngine::from(tera_engine))))
|
|
}
|
|
}
|