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