Skip to main content

teaql_runtime/
language.rs

1use teaql_core::Value;
2
3use crate::{CheckResult, CheckRule, ObjectLocation};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
6pub enum Language {
7    English,
8    Chinese,
9    TraditionalChinese,
10    Japanese,
11    Korean,
12    German,
13    French,
14    Spanish,
15    Portuguese,
16    Arabic,
17    Thai,
18    Indonesian,
19    Filipino,
20    Ukrainian,
21    Vietnamese,
22}
23
24impl Default for Language {
25    fn default() -> Self {
26        Self::English
27    }
28}
29
30impl Language {
31    pub const ALL: [Language; 15] = [
32        Language::English,
33        Language::Chinese,
34        Language::TraditionalChinese,
35        Language::Japanese,
36        Language::Korean,
37        Language::German,
38        Language::French,
39        Language::Spanish,
40        Language::Portuguese,
41        Language::Arabic,
42        Language::Thai,
43        Language::Indonesian,
44        Language::Filipino,
45        Language::Ukrainian,
46        Language::Vietnamese,
47    ];
48
49    pub fn code(self) -> &'static str {
50        match self {
51            Self::English => "en",
52            Self::Chinese => "zh-CN",
53            Self::TraditionalChinese => "zh-TW",
54            Self::Japanese => "ja",
55            Self::Korean => "ko",
56            Self::German => "de",
57            Self::French => "fr",
58            Self::Spanish => "es",
59            Self::Portuguese => "pt",
60            Self::Arabic => "ar",
61            Self::Thai => "th",
62            Self::Indonesian => "id",
63            Self::Filipino => "fil",
64            Self::Ukrainian => "uk",
65            Self::Vietnamese => "vi",
66        }
67    }
68
69    pub fn from_code(code: &str) -> Option<Self> {
70        match code {
71            "en" | "en-US" | "en-GB" => Some(Self::English),
72            "zh" | "zh-CN" | "cn" => Some(Self::Chinese),
73            "zh-TW" | "zh-HK" | "tw" => Some(Self::TraditionalChinese),
74            "ja" | "ja-JP" => Some(Self::Japanese),
75            "ko" | "ko-KR" => Some(Self::Korean),
76            "de" | "de-DE" => Some(Self::German),
77            "fr" | "fr-FR" => Some(Self::French),
78            "es" | "es-ES" => Some(Self::Spanish),
79            "pt" | "pt-BR" | "pt-PT" => Some(Self::Portuguese),
80            "ar" => Some(Self::Arabic),
81            "th" | "th-TH" => Some(Self::Thai),
82            "id" | "id-ID" => Some(Self::Indonesian),
83            "fil" | "tl" => Some(Self::Filipino),
84            "uk" | "uk-UA" => Some(Self::Ukrainian),
85            "vi" | "vi-VN" => Some(Self::Vietnamese),
86            _ => None,
87        }
88    }
89}
90
91pub trait MessageTranslator: Send + Sync {
92    fn language(&self) -> Language;
93    fn translate_check_result(&self, result: &CheckResult) -> String;
94}
95
96#[derive(Debug, Clone, Copy)]
97pub struct BuiltinTranslator {
98    language: Language,
99}
100
101impl BuiltinTranslator {
102    pub fn new(language: Language) -> Self {
103        Self { language }
104    }
105}
106
107impl MessageTranslator for BuiltinTranslator {
108    fn language(&self) -> Language {
109        self.language
110    }
111
112    fn translate_check_result(&self, result: &CheckResult) -> String {
113        translate_check_result(self.language, result)
114    }
115}
116
117pub fn translate_check_result(language: Language, result: &CheckResult) -> String {
118    let location = translate_location(language, &result.location);
119    let system = result
120        .system_value
121        .as_ref()
122        .map(format_value)
123        .unwrap_or_else(|| "-".to_owned());
124    let input = result
125        .input_value
126        .as_ref()
127        .map(format_value)
128        .unwrap_or_else(|| "-".to_owned());
129    let input_len = result
130        .input_value
131        .as_ref()
132        .and_then(|value| match value {
133            Value::Text(value) => Some(value.chars().count()),
134            _ => None,
135        })
136        .unwrap_or(0);
137
138    match (language, result.rule) {
139        (Language::English, CheckRule::Required) => format!("The {location} is required"),
140        (Language::English, CheckRule::Min) => {
141            format!("The {location} should be equal or greater than {system}, but input is {input}")
142        }
143        (Language::English, CheckRule::Max) => {
144            format!("The {location} should be equal or less than {system}, but input is {input}")
145        }
146        (Language::English, CheckRule::MinStringLength) => format!(
147            "The length of {location} should be equal or greater than {system}, but the length of {input} is {input_len}"
148        ),
149        (Language::English, CheckRule::MaxStringLength) => format!(
150            "The length of {location} should be equal or less than {system}, but the length of {input} is {input_len}"
151        ),
152
153        (Language::Chinese, CheckRule::Required) => format!("{location} 是必填项"),
154        (Language::Chinese, CheckRule::Min) => {
155            format!("{location} 应该大于等于 {system},但输入值为 {input}")
156        }
157        (Language::Chinese, CheckRule::Max) => {
158            format!("{location} 应该小于等于 {system},但输入值为 {input}")
159        }
160        (Language::Chinese, CheckRule::MinStringLength) => {
161            format!("{location} 的长度应大于等于 {system},但实际长度为 {input_len}")
162        }
163        (Language::Chinese, CheckRule::MaxStringLength) => {
164            format!("{location} 的长度应小于等于 {system},但实际长度为 {input_len}")
165        }
166
167        (Language::TraditionalChinese, CheckRule::Required) => format!("{location} 是必填的"),
168        (Language::TraditionalChinese, CheckRule::Min) => {
169            format!("{location} 應該等於或大於 {system},但輸入為 {input}")
170        }
171        (Language::TraditionalChinese, CheckRule::Max) => {
172            format!("{location} 應該等於或小於 {system},但輸入為 {input}")
173        }
174        (Language::TraditionalChinese, CheckRule::MinStringLength) => {
175            format!("{location} 的長度應該等於或大於 {system},但 {input} 的長度是 {input_len}")
176        }
177        (Language::TraditionalChinese, CheckRule::MaxStringLength) => {
178            format!("{location} 的長度應該等於或小於 {system},但 {input} 的長度是 {input_len}")
179        }
180
181        (Language::Japanese, CheckRule::Required) => format!("{location} は必須です"),
182        (Language::Japanese, CheckRule::Min) => {
183            format!("{location} は {system} 以上である必要があります。入力値は {input} です")
184        }
185        (Language::Japanese, CheckRule::Max) => {
186            format!("{location} は {system} 以下である必要があります。入力値は {input} です")
187        }
188        (Language::Japanese, CheckRule::MinStringLength) => {
189            format!(
190                "{location} の長さは {system} 以上である必要があります。実際の長さは {input_len} です"
191            )
192        }
193        (Language::Japanese, CheckRule::MaxStringLength) => {
194            format!(
195                "{location} の長さは {system} 以下である必要があります。実際の長さは {input_len} です"
196            )
197        }
198
199        (Language::Korean, CheckRule::Required) => format!("{location}은(는) 필수입니다"),
200        (Language::Korean, CheckRule::Min) => {
201            format!("{location}은(는) {system} 이상이어야 하지만 입력값은 {input}입니다")
202        }
203        (Language::Korean, CheckRule::Max) => {
204            format!("{location}은(는) {system} 이하여야 하지만 입력값은 {input}입니다")
205        }
206        (Language::Korean, CheckRule::MinStringLength) => {
207            format!("{location}의 길이는 {system} 이상이어야 하지만 실제 길이는 {input_len}입니다")
208        }
209        (Language::Korean, CheckRule::MaxStringLength) => {
210            format!("{location}의 길이는 {system} 이하여야 하지만 실제 길이는 {input_len}입니다")
211        }
212
213        (Language::German, CheckRule::Required) => format!("{location} ist erforderlich"),
214        (Language::German, CheckRule::Min) => {
215            format!("{location} muss mindestens {system} sein, aber die Eingabe ist {input}")
216        }
217        (Language::German, CheckRule::Max) => {
218            format!("{location} darf höchstens {system} sein, aber die Eingabe ist {input}")
219        }
220        (Language::German, CheckRule::MinStringLength) => {
221            format!("Die Länge von {location} muss mindestens {system} sein, ist aber {input_len}")
222        }
223        (Language::German, CheckRule::MaxStringLength) => {
224            format!("Die Länge von {location} darf höchstens {system} sein, ist aber {input_len}")
225        }
226
227        (Language::French, CheckRule::Required) => format!("{location} est obligatoire"),
228        (Language::French, CheckRule::Min) => {
229            format!(
230                "{location} doit être supérieur ou égal à {system}, mais la valeur saisie est {input}"
231            )
232        }
233        (Language::French, CheckRule::Max) => {
234            format!(
235                "{location} doit être inférieur ou égal à {system}, mais la valeur saisie est {input}"
236            )
237        }
238        (Language::French, CheckRule::MinStringLength) => {
239            format!(
240                "La longueur de {location} doit être supérieure ou égale à {system}, mais elle est {input_len}"
241            )
242        }
243        (Language::French, CheckRule::MaxStringLength) => {
244            format!(
245                "La longueur de {location} doit être inférieure ou égale à {system}, mais elle est {input_len}"
246            )
247        }
248
249        (Language::Spanish, CheckRule::Required) => format!("{location} es requerido/a"),
250        (Language::Spanish, CheckRule::Min) => {
251            format!(
252                "{location} debe ser mayor o igual que {system}, pero el valor ingresado es {input}"
253            )
254        }
255        (Language::Spanish, CheckRule::Max) => {
256            format!(
257                "{location} debe ser menor o igual que {system}, pero el valor ingresado es {input}"
258            )
259        }
260        (Language::Spanish, CheckRule::MinStringLength) => {
261            format!(
262                "La longitud de {location} debe ser mayor o igual que {system}, pero es {input_len}"
263            )
264        }
265        (Language::Spanish, CheckRule::MaxStringLength) => {
266            format!(
267                "La longitud de {location} debe ser menor o igual que {system}, pero es {input_len}"
268            )
269        }
270
271        (Language::Portuguese, CheckRule::Required) => format!("{location} é obrigatório"),
272        (Language::Portuguese, CheckRule::Min) => {
273            format!("{location} deve ser maior ou igual a {system}, mas a entrada é {input}")
274        }
275        (Language::Portuguese, CheckRule::Max) => {
276            format!("{location} deve ser menor ou igual a {system}, mas a entrada é {input}")
277        }
278        (Language::Portuguese, CheckRule::MinStringLength) => {
279            format!(
280                "O comprimento de {location} deve ser maior ou igual a {system}, mas é {input_len}"
281            )
282        }
283        (Language::Portuguese, CheckRule::MaxStringLength) => {
284            format!(
285                "O comprimento de {location} deve ser menor ou igual a {system}, mas é {input_len}"
286            )
287        }
288
289        (Language::Arabic, CheckRule::Required) => format!("{location} مطلوب"),
290        (Language::Arabic, CheckRule::Min) => {
291            format!("يجب أن يكون {location} مساويًا أو أكبر من {system}، لكن المُدخل هو {input}")
292        }
293        (Language::Arabic, CheckRule::Max) => {
294            format!("يجب أن يكون {location} مساويًا أو أصغر من {system}، لكن المُدخل هو {input}")
295        }
296        (Language::Arabic, CheckRule::MinStringLength) => {
297            format!(
298                "يجب أن يكون طول {location} مساويًا أو أكبر من {system}، لكن الطول هو {input_len}"
299            )
300        }
301        (Language::Arabic, CheckRule::MaxStringLength) => {
302            format!(
303                "يجب أن يكون طول {location} مساويًا أو أصغر من {system}، لكن الطول هو {input_len}"
304            )
305        }
306
307        (Language::Thai, CheckRule::Required) => format!("{location} เป็นสิ่งจำเป็น"),
308        (Language::Thai, CheckRule::Min) => {
309            format!("{location} ควรจะเท่ากับหรือมากกว่า {system} แต่ข้อมูลที่ป้อนคือ {input}")
310        }
311        (Language::Thai, CheckRule::Max) => {
312            format!("{location} ควรจะเท่ากับหรือน้อยกว่า {system} แต่ข้อมูลที่ป้อนคือ {input}")
313        }
314        (Language::Thai, CheckRule::MinStringLength) => {
315            format!("ความยาวของ {location} ควรจะเท่ากับหรือมากกว่า {system} แต่ความยาวคือ {input_len}")
316        }
317        (Language::Thai, CheckRule::MaxStringLength) => {
318            format!("ความยาวของ {location} ควรจะเท่ากับหรือน้อยกว่า {system} แต่ความยาวคือ {input_len}")
319        }
320
321        (Language::Indonesian, CheckRule::Required) => format!("{location} wajib diisi"),
322        (Language::Indonesian, CheckRule::Min) => {
323            format!(
324                "{location} harus sama dengan atau lebih besar dari {system}, tetapi input adalah {input}"
325            )
326        }
327        (Language::Indonesian, CheckRule::Max) => {
328            format!(
329                "{location} harus sama dengan atau lebih kecil dari {system}, tetapi input adalah {input}"
330            )
331        }
332        (Language::Indonesian, CheckRule::MinStringLength) => {
333            format!(
334                "Panjang {location} harus sama dengan atau lebih besar dari {system}, tetapi panjangnya {input_len}"
335            )
336        }
337        (Language::Indonesian, CheckRule::MaxStringLength) => {
338            format!(
339                "Panjang {location} harus sama dengan atau lebih kecil dari {system}, tetapi panjangnya {input_len}"
340            )
341        }
342
343        (Language::Filipino, CheckRule::Required) => format!("Ang {location} ay kinakailangan"),
344        (Language::Filipino, CheckRule::Min) => {
345            format!(
346                "Ang {location} ay dapat katumbas o mas malaki kaysa {system}, ngunit ang input ay {input}"
347            )
348        }
349        (Language::Filipino, CheckRule::Max) => {
350            format!(
351                "Ang {location} ay dapat katumbas o mas maliit kaysa {system}, ngunit ang input ay {input}"
352            )
353        }
354        (Language::Filipino, CheckRule::MinStringLength) => {
355            format!(
356                "Ang haba ng {location} ay dapat katumbas o mas malaki kaysa {system}, ngunit ang haba ay {input_len}"
357            )
358        }
359        (Language::Filipino, CheckRule::MaxStringLength) => {
360            format!(
361                "Ang haba ng {location} ay dapat katumbas o mas maliit kaysa {system}, ngunit ang haba ay {input_len}"
362            )
363        }
364
365        (Language::Ukrainian, CheckRule::Required) => format!("{location} є обов'язковим"),
366        (Language::Ukrainian, CheckRule::Min) => {
367            format!(
368                "{location} повинен бути рівним або більшим за {system}, але ввідне значення {input}"
369            )
370        }
371        (Language::Ukrainian, CheckRule::Max) => {
372            format!(
373                "{location} повинен бути рівним або меншим за {system}, але ввідне значення {input}"
374            )
375        }
376        (Language::Ukrainian, CheckRule::MinStringLength) => {
377            format!(
378                "Довжина {location} повинна бути рівною або більшою за {system}, але довжина становить {input_len}"
379            )
380        }
381        (Language::Ukrainian, CheckRule::MaxStringLength) => {
382            format!(
383                "Довжина {location} повинна бути рівною або меншою за {system}, але довжина становить {input_len}"
384            )
385        }
386
387        (Language::Vietnamese, CheckRule::Required) => format!("{location} là bắt buộc"),
388        (Language::Vietnamese, CheckRule::Min) => {
389            format!("{location} phải lớn hơn hoặc bằng {system}, nhưng giá trị nhập là {input}")
390        }
391        (Language::Vietnamese, CheckRule::Max) => {
392            format!("{location} phải nhỏ hơn hoặc bằng {system}, nhưng giá trị nhập là {input}")
393        }
394        (Language::Vietnamese, CheckRule::MinStringLength) => {
395            format!(
396                "Độ dài của {location} phải lớn hơn hoặc bằng {system}, nhưng độ dài là {input_len}"
397            )
398        }
399        (Language::Vietnamese, CheckRule::MaxStringLength) => {
400            format!(
401                "Độ dài của {location} phải nhỏ hơn hoặc bằng {system}, nhưng độ dài là {input_len}"
402            )
403        }
404    }
405}
406
407pub fn translate_location(_language: Language, location: &ObjectLocation) -> String {
408    title_case_path(&location.to_string())
409}
410
411fn title_case_path(path: &str) -> String {
412    path.split('.')
413        .map(|part| {
414            if let Some((name, index)) = part.split_once('[') {
415                format!("{}[{}", title_case_identifier(name), index)
416            } else {
417                title_case_identifier(part)
418            }
419        })
420        .collect::<Vec<_>>()
421        .join(".")
422}
423
424fn title_case_identifier(value: &str) -> String {
425    let mut output = String::new();
426    for (index, ch) in value.chars().enumerate() {
427        if index > 0 && ch.is_uppercase() {
428            output.push(' ');
429        }
430        if index == 0 {
431            output.extend(ch.to_uppercase());
432        } else {
433            output.extend(ch.to_lowercase());
434        }
435    }
436    output
437}
438
439fn format_value(value: &Value) -> String {
440    match value {
441        Value::Null => "null".to_owned(),
442        Value::Bool(value) => value.to_string(),
443        Value::I64(value) => value.to_string(),
444        Value::U64(value) => value.to_string(),
445        Value::F64(value) => value.to_string(),
446        Value::Decimal(value) => value.to_string(),
447        Value::Text(value) => value.clone(),
448        Value::Json(value) => value.to_string(),
449        Value::Date(value) => value.to_string(),
450        Value::Timestamp(value) => value.to_rfc3339(),
451        Value::Object(_) => "<object>".to_owned(),
452        Value::List(_) => "<list>".to_owned(),
453    }
454}