sqlite_graphrag/i18n/
mod.rs1use std::sync::OnceLock;
12
13#[derive(Copy, Clone, Debug, PartialEq, Eq, clap::ValueEnum)]
15pub enum Language {
16 #[value(name = "en", aliases = ["english", "EN"])]
18 English,
19 #[value(name = "pt", aliases = ["portugues", "portuguese", "pt-BR", "pt-br", "PT"])]
21 Portuguese,
22}
23
24impl Language {
25 pub fn from_str_opt(s: &str) -> Option<Self> {
28 match s.to_lowercase().as_str() {
29 "en" | "english" => Some(Language::English),
30 "pt" | "pt-br" | "portugues" | "portuguese" => Some(Language::Portuguese),
31 _ => None,
32 }
33 }
34
35 pub fn from_env_or_locale() -> Self {
37 if let Ok(Some(v)) = crate::config::get_setting("i18n.lang") {
39 if !v.is_empty() {
40 let lower = v.to_lowercase();
41 if lower.starts_with("pt") {
42 return Language::Portuguese;
43 }
44 if lower.starts_with("en") {
45 return Language::English;
46 }
47 tracing::warn!(target: "i18n",
48 value = %v,
49 "i18n.lang setting not recognized, falling back to OS locale"
50 );
51 }
52 }
53 for var in ["LC_ALL", "LC_MESSAGES", "LANG"] {
62 if let Ok(v) = std::env::var(var) {
63 if v.is_empty() {
64 continue;
65 }
66 let lower = v.to_lowercase();
67 if lower.starts_with("pt") {
68 return Language::Portuguese;
69 }
70 if lower.starts_with("en") {
71 return Language::English;
72 }
73 if var == "LC_ALL" {
76 return Language::English;
77 }
78 }
79 }
80 if let Some(locale) = sys_locale::get_locale() {
83 let lower = locale.to_lowercase();
84 if lower.starts_with("pt") {
85 return Language::Portuguese;
86 }
87 if lower.starts_with("en") {
88 return Language::English;
89 }
90 }
91 Language::English
92 }
93}
94
95static GLOBAL_LANGUAGE: OnceLock<Language> = OnceLock::new();
96
97pub fn init(explicit: Option<Language>) {
106 if GLOBAL_LANGUAGE.get().is_some() {
107 return;
108 }
109 let resolved = explicit.unwrap_or_else(Language::from_env_or_locale);
110 let _ = GLOBAL_LANGUAGE.set(resolved);
111}
112
113pub fn current() -> Language {
115 *GLOBAL_LANGUAGE.get_or_init(Language::from_env_or_locale)
116}
117
118pub fn tr(en: &'static str, pt: &'static str) -> &'static str {
126 match current() {
127 Language::English => en,
128 Language::Portuguese => pt,
129 }
130}
131
132pub fn relations_pruned(count: usize, relation: &str, namespace: &str) -> String {
138 format!("pruned {count} '{relation}' relationships in namespace '{namespace}'")
139}
140
141pub fn prune_dry_run(count: usize, relation: &str) -> String {
145 format!("dry run: {count} '{relation}' relationships would be removed")
146}
147
148pub fn prune_requires_yes() -> String {
152 "destructive operation requires --yes flag; use --dry-run to preview".to_string()
153}
154
155pub fn error_prefix() -> &'static str {
157 match current() {
158 Language::English => "Error",
159 Language::Portuguese => "Erro",
160 }
161}
162
163pub mod errors_msg {
170 pub fn memory_not_found(name: &str, namespace: &str) -> String {
172 format!("memory '{name}' not found in namespace '{namespace}'")
173 }
174
175 pub fn memory_or_entity_not_found(name: &str, namespace: &str) -> String {
177 format!("memory or entity '{name}' not found in namespace '{namespace}'")
178 }
179
180 pub fn database_not_found(path: &str) -> String {
182 format!("database not found at {path}. Run 'sqlite-graphrag init' first.")
183 }
184
185 pub fn entity_not_found(name: &str, namespace: &str) -> String {
187 format!("entity \"{name}\" does not exist in namespace \"{namespace}\"")
188 }
189
190 pub fn relationship_not_found(de: &str, rel: &str, para: &str, namespace: &str) -> String {
192 format!(
193 "relationship \"{de}\" --[{rel}]--> \"{para}\" does not exist in namespace \"{namespace}\""
194 )
195 }
196
197 pub fn duplicate_memory(name: &str, namespace: &str) -> String {
199 format!(
200 "memory '{name}' already exists in namespace '{namespace}'. Use --force-merge to update."
201 )
202 }
203
204 pub fn duplicate_memory_soft_deleted(name: &str, namespace: &str) -> String {
206 format!(
207 "memory '{name}' exists but is soft-deleted in namespace '{namespace}'; \
208 use --force-merge to restore and update, or `restore` to revive it"
209 )
210 }
211
212 pub fn optimistic_lock_conflict(expected: i64, current_ts: i64) -> String {
214 format!(
215 "optimistic lock conflict: expected updated_at={expected}, but current is {current_ts}"
216 )
217 }
218
219 pub fn version_not_found(version: i64, name: &str) -> String {
221 format!("version {version} not found for memory '{name}'")
222 }
223
224 pub fn no_recall_results(max_distance: f32, query: &str, namespace: &str) -> String {
226 format!(
227 "no results within --max-distance {max_distance} for query '{query}' in namespace '{namespace}'"
228 )
229 }
230
231 pub fn soft_deleted_memory_not_found(name: &str, namespace: &str) -> String {
233 format!("soft-deleted memory '{name}' not found in namespace '{namespace}'")
234 }
235
236 pub fn concurrent_process_conflict() -> String {
238 "optimistic lock conflict: memory was modified by another process".to_string()
239 }
240
241 pub fn entity_limit_exceeded(max: usize) -> String {
243 format!("entities exceed limit of {max}")
244 }
245
246 pub fn relationship_limit_exceeded(max: usize) -> String {
248 format!("relationships exceed limit of {max}")
249 }
250}
251
252pub mod errors_ops {
268 use super::{current, Language};
269
270 pub fn sqlite_busy_after_retries(max_retries: u32) -> String {
272 match current() {
273 Language::English => format!("SQLITE_BUSY after {max_retries} retries"),
274 Language::Portuguese => format!("SQLITE_BUSY após {max_retries} tentativas"),
275 }
276 }
277
278 pub fn llm_slot_acquire_timeout(wait_secs: u64, max_concurrent: u32) -> String {
280 match current() {
281 Language::English => format!(
282 "failed to acquire LLM slot within {wait_secs}s (max={max_concurrent} concurrent)"
283 ),
284 Language::Portuguese => format!(
285 "não foi possível obter um slot de LLM em {wait_secs}s (máx={max_concurrent} concorrentes)"
286 ),
287 }
288 }
289
290 pub fn memory_modified_concurrently(name: &str) -> String {
293 match current() {
294 Language::English => format!("memory '{name}' was modified concurrently; retry"),
295 Language::Portuguese => {
296 format!("memória '{name}' foi modificada concorrentemente; tente novamente")
297 }
298 }
299 }
300
301 pub fn rename_target_occupied(new_name: &str, memory_id: i64) -> String {
303 match current() {
304 Language::English => format!(
305 "target name '{new_name}' is already occupied by active memory id {memory_id}"
306 ),
307 Language::Portuguese => format!(
308 "o nome de destino '{new_name}' já está ocupado pela memória ativa id {memory_id}"
309 ),
310 }
311 }
312
313 pub fn active_namespace_limit_reached(max: u32, namespace: &str) -> String {
318 match current() {
319 Language::English => format!(
320 "active namespace limit of {max} reached while trying to create '{namespace}'"
321 ),
322 Language::Portuguese => {
323 format!("limite de {max} namespaces ativos atingido ao tentar criar '{namespace}'")
324 }
325 }
326 }
327
328 pub fn name_prefix_exceeds_name_cap(prefix_len: usize, cap: usize) -> String {
330 match current() {
331 Language::English => format!(
332 "--name-prefix is {prefix_len} chars; prefixed names would exceed the \
333 {cap}-char name cap (MAX_MEMORY_NAME_LEN)"
334 ),
335 Language::Portuguese => format!(
336 "--name-prefix tem {prefix_len} caracteres; nomes prefixados excederiam o \
337 teto de {cap} caracteres (MAX_MEMORY_NAME_LEN)"
338 ),
339 }
340 }
341
342 pub fn allocation_would_exceed_memory(capacity: usize, what: &str) -> String {
347 match current() {
348 Language::English => {
349 format!("allocation of {capacity} {what} would exceed available memory")
350 }
351 Language::Portuguese => {
352 format!("a alocação de {capacity} {what} excederia a memória disponível")
353 }
354 }
355 }
356
357 pub fn alloc_label_slot_metadata() -> &'static str {
359 super::tr("slot metadata entries", "entradas de metadados de slot")
360 }
361
362 pub fn alloc_label_process_items() -> &'static str {
364 super::tr("process items", "itens de processamento")
365 }
366
367 pub fn alloc_label_truncation_entries() -> &'static str {
369 super::tr("truncation entries", "entradas de truncamento")
370 }
371
372 pub fn duplicate_body_hash(hash_id: i64, name: &str) -> String {
375 match current() {
376 Language::English => format!(
377 "identical body already stored as memory id {hash_id} \
378 (dedup by body_hash); skipping '{name}'"
379 ),
380 Language::Portuguese => format!(
381 "corpo idêntico já armazenado como memória id {hash_id} \
382 (dedup por body_hash); ignorando '{name}'"
383 ),
384 }
385 }
386
387 pub fn batch_embedding_count_mismatch(vectors: usize, texts: usize) -> String {
390 match current() {
391 Language::English => {
392 format!("batch embedding returned {vectors} vectors for {texts} texts")
393 }
394 Language::Portuguese => {
395 format!("o embedding em lote retornou {vectors} vetores para {texts} textos")
396 }
397 }
398 }
399}
400
401pub mod validation;
403
404#[cfg(test)]
405mod tests;