1use std::sync::OnceLock;
14
15#[derive(Copy, Clone, Debug, PartialEq, Eq, clap::ValueEnum)]
16pub enum Language {
17 #[value(name = "en", aliases = ["english", "EN"])]
18 English,
19 #[value(name = "pt", aliases = ["portugues", "portuguese", "pt-BR", "pt-br", "PT"])]
20 Portuguese,
21}
22
23impl Language {
24 pub fn from_str_opt(s: &str) -> Option<Self> {
27 match s.to_lowercase().as_str() {
28 "en" | "english" => Some(Language::English),
29 "pt" | "pt-br" | "portugues" | "portuguese" => Some(Language::Portuguese),
30 _ => None,
31 }
32 }
33
34 pub fn from_env_or_locale() -> Self {
35 if let Ok(v) = std::env::var("SQLITE_GRAPHRAG_LANG") {
39 if !v.is_empty() {
40 let lower = v.to_lowercase();
41 if lower.starts_with("pt") {
42 return Language::Portuguese;
43 }
44 if lower.starts_with("en") {
45 return Language::English;
46 }
47 tracing::warn!(
49 value = %v,
50 "SQLITE_GRAPHRAG_LANG value not recognized, falling back to locale detection"
51 );
52 }
53 }
54 for var in &["LC_ALL", "LC_MESSAGES", "LANG"] {
61 if let Ok(v) = std::env::var(var) {
62 if v.is_empty() {
63 continue;
66 }
67 let lower = v.to_lowercase();
68 if lower.starts_with("pt") {
69 return Language::Portuguese;
70 }
71 if lower.starts_with("en") {
72 return Language::English;
73 }
74 break;
78 }
79 }
80 Language::English
81 }
82}
83
84static GLOBAL_LANGUAGE: OnceLock<Language> = OnceLock::new();
85
86pub fn init(explicit: Option<Language>) {
95 if GLOBAL_LANGUAGE.get().is_some() {
96 return;
97 }
98 let resolved = explicit.unwrap_or_else(Language::from_env_or_locale);
99 let _ = GLOBAL_LANGUAGE.set(resolved);
100}
101
102pub fn current() -> Language {
104 *GLOBAL_LANGUAGE.get_or_init(Language::from_env_or_locale)
105}
106
107pub fn tr(en: &'static str, pt: &'static str) -> &'static str {
115 match current() {
116 Language::English => en,
117 Language::Portuguese => pt,
118 }
119}
120
121pub fn error_prefix() -> &'static str {
123 match current() {
124 Language::English => "Error",
125 Language::Portuguese => "Erro",
126 }
127}
128
129pub mod errors_msg {
131 use super::current;
132 use crate::i18n::Language;
133
134 pub fn memory_not_found(nome: &str, namespace: &str) -> String {
135 match current() {
136 Language::English => {
137 format!("memory '{nome}' not found in namespace '{namespace}'")
138 }
139 Language::Portuguese => {
140 format!("memória '{nome}' não encontrada no namespace '{namespace}'")
141 }
142 }
143 }
144
145 pub fn memory_or_entity_not_found(name: &str, namespace: &str) -> String {
146 match current() {
147 Language::English => {
148 format!("memory or entity '{name}' not found in namespace '{namespace}'")
149 }
150 Language::Portuguese => {
151 format!("memória ou entidade '{name}' não encontrada no namespace '{namespace}'")
152 }
153 }
154 }
155
156 pub fn database_not_found(path: &str) -> String {
157 match current() {
158 Language::English => {
159 format!("database not found at {path}. Run 'sqlite-graphrag init' first.")
160 }
161 Language::Portuguese => format!(
162 "banco de dados não encontrado em {path}. Execute 'sqlite-graphrag init' primeiro."
163 ),
164 }
165 }
166
167 pub fn entity_not_found(nome: &str, namespace: &str) -> String {
168 match current() {
169 Language::English => {
170 format!("entity \"{nome}\" does not exist in namespace \"{namespace}\"")
171 }
172 Language::Portuguese => {
173 format!("entidade \"{nome}\" não existe no namespace \"{namespace}\"")
174 }
175 }
176 }
177
178 pub fn relationship_not_found(de: &str, rel: &str, para: &str, namespace: &str) -> String {
179 match current() {
180 Language::English => format!(
181 "relationship \"{de}\" --[{rel}]--> \"{para}\" does not exist in namespace \"{namespace}\""
182 ),
183 Language::Portuguese => format!(
184 "relacionamento \"{de}\" --[{rel}]--> \"{para}\" não existe no namespace \"{namespace}\""
185 ),
186 }
187 }
188
189 pub fn duplicate_memory(nome: &str, namespace: &str) -> String {
190 match current() {
191 Language::English => format!(
192 "memory '{nome}' already exists in namespace '{namespace}'. Use --force-merge to update."
193 ),
194 Language::Portuguese => format!(
195 "memória '{nome}' já existe no namespace '{namespace}'. Use --force-merge para atualizar."
196 ),
197 }
198 }
199
200 pub fn optimistic_lock_conflict(expected: i64, current_ts: i64) -> String {
201 match current() {
202 Language::English => format!(
203 "optimistic lock conflict: expected updated_at={expected}, but current is {current_ts}"
204 ),
205 Language::Portuguese => format!(
206 "conflito de optimistic lock: esperava updated_at={expected}, mas atual é {current_ts}"
207 ),
208 }
209 }
210
211 pub fn version_not_found(versao: i64, nome: &str) -> String {
212 match current() {
213 Language::English => format!("version {versao} not found for memory '{nome}'"),
214 Language::Portuguese => {
215 format!("versão {versao} não encontrada para a memória '{nome}'")
216 }
217 }
218 }
219
220 pub fn no_recall_results(max_distance: f32, query: &str, namespace: &str) -> String {
221 match current() {
222 Language::English => format!(
223 "no results within --max-distance {max_distance} for query '{query}' in namespace '{namespace}'"
224 ),
225 Language::Portuguese => format!(
226 "nenhum resultado dentro de --max-distance {max_distance} para a consulta '{query}' no namespace '{namespace}'"
227 ),
228 }
229 }
230
231 pub fn soft_deleted_memory_not_found(nome: &str, namespace: &str) -> String {
232 match current() {
233 Language::English => {
234 format!("soft-deleted memory '{nome}' not found in namespace '{namespace}'")
235 }
236 Language::Portuguese => {
237 format!("memória soft-deleted '{nome}' não encontrada no namespace '{namespace}'")
238 }
239 }
240 }
241
242 pub fn concurrent_process_conflict() -> String {
243 match current() {
244 Language::English => {
245 "optimistic lock conflict: memory was modified by another process".to_string()
246 }
247 Language::Portuguese => {
248 "conflito de optimistic lock: memória foi modificada por outro processo".to_string()
249 }
250 }
251 }
252
253 pub fn entity_limit_exceeded(max: usize) -> String {
254 match current() {
255 Language::English => format!("entities exceed limit of {max}"),
256 Language::Portuguese => format!("entidades excedem o limite de {max}"),
257 }
258 }
259
260 pub fn relationship_limit_exceeded(max: usize) -> String {
261 match current() {
262 Language::English => format!("relationships exceed limit of {max}"),
263 Language::Portuguese => format!("relacionamentos excedem o limite de {max}"),
264 }
265 }
266}
267
268pub mod validation {
270 use super::current;
271 use crate::i18n::Language;
272
273 pub fn name_length(max: usize) -> String {
274 match current() {
275 Language::English => format!("name must be 1-{max} chars"),
276 Language::Portuguese => format!("nome deve ter entre 1 e {max} caracteres"),
277 }
278 }
279
280 pub fn reserved_name() -> String {
281 match current() {
282 Language::English => {
283 "names and namespaces starting with __ are reserved for internal use".to_string()
284 }
285 Language::Portuguese => {
286 "nomes e namespaces iniciados com __ são reservados para uso interno".to_string()
287 }
288 }
289 }
290
291 pub fn name_kebab(nome: &str) -> String {
292 match current() {
293 Language::English => format!(
294 "name must be kebab-case slug (lowercase letters, digits, hyphens): '{nome}'"
295 ),
296 Language::Portuguese => {
297 format!("nome deve estar em kebab-case (minúsculas, dígitos, hífens): '{nome}'")
298 }
299 }
300 }
301
302 pub fn description_exceeds(max: usize) -> String {
303 match current() {
304 Language::English => format!("description must be <= {max} chars"),
305 Language::Portuguese => format!("descrição deve ter no máximo {max} caracteres"),
306 }
307 }
308
309 pub fn body_exceeds(max: usize) -> String {
310 match current() {
311 Language::English => format!("body exceeds {max} bytes"),
312 Language::Portuguese => format!("corpo excede {max} bytes"),
313 }
314 }
315
316 pub fn new_name_length(max: usize) -> String {
317 match current() {
318 Language::English => format!("new-name must be 1-{max} chars"),
319 Language::Portuguese => format!("novo nome deve ter entre 1 e {max} caracteres"),
320 }
321 }
322
323 pub fn new_name_kebab(nome: &str) -> String {
324 match current() {
325 Language::English => format!(
326 "new-name must be kebab-case slug (lowercase letters, digits, hyphens): '{nome}'"
327 ),
328 Language::Portuguese => format!(
329 "novo nome deve estar em kebab-case (minúsculas, dígitos, hífens): '{nome}'"
330 ),
331 }
332 }
333
334 pub fn namespace_length() -> String {
335 match current() {
336 Language::English => "namespace must be 1-80 chars".to_string(),
337 Language::Portuguese => "namespace deve ter entre 1 e 80 caracteres".to_string(),
338 }
339 }
340
341 pub fn namespace_format() -> String {
342 match current() {
343 Language::English => "namespace must be alphanumeric + hyphens/underscores".to_string(),
344 Language::Portuguese => {
345 "namespace deve ser alfanumérico com hífens/sublinhados".to_string()
346 }
347 }
348 }
349
350 pub fn path_traversal(p: &str) -> String {
351 match current() {
352 Language::English => format!("path traversal rejected: {p}"),
353 Language::Portuguese => format!("traversal de caminho rejeitado: {p}"),
354 }
355 }
356
357 pub fn invalid_tz(v: &str) -> String {
358 match current() {
359 Language::English => format!(
360 "SQLITE_GRAPHRAG_DISPLAY_TZ invalid: '{v}'; use an IANA name like 'America/Sao_Paulo'"
361 ),
362 Language::Portuguese => format!(
363 "SQLITE_GRAPHRAG_DISPLAY_TZ inválido: '{v}'; use um nome IANA como 'America/Sao_Paulo'"
364 ),
365 }
366 }
367
368 pub fn empty_query() -> String {
369 match current() {
370 Language::English => "query cannot be empty".to_string(),
371 Language::Portuguese => "a consulta não pode estar vazia".to_string(),
372 }
373 }
374
375 pub fn empty_body() -> String {
376 match current() {
377 Language::English => "body cannot be empty: provide --body, --body-file, or --body-stdin with content, or supply a graph via --entities-file/--graph-stdin".to_string(),
378 Language::Portuguese => "o corpo não pode estar vazio: forneça --body, --body-file ou --body-stdin com conteúdo, ou um grafo via --entities-file/--graph-stdin".to_string(),
379 }
380 }
381
382 pub fn invalid_namespace_config(path: &str, err: &str) -> String {
383 match current() {
384 Language::English => {
385 format!("invalid project namespace config '{path}': {err}")
386 }
387 Language::Portuguese => {
388 format!("configuração de namespace de projeto inválida '{path}': {err}")
389 }
390 }
391 }
392
393 pub fn invalid_projects_mapping(path: &str, err: &str) -> String {
394 match current() {
395 Language::English => format!("invalid projects mapping '{path}': {err}"),
396 Language::Portuguese => format!("mapeamento de projetos inválido '{path}': {err}"),
397 }
398 }
399
400 pub fn self_referential_link() -> String {
401 match current() {
402 Language::English => "--from and --to must be different entities — self-referential relationships are not supported".to_string(),
403 Language::Portuguese => "--from e --to devem ser entidades diferentes — relacionamentos auto-referenciais não são suportados".to_string(),
404 }
405 }
406
407 pub fn invalid_link_weight(weight: f64) -> String {
408 match current() {
409 Language::English => {
410 format!("--weight: must be between 0.0 and 1.0 (actual: {weight})")
411 }
412 Language::Portuguese => {
413 format!("--weight: deve estar entre 0.0 e 1.0 (atual: {weight})")
414 }
415 }
416 }
417
418 pub fn sync_destination_equals_source() -> String {
419 match current() {
420 Language::English => {
421 "destination path must differ from the source database path".to_string()
422 }
423 Language::Portuguese => {
424 "caminho de destino deve ser diferente do caminho do banco de dados fonte"
425 .to_string()
426 }
427 }
428 }
429
430 pub mod app_error_pt {
436 pub fn validation(msg: &str) -> String {
437 format!("erro de validação: {msg}")
438 }
439
440 pub fn duplicate(msg: &str) -> String {
441 format!("duplicata detectada: {msg}")
442 }
443
444 pub fn conflict(msg: &str) -> String {
445 format!("conflito: {msg}")
446 }
447
448 pub fn not_found(msg: &str) -> String {
449 format!("não encontrado: {msg}")
450 }
451
452 pub fn namespace_error(msg: &str) -> String {
453 format!("namespace não resolvido: {msg}")
454 }
455
456 pub fn limit_exceeded(msg: &str) -> String {
457 format!("limite excedido: {msg}")
458 }
459
460 pub fn database(err: &str) -> String {
461 format!("erro de banco de dados: {err}")
462 }
463
464 pub fn embedding(msg: &str) -> String {
465 format!("erro de embedding: {msg}")
466 }
467
468 pub fn vec_extension(msg: &str) -> String {
469 format!("extensão sqlite-vec falhou: {msg}")
470 }
471
472 pub fn db_busy(msg: &str) -> String {
473 format!("banco ocupado: {msg}")
474 }
475
476 pub fn batch_partial_failure(total: usize, failed: usize) -> String {
477 format!("falha parcial em batch: {failed} de {total} itens falharam")
478 }
479
480 pub fn io(err: &str) -> String {
481 format!("erro de I/O: {err}")
482 }
483
484 pub fn internal(err: &str) -> String {
485 format!("erro interno: {err}")
486 }
487
488 pub fn json(err: &str) -> String {
489 format!("erro de JSON: {err}")
490 }
491
492 pub fn lock_busy(msg: &str) -> String {
493 format!("lock ocupado: {msg}")
494 }
495
496 pub fn all_slots_full(max: usize, waited_secs: u64) -> String {
497 format!(
498 "todos os {max} slots de concorrência ocupados após aguardar {waited_secs}s \
499 (exit 75); use --max-concurrency ou aguarde outras invocações terminarem"
500 )
501 }
502
503 pub fn low_memory(available_mb: u64, required_mb: u64) -> String {
504 format!(
505 "memória disponível ({available_mb}MB) abaixo do mínimo requerido ({required_mb}MB) \
506 para carregar o modelo; aborte outras cargas ou use --skip-memory-guard (exit 77)"
507 )
508 }
509 }
510
511 pub mod runtime_pt {
517 pub fn embedding_heavy_must_measure_ram() -> String {
518 "comando intensivo em embedding precisa medir RAM disponível".to_string()
519 }
520
521 pub fn heavy_command_detected(available_mb: u64, safe_concurrency: usize) -> String {
522 format!(
523 "Comando pesado detectado; memória disponível: {available_mb} MB; \
524 concorrência segura: {safe_concurrency}"
525 )
526 }
527
528 pub fn reducing_concurrency(
529 requested_concurrency: usize,
530 effective_concurrency: usize,
531 ) -> String {
532 format!(
533 "Reduzindo a concorrência solicitada de {requested_concurrency} para \
534 {effective_concurrency} para evitar oversubscription de memória"
535 )
536 }
537
538 pub fn downloading_ner_model() -> &'static str {
539 "Baixando modelo NER (primeira execução, ~676 MB)..."
540 }
541
542 pub fn initializing_embedding_model() -> &'static str {
543 "Inicializando modelo de embedding (pode baixar na primeira execução)..."
544 }
545
546 pub fn embedding_chunks_serially(count: usize) -> String {
547 format!("Embedando {count} chunks serialmente para manter memória limitada...")
548 }
549
550 pub fn remember_step_input_validated(available_mb: u64) -> String {
551 format!("Etapa remember: entrada validada; memória disponível {available_mb} MB")
552 }
553
554 pub fn remember_step_chunking_completed(
555 total_passage_tokens: usize,
556 model_max_length: usize,
557 chunks_count: usize,
558 rss_mb: u64,
559 ) -> String {
560 format!(
561 "Etapa remember: tokenizer contou {total_passage_tokens} tokens de passagem \
562 (máximo do modelo {model_max_length}); chunking gerou {chunks_count} chunks; \
563 RSS do processo {rss_mb} MB"
564 )
565 }
566
567 pub fn remember_step_embeddings_completed(rss_mb: u64) -> String {
568 format!("Etapa remember: embeddings dos chunks concluídos; RSS do processo {rss_mb} MB")
569 }
570
571 pub fn restore_recomputing_embedding() -> &'static str {
572 "Recalculando embedding da memória restaurada..."
573 }
574 }
575}
576
577#[cfg(test)]
578mod tests {
579 use super::*;
580 use serial_test::serial;
581
582 #[test]
583 #[serial]
584 fn fallback_english_when_env_absent() {
585 std::env::remove_var("SQLITE_GRAPHRAG_LANG");
586 std::env::set_var("LC_ALL", "C");
587 std::env::set_var("LANG", "C");
588 assert_eq!(Language::from_env_or_locale(), Language::English);
589 std::env::remove_var("LC_ALL");
590 std::env::remove_var("LANG");
591 }
592
593 #[test]
594 #[serial]
595 fn env_pt_selects_portuguese() {
596 std::env::remove_var("LC_ALL");
597 std::env::remove_var("LANG");
598 std::env::set_var("SQLITE_GRAPHRAG_LANG", "pt");
599 assert_eq!(Language::from_env_or_locale(), Language::Portuguese);
600 std::env::remove_var("SQLITE_GRAPHRAG_LANG");
601 }
602
603 #[test]
604 #[serial]
605 fn env_pt_br_selects_portuguese() {
606 std::env::remove_var("LC_ALL");
607 std::env::remove_var("LANG");
608 std::env::set_var("SQLITE_GRAPHRAG_LANG", "pt-BR");
609 assert_eq!(Language::from_env_or_locale(), Language::Portuguese);
610 std::env::remove_var("SQLITE_GRAPHRAG_LANG");
611 }
612
613 #[test]
614 #[serial]
615 fn locale_ptbr_utf8_selects_portuguese() {
616 std::env::remove_var("SQLITE_GRAPHRAG_LANG");
617 std::env::set_var("LC_ALL", "pt_BR.UTF-8");
618 assert_eq!(Language::from_env_or_locale(), Language::Portuguese);
619 std::env::remove_var("LC_ALL");
620 }
621
622 #[test]
623 #[serial]
624 fn posix_precedence_lc_all_overrides_lang() {
625 std::env::remove_var("SQLITE_GRAPHRAG_LANG");
626 std::env::remove_var("LC_MESSAGES");
627 std::env::set_var("LC_ALL", "en_US.UTF-8");
628 std::env::set_var("LANG", "pt_BR.UTF-8");
629 assert_eq!(
630 Language::from_env_or_locale(),
631 Language::English,
632 "LC_ALL=en_US must override LANG=pt_BR per POSIX"
633 );
634 std::env::remove_var("LC_ALL");
635 std::env::remove_var("LANG");
636 }
637
638 #[test]
639 #[serial]
640 fn posix_precedence_lc_all_unrecognized_stops_iteration() {
641 std::env::remove_var("SQLITE_GRAPHRAG_LANG");
642 std::env::remove_var("LC_MESSAGES");
643 std::env::set_var("LC_ALL", "ja_JP.UTF-8");
644 std::env::set_var("LANG", "pt_BR.UTF-8");
645 assert_eq!(
646 Language::from_env_or_locale(),
647 Language::English,
648 "LC_ALL=ja_JP set must stop iteration; falls back to English default"
649 );
650 std::env::remove_var("LC_ALL");
651 std::env::remove_var("LANG");
652 }
653
654 #[test]
655 #[serial]
656 fn lang_pt_selects_portuguese_when_lc_all_unset() {
657 std::env::remove_var("SQLITE_GRAPHRAG_LANG");
658 std::env::remove_var("LC_ALL");
659 std::env::remove_var("LC_MESSAGES");
660 std::env::set_var("LANG", "pt_BR.UTF-8");
661 assert_eq!(Language::from_env_or_locale(), Language::Portuguese);
662 std::env::remove_var("LANG");
663 }
664
665 mod validation_tests {
666 use super::*;
667
668 #[test]
669 fn name_length_en() {
670 let msg = match Language::English {
671 Language::English => format!("name must be 1-{} chars", 80),
672 Language::Portuguese => format!("nome deve ter entre 1 e {} caracteres", 80),
673 };
674 assert!(msg.contains("name must be 1-80 chars"), "obtido: {msg}");
675 }
676
677 #[test]
678 fn name_length_pt() {
679 let msg = match Language::Portuguese {
680 Language::English => format!("name must be 1-{} chars", 80),
681 Language::Portuguese => format!("nome deve ter entre 1 e {} caracteres", 80),
682 };
683 assert!(
684 msg.contains("nome deve ter entre 1 e 80 caracteres"),
685 "obtido: {msg}"
686 );
687 }
688
689 #[test]
690 fn name_kebab_en() {
691 let nome = "Invalid_Name";
692 let msg = match Language::English {
693 Language::English => format!(
694 "name must be kebab-case slug (lowercase letters, digits, hyphens): '{nome}'"
695 ),
696 Language::Portuguese => {
697 format!("nome deve estar em kebab-case (minúsculas, dígitos, hífens): '{nome}'")
698 }
699 };
700 assert!(msg.contains("kebab-case slug"), "obtido: {msg}");
701 assert!(msg.contains("Invalid_Name"), "obtido: {msg}");
702 }
703
704 #[test]
705 fn name_kebab_pt() {
706 let nome = "Invalid_Name";
707 let msg = match Language::Portuguese {
708 Language::English => format!(
709 "name must be kebab-case slug (lowercase letters, digits, hyphens): '{nome}'"
710 ),
711 Language::Portuguese => {
712 format!("nome deve estar em kebab-case (minúsculas, dígitos, hífens): '{nome}'")
713 }
714 };
715 assert!(msg.contains("kebab-case"), "obtido: {msg}");
716 assert!(msg.contains("minúsculas"), "obtido: {msg}");
717 assert!(msg.contains("Invalid_Name"), "obtido: {msg}");
718 }
719
720 #[test]
721 fn description_exceeds_en() {
722 let msg = match Language::English {
723 Language::English => format!("description must be <= {} chars", 500),
724 Language::Portuguese => format!("descrição deve ter no máximo {} caracteres", 500),
725 };
726 assert!(msg.contains("description must be <= 500"), "obtido: {msg}");
727 }
728
729 #[test]
730 fn description_exceeds_pt() {
731 let msg = match Language::Portuguese {
732 Language::English => format!("description must be <= {} chars", 500),
733 Language::Portuguese => format!("descrição deve ter no máximo {} caracteres", 500),
734 };
735 assert!(
736 msg.contains("descrição deve ter no máximo 500"),
737 "obtido: {msg}"
738 );
739 }
740
741 #[test]
742 fn body_exceeds_en() {
743 let limite = crate::constants::MAX_MEMORY_BODY_LEN;
744 let msg = match Language::English {
745 Language::English => format!("body exceeds {limite} bytes"),
746 Language::Portuguese => format!("corpo excede {limite} bytes"),
747 };
748 assert!(msg.contains("body exceeds 512000"), "obtido: {msg}");
749 }
750
751 #[test]
752 fn body_exceeds_pt() {
753 let limite = crate::constants::MAX_MEMORY_BODY_LEN;
754 let msg = match Language::Portuguese {
755 Language::English => format!("body exceeds {limite} bytes"),
756 Language::Portuguese => format!("corpo excede {limite} bytes"),
757 };
758 assert!(msg.contains("corpo excede 512000"), "obtido: {msg}");
759 }
760
761 #[test]
762 fn new_name_length_en() {
763 let msg = match Language::English {
764 Language::English => format!("new-name must be 1-{} chars", 80),
765 Language::Portuguese => format!("novo nome deve ter entre 1 e {} caracteres", 80),
766 };
767 assert!(msg.contains("new-name must be 1-80"), "obtido: {msg}");
768 }
769
770 #[test]
771 fn new_name_length_pt() {
772 let msg = match Language::Portuguese {
773 Language::English => format!("new-name must be 1-{} chars", 80),
774 Language::Portuguese => format!("novo nome deve ter entre 1 e {} caracteres", 80),
775 };
776 assert!(
777 msg.contains("novo nome deve ter entre 1 e 80"),
778 "obtido: {msg}"
779 );
780 }
781
782 #[test]
783 fn new_name_kebab_en() {
784 let nome = "Bad Name";
785 let msg = match Language::English {
786 Language::English => format!(
787 "new-name must be kebab-case slug (lowercase letters, digits, hyphens): '{nome}'"
788 ),
789 Language::Portuguese => format!(
790 "novo nome deve estar em kebab-case (minúsculas, dígitos, hífens): '{nome}'"
791 ),
792 };
793 assert!(msg.contains("new-name must be kebab-case"), "obtido: {msg}");
794 }
795
796 #[test]
797 fn new_name_kebab_pt() {
798 let nome = "Bad Name";
799 let msg = match Language::Portuguese {
800 Language::English => format!(
801 "new-name must be kebab-case slug (lowercase letters, digits, hyphens): '{nome}'"
802 ),
803 Language::Portuguese => format!(
804 "novo nome deve estar em kebab-case (minúsculas, dígitos, hífens): '{nome}'"
805 ),
806 };
807 assert!(
808 msg.contains("novo nome deve estar em kebab-case"),
809 "obtido: {msg}"
810 );
811 }
812
813 #[test]
814 fn reserved_name_en() {
815 let msg = match Language::English {
816 Language::English => {
817 "names and namespaces starting with __ are reserved for internal use"
818 .to_string()
819 }
820 Language::Portuguese => {
821 "nomes e namespaces iniciados com __ são reservados para uso interno"
822 .to_string()
823 }
824 };
825 assert!(msg.contains("reserved for internal use"), "obtido: {msg}");
826 }
827
828 #[test]
829 fn reserved_name_pt() {
830 let msg = match Language::Portuguese {
831 Language::English => {
832 "names and namespaces starting with __ are reserved for internal use"
833 .to_string()
834 }
835 Language::Portuguese => {
836 "nomes e namespaces iniciados com __ são reservados para uso interno"
837 .to_string()
838 }
839 };
840 assert!(msg.contains("reservados para uso interno"), "obtido: {msg}");
841 }
842 }
843}