1#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
28pub enum Language {
29 #[default]
31 English,
32 French,
34 Spanish,
36 German,
38 Italian,
40 PortugueseBR,
42 PortuguesePT,
44 ChineseSimplified,
46}
47
48impl Language {
49 #[must_use]
65 pub const fn to_locale(self) -> &'static str {
66 match self {
67 Self::English => "en",
68 Self::French => "fr",
69 Self::Spanish => "es",
70 Self::German => "de",
71 Self::Italian => "it",
72 Self::PortugueseBR => "pt-BR",
73 Self::PortuguesePT => "pt-PT",
74 Self::ChineseSimplified => "zh-CN",
75 }
76 }
77}
78
79#[derive(Debug, Clone, Copy, Default)]
93pub struct Translations {
94 language: Language,
95}
96
97impl Translations {
98 #[must_use]
111 pub fn new(language: Language) -> Self {
112 rust_i18n::set_locale(language.to_locale());
113 Self { language }
114 }
115
116 #[must_use]
127 pub const fn language(&self) -> Language {
128 self.language
129 }
130
131 pub fn set_language(&mut self, language: Language) {
145 self.language = language;
146 rust_i18n::set_locale(language.to_locale());
147 }
148
149 #[must_use]
163 pub fn search_placeholder(&self) -> String {
164 rust_i18n::t!("search.placeholder", locale = self.language.to_locale())
165 .into_owned()
166 }
167
168 #[must_use]
179 pub fn replace_placeholder(&self) -> String {
180 rust_i18n::t!("replace.placeholder", locale = self.language.to_locale())
181 .into_owned()
182 }
183
184 #[must_use]
195 pub fn case_sensitive_label(&self) -> String {
196 rust_i18n::t!(
197 "settings.case_sensitive_label",
198 locale = self.language.to_locale()
199 )
200 .into_owned()
201 }
202
203 #[must_use]
214 pub fn previous_match_tooltip(&self) -> String {
215 rust_i18n::t!(
216 "search.previous_match_tooltip",
217 locale = self.language.to_locale()
218 )
219 .into_owned()
220 }
221
222 #[must_use]
233 pub fn next_match_tooltip(&self) -> String {
234 rust_i18n::t!(
235 "search.next_match_tooltip",
236 locale = self.language.to_locale()
237 )
238 .into_owned()
239 }
240
241 #[must_use]
252 pub fn close_search_tooltip(&self) -> String {
253 rust_i18n::t!(
254 "search.close_tooltip",
255 locale = self.language.to_locale()
256 )
257 .into_owned()
258 }
259
260 #[must_use]
271 pub fn replace_current_tooltip(&self) -> String {
272 rust_i18n::t!(
273 "replace.current_tooltip",
274 locale = self.language.to_locale()
275 )
276 .into_owned()
277 }
278
279 #[must_use]
290 pub fn replace_all_tooltip(&self) -> String {
291 rust_i18n::t!("replace.all_tooltip", locale = self.language.to_locale())
292 .into_owned()
293 }
294
295 #[must_use]
297 pub fn context_menu_undo(&self) -> String {
298 rust_i18n::t!("context_menu.undo", locale = self.language.to_locale())
299 .into_owned()
300 }
301
302 #[must_use]
304 pub fn context_menu_redo(&self) -> String {
305 rust_i18n::t!("context_menu.redo", locale = self.language.to_locale())
306 .into_owned()
307 }
308
309 #[must_use]
311 pub fn context_menu_cut(&self) -> String {
312 rust_i18n::t!("context_menu.cut", locale = self.language.to_locale())
313 .into_owned()
314 }
315
316 #[must_use]
318 pub fn context_menu_copy(&self) -> String {
319 rust_i18n::t!("context_menu.copy", locale = self.language.to_locale())
320 .into_owned()
321 }
322
323 #[must_use]
325 pub fn context_menu_paste(&self) -> String {
326 rust_i18n::t!("context_menu.paste", locale = self.language.to_locale())
327 .into_owned()
328 }
329
330 #[must_use]
332 pub fn context_menu_select_all(&self) -> String {
333 rust_i18n::t!(
334 "context_menu.select_all",
335 locale = self.language.to_locale()
336 )
337 .into_owned()
338 }
339
340 #[must_use]
342 pub fn context_menu_reveal_in_finder(&self) -> String {
343 rust_i18n::t!(
344 "context_menu.reveal_in_finder",
345 locale = self.language.to_locale()
346 )
347 .into_owned()
348 }
349
350 #[must_use]
352 pub fn context_menu_reveal_in_file_explorer(&self) -> String {
353 rust_i18n::t!(
354 "context_menu.reveal_in_file_explorer",
355 locale = self.language.to_locale()
356 )
357 .into_owned()
358 }
359
360 #[must_use]
362 pub fn context_menu_open_containing_folder(&self) -> String {
363 rust_i18n::t!(
364 "context_menu.open_containing_folder",
365 locale = self.language.to_locale()
366 )
367 .into_owned()
368 }
369
370 #[must_use]
372 pub fn context_menu_reveal_in_file_manager(&self) -> String {
373 if cfg!(target_os = "macos") {
374 self.context_menu_reveal_in_finder()
375 } else if cfg!(target_os = "windows") {
376 self.context_menu_reveal_in_file_explorer()
377 } else {
378 self.context_menu_open_containing_folder()
379 }
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use super::*;
386
387 #[test]
388 fn test_default_language() {
389 let translations = Translations::default();
390 assert_eq!(translations.language(), Language::English);
391 }
392
393 #[test]
394 fn test_new_with_language() {
395 let translations = Translations::new(Language::French);
396 assert_eq!(translations.language(), Language::French);
397 }
398
399 #[test]
400 fn test_set_language() {
401 let mut translations = Translations::new(Language::English);
402 translations.set_language(Language::Spanish);
403 assert_eq!(translations.language(), Language::Spanish);
404 }
405
406 #[test]
407 fn test_english_translations() {
408 let t = Translations::new(Language::English);
409 assert_eq!(t.search_placeholder(), "Search...");
410 assert_eq!(t.replace_placeholder(), "Replace...");
411 assert_eq!(t.case_sensitive_label(), "Case sensitive");
412 assert_eq!(t.previous_match_tooltip(), "Previous match (Shift+F3)");
413 assert_eq!(t.next_match_tooltip(), "Next match (F3 / Enter)");
414 assert_eq!(t.close_search_tooltip(), "Close search dialog (Esc)");
415 assert_eq!(t.replace_current_tooltip(), "Replace current match");
416 assert_eq!(t.replace_all_tooltip(), "Replace all matches");
417 }
418
419 #[test]
420 fn test_french_translations() {
421 let t = Translations::new(Language::French);
422 assert_eq!(t.search_placeholder(), "Rechercher...");
423 assert_eq!(t.replace_placeholder(), "Remplacer...");
424 assert_eq!(t.case_sensitive_label(), "Sensible à la casse");
425 assert_eq!(t.previous_match_tooltip(), "Résultat précédent (Maj+F3)");
426 assert_eq!(t.next_match_tooltip(), "Résultat suivant (F3 / Entrée)");
427 assert_eq!(t.close_search_tooltip(), "Fermer la recherche (Échap)");
428 assert_eq!(
429 t.replace_current_tooltip(),
430 "Remplacer l'occurrence actuelle"
431 );
432 assert_eq!(t.replace_all_tooltip(), "Tout remplacer");
433 }
434
435 #[test]
436 fn test_spanish_translations() {
437 let t = Translations::new(Language::Spanish);
438 assert_eq!(t.search_placeholder(), "Buscar...");
439 assert_eq!(t.replace_placeholder(), "Reemplazar...");
440 assert_eq!(t.case_sensitive_label(), "Distinguir mayúsculas");
441 assert_eq!(
442 t.previous_match_tooltip(),
443 "Coincidencia anterior (Mayús+F3)"
444 );
445 assert_eq!(
446 t.next_match_tooltip(),
447 "Siguiente coincidencia (F3 / Enter)"
448 );
449 assert_eq!(t.close_search_tooltip(), "Cerrar búsqueda (Esc)");
450 assert_eq!(
451 t.replace_current_tooltip(),
452 "Reemplazar coincidencia actual"
453 );
454 assert_eq!(t.replace_all_tooltip(), "Reemplazar todo");
455 }
456
457 #[test]
458 fn test_german_translations() {
459 let t = Translations::new(Language::German);
460 assert_eq!(t.search_placeholder(), "Suchen...");
461 assert_eq!(t.replace_placeholder(), "Ersetzen...");
462 assert_eq!(t.case_sensitive_label(), "Groß-/Kleinschreibung");
463 assert_eq!(
464 t.previous_match_tooltip(),
465 "Vorheriger Treffer (Umschalt+F3)"
466 );
467 assert_eq!(t.next_match_tooltip(), "Nächster Treffer (F3 / Enter)");
468 assert_eq!(t.close_search_tooltip(), "Suchdialog schließen (Esc)");
469 assert_eq!(t.replace_current_tooltip(), "Aktuellen Treffer ersetzen");
470 assert_eq!(t.replace_all_tooltip(), "Alle ersetzen");
471 }
472
473 #[test]
474 fn test_italian_translations() {
475 let t = Translations::new(Language::Italian);
476 assert_eq!(t.search_placeholder(), "Cerca...");
477 assert_eq!(t.replace_placeholder(), "Sostituisci...");
478 assert_eq!(t.case_sensitive_label(), "Distingui maiuscole");
479 assert_eq!(
480 t.previous_match_tooltip(),
481 "Risultato precedente (Maiusc+F3)"
482 );
483 assert_eq!(t.next_match_tooltip(), "Risultato successivo (F3 / Invio)");
484 assert_eq!(
485 t.close_search_tooltip(),
486 "Chiudi finestra di ricerca (Esc)"
487 );
488 assert_eq!(
489 t.replace_current_tooltip(),
490 "Sostituisci risultato corrente"
491 );
492 assert_eq!(t.replace_all_tooltip(), "Sostituisci tutto");
493 }
494
495 #[test]
496 fn test_portuguese_br_translations() {
497 let t = Translations::new(Language::PortugueseBR);
498 assert_eq!(t.search_placeholder(), "Pesquisar...");
499 assert_eq!(t.replace_placeholder(), "Substituir...");
500 assert_eq!(t.case_sensitive_label(), "Diferenciar maiúsculas");
501 assert_eq!(
502 t.previous_match_tooltip(),
503 "Correspondência anterior (Shift+F3)"
504 );
505 assert_eq!(
506 t.next_match_tooltip(),
507 "Próxima correspondência (F3 / Enter)"
508 );
509 assert_eq!(
510 t.close_search_tooltip(),
511 "Fechar diálogo de pesquisa (Esc)"
512 );
513 assert_eq!(
514 t.replace_current_tooltip(),
515 "Substituir correspondência atual"
516 );
517 assert_eq!(t.replace_all_tooltip(), "Substituir tudo");
518 }
519
520 #[test]
521 fn test_portuguese_pt_translations() {
522 let t = Translations::new(Language::PortuguesePT);
523 assert_eq!(t.search_placeholder(), "Pesquisar...");
524 assert_eq!(t.replace_placeholder(), "Substituir...");
525 assert_eq!(t.case_sensitive_label(), "Diferenciar maiúsculas");
526 assert_eq!(
527 t.previous_match_tooltip(),
528 "Correspondência anterior (Shift+F3)"
529 );
530 assert_eq!(
531 t.next_match_tooltip(),
532 "Próxima correspondência (F3 / Enter)"
533 );
534 assert_eq!(
535 t.close_search_tooltip(),
536 "Fechar diálogo de pesquisa (Esc)"
537 );
538 assert_eq!(
539 t.replace_current_tooltip(),
540 "Substituir correspondência actual"
541 );
542 assert_eq!(t.replace_all_tooltip(), "Substituir tudo");
543 }
544
545 #[test]
546 fn test_language_switching() {
547 let mut t = Translations::new(Language::English);
548 assert_eq!(t.search_placeholder(), "Search...");
549
550 t.set_language(Language::French);
551 assert_eq!(t.search_placeholder(), "Rechercher...");
552
553 t.set_language(Language::Spanish);
554 assert_eq!(t.search_placeholder(), "Buscar...");
555
556 t.set_language(Language::German);
557 assert_eq!(t.search_placeholder(), "Suchen...");
558
559 t.set_language(Language::Italian);
560 assert_eq!(t.search_placeholder(), "Cerca...");
561
562 t.set_language(Language::PortugueseBR);
563 assert_eq!(t.search_placeholder(), "Pesquisar...");
564
565 t.set_language(Language::PortuguesePT);
566 assert_eq!(t.search_placeholder(), "Pesquisar...");
567
568 t.set_language(Language::ChineseSimplified);
569 assert_eq!(t.search_placeholder(), "搜索...");
570 }
571
572 #[test]
573 fn test_context_menu_translations_cover_all_locales() {
574 let cases = [
575 (
576 Language::English,
577 [
578 "Undo",
579 "Redo",
580 "Cut",
581 "Copy",
582 "Paste",
583 "Select All",
584 "Reveal in Finder",
585 "Reveal in File Explorer",
586 "Open Containing Folder",
587 ],
588 ),
589 (
590 Language::French,
591 [
592 "Annuler",
593 "Rétablir",
594 "Couper",
595 "Copier",
596 "Coller",
597 "Tout sélectionner",
598 "Révéler dans le Finder",
599 "Afficher dans l'Explorateur de fichiers",
600 "Ouvrir le dossier contenant",
601 ],
602 ),
603 (
604 Language::Spanish,
605 [
606 "Deshacer",
607 "Rehacer",
608 "Cortar",
609 "Copiar",
610 "Pegar",
611 "Seleccionar todo",
612 "Mostrar en Finder",
613 "Mostrar en el Explorador de archivos",
614 "Abrir carpeta contenedora",
615 ],
616 ),
617 (
618 Language::German,
619 [
620 "Rückgängig",
621 "Wiederholen",
622 "Ausschneiden",
623 "Kopieren",
624 "Einfügen",
625 "Alle auswählen",
626 "Im Finder anzeigen",
627 "Im Datei-Explorer anzeigen",
628 "Übergeordneten Ordner öffnen",
629 ],
630 ),
631 (
632 Language::Italian,
633 [
634 "Annulla azione",
635 "Ripeti",
636 "Taglia",
637 "Copia",
638 "Incolla",
639 "Seleziona tutto",
640 "Visualizza in Finder",
641 "Visualizza in Esplora file",
642 "Apri cartella superiore",
643 ],
644 ),
645 (
646 Language::PortugueseBR,
647 [
648 "Desfazer",
649 "Refazer",
650 "Recortar",
651 "Copiar",
652 "Colar",
653 "Selecionar Tudo",
654 "Revelar no Finder",
655 "Revelar no Explorador de Arquivos",
656 "Abrir a Pasta Que Contém",
657 ],
658 ),
659 (
660 Language::PortuguesePT,
661 [
662 "Anular",
663 "Refazer",
664 "Cortar",
665 "Copiar",
666 "Colar",
667 "Selecionar tudo",
668 "Mostrar no Finder",
669 "Mostrar no Explorador de Ficheiros",
670 "Abrir pasta contentora",
671 ],
672 ),
673 (
674 Language::ChineseSimplified,
675 [
676 "撤消",
677 "恢复",
678 "剪切",
679 "复制",
680 "粘贴",
681 "选择全部",
682 "在访达中显示",
683 "在文件资源管理器中显示",
684 "打开所在的文件夹",
685 ],
686 ),
687 ];
688
689 for (language, expected) in cases {
690 let translations = Translations::new(language);
691 assert_eq!(translations.context_menu_undo(), expected[0]);
692 assert_eq!(translations.context_menu_redo(), expected[1]);
693 assert_eq!(translations.context_menu_cut(), expected[2]);
694 assert_eq!(translations.context_menu_copy(), expected[3]);
695 assert_eq!(translations.context_menu_paste(), expected[4]);
696 assert_eq!(translations.context_menu_select_all(), expected[5]);
697 assert_eq!(
698 translations.context_menu_reveal_in_finder(),
699 expected[6]
700 );
701 assert_eq!(
702 translations.context_menu_reveal_in_file_explorer(),
703 expected[7]
704 );
705 assert_eq!(
706 translations.context_menu_open_containing_folder(),
707 expected[8]
708 );
709 }
710 }
711}