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