Skip to content

Commit

Permalink
Fix crash of trans() function called on absent translation key (#793)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
zdenek-crha authored and Keats committed Sep 3, 2019
1 parent e77adc5 commit 5aadd3d
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 15 deletions.
55 changes: 47 additions & 8 deletions components/config/src/config.rs
Expand Up @@ -8,6 +8,7 @@ use toml;
use toml::Value as Toml;

use errors::Result;
use errors::Error;
use highlighting::THEME_SET;
use theme::Theme;
use utils::fs::read_file_with_error;
Expand Down Expand Up @@ -83,6 +84,8 @@ impl Default for Taxonomy {
}
}

type TranslateTerm = HashMap<String, String>;

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct Config {
Expand All @@ -100,8 +103,12 @@ pub struct Config {
pub default_language: String,
/// The list of supported languages outside of the default one
pub languages: Vec<Language>,

/// 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
pub highlight_code: bool,
Expand Down Expand Up @@ -299,6 +306,16 @@ impl Config {
// and this operation can be expensive.
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 {
Expand Down Expand Up @@ -447,9 +464,7 @@ a_value = 10
assert_eq!(extra["a_value"].as_integer().unwrap(), 10);
}

#[test]
fn can_use_language_configuration() {
let config = r#"
const CONFIG_TRANSLATION: &str = r#"
base_url = "https://remplace-par-ton-url.fr"
default_language = "fr"
Expand All @@ -459,14 +474,38 @@ title = "Un titre"
[translations.en]
title = "A title"
"#;

let config = Config::parse(config);
#[test]
fn can_use_language_configuration() {
let config = Config::parse(CONFIG_TRANSLATION);
assert!(config.is_ok());
let translations = config.unwrap().translations;
assert_eq!(translations["fr"]["title"].as_str().unwrap(), "Un titre");
assert_eq!(translations["en"]["title"].as_str().unwrap(), "A title");
assert_eq!(translations["fr"]["title"].as_str(), "Un titre");
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]
Expand Down
40 changes: 33 additions & 7 deletions components/templates/src/global_fns/mod.rs
Expand Up @@ -33,8 +33,12 @@ impl TeraFn for Trans {
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.")
.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())
}
}

Expand Down Expand Up @@ -505,9 +509,8 @@ mod tests {
assert!(static_fn.call(&args).is_err());
}

#[test]
fn can_translate_a_string() {
let trans_config = r#"

const TRANS_CONFIG: &str = r#"
base_url = "https://remplace-par-ton-url.fr"
default_language = "fr"
Expand All @@ -517,10 +520,11 @@ title = "Un titre"
[translations.en]
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 mut args = HashMap::new();

Expand All @@ -533,4 +537,26 @@ title = "A title"
args.insert("lang".to_string(), to_value("fr").unwrap());
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));
}
}

0 comments on commit 5aadd3d

Please sign in to comment.