Working localization

This commit is contained in:
Elnu 2023-06-21 13:24:51 -07:00
parent 245fcbcf1e
commit 3abc45cd4f
14 changed files with 135 additions and 40 deletions

View file

@ -8,3 +8,4 @@ pub mod user {
pub const USER_DISCRIMINATOR_COOKIE: &str = "user_discriminator";
pub const USER_AVATAR_COOKIE: &str = "user_avatar";
}
pub const LANG_COOKIE: &str = "lang";

View file

@ -1,5 +1,6 @@
use derive_more::From;
use rocket_dyn_templates::tera::{self, Value};
use serde::Serialize;
use std::{
collections::HashMap,
fs::{self, File},
@ -16,9 +17,11 @@ pub enum LoadCatalogsError {
MissingDefaultLanguage,
}
const DEFAULT: &str = "ja";
pub const DEFAULT: &str = "ja";
pub fn load_catalogs() -> Result<HashMap<String, Catalog>, LoadCatalogsError> {
type Catalogs = HashMap<String, Catalog>;
pub fn load_catalogs() -> Result<(Catalogs, Vec<LangCode>), LoadCatalogsError> {
let mut catalogs = HashMap::new();
for file in fs::read_dir("i18n")? {
let file = file?;
@ -51,19 +54,58 @@ pub fn load_catalogs() -> Result<HashMap<String, Catalog>, LoadCatalogsError> {
if !catalogs.contains_key(DEFAULT) {
return Err(LoadCatalogsError::MissingDefaultLanguage);
}
Ok(catalogs)
let langs = catalogs
.iter()
.map(|(code, catalog)|
LangCode {
code: code.clone(),
name: catalog
.gettext("lang")
.to_owned(),
}
).collect();
Ok((catalogs, langs))
}
pub fn i18n_filter(
value: &Value,
_args: &HashMap<String, Value>,
catalogs: &HashMap<String, Catalog>,
args: &HashMap<String, Value>,
catalogs: &Catalogs,
) -> tera::Result<Value> {
let key = value
.as_str()
.ok_or_else(|| tera::Error::msg("The translation key must be a string"))?;
let translation = catalogs.get(DEFAULT).expect("Missing catalog").gettext(key);
Ok(Value::String(translation.to_owned()))
let langs = args
.get("lang")
.map(|value| value.as_array())
.flatten()
.map(|array| {
let mut langs = Vec::with_capacity(array.len());
for lang in array {
langs.push(lang.as_str().unwrap());
}
langs
})
.unwrap_or_else(|| vec![DEFAULT]);
for lang in langs {
if let Some(catalog) = catalogs.get(lang) {
return Ok(Value::String(catalog.gettext(key).to_owned()));
}
}
panic!("Missing catalog");
}
#[derive(Serialize)]
pub struct LangCode {
pub code: String,
pub name: String,
}
pub fn langs_filter(
_value: &Value,
_args: &HashMap<String, Value>,
langs: &Vec<LangCode>,
) -> tera::Result<Value> {
Ok(serde_json::to_value(langs).unwrap())
}

View file

@ -2,7 +2,7 @@
extern crate rocket;
use poise::serenity_prelude::Http;
use rocket::fs::{relative, FileServer};
use rocket::{fs::{relative, FileServer}};
use rocket_dyn_templates::{tera, Template};
use sass_rocket_fairing::SassFairing;
use std::{collections::HashMap, env};
@ -20,6 +20,8 @@ use routes::*;
mod i18n;
use i18n::{i18n_filter, load_catalogs};
use crate::i18n::langs_filter;
mod prelude;
#[launch]
@ -43,14 +45,20 @@ async fn rocket() -> _ {
routes![get_challenge, login, post_login, success, logout, testing],
)
.mount("/css", FileServer::from(relative!("styles/css")))
.attach(Template::custom(|engines| {
.attach(Template::custom(move |engines| {
use tera::Value;
let catalogs = load_catalogs().unwrap();
let (catalogs, langs) = load_catalogs().unwrap();
engines.tera.register_filter(
"i18n",
move |value: &Value, args: &HashMap<String, Value>| {
i18n_filter(value, args, &catalogs)
},
);
engines.tera.register_filter(
"langs",
move |value: &Value, args: &HashMap<String, Value>| {
langs_filter(value, args, &langs)
}
)
}))
.attach(SassFairing::default())

View file

@ -3,19 +3,25 @@ use std::ops::Deref;
use rocket::{http::CookieJar, State};
use rocket_dyn_templates::{context, Template};
use crate::models::{Challenge, Settings, User};
use crate::{models::{Challenge, Settings, User}, cookies::LANG_COOKIE, i18n::DEFAULT as DEFAULT_LANG, utils::AcceptLanguage};
#[get("/<challenge>")]
pub async fn get_challenge(
challenge: u32,
cookies: &CookieJar<'_>,
settings: &State<Settings>,
accept_language: AcceptLanguage,
) -> Template {
Template::render(
"index",
context! {
challenge,
settings: settings.deref(),
lang: cookies
.get(LANG_COOKIE)
.map(|cookie| vec![cookie.value().to_owned()])
.or_else(|| accept_language.0)
.unwrap_or_else(|| vec![DEFAULT_LANG.to_owned()]),
user: User::get(cookies).await.unwrap(),
content: Challenge::get(challenge),
},

30
src/utils/headers.rs Normal file
View file

@ -0,0 +1,30 @@
use std::convert::Infallible;
use rocket::{
request::{self, FromRequest},
Request,
};
macro_rules! header {
($name:ident, $header:expr) => {
header!($name, $header, String, |header| header.to_owned());
};
($name:ident, $header:expr, $type:ty, $handler:expr) => {
pub struct $name(pub Option<$type>);
#[rocket::async_trait]
impl<'r> FromRequest<'r> for $name {
type Error = Infallible;
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
let header = req.headers().get_one($header);
request::Outcome::Success(Self(header.map($handler)))
}
}
};
}
header!(Referer, "Referer");
header!(AcceptLanguage, "Accept-Language", Vec<String>, |header| {
accept_language::parse(header)
});

View file

@ -1,5 +1,5 @@
mod kyujitai;
pub use kyujitai::Kyujitai;
mod referer;
pub use referer::Referer;
mod headers;
pub use headers::*;

View file

@ -1,18 +0,0 @@
use std::convert::Infallible;
use rocket::{
request::{self, FromRequest},
Request,
};
pub struct Referer(pub Option<String>);
#[rocket::async_trait]
impl<'r> FromRequest<'r> for Referer {
type Error = Infallible;
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
let referer = req.headers().get_one("Referer");
request::Outcome::Success(Referer(referer.map(|referer| referer.to_owned())))
}
}