Working localization
This commit is contained in:
parent
245fcbcf1e
commit
3abc45cd4f
14 changed files with 135 additions and 40 deletions
|
@ -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";
|
58
src/i18n.rs
58
src/i18n.rs
|
@ -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())
|
||||
}
|
14
src/main.rs
14
src/main.rs
|
@ -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())
|
||||
|
|
|
@ -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
30
src/utils/headers.rs
Normal 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)
|
||||
});
|
|
@ -1,5 +1,5 @@
|
|||
mod kyujitai;
|
||||
pub use kyujitai::Kyujitai;
|
||||
|
||||
mod referer;
|
||||
pub use referer::Referer;
|
||||
mod headers;
|
||||
pub use headers::*;
|
||||
|
|
|
@ -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())))
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue