Fix crash of trans() function called on absent translation key (#793)
Add method get_translation(lang, key) into Config struct that retrieves translated term from parsed configuration or error when either desired language or key is missing. Use the new method in Trans struct implementing global Tera function trans(). Add unit test to cover both happy and error path for translation retrieval in both config and templates crate.
This commit is contained in:
parent
e77adc56fd
commit
5aadd3d4f2
|
@ -8,6 +8,7 @@ use toml;
|
||||||
use toml::Value as Toml;
|
use toml::Value as Toml;
|
||||||
|
|
||||||
use errors::Result;
|
use errors::Result;
|
||||||
|
use errors::Error;
|
||||||
use highlighting::THEME_SET;
|
use highlighting::THEME_SET;
|
||||||
use theme::Theme;
|
use theme::Theme;
|
||||||
use utils::fs::read_file_with_error;
|
use utils::fs::read_file_with_error;
|
||||||
|
@ -83,6 +84,8 @@ impl Default for Taxonomy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TranslateTerm = HashMap<String, String>;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
@ -100,8 +103,12 @@ pub struct Config {
|
||||||
pub default_language: String,
|
pub default_language: String,
|
||||||
/// The list of supported languages outside of the default one
|
/// The list of supported languages outside of the default one
|
||||||
pub languages: Vec<Language>,
|
pub languages: Vec<Language>,
|
||||||
|
|
||||||
/// Languages list and translated strings
|
/// Languages list and translated strings
|
||||||
pub translations: HashMap<String, Toml>,
|
///
|
||||||
|
/// The `String` key of `HashMap` is a language name, the value should be toml crate `Table`
|
||||||
|
/// with String key representing term and value another `String` representing its translation.
|
||||||
|
pub translations: HashMap<String, TranslateTerm>,
|
||||||
|
|
||||||
/// Whether to highlight all code blocks found in markdown files. Defaults to false
|
/// Whether to highlight all code blocks found in markdown files. Defaults to false
|
||||||
pub highlight_code: bool,
|
pub highlight_code: bool,
|
||||||
|
@ -299,6 +306,16 @@ impl Config {
|
||||||
// and this operation can be expensive.
|
// and this operation can be expensive.
|
||||||
self.highlight_code = false;
|
self.highlight_code = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_translation<S: AsRef<str>>(&self, lang: S, key: S) -> Result<String> {
|
||||||
|
let terms = self.translations.get(lang.as_ref()).ok_or_else(|| {
|
||||||
|
Error::msg(format!("Translation for language '{}' is missing", lang.as_ref()))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
terms.get(key.as_ref()).ok_or_else(|| {
|
||||||
|
Error::msg(format!("Translation key '{}' for language '{}' is missing", key.as_ref(), lang.as_ref()))
|
||||||
|
}).map(|term| term.to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
|
@ -447,9 +464,7 @@ a_value = 10
|
||||||
assert_eq!(extra["a_value"].as_integer().unwrap(), 10);
|
assert_eq!(extra["a_value"].as_integer().unwrap(), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
const CONFIG_TRANSLATION: &str = r#"
|
||||||
fn can_use_language_configuration() {
|
|
||||||
let config = r#"
|
|
||||||
base_url = "https://remplace-par-ton-url.fr"
|
base_url = "https://remplace-par-ton-url.fr"
|
||||||
default_language = "fr"
|
default_language = "fr"
|
||||||
|
|
||||||
|
@ -459,14 +474,38 @@ title = "Un titre"
|
||||||
|
|
||||||
[translations.en]
|
[translations.en]
|
||||||
title = "A title"
|
title = "A title"
|
||||||
|
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let config = Config::parse(config);
|
#[test]
|
||||||
|
fn can_use_language_configuration() {
|
||||||
|
let config = Config::parse(CONFIG_TRANSLATION);
|
||||||
assert!(config.is_ok());
|
assert!(config.is_ok());
|
||||||
let translations = config.unwrap().translations;
|
let translations = config.unwrap().translations;
|
||||||
assert_eq!(translations["fr"]["title"].as_str().unwrap(), "Un titre");
|
assert_eq!(translations["fr"]["title"].as_str(), "Un titre");
|
||||||
assert_eq!(translations["en"]["title"].as_str().unwrap(), "A title");
|
assert_eq!(translations["en"]["title"].as_str(), "A title");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_use_present_translation() {
|
||||||
|
let config = Config::parse(CONFIG_TRANSLATION).unwrap();
|
||||||
|
assert_eq!(config.get_translation("fr", "title").unwrap(), "Un titre");
|
||||||
|
assert_eq!(config.get_translation("en", "title").unwrap(), "A title");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_on_absent_translation_lang() {
|
||||||
|
let config = Config::parse(CONFIG_TRANSLATION).unwrap();
|
||||||
|
let error = config.get_translation("absent", "key").unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!("Translation for language 'absent' is missing", format!("{}", error));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_on_absent_translation_key() {
|
||||||
|
let config = Config::parse(CONFIG_TRANSLATION).unwrap();
|
||||||
|
let error = config.get_translation("en", "absent").unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!("Translation key 'absent' for language 'en' is missing", format!("{}", error));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -33,8 +33,12 @@ impl TeraFn for Trans {
|
||||||
let key = required_arg!(String, args.get("key"), "`trans` requires a `key` argument.");
|
let key = required_arg!(String, args.get("key"), "`trans` requires a `key` argument.");
|
||||||
let lang = optional_arg!(String, args.get("lang"), "`trans`: `lang` must be a string.")
|
let lang = optional_arg!(String, args.get("lang"), "`trans`: `lang` must be a string.")
|
||||||
.unwrap_or_else(|| self.config.default_language.clone());
|
.unwrap_or_else(|| self.config.default_language.clone());
|
||||||
let translations = &self.config.translations[lang.as_str()];
|
|
||||||
Ok(to_value(&translations[key.as_str()]).unwrap())
|
let term = self.config.get_translation(lang, key).map_err(|e| {
|
||||||
|
Error::chain("Failed to retreive term translation", e)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(to_value(term).unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -505,9 +509,8 @@ mod tests {
|
||||||
assert!(static_fn.call(&args).is_err());
|
assert!(static_fn.call(&args).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_translate_a_string() {
|
const TRANS_CONFIG: &str = r#"
|
||||||
let trans_config = r#"
|
|
||||||
base_url = "https://remplace-par-ton-url.fr"
|
base_url = "https://remplace-par-ton-url.fr"
|
||||||
default_language = "fr"
|
default_language = "fr"
|
||||||
|
|
||||||
|
@ -517,10 +520,11 @@ title = "Un titre"
|
||||||
|
|
||||||
[translations.en]
|
[translations.en]
|
||||||
title = "A title"
|
title = "A title"
|
||||||
|
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let config = Config::parse(trans_config).unwrap();
|
#[test]
|
||||||
|
fn can_translate_a_string() {
|
||||||
|
let config = Config::parse(TRANS_CONFIG).unwrap();
|
||||||
let static_fn = Trans::new(config);
|
let static_fn = Trans::new(config);
|
||||||
let mut args = HashMap::new();
|
let mut args = HashMap::new();
|
||||||
|
|
||||||
|
@ -533,4 +537,26 @@ title = "A title"
|
||||||
args.insert("lang".to_string(), to_value("fr").unwrap());
|
args.insert("lang".to_string(), to_value("fr").unwrap());
|
||||||
assert_eq!(static_fn.call(&args).unwrap(), "Un titre");
|
assert_eq!(static_fn.call(&args).unwrap(), "Un titre");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_on_absent_translation_lang() {
|
||||||
|
let mut args = HashMap::new();
|
||||||
|
args.insert("lang".to_string(), to_value("absent").unwrap());
|
||||||
|
args.insert("key".to_string(), to_value("title").unwrap());
|
||||||
|
|
||||||
|
let config = Config::parse(TRANS_CONFIG).unwrap();
|
||||||
|
let error = Trans::new(config).call(&args).unwrap_err();
|
||||||
|
assert_eq!("Failed to retreive term translation", format!("{}", error));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_on_absent_translation_key() {
|
||||||
|
let mut args = HashMap::new();
|
||||||
|
args.insert("lang".to_string(), to_value("en").unwrap());
|
||||||
|
args.insert("key".to_string(), to_value("absent").unwrap());
|
||||||
|
|
||||||
|
let config = Config::parse(TRANS_CONFIG).unwrap();
|
||||||
|
let error = Trans::new(config).call(&args).unwrap_err();
|
||||||
|
assert_eq!("Failed to retreive term translation", format!("{}", error));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue