Skip to main content

context7_cli/
i18n.rs

1/// Bilingual internationalisation (EN / PT-BR) for user-facing messages.
2///
3/// Language resolution order:
4/// 1. CLI flag `--lang en|pt`
5/// 2. Environment variable `CONTEXT7_LANG`
6/// 3. `sys_locale::get_locale()` — locale starting with `"pt"` → Portuguese
7/// 4. Default: English
8///
9/// Call [`definir_idioma`] once at startup (in `run()`), then call
10/// [`idioma_atual`] or [`t`] anywhere to retrieve localised strings.
11use std::sync::OnceLock;
12
13// ─── IDIOMA ───────────────────────────────────────────────────────────────────
14
15/// Supported display languages.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum Idioma {
18    /// English output.
19    English,
20    /// Brazilian Portuguese output.
21    Portugues,
22}
23
24/// Global language setting — written once at startup, read-only thereafter.
25static IDIOMA_GLOBAL: OnceLock<Idioma> = OnceLock::new();
26
27/// Returns the currently configured language.
28///
29/// Defaults to [`Idioma::English`] if [`definir_idioma`] has not been called.
30pub fn idioma_atual() -> Idioma {
31    *IDIOMA_GLOBAL.get().unwrap_or(&Idioma::English)
32}
33
34/// Sets the global language. Silently ignored if already set (OnceLock semantics).
35pub fn definir_idioma(idioma: Idioma) {
36    let _ = IDIOMA_GLOBAL.set(idioma);
37}
38
39/// Resolves the language from CLI flag, env var, or system locale.
40///
41/// Resolution order:
42/// 1. `cli_lang` — value of `--lang` flag (if provided)
43/// 2. `CONTEXT7_LANG` environment variable
44/// 3. `sys_locale::get_locale()` — BCP 47 locale (e.g. `"pt-BR"`)
45/// 4. Default: English
46pub fn resolver_idioma(cli_lang: Option<&str>) -> Idioma {
47    // 1. CLI flag
48    if let Some(lang) = cli_lang {
49        return parse_lang_str(lang);
50    }
51
52    // 2. Environment variable
53    if let Ok(env_lang) = std::env::var("CONTEXT7_LANG") {
54        return parse_lang_str(&env_lang);
55    }
56
57    // 3. System locale
58    if let Some(locale) = sys_locale::get_locale() {
59        if locale.to_lowercase().starts_with("pt") {
60            return Idioma::Portugues;
61        }
62    }
63
64    // 4. Default
65    Idioma::English
66}
67
68fn parse_lang_str(s: &str) -> Idioma {
69    match s.to_lowercase().as_str() {
70        "pt" | "pt-br" | "pt_br" | "portugues" | "português" => Idioma::Portugues,
71        _ => Idioma::English,
72    }
73}
74
75// ─── MENSAGEM ─────────────────────────────────────────────────────────────────
76
77/// All user-facing messages indexed by variant.
78///
79/// Each variant maps to a pair of `(English, Portuguese)` strings.
80#[derive(Debug, Clone, Copy)]
81pub enum Mensagem {
82    // Keys subcommand (15 variants)
83    /// "Key added successfully at: {path}"
84    ChaveAdicionada,
85    /// "Key already exists (skipping)."
86    ChaveJaExistia,
87    /// "No key stored."
88    NenhumaChaveArmazenada,
89    /// "Use `context7 keys add <KEY>` to add a key."
90    UsarKeysAdd,
91    /// "{n} key(s) stored:"
92    ContadorChaves,
93    /// "No key stored to remove."
94    NenhumaChaveParaRemover,
95    /// "Index {i} invalid. Use a number between 1 and {n}."
96    IndiceInvalido,
97    /// "Key {masked} removed successfully."
98    ChaveRemovidaSucesso,
99    /// "Operation cancelled."
100    OperacaoCancelada,
101    /// "All keys removed."
102    TodasChavesRemovidas,
103    /// "System does not support XDG directories."
104    SistemaXdgNaoSuportado,
105    /// "{imported}/{total} key(s) imported successfully."
106    ChavesImportadasSucesso,
107    /// "No CONTEXT7_API= key found in: {file}"
108    NenhumaChaveContext7NoArquivo,
109    /// "Are you sure you want to remove ALL keys? [y/N] " / "[s/N] "
110    ConfirmarRemoverTodas,
111    /// Accepted confirmation responses: "y"/"yes" or "s"/"sim"
112    RespostaConfirmacao,
113    /// "API key cannot be empty. Get a key at <https://context7.com>"
114    ChaveVaziaOuInvalida,
115    /// "Warning: key does not match expected format (ctx7sk-...). API calls may fail."
116    AvisoFormatoChave,
117
118    // Library / Docs (8 variants)
119    /// "No library found."
120    NenhumaBibliotecaEncontrada,
121    /// "Libraries found:"
122    BibliotecasEncontradas,
123    /// "Trust:"
124    ConfiancaScore,
125    /// "No documentation found."
126    NenhumaDocumentacaoEncontrada,
127    /// "Documentation:"
128    TituloDocumentacao,
129    /// "Sources:"
130    TituloFontes,
131    /// "No content available."
132    SemConteudoDisponivel,
133    /// "Searching library: {name}"
134    BuscandoBiblioteca,
135
136    // HTTP / Network errors (8 variants)
137    /// "Network error searching library: {err}"
138    ErroDeRede,
139    /// "Network error fetching documentation: {err}"
140    ErroDeRedeDocs,
141    /// "Failed to deserialise JSON response: {err}"
142    FalhaDesserializar,
143    /// "Rate limit reached (429), waiting for retry…"
144    RateLimitAtingido,
145    /// "Server error ({status}), retrying…"
146    ErrodoServidor,
147    /// "Invalid API key (401/403), trying next…"
148    ChaveApiInvalida,
149    /// "Attempt {n}/{max}"
150    Tentativa,
151    /// "Waiting {ms}ms before retrying…"
152    AguardandoRetry,
153
154    // Config / XDG (7 variants)
155    /// "System does not support XDG — cannot save configuration"
156    ErroCaminhoXdg,
157    /// "Failed to read XDG config at: {path}"
158    FalhaLerConfig,
159    /// "Invalid TOML at: {path}"
160    FalhaTomlInvalido,
161    /// "Failed to write config at: {path}"
162    FalhaEscreverConfig,
163    /// "Failed to create directory: {path}"
164    FalhaCriarDiretorio,
165    /// "No API key configured. Set CONTEXT7_API_KEYS or use `keys add`."
166    NenhumaChaveConfigurada,
167    /// "Failed to serialise configuration to TOML"
168    FalhaSerializarToml,
169
170    // Logging / info (8 variants)
171    /// "Keys loaded from CONTEXT7_API_KEYS environment variable"
172    ChavesCarregadasEnvVar,
173    /// "Keys loaded from XDG configuration"
174    ChavesCarregadasXdg,
175    /// "Failed to read XDG configuration (continuing): {err}"
176    FalhaLerXdgContinuando,
177    /// "Starting context7 with {n} API keys available"
178    IniciandoComChaves,
179    /// "Keys loaded from compile-time CONTEXT7_API_KEYS"
180    ChavesCarregadasCompileTime,
181    /// "Failed to serialise results to JSON"
182    FalhaSerializarJson,
183    /// "Failed to serialise documentation to JSON"
184    FalhaSerializarDocs,
185    /// "Failed to search library '{name}'"
186    FalhaBuscarBiblioteca,
187
188    // Permissions / IO (2 variants)
189    /// "Failed to read metadata of: {path}"
190    FalhaLerMetadados,
191    /// "Failed to set permissions on: {path}"
192    FalhaDefinirPermissoes,
193
194    // API / Docs errors (4 variants)
195    /// "Failed to fetch documentation for: {library_id}"
196    FalhaBuscarDocumentacao,
197    /// "Failed to create HTTP client"
198    FalhaCriarClienteHttp,
199    /// "No documentation available"
200    SemDocumentacaoDisponivel,
201    /// "Library not found. Verify the ID via `context7 library <name>`."
202    BibliotecaNaoEncontradaApi,
203
204    // Health subcommand (7 variants)
205    /// "Running health checks…"
206    HealthExecutando,
207    /// "Config: OK"
208    HealthConfigOk,
209    /// "Config: FAILED"
210    HealthConfigFalhou,
211    /// "API keys: {n} configured"
212    HealthKeysOk,
213    /// "API keys: none configured (exit 66)"
214    HealthKeysFaltando,
215    /// "API: reachable"
216    HealthApiOk,
217    /// "API: offline or unreachable (exit 69)"
218    HealthApiOffline,
219}
220
221impl Mensagem {
222    /// Returns the localised text for this message in the given language.
223    ///
224    /// Prefer this over the global [`t`] function when you need deterministic
225    /// translations without depending on the process-wide language setting
226    /// (useful for tests and library usage).
227    pub fn texto(self, idioma: Idioma) -> &'static str {
228        match idioma {
229            Idioma::English => en(self),
230            Idioma::Portugues => pt(self),
231        }
232    }
233}
234
235/// Returns the localised string for a message in the current language.
236///
237/// For parameterised messages use `format!("{} {}", t(Mensagem::Foo), param)`.
238pub fn t(msg: Mensagem) -> &'static str {
239    match idioma_atual() {
240        Idioma::English => en(msg),
241        Idioma::Portugues => pt(msg),
242    }
243}
244
245fn en(msg: Mensagem) -> &'static str {
246    match msg {
247        // Keys
248        Mensagem::ChaveAdicionada => "Key added successfully at:",
249        Mensagem::ChaveJaExistia => "Key already exists (skipping).",
250        Mensagem::NenhumaChaveArmazenada => "No key stored.",
251        Mensagem::UsarKeysAdd => "Use `context7 keys add <KEY>` to add a key.",
252        Mensagem::ContadorChaves => "key(s) stored:",
253        Mensagem::NenhumaChaveParaRemover => "No key stored to remove.",
254        Mensagem::IndiceInvalido => "Invalid index. Use a number between 1 and",
255        Mensagem::ChaveRemovidaSucesso => "Key removed successfully.",
256        Mensagem::OperacaoCancelada => "Operation cancelled.",
257        Mensagem::TodasChavesRemovidas => "All keys removed.",
258        Mensagem::SistemaXdgNaoSuportado => "System does not support XDG directories.",
259        Mensagem::ChavesImportadasSucesso => "key(s) imported successfully.",
260        Mensagem::NenhumaChaveContext7NoArquivo => "No CONTEXT7_API= key found in:",
261        Mensagem::ConfirmarRemoverTodas => "Are you sure you want to remove ALL keys? [y/N] ",
262        Mensagem::RespostaConfirmacao => "y|yes",
263        Mensagem::ChaveVaziaOuInvalida => {
264            "API key cannot be empty. Get a key at https://context7.com"
265        }
266        Mensagem::AvisoFormatoChave => {
267            "Warning: key does not match expected format (ctx7sk-...). API calls may fail."
268        }
269
270        // Library / Docs
271        Mensagem::NenhumaBibliotecaEncontrada => "No library found.",
272        Mensagem::BibliotecasEncontradas => "Libraries found:",
273        Mensagem::ConfiancaScore => "trust",
274        Mensagem::NenhumaDocumentacaoEncontrada => "No documentation found.",
275        Mensagem::TituloDocumentacao => "Documentation:",
276        Mensagem::TituloFontes => "Sources:",
277        Mensagem::SemConteudoDisponivel => "No content available.",
278        Mensagem::BuscandoBiblioteca => "Searching library:",
279
280        // HTTP / Network
281        Mensagem::ErroDeRede => "Network error searching library:",
282        Mensagem::ErroDeRedeDocs => "Network error fetching documentation:",
283        Mensagem::FalhaDesserializar => "Failed to deserialise JSON response:",
284        Mensagem::RateLimitAtingido => "Rate limit reached (429), waiting for retry…",
285        Mensagem::ErrodoServidor => "Server error, retrying…",
286        Mensagem::ChaveApiInvalida => "Invalid API key (401/403), trying next…",
287        Mensagem::Tentativa => "Attempt",
288        Mensagem::AguardandoRetry => "Waiting before retrying…",
289
290        // Config / XDG
291        Mensagem::ErroCaminhoXdg => "System does not support XDG — cannot save configuration",
292        Mensagem::FalhaLerConfig => "Failed to read XDG config at:",
293        Mensagem::FalhaTomlInvalido => "Invalid TOML at:",
294        Mensagem::FalhaEscreverConfig => "Failed to write config at:",
295        Mensagem::FalhaCriarDiretorio => "Failed to create directory:",
296        Mensagem::NenhumaChaveConfigurada => {
297            "No API key configured. Set CONTEXT7_API_KEYS or use `context7 keys add <KEY>`."
298        }
299        Mensagem::FalhaSerializarToml => "Failed to serialise configuration to TOML",
300
301        // Logging / info
302        Mensagem::ChavesCarregadasEnvVar => {
303            "Keys loaded from CONTEXT7_API_KEYS environment variable"
304        }
305        Mensagem::ChavesCarregadasXdg => "Keys loaded from XDG configuration",
306        Mensagem::FalhaLerXdgContinuando => "Failed to read XDG configuration (continuing):",
307        Mensagem::IniciandoComChaves => "Starting context7 with",
308        Mensagem::ChavesCarregadasCompileTime => "Keys loaded from compile-time CONTEXT7_API_KEYS",
309        Mensagem::FalhaSerializarJson => "Failed to serialise results to JSON",
310        Mensagem::FalhaSerializarDocs => "Failed to serialise documentation to JSON",
311        Mensagem::FalhaBuscarBiblioteca => "Failed to search library",
312
313        // Permissions / IO
314        Mensagem::FalhaLerMetadados => "Failed to read metadata of:",
315        Mensagem::FalhaDefinirPermissoes => "Failed to set permissions on:",
316
317        // API / Docs errors
318        Mensagem::FalhaBuscarDocumentacao => "Failed to fetch documentation for:",
319        Mensagem::FalhaCriarClienteHttp => "Failed to create HTTP client",
320        Mensagem::SemDocumentacaoDisponivel => "No documentation available",
321        Mensagem::BibliotecaNaoEncontradaApi => {
322            "Library not found. Verify the ID via `context7 library <name>`."
323        }
324
325        // Health
326        Mensagem::HealthExecutando => "Running health checks…",
327        Mensagem::HealthConfigOk => "Config: OK",
328        Mensagem::HealthConfigFalhou => "Config: FAILED",
329        Mensagem::HealthKeysOk => "API keys configured:",
330        Mensagem::HealthKeysFaltando => "API keys: none configured",
331        Mensagem::HealthApiOk => "API: reachable",
332        Mensagem::HealthApiOffline => "API: offline or unreachable",
333    }
334}
335
336fn pt(msg: Mensagem) -> &'static str {
337    match msg {
338        // Keys
339        Mensagem::ChaveAdicionada => "Chave adicionada com sucesso em:",
340        Mensagem::ChaveJaExistia => "Chave já existente (ignorando).",
341        Mensagem::NenhumaChaveArmazenada => "Nenhuma chave armazenada.",
342        Mensagem::UsarKeysAdd => "Use `context7 keys add <CHAVE>` para adicionar uma chave.",
343        Mensagem::ContadorChaves => "chave(s) armazenada(s):",
344        Mensagem::NenhumaChaveParaRemover => "Nenhuma chave armazenada para remover.",
345        Mensagem::IndiceInvalido => "Índice inválido. Use um número entre 1 e",
346        Mensagem::ChaveRemovidaSucesso => "Chave removida com sucesso.",
347        Mensagem::OperacaoCancelada => "Operação cancelada.",
348        Mensagem::TodasChavesRemovidas => "Todas as chaves foram removidas.",
349        Mensagem::SistemaXdgNaoSuportado => "Sistema não suporta diretórios XDG.",
350        Mensagem::ChavesImportadasSucesso => "chave(s) importada(s) com sucesso.",
351        Mensagem::NenhumaChaveContext7NoArquivo => "Nenhuma chave CONTEXT7_API= encontrada em:",
352        Mensagem::ConfirmarRemoverTodas => "Tem certeza que deseja remover TODAS as chaves? [s/N] ",
353        Mensagem::RespostaConfirmacao => "s|sim",
354        Mensagem::ChaveVaziaOuInvalida => {
355            "Chave de API não pode ser vazia. Obtenha uma em https://context7.com"
356        }
357        Mensagem::AvisoFormatoChave => {
358            "Aviso: chave não corresponde ao formato esperado (ctx7sk-...). Chamadas de API podem falhar."
359        }
360
361        // Library / Docs
362        Mensagem::NenhumaBibliotecaEncontrada => "Nenhuma biblioteca encontrada.",
363        Mensagem::BibliotecasEncontradas => "Bibliotecas encontradas:",
364        Mensagem::ConfiancaScore => "confiança",
365        Mensagem::NenhumaDocumentacaoEncontrada => "Nenhuma documentação encontrada.",
366        Mensagem::TituloDocumentacao => "Documentação:",
367        Mensagem::TituloFontes => "Fontes:",
368        Mensagem::SemConteudoDisponivel => "Sem conteúdo disponível.",
369        Mensagem::BuscandoBiblioteca => "Buscando biblioteca:",
370
371        // HTTP / Network
372        Mensagem::ErroDeRede => "Erro de rede ao buscar biblioteca:",
373        Mensagem::ErroDeRedeDocs => "Erro de rede ao buscar documentação:",
374        Mensagem::FalhaDesserializar => "Falha ao desserializar resposta JSON:",
375        Mensagem::RateLimitAtingido => "Rate limit atingido (429), aguardando retry…",
376        Mensagem::ErrodoServidor => "Erro do servidor, tentando novamente…",
377        Mensagem::ChaveApiInvalida => "Chave de API inválida (401/403), tentando próxima…",
378        Mensagem::Tentativa => "Tentativa",
379        Mensagem::AguardandoRetry => "Aguardando antes de tentar novamente…",
380
381        // Config / XDG
382        Mensagem::ErroCaminhoXdg => {
383            "Sistema não suporta diretórios XDG — impossível salvar configuração"
384        }
385        Mensagem::FalhaLerConfig => "Falha ao ler configuração XDG em:",
386        Mensagem::FalhaTomlInvalido => "TOML inválido em:",
387        Mensagem::FalhaEscreverConfig => "Falha ao escrever config em:",
388        Mensagem::FalhaCriarDiretorio => "Falha ao criar diretório:",
389        Mensagem::NenhumaChaveConfigurada => {
390            "Nenhuma chave de API encontrada. Configure CONTEXT7_API_KEYS ou use `context7 keys add <CHAVE>`."
391        }
392        Mensagem::FalhaSerializarToml => "Falha ao serializar configuração para TOML",
393
394        // Logging / info
395        Mensagem::ChavesCarregadasEnvVar => {
396            "Chaves carregadas via variável de ambiente CONTEXT7_API_KEYS"
397        }
398        Mensagem::ChavesCarregadasXdg => "Chaves carregadas via configuração XDG",
399        Mensagem::FalhaLerXdgContinuando => "Falha ao ler configuração XDG (continuando):",
400        Mensagem::IniciandoComChaves => "Iniciando context7 com",
401        Mensagem::ChavesCarregadasCompileTime => {
402            "Chaves carregadas via compile-time CONTEXT7_API_KEYS"
403        }
404        Mensagem::FalhaSerializarJson => "Falha ao serializar resultados para JSON",
405        Mensagem::FalhaSerializarDocs => "Falha ao serializar documentação para JSON",
406        Mensagem::FalhaBuscarBiblioteca => "Falha ao buscar biblioteca",
407
408        // Permissions / IO
409        Mensagem::FalhaLerMetadados => "Falha ao ler metadados de:",
410        Mensagem::FalhaDefinirPermissoes => "Falha ao definir permissões em:",
411
412        // API / Docs errors
413        Mensagem::FalhaBuscarDocumentacao => "Falha ao buscar documentação para:",
414        Mensagem::FalhaCriarClienteHttp => "Falha ao criar cliente HTTP",
415        Mensagem::SemDocumentacaoDisponivel => "Nenhuma documentação disponível",
416        Mensagem::BibliotecaNaoEncontradaApi => {
417            "Biblioteca não encontrada. Verifique o ID via `context7 library <nome>`."
418        }
419
420        // Health
421        Mensagem::HealthExecutando => "Executando verificações de saúde…",
422        Mensagem::HealthConfigOk => "Config: OK",
423        Mensagem::HealthConfigFalhou => "Config: FALHOU",
424        Mensagem::HealthKeysOk => "Chaves de API configuradas:",
425        Mensagem::HealthKeysFaltando => "Chaves de API: nenhuma configurada",
426        Mensagem::HealthApiOk => "API: acessível",
427        Mensagem::HealthApiOffline => "API: offline ou inacessível",
428    }
429}
430
431// ─── TESTES ───────────────────────────────────────────────────────────────────
432
433#[cfg(test)]
434mod testes {
435    use super::*;
436
437    #[test]
438    fn testa_idioma_atual_padrao_e_english() {
439        // Se OnceLock ainda não foi setado neste processo de teste, deve ser English
440        // (pode ter sido setado por outro teste — verificamos apenas que não panics)
441        let _ = idioma_atual();
442    }
443
444    #[test]
445    fn testa_resolver_idioma_cli_flag_pt() {
446        assert_eq!(resolver_idioma(Some("pt")), Idioma::Portugues);
447        assert_eq!(resolver_idioma(Some("pt-BR")), Idioma::Portugues);
448        assert_eq!(resolver_idioma(Some("PT_BR")), Idioma::Portugues);
449    }
450
451    #[test]
452    fn testa_resolver_idioma_cli_flag_en() {
453        assert_eq!(resolver_idioma(Some("en")), Idioma::English);
454        assert_eq!(resolver_idioma(Some("en-US")), Idioma::English);
455    }
456
457    #[test]
458    fn testa_resolver_idioma_sem_flag_nem_env_retorna_english_ou_pt() {
459        // Sem flag e sem env, deve retornar English ou Portugues (dependendo do sistema)
460        let idioma = resolver_idioma(None);
461        assert!(idioma == Idioma::English || idioma == Idioma::Portugues);
462    }
463
464    #[test]
465    fn testa_t_mensagem_nenhuma_chave_en() {
466        let msg_en = en(Mensagem::NenhumaChaveArmazenada);
467        assert!(!msg_en.is_empty());
468        assert!(
469            msg_en.to_lowercase().contains("no") || msg_en.to_lowercase().contains("key"),
470            "EN deve conter 'no' ou 'key', obteve: {}",
471            msg_en
472        );
473    }
474
475    #[test]
476    fn testa_t_mensagem_nenhuma_chave_pt() {
477        let msg_pt = pt(Mensagem::NenhumaChaveArmazenada);
478        assert!(!msg_pt.is_empty());
479        assert!(
480            msg_pt.to_lowercase().contains("nenhuma") || msg_pt.to_lowercase().contains("chave"),
481            "PT deve conter 'nenhuma' ou 'chave', obteve: {}",
482            msg_pt
483        );
484    }
485
486    #[test]
487    fn testa_confirmacao_resposta_en_contem_y() {
488        let confirmacao = en(Mensagem::RespostaConfirmacao);
489        assert!(
490            confirmacao.contains('y'),
491            "EN: resposta confirmação deve conter 'y'"
492        );
493    }
494
495    #[test]
496    fn testa_confirmacao_resposta_pt_contem_s() {
497        let confirmacao = pt(Mensagem::RespostaConfirmacao);
498        assert!(
499            confirmacao.contains('s'),
500            "PT: resposta confirmação deve conter 's'"
501        );
502    }
503
504    #[test]
505    fn testa_todas_variantes_en_nao_sao_vazias() {
506        let variantes = [
507            Mensagem::ChaveAdicionada,
508            Mensagem::ChaveJaExistia,
509            Mensagem::NenhumaChaveArmazenada,
510            Mensagem::UsarKeysAdd,
511            Mensagem::ContadorChaves,
512            Mensagem::NenhumaChaveParaRemover,
513            Mensagem::IndiceInvalido,
514            Mensagem::ChaveRemovidaSucesso,
515            Mensagem::OperacaoCancelada,
516            Mensagem::TodasChavesRemovidas,
517            Mensagem::SistemaXdgNaoSuportado,
518            Mensagem::ChavesImportadasSucesso,
519            Mensagem::NenhumaChaveContext7NoArquivo,
520            Mensagem::ConfirmarRemoverTodas,
521            Mensagem::RespostaConfirmacao,
522            Mensagem::NenhumaBibliotecaEncontrada,
523            Mensagem::BibliotecasEncontradas,
524            Mensagem::ConfiancaScore,
525            Mensagem::NenhumaDocumentacaoEncontrada,
526            Mensagem::TituloDocumentacao,
527            Mensagem::TituloFontes,
528            Mensagem::SemConteudoDisponivel,
529            Mensagem::BuscandoBiblioteca,
530            Mensagem::ErroDeRede,
531            Mensagem::ErroDeRedeDocs,
532            Mensagem::FalhaDesserializar,
533            Mensagem::RateLimitAtingido,
534            Mensagem::ErrodoServidor,
535            Mensagem::ChaveApiInvalida,
536            Mensagem::Tentativa,
537            Mensagem::AguardandoRetry,
538            Mensagem::ErroCaminhoXdg,
539            Mensagem::FalhaLerConfig,
540            Mensagem::FalhaTomlInvalido,
541            Mensagem::FalhaEscreverConfig,
542            Mensagem::FalhaCriarDiretorio,
543            Mensagem::NenhumaChaveConfigurada,
544            Mensagem::FalhaSerializarToml,
545            Mensagem::ChavesCarregadasEnvVar,
546            Mensagem::ChavesCarregadasXdg,
547            Mensagem::FalhaLerXdgContinuando,
548            Mensagem::IniciandoComChaves,
549            Mensagem::ChavesCarregadasCompileTime,
550            Mensagem::FalhaSerializarJson,
551            Mensagem::FalhaSerializarDocs,
552            Mensagem::FalhaBuscarBiblioteca,
553            Mensagem::FalhaLerMetadados,
554            Mensagem::FalhaDefinirPermissoes,
555            Mensagem::FalhaBuscarDocumentacao,
556            Mensagem::FalhaCriarClienteHttp,
557            Mensagem::SemDocumentacaoDisponivel,
558            Mensagem::BibliotecaNaoEncontradaApi,
559            Mensagem::ChaveVaziaOuInvalida,
560            Mensagem::AvisoFormatoChave,
561            Mensagem::HealthExecutando,
562            Mensagem::HealthConfigOk,
563            Mensagem::HealthConfigFalhou,
564            Mensagem::HealthKeysOk,
565            Mensagem::HealthKeysFaltando,
566            Mensagem::HealthApiOk,
567            Mensagem::HealthApiOffline,
568        ];
569
570        for v in &variantes {
571            let msg_en = en(*v);
572            let msg_pt = pt(*v);
573            assert!(
574                !msg_en.is_empty(),
575                "EN mensagem vazia para variante {:?}",
576                v
577            );
578            assert!(
579                !msg_pt.is_empty(),
580                "PT mensagem vazia para variante {:?}",
581                v
582            );
583        }
584    }
585}