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