use turnframe_core::locale::{Locale, LocalizedText};
pub trait ServerCopy {
fn name(&self) -> &'static str;
fn entries(&self) -> Vec<(&'static str, &LocalizedText)>;
fn entries_mut(&mut self) -> Vec<(&'static str, &mut LocalizedText)>;
#[must_use]
fn translated(mut self, locale: impl Into<Locale>, texts: &[(&str, &str)]) -> Self
where
Self: Sized,
{
let locale = locale.into();
for (name, text) in self.entries_mut() {
if let Some((_, translation)) = texts.iter().find(|(field, _)| *field == name) {
text.translations
.insert(locale.clone(), (*translation).to_owned());
}
}
self
}
}
#[must_use]
pub fn missing(copy: &dyn ServerCopy, locale: &Locale) -> Vec<&'static str> {
if locale.same_language(&Locale::from("en")) {
return Vec::new();
}
copy.entries()
.into_iter()
.filter(|(_, text)| {
!text
.translations
.keys()
.any(|translated| translated.same_language(locale))
})
.map(|(name, _)| name)
.collect()
}
macro_rules! server_copy {
($ty:ty, [$($field:ident),* $(,)?]) => {
impl $crate::copy::ServerCopy for $ty {
fn name(&self) -> &'static str {
stringify!($ty)
}
fn entries(&self) -> Vec<(&'static str, &turnframe_core::locale::LocalizedText)> {
let Self { $($field),* } = self;
vec![$((stringify!($field), $field)),*]
}
fn entries_mut(
&mut self,
) -> Vec<(&'static str, &mut turnframe_core::locale::LocalizedText)> {
let Self { $($field),* } = self;
vec![$((stringify!($field), $field)),*]
}
}
};
}
pub(crate) use server_copy;
#[cfg(test)]
mod tests {
use super::*;
use crate::attachments::AttachmentCopy;
use crate::compose::{AskCopy, CompositionCopy};
use crate::policy::ConfirmationCopy;
use crate::reduce::NoticeCopy;
fn built_in() -> Vec<Box<dyn ServerCopy>> {
vec![
Box::new(NoticeCopy::default()),
Box::new(CompositionCopy::default()),
Box::new(AskCopy::default()),
Box::new(ConfirmationCopy::default()),
Box::new(AttachmentCopy::default()),
]
}
fn placeholders(text: &str) -> Vec<&str> {
let mut found: Vec<&str> = text
.match_indices('{')
.filter_map(|(at, _)| text[at..].find('}').map(|end| &text[at..=at + end]))
.collect();
found.sort_unstable();
found
}
#[test]
fn every_built_in_sentence_speaks_italian_with_the_same_placeholders() {
let italian = Locale::from("it-IT");
for copy in built_in() {
assert!(
missing(copy.as_ref(), &italian).is_empty(),
"{}",
copy.name()
);
for (name, text) in copy.entries() {
assert_eq!(
placeholders(text.resolve(&italian)),
placeholders(&text.default),
"{}.{name}",
copy.name()
);
}
}
}
#[test]
fn a_language_no_sentence_speaks_is_reported_sentence_by_sentence() {
let german = Locale::from("de-DE");
let copy = AskCopy::default();
assert_eq!(missing(©, &german).len(), copy.entries().len());
assert!(missing(©, &Locale::from("en-GB")).is_empty());
}
}