From 76e359ea8925b226c49ec9d68adcf1f7d151bbcc Mon Sep 17 00:00:00 2001 From: ElnuDev Date: Tue, 20 Jun 2023 13:47:19 -0700 Subject: [PATCH] Implement i18n backend --- Cargo.lock | 75 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 +- i18n/.gitignore | 1 + i18n/en.po | 2 + i18n/ja.po | 2 + shell.nix | 1 + src/i18n.rs | 55 +++++++++++++++++++++++++++ src/main.rs | 17 +++++++-- src/routes/get_challenge.rs | 6 --- templates/modal.html.tera | 2 +- 10 files changed, 152 insertions(+), 12 deletions(-) create mode 100644 i18n/.gitignore create mode 100644 i18n/en.po create mode 100644 i18n/ja.po create mode 100644 src/i18n.rs diff --git a/Cargo.lock b/Cargo.lock index 0f55c92..a1c2011 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -711,6 +711,70 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +[[package]] +name = "encoding" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" +dependencies = [ + "encoding-index-japanese", + "encoding-index-korean", + "encoding-index-simpchinese", + "encoding-index-singlebyte", + "encoding-index-tradchinese", +] + +[[package]] +name = "encoding-index-japanese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-korean" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-simpchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-singlebyte" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-tradchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding_index_tests" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" + [[package]] name = "encoding_rs" version = "0.8.32" @@ -987,6 +1051,16 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "gettext" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ebb594e753d5997e4be036e5a8cf048ab9414352870fb45c779557bbc9ba971" +dependencies = [ + "byteorder", + "encoding", +] + [[package]] name = "ghash" version = "0.5.0" @@ -3034,6 +3108,7 @@ dependencies = [ "comrak", "derive_more", "dotenv", + "gettext", "poise", "reqwest", "rocket 0.5.0-rc.3", diff --git a/Cargo.toml b/Cargo.toml index a39a7de..9d5d342 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ chrono = { version = "0.4.26", features = ["serde"] } comrak = "0.18.0" derive_more = "0.99.17" dotenv = "0.15.0" +gettext = "0.4.0" poise = "0.5.5" reqwest = "0.11.18" rocket = { version = "=0.5.0-rc.3", features = ["secrets"] } @@ -18,4 +19,4 @@ rocket_dyn_templates = { version = "0.1.0-rc.3", features = ["tera"] } sass-rocket-fairing = "0.2.0" serde = "1.0.163" serde_json = "1.0.96" -serde_yaml = "0.9.21" +serde_yaml = "0.9.21" \ No newline at end of file diff --git a/i18n/.gitignore b/i18n/.gitignore new file mode 100644 index 0000000..c90071a --- /dev/null +++ b/i18n/.gitignore @@ -0,0 +1 @@ +*.mo \ No newline at end of file diff --git a/i18n/en.po b/i18n/en.po new file mode 100644 index 0000000..e07a00d --- /dev/null +++ b/i18n/en.po @@ -0,0 +1,2 @@ +msgid "title" +msgstr "Tegaki Tuesday" diff --git a/i18n/ja.po b/i18n/ja.po new file mode 100644 index 0000000..b1ef571 --- /dev/null +++ b/i18n/ja.po @@ -0,0 +1,2 @@ +msgid "title" +msgstr "手書きの火曜日" diff --git a/shell.nix b/shell.nix index 97b3598..91c2b67 100644 --- a/shell.nix +++ b/shell.nix @@ -17,5 +17,6 @@ pkgs.mkShell { bacon pkg-config openssl + gettext ]; } diff --git a/src/i18n.rs b/src/i18n.rs new file mode 100644 index 0000000..f05688e --- /dev/null +++ b/src/i18n.rs @@ -0,0 +1,55 @@ +use std::{collections::HashMap, fs::{self, File}, io::BufReader, process::Command}; +use derive_more::From; +use rocket_dyn_templates::tera::{self, Value}; + +use gettext::Catalog; + +#[derive(From, Debug)] +pub enum LoadCatalogsError { + Io(std::io::Error), + Parse(gettext::Error), + MissingDefaultLanguage, +} + +const DEFAULT: &str = "ja"; + +pub fn load_catalogs() -> Result, LoadCatalogsError> { + let mut catalogs = HashMap::new(); + for file in fs::read_dir("i18n")? { + let file = file?; + let path = file.path(); + if !file.file_type()?.is_file() || !path.extension().map(|extension| extension.eq("po")).unwrap_or(false) { + continue; + } + let language_code = path + .file_stem() + .expect("Invalid translation file name") + .to_string_lossy() + .to_string(); + let po_file_path = path; + let mo_file_path = format!("i18n/{}.mo", language_code); + Command::new("msgfmt") + .arg(&po_file_path) + .arg("-o") + .arg(&mo_file_path) + .output()?; + catalogs.insert(language_code, Catalog::parse(BufReader::new(File::open(mo_file_path)?))?); + } + if !catalogs.contains_key(DEFAULT) { + return Err(LoadCatalogsError::MissingDefaultLanguage); + } + Ok(catalogs) +} + +pub fn i18n_filter(value: &Value, _args: &HashMap, catalogs: &HashMap) -> tera::Result { + 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())) +} diff --git a/src/main.rs b/src/main.rs index 82c02f7..6eeaacd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,9 +3,9 @@ extern crate rocket; use poise::serenity_prelude::Http; use rocket::fs::{relative, FileServer}; -use rocket_dyn_templates::Template; +use rocket_dyn_templates::{Template, tera}; use sass_rocket_fairing::SassFairing; -use std::env; +use std::{env, collections::HashMap}; mod models; use models::Settings; @@ -17,6 +17,9 @@ mod cookies; mod routes; use routes::*; +mod i18n; +use i18n::{load_catalogs, i18n_filter}; + mod prelude; #[launch] @@ -40,6 +43,12 @@ async fn rocket() -> _ { routes![get_challenge, login, post_login, success, logout, testing], ) .mount("/css", FileServer::from(relative!("styles/css"))) - .attach(Template::fairing()) + .attach(Template::custom(|engines| { + use tera::Value; + let catalogs = load_catalogs().unwrap(); + engines.tera.register_filter("i18n", move |value: &Value, args: &HashMap| { + i18n_filter(value, args, &catalogs) + }) + })) .attach(SassFairing::default()) -} +} \ No newline at end of file diff --git a/src/routes/get_challenge.rs b/src/routes/get_challenge.rs index 4f1beb7..bbb94a3 100644 --- a/src/routes/get_challenge.rs +++ b/src/routes/get_challenge.rs @@ -7,12 +7,6 @@ use crate::models::{Challenge, User, Settings}; #[get("/")] pub async fn get_challenge(challenge: u32, cookies: &CookieJar<'_>, settings: &State) -> Template { - println!( - "{:?}", - cookies - .get_private("user_name") - .map(|cookie| cookie.value().to_owned()) - ); Template::render( "index", context! { diff --git a/templates/modal.html.tera b/templates/modal.html.tera index 29aeaa5..be9b83a 100644 --- a/templates/modal.html.tera +++ b/templates/modal.html.tera @@ -1,5 +1,5 @@ -

Welcome to Tegaki Tuesday!

+

Welcome to {{ "title" | i18n }}!

In order to participate in challenges, you must be a member of a participating Discord server.

Join a participating server