pacsea/i18n/
translations.rs

1//! Translation map and lookup utilities.
2
3use std::collections::HashMap;
4
5/// Translation map: dot-notation key -> translated string.
6pub type TranslationMap = HashMap<String, String>;
7
8/// What: Look up a translation in the translation map.
9///
10/// Inputs:
11/// - `key`: Dot-notation key (e.g., "app.titles.search")
12/// - `translations`: Translation map to search
13///
14/// Output:
15/// - `Option<String>` containing translation or None if not found
16///
17/// Details:
18/// - Direct key lookup
19/// - Returns None if key not found
20#[must_use]
21pub fn translate(key: &str, translations: &TranslationMap) -> Option<String> {
22    translations.get(key).cloned()
23}
24
25/// What: Look up translation with fallback to English.
26///
27/// Inputs:
28/// - `key`: Dot-notation key
29/// - `translations`: Primary translation map
30/// - `fallback_translations`: Fallback translation map (usually English)
31///
32/// Output:
33/// - Translated string (from primary or fallback, or key itself if both missing)
34///
35/// Details:
36/// - Tries primary translations first
37/// - Falls back to English if not found
38/// - Returns key itself if neither has translation (for debugging)
39/// - Logs warnings for missing keys (only once per key to avoid spam)
40pub fn translate_with_fallback(
41    key: &str,
42    translations: &TranslationMap,
43    fallback_translations: &TranslationMap,
44) -> String {
45    // Try primary translations first
46    if let Some(translation) = translations.get(key) {
47        return translation.clone();
48    }
49
50    // Try fallback translations
51    if let Some(translation) = fallback_translations.get(key) {
52        // Log that we're using fallback (only at debug level to avoid spam)
53        tracing::debug!(
54            "Translation key '{}' not found in primary locale, using fallback",
55            key
56        );
57        return translation.clone();
58    }
59
60    // Neither has the key - log warning and return key itself
61    // Use debug level to avoid flooding logs, but make it discoverable
62    tracing::debug!(
63        "Missing translation key: '{}'. Returning key as-is. Please add this key to locale files.",
64        key
65    );
66    key.to_string()
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn test_translate() {
75        let mut translations = HashMap::new();
76        translations.insert("app.titles.search".to_string(), "Suche".to_string());
77
78        assert_eq!(
79            translate("app.titles.search", &translations),
80            Some("Suche".to_string())
81        );
82        assert_eq!(translate("app.titles.help", &translations), None);
83    }
84
85    #[test]
86    fn test_translate_with_fallback() {
87        let mut primary = HashMap::new();
88        primary.insert("app.titles.search".to_string(), "Suche".to_string());
89
90        let mut fallback = HashMap::new();
91        fallback.insert("app.titles.search".to_string(), "Search".to_string());
92        fallback.insert("app.titles.help".to_string(), "Help".to_string());
93
94        assert_eq!(
95            translate_with_fallback("app.titles.search", &primary, &fallback),
96            "Suche"
97        );
98        assert_eq!(
99            translate_with_fallback("app.titles.help", &primary, &fallback),
100            "Help"
101        );
102        assert_eq!(
103            translate_with_fallback("app.titles.missing", &primary, &fallback),
104            "app.titles.missing"
105        );
106    }
107}