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