1use std::sync::OnceLock;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum Idioma {
18 English,
20 Portugues,
22}
23
24static IDIOMA_GLOBAL: OnceLock<Idioma> = OnceLock::new();
26
27pub fn idioma_atual() -> Idioma {
31 *IDIOMA_GLOBAL.get().unwrap_or(&Idioma::English)
32}
33
34pub fn definir_idioma(idioma: Idioma) {
36 let _ = IDIOMA_GLOBAL.set(idioma);
37}
38
39pub fn resolver_idioma(cli_lang: Option<&str>) -> Idioma {
47 if let Some(lang) = cli_lang {
49 return parse_lang_str(lang);
50 }
51
52 if let Ok(env_lang) = std::env::var("CONTEXT7_LANG") {
54 return parse_lang_str(&env_lang);
55 }
56
57 if let Some(locale) = sys_locale::get_locale() {
59 if locale.to_lowercase().starts_with("pt") {
60 return Idioma::Portugues;
61 }
62 }
63
64 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#[derive(Debug, Clone, Copy)]
81pub enum Mensagem {
82 ChaveAdicionada,
85 ChaveJaExistia,
87 NenhumaChaveArmazenada,
89 UsarKeysAdd,
91 ContadorChaves,
93 NenhumaChaveParaRemover,
95 IndiceInvalido,
97 ChaveRemovidaSucesso,
99 OperacaoCancelada,
101 TodasChavesRemovidas,
103 SistemaXdgNaoSuportado,
105 ChavesImportadasSucesso,
107 NenhumaChaveContext7NoArquivo,
109 ConfirmarRemoverTodas,
111 RespostaConfirmacao,
113 ChaveVaziaOuInvalida,
115 AvisoFormatoChave,
117
118 NenhumaBibliotecaEncontrada,
121 BibliotecasEncontradas,
123 ConfiancaScore,
125 NenhumaDocumentacaoEncontrada,
127 TituloDocumentacao,
129 TituloFontes,
131 SemConteudoDisponivel,
133 BuscandoBiblioteca,
135
136 ErroDeRede,
139 ErroDeRedeDocs,
141 FalhaDesserializar,
143 RateLimitAtingido,
145 ErrodoServidor,
147 ChaveApiInvalida,
149 Tentativa,
151 AguardandoRetry,
153
154 ErroCaminhoXdg,
157 FalhaLerConfig,
159 FalhaTomlInvalido,
161 FalhaEscreverConfig,
163 FalhaCriarDiretorio,
165 NenhumaChaveConfigurada,
167 FalhaSerializarToml,
169
170 ChavesCarregadasEnvVar,
173 ChavesCarregadasXdg,
175 FalhaLerXdgContinuando,
177 IniciandoComChaves,
179 ChavesCarregadasCompileTime,
181 FalhaSerializarJson,
183 FalhaSerializarDocs,
185 FalhaBuscarBiblioteca,
187
188 FalhaLerMetadados,
191 FalhaDefinirPermissoes,
193
194 FalhaBuscarDocumentacao,
197 FalhaCriarClienteHttp,
199 SemDocumentacaoDisponivel,
201 BibliotecaNaoEncontradaApi,
203
204 HealthExecutando,
207 HealthConfigOk,
209 HealthConfigFalhou,
211 HealthKeysOk,
213 HealthKeysFaltando,
215 HealthApiOk,
217 HealthApiOffline,
219}
220
221impl Mensagem {
222 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
235pub 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 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 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 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 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 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 Mensagem::FalhaLerMetadados => "Failed to read metadata of:",
315 Mensagem::FalhaDefinirPermissoes => "Failed to set permissions on:",
316
317 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 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 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 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 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 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 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 Mensagem::FalhaLerMetadados => "Falha ao ler metadados de:",
410 Mensagem::FalhaDefinirPermissoes => "Falha ao definir permissões em:",
411
412 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 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#[cfg(test)]
434mod testes {
435 use super::*;
436
437 #[test]
438 fn testa_idioma_atual_padrao_e_english() {
439 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 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}