use std::collections::{BTreeMap, BTreeSet};
use toml::de::{DeTable, DeValue};
use crate::locales;
fn messages(file: &str, text: &str) -> BTreeMap<String, String> {
let root = DeTable::parse(text).unwrap_or_else(|error| panic!("{file} is not TOML: {error}"));
let mut out = BTreeMap::new();
for (section, value) in root.get_ref() {
if section.get_ref() != "meta" {
flatten(file, section.get_ref(), value.get_ref(), &mut out);
}
}
out
}
fn flatten(file: &str, prefix: &str, value: &DeValue<'_>, out: &mut BTreeMap<String, String>) {
match value {
DeValue::String(text) => {
out.insert(prefix.to_owned(), text.to_string());
}
DeValue::Table(table) => {
for (key, value) in table {
flatten(file, &format!("{prefix}.{}", key.get_ref()), value.get_ref(), out);
}
}
_ => panic!("{file}: `{prefix}` is not text"),
}
}
fn placeholders(text: &str) -> BTreeSet<&str> {
text.split('{').skip(1).filter_map(|rest| rest.split_once('}').map(|(name, _)| name)).collect()
}
fn english() -> BTreeMap<String, String> {
let (file, text) = locales()[0];
assert_eq!(file, "en.toml");
messages(file, text)
}
fn message_of(key: &str) -> &str {
match key.rsplit_once('.') {
Some((base, form)) if ["zero", "one", "two", "few", "many", "other"].contains(&form) => base,
_ => key,
}
}
fn by_message(messages: &BTreeMap<String, String>) -> BTreeMap<&str, BTreeSet<&str>> {
let mut out: BTreeMap<&str, BTreeSet<&str>> = BTreeMap::new();
for (key, value) in messages {
out.entry(message_of(key)).or_default().extend(placeholders(value));
}
out
}
#[test]
fn every_language_has_every_key_with_its_placeholders() {
let english = english();
let english = by_message(&english);
assert_eq!(locales().len(), 9, "the family's nine languages");
for &(file, text) in &locales()[1..] {
let other = messages(file, text);
let other = by_message(&other);
for (key, wanted) in &english {
let found = other.get(key).unwrap_or_else(|| panic!("{file} has no `{key}`"));
assert_eq!(found, wanted, "{file} `{key}` changes the placeholders");
}
for key in other.keys() {
assert!(english.contains_key(key), "{file} has `{key}`, which English does not");
}
}
}
#[test]
fn every_language_says_things_in_its_own_words() {
let english = english();
for &(file, text) in &locales()[1..] {
let other = messages(file, text);
let same: Vec<&String> = english.keys().filter(|key| other.get(*key) == english.get(*key)).collect();
assert!(
same.len() * 10 <= english.len(),
"{file} leaves {} of {} values in English: {same:?}",
same.len(),
english.len()
);
}
}