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