1use std::sync::OnceLock;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum Language {
19 English,
21 Portuguese,
23}
24
25static GLOBAL_LANGUAGE: OnceLock<Language> = OnceLock::new();
27
28pub fn current_language() -> Language {
32 *GLOBAL_LANGUAGE.get().unwrap_or(&Language::English)
33}
34
35pub fn set_language(language: Language) {
37 let _ = GLOBAL_LANGUAGE.set(language);
38}
39
40#[must_use]
48pub fn resolve_language(cli_lang: Option<&str>) -> Language {
49 if let Some(lang) = cli_lang {
51 return parse_lang_str(lang);
52 }
53
54 if let Ok(env_lang) = std::env::var("CONTEXT7_LANG") {
56 return parse_lang_str(&env_lang);
57 }
58
59 if let Some(locale) = sys_locale::get_locale() {
61 if locale.to_lowercase().starts_with("pt") {
62 return Language::Portuguese;
63 }
64 }
65
66 Language::English
68}
69
70fn parse_lang_str(s: &str) -> Language {
71 match s.to_lowercase().as_str() {
72 "pt" | "pt-br" | "pt_br" | "portugues" | "português" => Language::Portuguese,
73 _ => Language::English,
74 }
75}
76
77#[derive(Debug, Clone, Copy)]
83pub enum Message {
84 KeyAdded,
87 KeyAlreadyExisted,
89 NoStoredKey,
91 UseKeysAdd,
93 KeysCount,
95 NoKeysToRemove,
97 InvalidIndex,
99 KeyRemovedSuccess,
101 OperationCancelled,
103 AllKeysRemoved,
105 XdgSystemNotSupported,
107 KeysImportedSuccess,
109 NoContext7KeyInFile,
111 ConfirmRemoveAll,
113 ConfirmationResponse,
115 EmptyOrInvalidKey,
117 KeyFormatWarning,
119
120 NoLibraryFound,
123 LibrariesFound,
125 TrustScore,
127 NoDocumentationFound,
129 DocumentationTitle,
131 SourcesTitle,
133 NoContentAvailable,
135 SearchingLibrary,
137
138 NetworkError,
141 NetworkErrorDocs,
143 DeserialiseFailure,
145 RateLimitReached,
147 ServerError,
149 InvalidApiKey,
151 Attempt,
153 WaitingForRetry,
155
156 XdgPathError,
159 ConfigReadFailure,
161 InvalidTomlFailure,
163 ConfigWriteFailure,
165 DirectoryCreateFailure,
167 NoKeyConfigured,
169 TomlSerialiseFailure,
171
172 KeysLoadedFromEnvVar,
175 KeysLoadedFromXdg,
177 XdgReadFailureContinuing,
179 StartingWithKeys,
181 KeysLoadedAtCompileTime,
183 JsonSerialiseFailure,
185 DocsSerialiseFailure,
187 LibrarySearchFailure,
189
190 MetadataReadFailure,
193 PermissionSetFailure,
195
196 DocsFetchFailure,
199 HttpClientCreateFailure,
201 NoDocumentationAvailable,
203 LibraryNotFoundApi,
205
206 HealthRunning,
209 HealthConfigOk,
211 HealthConfigFailed,
213 HealthKeysOk,
215 HealthKeysMissing,
217 HealthApiOk,
219 HealthApiOffline,
221}
222
223impl Message {
224 pub fn text(self, language: Language) -> &'static str {
230 match language {
231 Language::English => en(self),
232 Language::Portuguese => pt(self),
233 }
234 }
235}
236
237#[must_use]
241pub fn t(msg: Message) -> &'static str {
242 match current_language() {
243 Language::English => en(msg),
244 Language::Portuguese => pt(msg),
245 }
246}
247
248fn en(msg: Message) -> &'static str {
249 match msg {
250 Message::KeyAdded => "Key added successfully at:",
252 Message::KeyAlreadyExisted => "Key already exists (skipping).",
253 Message::NoStoredKey => "No key stored.",
254 Message::UseKeysAdd => "Use `context7 keys add <KEY>` to add a key.",
255 Message::KeysCount => "key(s) stored:",
256 Message::NoKeysToRemove => "No key stored to remove.",
257 Message::InvalidIndex => "Invalid index. Use a number between 1 and",
258 Message::KeyRemovedSuccess => "Key removed successfully.",
259 Message::OperationCancelled => "Operation cancelled.",
260 Message::AllKeysRemoved => "All keys removed.",
261 Message::XdgSystemNotSupported => "System does not support XDG directories.",
262 Message::KeysImportedSuccess => "key(s) imported successfully.",
263 Message::NoContext7KeyInFile => "No CONTEXT7_API= key found in:",
264 Message::ConfirmRemoveAll => "Are you sure you want to remove ALL keys? [y/N] ",
265 Message::ConfirmationResponse => "y|yes",
266 Message::EmptyOrInvalidKey => {
267 "API key cannot be empty. Get a key at https://context7.com"
268 }
269 Message::KeyFormatWarning => {
270 "Warning: key does not match expected format (ctx7sk-...). API calls may fail."
271 }
272
273 Message::NoLibraryFound => "No library found.",
275 Message::LibrariesFound => "Libraries found:",
276 Message::TrustScore => "trust",
277 Message::NoDocumentationFound => "No documentation found.",
278 Message::DocumentationTitle => "Documentation:",
279 Message::SourcesTitle => "Sources:",
280 Message::NoContentAvailable => "No content available.",
281 Message::SearchingLibrary => "Searching library:",
282
283 Message::NetworkError => "Network error searching library:",
285 Message::NetworkErrorDocs => "Network error fetching documentation:",
286 Message::DeserialiseFailure => "Failed to deserialise JSON response:",
287 Message::RateLimitReached => "Rate limit reached (429), waiting for retry…",
288 Message::ServerError => "Server error, retrying…",
289 Message::InvalidApiKey => "Invalid API key (401/403), trying next…",
290 Message::Attempt => "Attempt",
291 Message::WaitingForRetry => "Waiting before retrying…",
292
293 Message::XdgPathError => "System does not support XDG — cannot save configuration",
295 Message::ConfigReadFailure => "Failed to read XDG config at:",
296 Message::InvalidTomlFailure => "Invalid TOML at:",
297 Message::ConfigWriteFailure => "Failed to write config at:",
298 Message::DirectoryCreateFailure => "Failed to create directory:",
299 Message::NoKeyConfigured => {
300 "No API key configured. Set CONTEXT7_API_KEYS or use `context7 keys add <KEY>`."
301 }
302 Message::TomlSerialiseFailure => "Failed to serialise configuration to TOML",
303
304 Message::KeysLoadedFromEnvVar => {
306 "Keys loaded from CONTEXT7_API_KEYS environment variable"
307 }
308 Message::KeysLoadedFromXdg => "Keys loaded from XDG configuration",
309 Message::XdgReadFailureContinuing => "Failed to read XDG configuration (continuing):",
310 Message::StartingWithKeys => "Starting context7 with",
311 Message::KeysLoadedAtCompileTime => "Keys loaded from compile-time CONTEXT7_API_KEYS",
312 Message::JsonSerialiseFailure => "Failed to serialise results to JSON",
313 Message::DocsSerialiseFailure => "Failed to serialise documentation to JSON",
314 Message::LibrarySearchFailure => "Failed to search library",
315
316 Message::MetadataReadFailure => "Failed to read metadata of:",
318 Message::PermissionSetFailure => "Failed to set permissions on:",
319
320 Message::DocsFetchFailure => "Failed to fetch documentation for:",
322 Message::HttpClientCreateFailure => "Failed to create HTTP client",
323 Message::NoDocumentationAvailable => "No documentation available",
324 Message::LibraryNotFoundApi => {
325 "Library not found. Verify the ID via `context7 library <name>`."
326 }
327
328 Message::HealthRunning => "Running health checks…",
330 Message::HealthConfigOk => "Config: OK",
331 Message::HealthConfigFailed => "Config: FAILED",
332 Message::HealthKeysOk => "API keys configured:",
333 Message::HealthKeysMissing => "API keys: none configured",
334 Message::HealthApiOk => "API: reachable",
335 Message::HealthApiOffline => "API: offline or unreachable",
336 }
337}
338
339fn pt(msg: Message) -> &'static str {
340 match msg {
341 Message::KeyAdded => "Chave adicionada com sucesso em:",
343 Message::KeyAlreadyExisted => "Chave já existente (ignorando).",
344 Message::NoStoredKey => "Nenhuma chave armazenada.",
345 Message::UseKeysAdd => "Use `context7 keys add <CHAVE>` para adicionar uma chave.",
346 Message::KeysCount => "chave(s) armazenada(s):",
347 Message::NoKeysToRemove => "Nenhuma chave armazenada para remover.",
348 Message::InvalidIndex => "Índice inválido. Use um número entre 1 e",
349 Message::KeyRemovedSuccess => "Chave removida com sucesso.",
350 Message::OperationCancelled => "Operação cancelada.",
351 Message::AllKeysRemoved => "Todas as chaves foram removidas.",
352 Message::XdgSystemNotSupported => "Sistema não suporta diretórios XDG.",
353 Message::KeysImportedSuccess => "chave(s) importada(s) com sucesso.",
354 Message::NoContext7KeyInFile => "Nenhuma chave CONTEXT7_API= encontrada em:",
355 Message::ConfirmRemoveAll => "Tem certeza que deseja remover TODAS as chaves? [s/N] ",
356 Message::ConfirmationResponse => "s|sim",
357 Message::EmptyOrInvalidKey => {
358 "Chave de API não pode ser vazia. Obtenha uma em https://context7.com"
359 }
360 Message::KeyFormatWarning => {
361 "Aviso: chave não corresponde ao formato esperado (ctx7sk-...). Chamadas de API podem falhar."
362 }
363
364 Message::NoLibraryFound => "Nenhuma biblioteca encontrada.",
366 Message::LibrariesFound => "Bibliotecas encontradas:",
367 Message::TrustScore => "confiança",
368 Message::NoDocumentationFound => "Nenhuma documentação encontrada.",
369 Message::DocumentationTitle => "Documentação:",
370 Message::SourcesTitle => "Fontes:",
371 Message::NoContentAvailable => "Sem conteúdo disponível.",
372 Message::SearchingLibrary => "Buscando biblioteca:",
373
374 Message::NetworkError => "Erro de rede ao buscar biblioteca:",
376 Message::NetworkErrorDocs => "Erro de rede ao buscar documentação:",
377 Message::DeserialiseFailure => "Falha ao desserializar resposta JSON:",
378 Message::RateLimitReached => "Rate limit atingido (429), aguardando retry…",
379 Message::ServerError => "Erro do servidor, tentando novamente…",
380 Message::InvalidApiKey => "Chave de API inválida (401/403), tentando próxima…",
381 Message::Attempt => "Attempt",
382 Message::WaitingForRetry => "Aguardando antes de tentar novamente…",
383
384 Message::XdgPathError => {
386 "Sistema não suporta diretórios XDG — impossível salvar configuração"
387 }
388 Message::ConfigReadFailure => "Falha ao ler configuração XDG em:",
389 Message::InvalidTomlFailure => "TOML inválido em:",
390 Message::ConfigWriteFailure => "Falha ao escrever config em:",
391 Message::DirectoryCreateFailure => "Falha ao criar diretório:",
392 Message::NoKeyConfigured => {
393 "Nenhuma chave de API encontrada. Configure CONTEXT7_API_KEYS ou use `context7 keys add <CHAVE>`."
394 }
395 Message::TomlSerialiseFailure => "Falha ao serializar configuração para TOML",
396
397 Message::KeysLoadedFromEnvVar => {
399 "Chaves carregadas via variável de ambiente CONTEXT7_API_KEYS"
400 }
401 Message::KeysLoadedFromXdg => "Chaves carregadas via configuração XDG",
402 Message::XdgReadFailureContinuing => "Falha ao ler configuração XDG (continuando):",
403 Message::StartingWithKeys => "Iniciando context7 com",
404 Message::KeysLoadedAtCompileTime => {
405 "Chaves carregadas via compile-time CONTEXT7_API_KEYS"
406 }
407 Message::JsonSerialiseFailure => "Falha ao serializar resultados para JSON",
408 Message::DocsSerialiseFailure => "Falha ao serializar documentação para JSON",
409 Message::LibrarySearchFailure => "Falha ao buscar biblioteca",
410
411 Message::MetadataReadFailure => "Falha ao ler metadados de:",
413 Message::PermissionSetFailure => "Falha ao definir permissões em:",
414
415 Message::DocsFetchFailure => "Falha ao buscar documentação para:",
417 Message::HttpClientCreateFailure => "Falha ao criar cliente HTTP",
418 Message::NoDocumentationAvailable => "Nenhuma documentação disponível",
419 Message::LibraryNotFoundApi => {
420 "Biblioteca não encontrada. Verifique o ID via `context7 library <nome>`."
421 }
422
423 Message::HealthRunning => "Executando verificações de saúde…",
425 Message::HealthConfigOk => "Config: OK",
426 Message::HealthConfigFailed => "Config: FALHOU",
427 Message::HealthKeysOk => "Chaves de API configuradas:",
428 Message::HealthKeysMissing => "Chaves de API: nenhuma configurada",
429 Message::HealthApiOk => "API: acessível",
430 Message::HealthApiOffline => "API: offline ou inacessível",
431 }
432}
433
434#[cfg(test)]
437mod tests {
438 use super::*;
439
440 #[test]
441 fn test_current_language_default_is_english() {
442 let _ = current_language();
444 }
445
446 #[test]
447 fn test_resolve_language_cli_flag_pt() {
448 assert_eq!(resolve_language(Some("pt")), Language::Portuguese);
449 assert_eq!(resolve_language(Some("pt-BR")), Language::Portuguese);
450 assert_eq!(resolve_language(Some("PT_BR")), Language::Portuguese);
451 }
452
453 #[test]
454 fn test_resolve_language_cli_flag_en() {
455 assert_eq!(resolve_language(Some("en")), Language::English);
456 assert_eq!(resolve_language(Some("en-US")), Language::English);
457 }
458
459 #[test]
460 fn test_resolve_language_no_flag_no_env_returns_english_or_pt() {
461 let language = resolve_language(None);
463 assert!(language == Language::English || language == Language::Portuguese);
464 }
465
466 #[test]
467 fn test_t_message_no_key_en() {
468 let msg_en = en(Message::NoStoredKey);
469 assert!(!msg_en.is_empty());
470 assert!(
471 msg_en.to_lowercase().contains("no") || msg_en.to_lowercase().contains("key"),
472 "EN must contain 'no' or 'key', got: {}",
473 msg_en
474 );
475 }
476
477 #[test]
478 fn test_t_message_no_key_pt() {
479 let msg_pt = pt(Message::NoStoredKey);
480 assert!(!msg_pt.is_empty());
481 assert!(
482 msg_pt.to_lowercase().contains("nenhuma") || msg_pt.to_lowercase().contains("key"),
483 "PT deve conter 'nenhuma' ou 'key', obteve: {}",
484 msg_pt
485 );
486 }
487
488 #[test]
489 fn test_confirmation_response_en_contains_y() {
490 let confirmacao = en(Message::ConfirmationResponse);
491 assert!(
492 confirmacao.contains('y'),
493 "EN: confirmation response must contain 'y'"
494 );
495 }
496
497 #[test]
498 fn test_confirmation_response_pt_contains_s() {
499 let confirmacao = pt(Message::ConfirmationResponse);
500 assert!(
501 confirmacao.contains('s'),
502 "PT: confirmation response must contain 's'"
503 );
504 }
505
506 #[test]
507 fn test_all_variants_en_not_empty() {
508 let variantes = [
509 Message::KeyAdded,
510 Message::KeyAlreadyExisted,
511 Message::NoStoredKey,
512 Message::UseKeysAdd,
513 Message::KeysCount,
514 Message::NoKeysToRemove,
515 Message::InvalidIndex,
516 Message::KeyRemovedSuccess,
517 Message::OperationCancelled,
518 Message::AllKeysRemoved,
519 Message::XdgSystemNotSupported,
520 Message::KeysImportedSuccess,
521 Message::NoContext7KeyInFile,
522 Message::ConfirmRemoveAll,
523 Message::ConfirmationResponse,
524 Message::NoLibraryFound,
525 Message::LibrariesFound,
526 Message::TrustScore,
527 Message::NoDocumentationFound,
528 Message::DocumentationTitle,
529 Message::SourcesTitle,
530 Message::NoContentAvailable,
531 Message::SearchingLibrary,
532 Message::NetworkError,
533 Message::NetworkErrorDocs,
534 Message::DeserialiseFailure,
535 Message::RateLimitReached,
536 Message::ServerError,
537 Message::InvalidApiKey,
538 Message::Attempt,
539 Message::WaitingForRetry,
540 Message::XdgPathError,
541 Message::ConfigReadFailure,
542 Message::InvalidTomlFailure,
543 Message::ConfigWriteFailure,
544 Message::DirectoryCreateFailure,
545 Message::NoKeyConfigured,
546 Message::TomlSerialiseFailure,
547 Message::KeysLoadedFromEnvVar,
548 Message::KeysLoadedFromXdg,
549 Message::XdgReadFailureContinuing,
550 Message::StartingWithKeys,
551 Message::KeysLoadedAtCompileTime,
552 Message::JsonSerialiseFailure,
553 Message::DocsSerialiseFailure,
554 Message::LibrarySearchFailure,
555 Message::MetadataReadFailure,
556 Message::PermissionSetFailure,
557 Message::DocsFetchFailure,
558 Message::HttpClientCreateFailure,
559 Message::NoDocumentationAvailable,
560 Message::LibraryNotFoundApi,
561 Message::EmptyOrInvalidKey,
562 Message::KeyFormatWarning,
563 Message::HealthRunning,
564 Message::HealthConfigOk,
565 Message::HealthConfigFailed,
566 Message::HealthKeysOk,
567 Message::HealthKeysMissing,
568 Message::HealthApiOk,
569 Message::HealthApiOffline,
570 ];
571
572 for v in &variantes {
573 let msg_en = en(*v);
574 let msg_pt = pt(*v);
575 assert!(
576 !msg_en.is_empty(),
577 "EN message vazia para variante {:?}",
578 v
579 );
580 assert!(
581 !msg_pt.is_empty(),
582 "PT message vazia para variante {:?}",
583 v
584 );
585 }
586 }
587}