turnframe_runtime/
copy.rs1use turnframe_core::locale::{Locale, LocalizedText};
7
8pub trait ServerCopy {
10 fn name(&self) -> &'static str;
12 fn entries(&self) -> Vec<(&'static str, &LocalizedText)>;
14 fn entries_mut(&mut self) -> Vec<(&'static str, &mut LocalizedText)>;
16
17 #[must_use]
20 fn translated(mut self, locale: impl Into<Locale>, texts: &[(&str, &str)]) -> Self
21 where
22 Self: Sized,
23 {
24 let locale = locale.into();
25 for (name, text) in self.entries_mut() {
26 if let Some((_, translation)) = texts.iter().find(|(field, _)| *field == name) {
27 text.translations
28 .insert(locale.clone(), (*translation).to_owned());
29 }
30 }
31 self
32 }
33}
34
35#[must_use]
38pub fn missing(copy: &dyn ServerCopy, locale: &Locale) -> Vec<&'static str> {
39 if locale.same_language(&Locale::from("en")) {
40 return Vec::new();
41 }
42 copy.entries()
43 .into_iter()
44 .filter(|(_, text)| {
45 !text
46 .translations
47 .keys()
48 .any(|translated| translated.same_language(locale))
49 })
50 .map(|(name, _)| name)
51 .collect()
52}
53
54macro_rules! server_copy {
57 ($ty:ty, [$($field:ident),* $(,)?]) => {
58 impl $crate::copy::ServerCopy for $ty {
59 fn name(&self) -> &'static str {
60 stringify!($ty)
61 }
62
63 fn entries(&self) -> Vec<(&'static str, &turnframe_core::locale::LocalizedText)> {
64 let Self { $($field),* } = self;
65 vec![$((stringify!($field), $field)),*]
66 }
67
68 fn entries_mut(
69 &mut self,
70 ) -> Vec<(&'static str, &mut turnframe_core::locale::LocalizedText)> {
71 let Self { $($field),* } = self;
72 vec![$((stringify!($field), $field)),*]
73 }
74 }
75 };
76}
77pub(crate) use server_copy;
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82 use crate::attachments::AttachmentCopy;
83 use crate::compose::{AskCopy, CompositionCopy};
84 use crate::policy::ConfirmationCopy;
85 use crate::reduce::NoticeCopy;
86
87 fn built_in() -> Vec<Box<dyn ServerCopy>> {
88 vec![
89 Box::new(NoticeCopy::default()),
90 Box::new(CompositionCopy::default()),
91 Box::new(AskCopy::default()),
92 Box::new(ConfirmationCopy::default()),
93 Box::new(AttachmentCopy::default()),
94 ]
95 }
96
97 fn placeholders(text: &str) -> Vec<&str> {
98 let mut found: Vec<&str> = text
99 .match_indices('{')
100 .filter_map(|(at, _)| text[at..].find('}').map(|end| &text[at..=at + end]))
101 .collect();
102 found.sort_unstable();
103 found
104 }
105
106 #[test]
107 fn every_built_in_sentence_speaks_italian_with_the_same_placeholders() {
108 let italian = Locale::from("it-IT");
109 for copy in built_in() {
110 assert!(
111 missing(copy.as_ref(), &italian).is_empty(),
112 "{}",
113 copy.name()
114 );
115 for (name, text) in copy.entries() {
116 assert_eq!(
117 placeholders(text.resolve(&italian)),
118 placeholders(&text.default),
119 "{}.{name}",
120 copy.name()
121 );
122 }
123 }
124 }
125
126 #[test]
127 fn a_language_no_sentence_speaks_is_reported_sentence_by_sentence() {
128 let german = Locale::from("de-DE");
129 let copy = AskCopy::default();
130 assert_eq!(missing(©, &german).len(), copy.entries().len());
131 assert!(missing(©, &Locale::from("en-GB")).is_empty());
132 }
133}