use std::sync::OnceLock;
#[derive(Copy, Clone, Debug, PartialEq, Eq, clap::ValueEnum)]
pub enum Language {
#[value(name = "en", aliases = ["english", "EN"])]
English,
#[value(name = "pt", aliases = ["portugues", "portuguese", "pt-BR", "pt-br", "PT"])]
Portuguese,
}
impl Language {
pub fn from_str_opt(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"en" | "english" => Some(Language::English),
"pt" | "pt-br" | "portugues" | "portuguese" => Some(Language::Portuguese),
_ => None,
}
}
pub fn from_env_or_locale() -> Self {
if let Ok(Some(v)) = crate::config::get_setting("i18n.lang") {
if !v.is_empty() {
let lower = v.to_lowercase();
if lower.starts_with("pt") {
return Language::Portuguese;
}
if lower.starts_with("en") {
return Language::English;
}
tracing::warn!(target: "i18n",
value = %v,
"i18n.lang setting not recognized, falling back to OS locale"
);
}
}
for var in ["LC_ALL", "LC_MESSAGES", "LANG"] {
if let Ok(v) = std::env::var(var) {
if v.is_empty() {
continue;
}
let lower = v.to_lowercase();
if lower.starts_with("pt") {
return Language::Portuguese;
}
if lower.starts_with("en") {
return Language::English;
}
if var == "LC_ALL" {
return Language::English;
}
}
}
if let Some(locale) = sys_locale::get_locale() {
let lower = locale.to_lowercase();
if lower.starts_with("pt") {
return Language::Portuguese;
}
if lower.starts_with("en") {
return Language::English;
}
}
Language::English
}
}
static GLOBAL_LANGUAGE: OnceLock<Language> = OnceLock::new();
pub fn init(explicit: Option<Language>) {
if GLOBAL_LANGUAGE.get().is_some() {
return;
}
let resolved = explicit.unwrap_or_else(Language::from_env_or_locale);
let _ = GLOBAL_LANGUAGE.set(resolved);
}
pub fn current() -> Language {
*GLOBAL_LANGUAGE.get_or_init(Language::from_env_or_locale)
}
pub fn tr(en: &'static str, pt: &'static str) -> &'static str {
match current() {
Language::English => en,
Language::Portuguese => pt,
}
}
pub fn relations_pruned(count: usize, relation: &str, namespace: &str) -> String {
format!("pruned {count} '{relation}' relationships in namespace '{namespace}'")
}
pub fn prune_dry_run(count: usize, relation: &str) -> String {
format!("dry run: {count} '{relation}' relationships would be removed")
}
pub fn prune_requires_yes() -> String {
"destructive operation requires --yes flag; use --dry-run to preview".to_string()
}
pub fn error_prefix() -> &'static str {
match current() {
Language::English => "Error",
Language::Portuguese => "Erro",
}
}
pub mod errors_msg {
pub fn memory_not_found(nome: &str, namespace: &str) -> String {
format!("memory '{nome}' not found in namespace '{namespace}'")
}
pub fn memory_or_entity_not_found(name: &str, namespace: &str) -> String {
format!("memory or entity '{name}' not found in namespace '{namespace}'")
}
pub fn database_not_found(path: &str) -> String {
format!("database not found at {path}. Run 'sqlite-graphrag init' first.")
}
pub fn entity_not_found(nome: &str, namespace: &str) -> String {
format!("entity \"{nome}\" does not exist in namespace \"{namespace}\"")
}
pub fn relationship_not_found(de: &str, rel: &str, para: &str, namespace: &str) -> String {
format!(
"relationship \"{de}\" --[{rel}]--> \"{para}\" does not exist in namespace \"{namespace}\""
)
}
pub fn duplicate_memory(nome: &str, namespace: &str) -> String {
format!(
"memory '{nome}' already exists in namespace '{namespace}'. Use --force-merge to update."
)
}
pub fn duplicate_memory_soft_deleted(name: &str, namespace: &str) -> String {
format!(
"memory '{name}' exists but is soft-deleted in namespace '{namespace}'; \
use --force-merge to restore and update, or `restore` to revive it"
)
}
pub fn optimistic_lock_conflict(expected: i64, current_ts: i64) -> String {
format!(
"optimistic lock conflict: expected updated_at={expected}, but current is {current_ts}"
)
}
pub fn version_not_found(versao: i64, nome: &str) -> String {
format!("version {versao} not found for memory '{nome}'")
}
pub fn no_recall_results(max_distance: f32, query: &str, namespace: &str) -> String {
format!(
"no results within --max-distance {max_distance} for query '{query}' in namespace '{namespace}'"
)
}
pub fn soft_deleted_memory_not_found(nome: &str, namespace: &str) -> String {
format!("soft-deleted memory '{nome}' not found in namespace '{namespace}'")
}
pub fn concurrent_process_conflict() -> String {
"optimistic lock conflict: memory was modified by another process".to_string()
}
pub fn entity_limit_exceeded(max: usize) -> String {
format!("entities exceed limit of {max}")
}
pub fn relationship_limit_exceeded(max: usize) -> String {
format!("relationships exceed limit of {max}")
}
}
pub mod validation;
#[cfg(test)]
mod tests;