use std::sync::OnceLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Language {
English,
Portuguese,
}
static GLOBAL_LANGUAGE: OnceLock<Language> = OnceLock::new();
pub fn current_language() -> Language {
*GLOBAL_LANGUAGE.get().unwrap_or(&Language::English)
}
pub fn set_language(language: Language) {
let _ = GLOBAL_LANGUAGE.set(language);
}
#[must_use]
pub fn resolve_language(cli_lang: Option<&str>) -> Language {
if let Some(lang) = cli_lang {
return parse_lang_str(lang);
}
if let Ok(env_lang) = std::env::var("CONTEXT7_LANG") {
return parse_lang_str(&env_lang);
}
if let Some(locale) = sys_locale::get_locale() {
if locale.to_lowercase().starts_with("pt") {
return Language::Portuguese;
}
}
Language::English
}
fn parse_lang_str(s: &str) -> Language {
match s.to_lowercase().as_str() {
"pt" | "pt-br" | "pt_br" | "portugues" | "português" => Language::Portuguese,
_ => Language::English,
}
}
#[derive(Debug, Clone, Copy)]
pub enum Message {
KeyAdded,
KeyAlreadyExisted,
NoStoredKey,
UseKeysAdd,
KeysCount,
NoKeysToRemove,
InvalidIndex,
KeyRemovedSuccess,
OperationCancelled,
AllKeysRemoved,
XdgSystemNotSupported,
KeysImportedSuccess,
NoContext7KeyInFile,
ConfirmRemoveAll,
ConfirmationResponse,
EmptyOrInvalidKey,
KeyFormatWarning,
NoLibraryFound,
LibrariesFound,
TrustScore,
NoDocumentationFound,
DocumentationTitle,
SourcesTitle,
NoContentAvailable,
SearchingLibrary,
NetworkError,
NetworkErrorDocs,
DeserialiseFailure,
RateLimitReached,
ServerError,
InvalidApiKey,
Attempt,
WaitingForRetry,
XdgPathError,
ConfigReadFailure,
InvalidTomlFailure,
ConfigWriteFailure,
DirectoryCreateFailure,
NoKeyConfigured,
TomlSerialiseFailure,
KeysLoadedFromEnvVar,
KeysLoadedFromXdg,
XdgReadFailureContinuing,
StartingWithKeys,
KeysLoadedAtCompileTime,
JsonSerialiseFailure,
DocsSerialiseFailure,
LibrarySearchFailure,
MetadataReadFailure,
PermissionSetFailure,
DocsFetchFailure,
HttpClientCreateFailure,
NoDocumentationAvailable,
LibraryNotFoundApi,
HealthRunning,
HealthConfigOk,
HealthConfigFailed,
HealthKeysOk,
HealthKeysMissing,
HealthApiOk,
HealthApiOffline,
}
impl Message {
pub fn text(self, language: Language) -> &'static str {
match language {
Language::English => en(self),
Language::Portuguese => pt(self),
}
}
}
#[must_use]
pub fn t(msg: Message) -> &'static str {
match current_language() {
Language::English => en(msg),
Language::Portuguese => pt(msg),
}
}
fn en(msg: Message) -> &'static str {
match msg {
Message::KeyAdded => "Key added successfully at:",
Message::KeyAlreadyExisted => "Key already exists (skipping).",
Message::NoStoredKey => "No key stored.",
Message::UseKeysAdd => "Use `context7 keys add <KEY>` to add a key.",
Message::KeysCount => "key(s) stored:",
Message::NoKeysToRemove => "No key stored to remove.",
Message::InvalidIndex => "Invalid index. Use a number between 1 and",
Message::KeyRemovedSuccess => "Key removed successfully.",
Message::OperationCancelled => "Operation cancelled.",
Message::AllKeysRemoved => "All keys removed.",
Message::XdgSystemNotSupported => "System does not support XDG directories.",
Message::KeysImportedSuccess => "key(s) imported successfully.",
Message::NoContext7KeyInFile => "No CONTEXT7_API= key found in:",
Message::ConfirmRemoveAll => "Are you sure you want to remove ALL keys? [y/N] ",
Message::ConfirmationResponse => "y|yes",
Message::EmptyOrInvalidKey => {
"API key cannot be empty. Get a key at https://context7.com"
}
Message::KeyFormatWarning => {
"Warning: key does not match expected format (ctx7sk-...). API calls may fail."
}
Message::NoLibraryFound => "No library found.",
Message::LibrariesFound => "Libraries found:",
Message::TrustScore => "trust",
Message::NoDocumentationFound => "No documentation found.",
Message::DocumentationTitle => "Documentation:",
Message::SourcesTitle => "Sources:",
Message::NoContentAvailable => "No content available.",
Message::SearchingLibrary => "Searching library:",
Message::NetworkError => "Network error searching library:",
Message::NetworkErrorDocs => "Network error fetching documentation:",
Message::DeserialiseFailure => "Failed to deserialise JSON response:",
Message::RateLimitReached => "Rate limit reached (429), waiting for retry…",
Message::ServerError => "Server error, retrying…",
Message::InvalidApiKey => "Invalid API key (401/403), trying next…",
Message::Attempt => "Attempt",
Message::WaitingForRetry => "Waiting before retrying…",
Message::XdgPathError => "System does not support XDG — cannot save configuration",
Message::ConfigReadFailure => "Failed to read XDG config at:",
Message::InvalidTomlFailure => "Invalid TOML at:",
Message::ConfigWriteFailure => "Failed to write config at:",
Message::DirectoryCreateFailure => "Failed to create directory:",
Message::NoKeyConfigured => {
"No API key configured. Set CONTEXT7_API_KEYS or use `context7 keys add <KEY>`."
}
Message::TomlSerialiseFailure => "Failed to serialise configuration to TOML",
Message::KeysLoadedFromEnvVar => {
"Keys loaded from CONTEXT7_API_KEYS environment variable"
}
Message::KeysLoadedFromXdg => "Keys loaded from XDG configuration",
Message::XdgReadFailureContinuing => "Failed to read XDG configuration (continuing):",
Message::StartingWithKeys => "Starting context7 with",
Message::KeysLoadedAtCompileTime => "Keys loaded from compile-time CONTEXT7_API_KEYS",
Message::JsonSerialiseFailure => "Failed to serialise results to JSON",
Message::DocsSerialiseFailure => "Failed to serialise documentation to JSON",
Message::LibrarySearchFailure => "Failed to search library",
Message::MetadataReadFailure => "Failed to read metadata of:",
Message::PermissionSetFailure => "Failed to set permissions on:",
Message::DocsFetchFailure => "Failed to fetch documentation for:",
Message::HttpClientCreateFailure => "Failed to create HTTP client",
Message::NoDocumentationAvailable => "No documentation available",
Message::LibraryNotFoundApi => {
"Library not found. Verify the ID via `context7 library <name>`."
}
Message::HealthRunning => "Running health checks…",
Message::HealthConfigOk => "Config: OK",
Message::HealthConfigFailed => "Config: FAILED",
Message::HealthKeysOk => "API keys configured:",
Message::HealthKeysMissing => "API keys: none configured",
Message::HealthApiOk => "API: reachable",
Message::HealthApiOffline => "API: offline or unreachable",
}
}
fn pt(msg: Message) -> &'static str {
match msg {
Message::KeyAdded => "Chave adicionada com sucesso em:",
Message::KeyAlreadyExisted => "Chave já existente (ignorando).",
Message::NoStoredKey => "Nenhuma chave armazenada.",
Message::UseKeysAdd => "Use `context7 keys add <CHAVE>` para adicionar uma chave.",
Message::KeysCount => "chave(s) armazenada(s):",
Message::NoKeysToRemove => "Nenhuma chave armazenada para remover.",
Message::InvalidIndex => "Índice inválido. Use um número entre 1 e",
Message::KeyRemovedSuccess => "Chave removida com sucesso.",
Message::OperationCancelled => "Operação cancelada.",
Message::AllKeysRemoved => "Todas as chaves foram removidas.",
Message::XdgSystemNotSupported => "Sistema não suporta diretórios XDG.",
Message::KeysImportedSuccess => "chave(s) importada(s) com sucesso.",
Message::NoContext7KeyInFile => "Nenhuma chave CONTEXT7_API= encontrada em:",
Message::ConfirmRemoveAll => "Tem certeza que deseja remover TODAS as chaves? [s/N] ",
Message::ConfirmationResponse => "s|sim",
Message::EmptyOrInvalidKey => {
"Chave de API não pode ser vazia. Obtenha uma em https://context7.com"
}
Message::KeyFormatWarning => {
"Aviso: chave não corresponde ao formato esperado (ctx7sk-...). Chamadas de API podem falhar."
}
Message::NoLibraryFound => "Nenhuma biblioteca encontrada.",
Message::LibrariesFound => "Bibliotecas encontradas:",
Message::TrustScore => "confiança",
Message::NoDocumentationFound => "Nenhuma documentação encontrada.",
Message::DocumentationTitle => "Documentação:",
Message::SourcesTitle => "Fontes:",
Message::NoContentAvailable => "Sem conteúdo disponível.",
Message::SearchingLibrary => "Buscando biblioteca:",
Message::NetworkError => "Erro de rede ao buscar biblioteca:",
Message::NetworkErrorDocs => "Erro de rede ao buscar documentação:",
Message::DeserialiseFailure => "Falha ao desserializar resposta JSON:",
Message::RateLimitReached => "Rate limit atingido (429), aguardando retry…",
Message::ServerError => "Erro do servidor, tentando novamente…",
Message::InvalidApiKey => "Chave de API inválida (401/403), tentando próxima…",
Message::Attempt => "Attempt",
Message::WaitingForRetry => "Aguardando antes de tentar novamente…",
Message::XdgPathError => {
"Sistema não suporta diretórios XDG — impossível salvar configuração"
}
Message::ConfigReadFailure => "Falha ao ler configuração XDG em:",
Message::InvalidTomlFailure => "TOML inválido em:",
Message::ConfigWriteFailure => "Falha ao escrever config em:",
Message::DirectoryCreateFailure => "Falha ao criar diretório:",
Message::NoKeyConfigured => {
"Nenhuma chave de API encontrada. Configure CONTEXT7_API_KEYS ou use `context7 keys add <CHAVE>`."
}
Message::TomlSerialiseFailure => "Falha ao serializar configuração para TOML",
Message::KeysLoadedFromEnvVar => {
"Chaves carregadas via variável de ambiente CONTEXT7_API_KEYS"
}
Message::KeysLoadedFromXdg => "Chaves carregadas via configuração XDG",
Message::XdgReadFailureContinuing => "Falha ao ler configuração XDG (continuando):",
Message::StartingWithKeys => "Iniciando context7 com",
Message::KeysLoadedAtCompileTime => {
"Chaves carregadas via compile-time CONTEXT7_API_KEYS"
}
Message::JsonSerialiseFailure => "Falha ao serializar resultados para JSON",
Message::DocsSerialiseFailure => "Falha ao serializar documentação para JSON",
Message::LibrarySearchFailure => "Falha ao buscar biblioteca",
Message::MetadataReadFailure => "Falha ao ler metadados de:",
Message::PermissionSetFailure => "Falha ao definir permissões em:",
Message::DocsFetchFailure => "Falha ao buscar documentação para:",
Message::HttpClientCreateFailure => "Falha ao criar cliente HTTP",
Message::NoDocumentationAvailable => "Nenhuma documentação disponível",
Message::LibraryNotFoundApi => {
"Biblioteca não encontrada. Verifique o ID via `context7 library <nome>`."
}
Message::HealthRunning => "Executando verificações de saúde…",
Message::HealthConfigOk => "Config: OK",
Message::HealthConfigFailed => "Config: FALHOU",
Message::HealthKeysOk => "Chaves de API configuradas:",
Message::HealthKeysMissing => "Chaves de API: nenhuma configurada",
Message::HealthApiOk => "API: acessível",
Message::HealthApiOffline => "API: offline ou inacessível",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_current_language_default_is_english() {
let _ = current_language();
}
#[test]
fn test_resolve_language_cli_flag_pt() {
assert_eq!(resolve_language(Some("pt")), Language::Portuguese);
assert_eq!(resolve_language(Some("pt-BR")), Language::Portuguese);
assert_eq!(resolve_language(Some("PT_BR")), Language::Portuguese);
}
#[test]
fn test_resolve_language_cli_flag_en() {
assert_eq!(resolve_language(Some("en")), Language::English);
assert_eq!(resolve_language(Some("en-US")), Language::English);
}
#[test]
fn test_resolve_language_no_flag_no_env_returns_english_or_pt() {
let language = resolve_language(None);
assert!(language == Language::English || language == Language::Portuguese);
}
#[test]
fn test_t_message_no_key_en() {
let msg_en = en(Message::NoStoredKey);
assert!(!msg_en.is_empty());
assert!(
msg_en.to_lowercase().contains("no") || msg_en.to_lowercase().contains("key"),
"EN must contain 'no' or 'key', got: {}",
msg_en
);
}
#[test]
fn test_t_message_no_key_pt() {
let msg_pt = pt(Message::NoStoredKey);
assert!(!msg_pt.is_empty());
assert!(
msg_pt.to_lowercase().contains("nenhuma") || msg_pt.to_lowercase().contains("key"),
"PT deve conter 'nenhuma' ou 'key', obteve: {}",
msg_pt
);
}
#[test]
fn test_confirmation_response_en_contains_y() {
let confirmacao = en(Message::ConfirmationResponse);
assert!(
confirmacao.contains('y'),
"EN: confirmation response must contain 'y'"
);
}
#[test]
fn test_confirmation_response_pt_contains_s() {
let confirmacao = pt(Message::ConfirmationResponse);
assert!(
confirmacao.contains('s'),
"PT: confirmation response must contain 's'"
);
}
#[test]
fn test_all_variants_en_not_empty() {
let variantes = [
Message::KeyAdded,
Message::KeyAlreadyExisted,
Message::NoStoredKey,
Message::UseKeysAdd,
Message::KeysCount,
Message::NoKeysToRemove,
Message::InvalidIndex,
Message::KeyRemovedSuccess,
Message::OperationCancelled,
Message::AllKeysRemoved,
Message::XdgSystemNotSupported,
Message::KeysImportedSuccess,
Message::NoContext7KeyInFile,
Message::ConfirmRemoveAll,
Message::ConfirmationResponse,
Message::NoLibraryFound,
Message::LibrariesFound,
Message::TrustScore,
Message::NoDocumentationFound,
Message::DocumentationTitle,
Message::SourcesTitle,
Message::NoContentAvailable,
Message::SearchingLibrary,
Message::NetworkError,
Message::NetworkErrorDocs,
Message::DeserialiseFailure,
Message::RateLimitReached,
Message::ServerError,
Message::InvalidApiKey,
Message::Attempt,
Message::WaitingForRetry,
Message::XdgPathError,
Message::ConfigReadFailure,
Message::InvalidTomlFailure,
Message::ConfigWriteFailure,
Message::DirectoryCreateFailure,
Message::NoKeyConfigured,
Message::TomlSerialiseFailure,
Message::KeysLoadedFromEnvVar,
Message::KeysLoadedFromXdg,
Message::XdgReadFailureContinuing,
Message::StartingWithKeys,
Message::KeysLoadedAtCompileTime,
Message::JsonSerialiseFailure,
Message::DocsSerialiseFailure,
Message::LibrarySearchFailure,
Message::MetadataReadFailure,
Message::PermissionSetFailure,
Message::DocsFetchFailure,
Message::HttpClientCreateFailure,
Message::NoDocumentationAvailable,
Message::LibraryNotFoundApi,
Message::EmptyOrInvalidKey,
Message::KeyFormatWarning,
Message::HealthRunning,
Message::HealthConfigOk,
Message::HealthConfigFailed,
Message::HealthKeysOk,
Message::HealthKeysMissing,
Message::HealthApiOk,
Message::HealthApiOffline,
];
for v in &variantes {
let msg_en = en(*v);
let msg_pt = pt(*v);
assert!(
!msg_en.is_empty(),
"EN message vazia para variante {:?}",
v
);
assert!(
!msg_pt.is_empty(),
"PT message vazia para variante {:?}",
v
);
}
}
}