mirror of
https://github.com/actix/examples
synced 2025-06-28 09:50:36 +02:00
add fluent templates example
This commit is contained in:
40
templating/fluent/src/lang_choice.rs
Normal file
40
templating/fluent/src/lang_choice.rs
Normal file
@ -0,0 +1,40 @@
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
future::{ready, Ready},
|
||||
};
|
||||
|
||||
use actix_web::{dev, http::header::AcceptLanguage, FromRequest, HttpMessage as _, HttpRequest};
|
||||
use fluent_templates::LanguageIdentifier;
|
||||
use serde::Serialize;
|
||||
|
||||
/// A convenient extractor that finds the clients's preferred language based on an Accept-Language
|
||||
/// header and falls back to English if header is not found. Serializes easily in Handlebars data.
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct LangChoice(String);
|
||||
|
||||
impl LangChoice {
|
||||
pub(crate) fn from_req(req: &HttpRequest) -> Self {
|
||||
let lang = req
|
||||
.get_header::<AcceptLanguage>()
|
||||
.and_then(|lang| lang.preference().into_item())
|
||||
.map(|lang| lang.to_string())
|
||||
.map_or_else(|| "en".to_owned(), |lang| lang.to_string());
|
||||
|
||||
Self(lang)
|
||||
}
|
||||
|
||||
pub fn lang_id(&self) -> LanguageIdentifier {
|
||||
// unwrap: lang ID should be valid given extraction method
|
||||
self.0.parse().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRequest for LangChoice {
|
||||
type Error = Infallible;
|
||||
type Future = Ready<Result<Self, Self::Error>>;
|
||||
|
||||
fn from_request(req: &HttpRequest, _pl: &mut dev::Payload) -> Self::Future {
|
||||
ready(Ok(Self::from_req(req)))
|
||||
}
|
||||
}
|
127
templating/fluent/src/main.rs
Normal file
127
templating/fluent/src/main.rs
Normal file
@ -0,0 +1,127 @@
|
||||
use std::io;
|
||||
|
||||
use actix_web::{
|
||||
body::BoxBody,
|
||||
dev::ServiceResponse,
|
||||
get,
|
||||
http::{header::ContentType, StatusCode},
|
||||
middleware::{ErrorHandlerResponse, ErrorHandlers},
|
||||
web, App, HttpResponse, HttpServer, Responder, Result,
|
||||
};
|
||||
use actix_web_lab::{extract::Path, respond::Html};
|
||||
use fluent_templates::{static_loader, FluentLoader, Loader as _};
|
||||
use handlebars::Handlebars;
|
||||
use serde_json::json;
|
||||
|
||||
mod lang_choice;
|
||||
use self::lang_choice::LangChoice;
|
||||
|
||||
static_loader! {
|
||||
static LOCALES = {
|
||||
locales: "./locales",
|
||||
fallback_language: "en",
|
||||
|
||||
// removes unicode isolating marks around arguments
|
||||
// you typically should only set to false when testing.
|
||||
customise: |bundle| bundle.set_use_isolating(false),
|
||||
};
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
async fn index(hb: web::Data<Handlebars<'_>>, lang: LangChoice) -> impl Responder {
|
||||
let data = json!({ "lang": lang });
|
||||
let body = hb.render("index", &data).unwrap();
|
||||
Html(body)
|
||||
}
|
||||
|
||||
#[get("/{user}/{data}")]
|
||||
async fn user(
|
||||
hb: web::Data<Handlebars<'_>>,
|
||||
Path(info): Path<(String, String)>,
|
||||
lang: LangChoice,
|
||||
) -> impl Responder {
|
||||
let data = json!({
|
||||
"lang": lang,
|
||||
"user": info.0,
|
||||
"data": info.1
|
||||
});
|
||||
let body = hb.render("user", &data).unwrap();
|
||||
Html(body)
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
// Handlebars uses a repository for the compiled templates. This object must be shared between
|
||||
// the application threads, and is therefore passed to the App in an Arc.
|
||||
let mut handlebars = Handlebars::new();
|
||||
|
||||
// register template dir with Handlebars registry
|
||||
handlebars
|
||||
.register_templates_directory(".html", "./templates")
|
||||
.unwrap();
|
||||
|
||||
// register Fluent helper with Handlebars registry
|
||||
handlebars.register_helper("fluent", Box::new(FluentLoader::new(&*LOCALES)));
|
||||
|
||||
let handlebars = web::Data::new(handlebars);
|
||||
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.wrap(error_handlers())
|
||||
.app_data(web::Data::clone(&handlebars))
|
||||
.service(index)
|
||||
.service(user)
|
||||
})
|
||||
.bind(("127.0.0.1", 8080))?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
||||
// Custom error handlers, to return HTML responses when an error occurs.
|
||||
fn error_handlers() -> ErrorHandlers<BoxBody> {
|
||||
ErrorHandlers::new().handler(StatusCode::NOT_FOUND, not_found)
|
||||
}
|
||||
|
||||
// Error handler for a 404 Page not found error.
|
||||
fn not_found<B>(res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<BoxBody>> {
|
||||
let lang = LangChoice::from_req(res.request()).lang_id();
|
||||
let error = LOCALES.lookup(&lang, "error-not-found").unwrap();
|
||||
|
||||
let response = get_error_response(&res, &error);
|
||||
|
||||
Ok(ErrorHandlerResponse::Response(ServiceResponse::new(
|
||||
res.into_parts().0,
|
||||
response.map_into_left_body(),
|
||||
)))
|
||||
}
|
||||
|
||||
// Generic error handler.
|
||||
fn get_error_response<B>(res: &ServiceResponse<B>, error: &str) -> HttpResponse<BoxBody> {
|
||||
let req = res.request();
|
||||
let lang = LangChoice::from_req(req);
|
||||
|
||||
// Provide a fallback to a simple plain text response in case an error occurs during the
|
||||
// rendering of the error page.
|
||||
|
||||
let hb = req
|
||||
.app_data::<web::Data<Handlebars>>()
|
||||
.expect("correctly set up handlebars in app data");
|
||||
|
||||
let data = json!({
|
||||
"lang": lang,
|
||||
"error": error,
|
||||
"status_code": res.status().as_str()
|
||||
});
|
||||
|
||||
let body = hb.render("error", &data);
|
||||
|
||||
match body {
|
||||
Ok(body) => HttpResponse::build(res.status())
|
||||
.content_type(ContentType::html())
|
||||
.body(body),
|
||||
|
||||
Err(_) => HttpResponse::build(res.status())
|
||||
.content_type(ContentType::plaintext())
|
||||
.body(error.to_string()),
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user