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
205impl Mensagem {
206 pub fn texto(self, idioma: Idioma) -> &'static str {
212 match idioma {
213 Idioma::English => en(self),
214 Idioma::Portugues => pt(self),
215 }
216 }
217}
218
219pub fn t(msg: Mensagem) -> &'static str {
223 match idioma_atual() {
224 Idioma::English => en(msg),
225 Idioma::Portugues => pt(msg),
226 }
227}
228
229fn en(msg: Mensagem) -> &'static str {
230 match msg {
231 Mensagem::ChaveAdicionada => "Key added successfully at:",
233 Mensagem::ChaveJaExistia => "Key already exists (skipping).",
234 Mensagem::NenhumaChaveArmazenada => "No key stored.",
235 Mensagem::UsarKeysAdd => "Use `context7 keys add <KEY>` to add a key.",
236 Mensagem::ContadorChaves => "key(s) stored:",
237 Mensagem::NenhumaChaveParaRemover => "No key stored to remove.",
238 Mensagem::IndiceInvalido => "Invalid index. Use a number between 1 and",
239 Mensagem::ChaveRemovidaSucesso => "Key removed successfully.",
240 Mensagem::OperacaoCancelada => "Operation cancelled.",
241 Mensagem::TodasChavesRemovidas => "All keys removed.",
242 Mensagem::SistemaXdgNaoSuportado => "System does not support XDG directories.",
243 Mensagem::ChavesImportadasSucesso => "key(s) imported successfully.",
244 Mensagem::NenhumaChaveContext7NoArquivo => "No CONTEXT7_API= key found in:",
245 Mensagem::ConfirmarRemoverTodas => "Are you sure you want to remove ALL keys? [y/N] ",
246 Mensagem::RespostaConfirmacao => "y|yes",
247 Mensagem::ChaveVaziaOuInvalida => {
248 "API key cannot be empty. Get a key at https://context7.com"
249 }
250 Mensagem::AvisoFormatoChave => {
251 "Warning: key does not match expected format (ctx7sk-...). API calls may fail."
252 }
253
254 Mensagem::NenhumaBibliotecaEncontrada => "No library found.",
256 Mensagem::BibliotecasEncontradas => "Libraries found:",
257 Mensagem::ConfiancaScore => "trust",
258 Mensagem::NenhumaDocumentacaoEncontrada => "No documentation found.",
259 Mensagem::TituloDocumentacao => "Documentation:",
260 Mensagem::TituloFontes => "Sources:",
261 Mensagem::SemConteudoDisponivel => "No content available.",
262 Mensagem::BuscandoBiblioteca => "Searching library:",
263
264 Mensagem::ErroDeRede => "Network error searching library:",
266 Mensagem::ErroDeRedeDocs => "Network error fetching documentation:",
267 Mensagem::FalhaDesserializar => "Failed to deserialise JSON response:",
268 Mensagem::RateLimitAtingido => "Rate limit reached (429), waiting for retry…",
269 Mensagem::ErrodoServidor => "Server error, retrying…",
270 Mensagem::ChaveApiInvalida => "Invalid API key (401/403), trying next…",
271 Mensagem::Tentativa => "Attempt",
272 Mensagem::AguardandoRetry => "Waiting before retrying…",
273
274 Mensagem::ErroCaminhoXdg => "System does not support XDG — cannot save configuration",
276 Mensagem::FalhaLerConfig => "Failed to read XDG config at:",
277 Mensagem::FalhaTomlInvalido => "Invalid TOML at:",
278 Mensagem::FalhaEscreverConfig => "Failed to write config at:",
279 Mensagem::FalhaCriarDiretorio => "Failed to create directory:",
280 Mensagem::NenhumaChaveConfigurada => {
281 "No API key configured. Set CONTEXT7_API_KEYS or use `context7 keys add <KEY>`."
282 }
283 Mensagem::FalhaSerializarToml => "Failed to serialise configuration to TOML",
284
285 Mensagem::ChavesCarregadasEnvVar => {
287 "Keys loaded from CONTEXT7_API_KEYS environment variable"
288 }
289 Mensagem::ChavesCarregadasXdg => "Keys loaded from XDG configuration",
290 Mensagem::FalhaLerXdgContinuando => "Failed to read XDG configuration (continuing):",
291 Mensagem::IniciandoComChaves => "Starting context7 with",
292 Mensagem::ChavesCarregadasCompileTime => "Keys loaded from compile-time CONTEXT7_API_KEYS",
293 Mensagem::FalhaSerializarJson => "Failed to serialise results to JSON",
294 Mensagem::FalhaSerializarDocs => "Failed to serialise documentation to JSON",
295 Mensagem::FalhaBuscarBiblioteca => "Failed to search library",
296
297 Mensagem::FalhaLerMetadados => "Failed to read metadata of:",
299 Mensagem::FalhaDefinirPermissoes => "Failed to set permissions on:",
300
301 Mensagem::FalhaBuscarDocumentacao => "Failed to fetch documentation for:",
303 Mensagem::FalhaCriarClienteHttp => "Failed to create HTTP client",
304 Mensagem::SemDocumentacaoDisponivel => "No documentation available",
305 Mensagem::BibliotecaNaoEncontradaApi => {
306 "Library not found. Verify the ID via `context7 library <name>`."
307 }
308 }
309}
310
311fn pt(msg: Mensagem) -> &'static str {
312 match msg {
313 Mensagem::ChaveAdicionada => "Chave adicionada com sucesso em:",
315 Mensagem::ChaveJaExistia => "Chave já existente (ignorando).",
316 Mensagem::NenhumaChaveArmazenada => "Nenhuma chave armazenada.",
317 Mensagem::UsarKeysAdd => "Use `context7 keys add <CHAVE>` para adicionar uma chave.",
318 Mensagem::ContadorChaves => "chave(s) armazenada(s):",
319 Mensagem::NenhumaChaveParaRemover => "Nenhuma chave armazenada para remover.",
320 Mensagem::IndiceInvalido => "Índice inválido. Use um número entre 1 e",
321 Mensagem::ChaveRemovidaSucesso => "Chave removida com sucesso.",
322 Mensagem::OperacaoCancelada => "Operação cancelada.",
323 Mensagem::TodasChavesRemovidas => "Todas as chaves foram removidas.",
324 Mensagem::SistemaXdgNaoSuportado => "Sistema não suporta diretórios XDG.",
325 Mensagem::ChavesImportadasSucesso => "chave(s) importada(s) com sucesso.",
326 Mensagem::NenhumaChaveContext7NoArquivo => "Nenhuma chave CONTEXT7_API= encontrada em:",
327 Mensagem::ConfirmarRemoverTodas => "Tem certeza que deseja remover TODAS as chaves? [s/N] ",
328 Mensagem::RespostaConfirmacao => "s|sim",
329 Mensagem::ChaveVaziaOuInvalida => {
330 "Chave de API não pode ser vazia. Obtenha uma em https://context7.com"
331 }
332 Mensagem::AvisoFormatoChave => {
333 "Aviso: chave não corresponde ao formato esperado (ctx7sk-...). Chamadas de API podem falhar."
334 }
335
336 Mensagem::NenhumaBibliotecaEncontrada => "Nenhuma biblioteca encontrada.",
338 Mensagem::BibliotecasEncontradas => "Bibliotecas encontradas:",
339 Mensagem::ConfiancaScore => "confiança",
340 Mensagem::NenhumaDocumentacaoEncontrada => "Nenhuma documentação encontrada.",
341 Mensagem::TituloDocumentacao => "Documentação:",
342 Mensagem::TituloFontes => "Fontes:",
343 Mensagem::SemConteudoDisponivel => "Sem conteúdo disponível.",
344 Mensagem::BuscandoBiblioteca => "Buscando biblioteca:",
345
346 Mensagem::ErroDeRede => "Erro de rede ao buscar biblioteca:",
348 Mensagem::ErroDeRedeDocs => "Erro de rede ao buscar documentação:",
349 Mensagem::FalhaDesserializar => "Falha ao desserializar resposta JSON:",
350 Mensagem::RateLimitAtingido => "Rate limit atingido (429), aguardando retry…",
351 Mensagem::ErrodoServidor => "Erro do servidor, tentando novamente…",
352 Mensagem::ChaveApiInvalida => "Chave de API inválida (401/403), tentando próxima…",
353 Mensagem::Tentativa => "Tentativa",
354 Mensagem::AguardandoRetry => "Aguardando antes de tentar novamente…",
355
356 Mensagem::ErroCaminhoXdg => {
358 "Sistema não suporta diretórios XDG — impossível salvar configuração"
359 }
360 Mensagem::FalhaLerConfig => "Falha ao ler configuração XDG em:",
361 Mensagem::FalhaTomlInvalido => "TOML inválido em:",
362 Mensagem::FalhaEscreverConfig => "Falha ao escrever config em:",
363 Mensagem::FalhaCriarDiretorio => "Falha ao criar diretório:",
364 Mensagem::NenhumaChaveConfigurada => {
365 "Nenhuma chave de API encontrada. Configure CONTEXT7_API_KEYS ou use `context7 keys add <CHAVE>`."
366 }
367 Mensagem::FalhaSerializarToml => "Falha ao serializar configuração para TOML",
368
369 Mensagem::ChavesCarregadasEnvVar => {
371 "Chaves carregadas via variável de ambiente CONTEXT7_API_KEYS"
372 }
373 Mensagem::ChavesCarregadasXdg => "Chaves carregadas via configuração XDG",
374 Mensagem::FalhaLerXdgContinuando => "Falha ao ler configuração XDG (continuando):",
375 Mensagem::IniciandoComChaves => "Iniciando context7 com",
376 Mensagem::ChavesCarregadasCompileTime => {
377 "Chaves carregadas via compile-time CONTEXT7_API_KEYS"
378 }
379 Mensagem::FalhaSerializarJson => "Falha ao serializar resultados para JSON",
380 Mensagem::FalhaSerializarDocs => "Falha ao serializar documentação para JSON",
381 Mensagem::FalhaBuscarBiblioteca => "Falha ao buscar biblioteca",
382
383 Mensagem::FalhaLerMetadados => "Falha ao ler metadados de:",
385 Mensagem::FalhaDefinirPermissoes => "Falha ao definir permissões em:",
386
387 Mensagem::FalhaBuscarDocumentacao => "Falha ao buscar documentação para:",
389 Mensagem::FalhaCriarClienteHttp => "Falha ao criar cliente HTTP",
390 Mensagem::SemDocumentacaoDisponivel => "Nenhuma documentação disponível",
391 Mensagem::BibliotecaNaoEncontradaApi => {
392 "Biblioteca não encontrada. Verifique o ID via `context7 library <nome>`."
393 }
394 }
395}
396
397#[cfg(test)]
400mod testes {
401 use super::*;
402
403 #[test]
404 fn testa_idioma_atual_padrao_e_english() {
405 let _ = idioma_atual();
408 }
409
410 #[test]
411 fn testa_resolver_idioma_cli_flag_pt() {
412 assert_eq!(resolver_idioma(Some("pt")), Idioma::Portugues);
413 assert_eq!(resolver_idioma(Some("pt-BR")), Idioma::Portugues);
414 assert_eq!(resolver_idioma(Some("PT_BR")), Idioma::Portugues);
415 }
416
417 #[test]
418 fn testa_resolver_idioma_cli_flag_en() {
419 assert_eq!(resolver_idioma(Some("en")), Idioma::English);
420 assert_eq!(resolver_idioma(Some("en-US")), Idioma::English);
421 }
422
423 #[test]
424 fn testa_resolver_idioma_sem_flag_nem_env_retorna_english_ou_pt() {
425 let idioma = resolver_idioma(None);
427 assert!(idioma == Idioma::English || idioma == Idioma::Portugues);
428 }
429
430 #[test]
431 fn testa_t_mensagem_nenhuma_chave_en() {
432 let msg_en = en(Mensagem::NenhumaChaveArmazenada);
433 assert!(!msg_en.is_empty());
434 assert!(
435 msg_en.to_lowercase().contains("no") || msg_en.to_lowercase().contains("key"),
436 "EN deve conter 'no' ou 'key', obteve: {}",
437 msg_en
438 );
439 }
440
441 #[test]
442 fn testa_t_mensagem_nenhuma_chave_pt() {
443 let msg_pt = pt(Mensagem::NenhumaChaveArmazenada);
444 assert!(!msg_pt.is_empty());
445 assert!(
446 msg_pt.to_lowercase().contains("nenhuma") || msg_pt.to_lowercase().contains("chave"),
447 "PT deve conter 'nenhuma' ou 'chave', obteve: {}",
448 msg_pt
449 );
450 }
451
452 #[test]
453 fn testa_confirmacao_resposta_en_contem_y() {
454 let confirmacao = en(Mensagem::RespostaConfirmacao);
455 assert!(
456 confirmacao.contains('y'),
457 "EN: resposta confirmação deve conter 'y'"
458 );
459 }
460
461 #[test]
462 fn testa_confirmacao_resposta_pt_contem_s() {
463 let confirmacao = pt(Mensagem::RespostaConfirmacao);
464 assert!(
465 confirmacao.contains('s'),
466 "PT: resposta confirmação deve conter 's'"
467 );
468 }
469
470 #[test]
471 fn testa_todas_variantes_en_nao_sao_vazias() {
472 let variantes = [
473 Mensagem::ChaveAdicionada,
474 Mensagem::ChaveJaExistia,
475 Mensagem::NenhumaChaveArmazenada,
476 Mensagem::UsarKeysAdd,
477 Mensagem::ContadorChaves,
478 Mensagem::NenhumaChaveParaRemover,
479 Mensagem::IndiceInvalido,
480 Mensagem::ChaveRemovidaSucesso,
481 Mensagem::OperacaoCancelada,
482 Mensagem::TodasChavesRemovidas,
483 Mensagem::SistemaXdgNaoSuportado,
484 Mensagem::ChavesImportadasSucesso,
485 Mensagem::NenhumaChaveContext7NoArquivo,
486 Mensagem::ConfirmarRemoverTodas,
487 Mensagem::RespostaConfirmacao,
488 Mensagem::NenhumaBibliotecaEncontrada,
489 Mensagem::BibliotecasEncontradas,
490 Mensagem::ConfiancaScore,
491 Mensagem::NenhumaDocumentacaoEncontrada,
492 Mensagem::TituloDocumentacao,
493 Mensagem::TituloFontes,
494 Mensagem::SemConteudoDisponivel,
495 Mensagem::BuscandoBiblioteca,
496 Mensagem::ErroDeRede,
497 Mensagem::ErroDeRedeDocs,
498 Mensagem::FalhaDesserializar,
499 Mensagem::RateLimitAtingido,
500 Mensagem::ErrodoServidor,
501 Mensagem::ChaveApiInvalida,
502 Mensagem::Tentativa,
503 Mensagem::AguardandoRetry,
504 Mensagem::ErroCaminhoXdg,
505 Mensagem::FalhaLerConfig,
506 Mensagem::FalhaTomlInvalido,
507 Mensagem::FalhaEscreverConfig,
508 Mensagem::FalhaCriarDiretorio,
509 Mensagem::NenhumaChaveConfigurada,
510 Mensagem::FalhaSerializarToml,
511 Mensagem::ChavesCarregadasEnvVar,
512 Mensagem::ChavesCarregadasXdg,
513 Mensagem::FalhaLerXdgContinuando,
514 Mensagem::IniciandoComChaves,
515 Mensagem::ChavesCarregadasCompileTime,
516 Mensagem::FalhaSerializarJson,
517 Mensagem::FalhaSerializarDocs,
518 Mensagem::FalhaBuscarBiblioteca,
519 Mensagem::FalhaLerMetadados,
520 Mensagem::FalhaDefinirPermissoes,
521 Mensagem::FalhaBuscarDocumentacao,
522 Mensagem::FalhaCriarClienteHttp,
523 Mensagem::SemDocumentacaoDisponivel,
524 Mensagem::BibliotecaNaoEncontradaApi,
525 Mensagem::ChaveVaziaOuInvalida,
526 Mensagem::AvisoFormatoChave,
527 ];
528
529 for v in &variantes {
530 let msg_en = en(*v);
531 let msg_pt = pt(*v);
532 assert!(
533 !msg_en.is_empty(),
534 "EN mensagem vazia para variante {:?}",
535 v
536 );
537 assert!(
538 !msg_pt.is_empty(),
539 "PT mensagem vazia para variante {:?}",
540 v
541 );
542 }
543 }
544}