Switch over to relying on the y10n crate
This is still just local but looking promising for segmenting the localization work I need to do
This commit is contained in:
parent
5337e8d7ca
commit
75fb8cf760
|
@ -804,16 +804,15 @@ dependencies = [
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"glob",
|
"glob",
|
||||||
"handlebars",
|
"handlebars",
|
||||||
"lazy_static",
|
|
||||||
"log",
|
"log",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
"regex",
|
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_qs",
|
"serde_qs",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"tide",
|
"tide",
|
||||||
|
"y10n",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2034,6 +2033,18 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "y10n"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"glob",
|
||||||
|
"lazy_static",
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"serde_yaml",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yaml-rust"
|
name = "yaml-rust"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
|
|
|
@ -12,10 +12,8 @@ dotenv = "*"
|
||||||
glob = "0"
|
glob = "0"
|
||||||
# For rendering simple HTTP views
|
# For rendering simple HTTP views
|
||||||
handlebars = { version = "4", features = ["dir_source"] }
|
handlebars = { version = "4", features = ["dir_source"] }
|
||||||
lazy_static = "1"
|
|
||||||
log = "*"
|
log = "*"
|
||||||
pretty_env_logger = "0.3"
|
pretty_env_logger = "0.3"
|
||||||
regex = "1"
|
|
||||||
# Used for embedding templates
|
# Used for embedding templates
|
||||||
rust-embed = "5"
|
rust-embed = "5"
|
||||||
|
|
||||||
|
@ -28,3 +26,6 @@ serde_yaml = "0.8"
|
||||||
|
|
||||||
# Web framework
|
# Web framework
|
||||||
tide = "*"
|
tide = "*"
|
||||||
|
|
||||||
|
# Localization support
|
||||||
|
y10n = { "path" = "../y10n" }
|
||||||
|
|
12
src/dao.rs
12
src/dao.rs
|
@ -34,9 +34,9 @@ pub mod v1 {
|
||||||
Ok(path) => {
|
Ok(path) => {
|
||||||
projects.push(
|
projects.push(
|
||||||
serde_yaml::from_reader(File::open(path).expect("Failed to open path"))
|
serde_yaml::from_reader(File::open(path).expect("Failed to open path"))
|
||||||
.expect("Failed to load")
|
.expect("Failed to load"),
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
Err(e) => println!("{:?}", e),
|
Err(e) => println!("{:?}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,17 +46,13 @@ pub mod v1 {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub enum ScmType {
|
pub enum ScmType {
|
||||||
Git {
|
Git { url: String },
|
||||||
url: String,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub enum TriggerType {
|
pub enum TriggerType {
|
||||||
Manual,
|
Manual,
|
||||||
Cron {
|
Cron { schedule: String },
|
||||||
schedule: String,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
81
src/i18n.rs
81
src/i18n.rs
|
@ -1,81 +0,0 @@
|
||||||
/**
|
|
||||||
* The i18n module provides some simple internationalization support
|
|
||||||
* in a way that is compatible with handlebars rendering
|
|
||||||
*/
|
|
||||||
use log::*;
|
|
||||||
|
|
||||||
pub fn parse_languages(header: &str) -> Vec<Language> {
|
|
||||||
trace!("Parsing languages from: {}", header);
|
|
||||||
let mut results = vec![];
|
|
||||||
|
|
||||||
for part in header.split(",") {
|
|
||||||
if let Ok(language) = Language::from(part) {
|
|
||||||
results.push(language);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
results
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Language {
|
|
||||||
pub code: String,
|
|
||||||
region: Option<String>,
|
|
||||||
quality: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref LANG_REGEX: regex::Regex = regex::Regex::new(r"(?P<code>\w+)-?(?P<region>\w+)?(;q=(?P<quality>([0-9]*[.])?[0-9]+)?)?").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Language {
|
|
||||||
fn from(segment: &str) -> Result<Language, Error> {
|
|
||||||
if let Some(captures) = LANG_REGEX.captures(segment) {
|
|
||||||
println!("caps: {:?}", captures);
|
|
||||||
Ok(Language {
|
|
||||||
code: captures.name("code").map_or("unknown".to_string(), |c| c.as_str().to_string()),
|
|
||||||
region: captures.name("region").map_or(None, |c| Some(c.as_str().to_string())),
|
|
||||||
quality: captures.name("quality").map_or(1.0, |c| c.as_str().parse().unwrap_or(0.0)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Err(Error::Generic)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
enum Error {
|
|
||||||
Generic,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn language_from_segment() {
|
|
||||||
let lang = Language::from("en-US");
|
|
||||||
assert!(lang.is_ok());
|
|
||||||
let lang = lang.unwrap();
|
|
||||||
assert_eq!("en", lang.code);
|
|
||||||
assert_eq!(Some("US".to_string()), lang.region);
|
|
||||||
assert_eq!(1.0, lang.quality);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_langs_simple() {
|
|
||||||
let header = "en-US,en;q=0.5";
|
|
||||||
let langs = parse_languages(&header);
|
|
||||||
assert_eq!(langs.len(), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_langs_multi() {
|
|
||||||
let header = "en-US,en;q=0.7,de-DE;q=0.3";
|
|
||||||
let langs = parse_languages(&header);
|
|
||||||
assert_eq!(langs.len(), 3);
|
|
||||||
let de = langs.get(2).unwrap();
|
|
||||||
assert_eq!("de", de.code);
|
|
||||||
assert_eq!(0.3, de.quality);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,13 +3,10 @@
|
||||||
*/
|
*/
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
|
|
||||||
use log::*;
|
use log::*;
|
||||||
|
|
||||||
mod dao;
|
mod dao;
|
||||||
mod i18n;
|
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
#[async_std::main]
|
#[async_std::main]
|
||||||
|
@ -36,7 +33,7 @@ async fn main() -> Result<(), std::io::Error> {
|
||||||
Some(l) => l.as_str(),
|
Some(l) => l.as_str(),
|
||||||
None => "en",
|
None => "en",
|
||||||
};
|
};
|
||||||
let langs = crate::i18n::parse_languages(lang);
|
let langs = y10n::parse_accept_language(lang);
|
||||||
info!("Lang: {:?}", langs);
|
info!("Lang: {:?}", langs);
|
||||||
|
|
||||||
req.state().render("index", &langs, Some(data)).await
|
req.state().render("index", &langs, Some(data)).await
|
||||||
|
|
64
src/state.rs
64
src/state.rs
|
@ -1,14 +1,12 @@
|
||||||
use async_std::sync::{Arc, RwLock};
|
use async_std::sync::{Arc, RwLock};
|
||||||
use handlebars::Handlebars;
|
use handlebars::Handlebars;
|
||||||
use log::*;
|
use log::*;
|
||||||
use std::fs::File;
|
use y10n::*;
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use crate::i18n::Language;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct AppState<'a> {
|
pub struct AppState<'a> {
|
||||||
hb: Arc<RwLock<Handlebars<'a>>>,
|
hb: Arc<RwLock<Handlebars<'a>>>,
|
||||||
|
y10n: Arc<RwLock<Y10n>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState<'_> {
|
impl AppState<'_> {
|
||||||
|
@ -17,8 +15,11 @@ impl AppState<'_> {
|
||||||
// This ensures that we get errors rather than empty strings for bad values
|
// This ensures that we get errors rather than empty strings for bad values
|
||||||
hb.set_strict_mode(true);
|
hb.set_strict_mode(true);
|
||||||
|
|
||||||
|
let y10n = Y10n::from_glob("i18n/**/*.yml");
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
hb: Arc::new(RwLock::new(hb)),
|
hb: Arc::new(RwLock::new(hb)),
|
||||||
|
y10n: Arc::new(RwLock::new(y10n)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,24 +45,17 @@ impl AppState<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(data) = &data {
|
if let Some(data) = &data {
|
||||||
if ! data.is_object() {
|
if !data.is_object() {
|
||||||
warn!("The render function was called without a map, this can lead to funny results");
|
warn!(
|
||||||
|
"The render function was called without a map, this can lead to funny results"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut i18n = serde_yaml::Value::Null;
|
|
||||||
|
|
||||||
// merge in the langages from lowest priority
|
let y10n = self.y10n.read().await;
|
||||||
for lang in langs.iter().rev() {
|
let localize = y10n.localize(&langs);
|
||||||
let filename = format!("i18n/{}.yml", lang.code);
|
let mut mustache = json!({ "t": localize });
|
||||||
if ! Path::exists(Path::new(&filename)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let t: serde_yaml::Value = serde_yaml::from_reader(File::open(filename)?)?;
|
|
||||||
merge_yaml(&mut i18n, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut mustache = json!({"t" : i18n});
|
|
||||||
merge(&mut mustache, data.unwrap_or(serde_json::Value::Null));
|
merge(&mut mustache, data.unwrap_or(serde_json::Value::Null));
|
||||||
|
|
||||||
let hb = self.hb.read().await;
|
let hb = self.hb.read().await;
|
||||||
|
@ -70,11 +64,11 @@ impl AppState<'_> {
|
||||||
let mut body = tide::Body::from_string(view);
|
let mut body = tide::Body::from_string(view);
|
||||||
body.set_mime("text/html");
|
body.set_mime("text/html");
|
||||||
Ok(body)
|
Ok(body)
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to render {}: {:?}", name, e);
|
error!("Failed to render {}: {:?}", name, e);
|
||||||
Err(e.into())
|
Err(e.into())
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,11 +79,10 @@ fn merge(a: &mut serde_json::Value, b: serde_json::Value) {
|
||||||
let a = a.as_object_mut().unwrap();
|
let a = a.as_object_mut().unwrap();
|
||||||
for (k, v) in b {
|
for (k, v) in b {
|
||||||
if v.is_array() && a.contains_key(&k) && a.get(&k).as_ref().unwrap().is_array() {
|
if v.is_array() && a.contains_key(&k) && a.get(&k).as_ref().unwrap().is_array() {
|
||||||
let mut _a = a.get(&k).unwrap().as_array().unwrap().to_owned();
|
let mut _a = a.get(&k).unwrap().as_array().unwrap().to_owned();
|
||||||
_a.append(&mut v.as_array().unwrap().to_owned());
|
_a.append(&mut v.as_array().unwrap().to_owned());
|
||||||
a[&k] = serde_json::Value::from(_a);
|
a[&k] = serde_json::Value::from(_a);
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
merge(a.entry(k).or_insert(serde_json::Value::Null), v);
|
merge(a.entry(k).or_insert(serde_json::Value::Null), v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,24 +90,3 @@ fn merge(a: &mut serde_json::Value, b: serde_json::Value) {
|
||||||
(a, b) => *a = b,
|
(a, b) => *a = b,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn merge_yaml(a: &mut serde_yaml::Value, b: serde_yaml::Value) {
|
|
||||||
match (a, b) {
|
|
||||||
(a @ &mut serde_yaml::Value::Mapping(_), serde_yaml::Value::Mapping(b)) => {
|
|
||||||
let a = a.as_mapping_mut().unwrap();
|
|
||||||
for (k, v) in b {
|
|
||||||
if v.is_sequence() && a.contains_key(&k) && a[&k].is_sequence() {
|
|
||||||
let mut _b = a.get(&k).unwrap().as_sequence().unwrap().to_owned();
|
|
||||||
_b.append(&mut v.as_sequence().unwrap().to_owned());
|
|
||||||
a[&k] = serde_yaml::Value::from(_b);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if !a.contains_key(&k) {a.insert(k.to_owned(), v.to_owned());}
|
|
||||||
else { merge_yaml(&mut a[&k], v); }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
(a, b) => *a = b,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue