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}