Skip to main content

slack_rs/auth/
i18n.rs

1//! Internationalization messages for auth commands
2
3use std::collections::HashMap;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum Language {
7    English,
8    Japanese,
9}
10
11impl Language {
12    pub fn from_code(code: &str) -> Option<Self> {
13        match code {
14            "en" | "EN" => Some(Language::English),
15            "ja" | "JA" => Some(Language::Japanese),
16            _ => None,
17        }
18    }
19}
20
21/// Message catalog for export/import operations
22pub struct Messages {
23    lang: Language,
24    messages: HashMap<&'static str, (&'static str, &'static str)>,
25}
26
27impl Messages {
28    pub fn new(lang: Language) -> Self {
29        let mut messages = HashMap::new();
30
31        // Warning messages
32        messages.insert(
33            "warn.export_sensitive",
34            (
35                "WARNING: You are about to export sensitive authentication data.\n\
36                 This file will contain access tokens that can be used to access your Slack workspaces.\n\
37                 Store this file securely and delete it after use.",
38                "警告: 機密認証情報をエクスポートしようとしています。\n\
39                 このファイルには Slack ワークスペースへのアクセスに使用できるアクセストークンが含まれます。\n\
40                 このファイルは安全に保管し、使用後は削除してください。"
41            ),
42        );
43
44        messages.insert(
45            "warn.export_skipped",
46            (
47                "Warning: The following profiles were skipped due to missing tokens:",
48                "警告: 以下のプロファイルはトークンが見つからないためスキップされました:",
49            ),
50        );
51
52        // Prompt messages
53        messages.insert(
54            "prompt.passphrase",
55            ("Enter passphrase: ", "パスフレーズを入力してください: "),
56        );
57
58        messages.insert(
59            "prompt.passphrase_confirm",
60            ("Confirm passphrase: ", "パスフレーズを再入力してください: "),
61        );
62
63        // Error messages
64        messages.insert(
65            "error.bad_permissions",
66            (
67                "Error: File must have 0600 permissions (owner read/write only)",
68                "エラー: ファイルは 0600 パーミッション(所有者の読み書きのみ)である必要があります"
69            ),
70        );
71
72        messages.insert(
73            "error.passphrase_mismatch",
74            (
75                "Error: Passphrases do not match",
76                "エラー: パスフレーズが一致しません",
77            ),
78        );
79
80        messages.insert(
81            "error.empty_passphrase",
82            (
83                "Error: Empty passphrase not allowed",
84                "エラー: 空のパスフレーズは許可されていません",
85            ),
86        );
87
88        messages.insert(
89            "error.confirmation_required",
90            (
91                "Error: Export requires --yes flag for confirmation",
92                "エラー: エクスポートには --yes フラグによる確認が必要です",
93            ),
94        );
95
96        messages.insert(
97            "error.profile_exists",
98            (
99                "Error: Profile already exists (use --force to overwrite)",
100                "エラー: プロファイルがすでに存在します(上書きするには --force を使用してください)"
101            ),
102        );
103
104        // Success messages
105        messages.insert(
106            "success.export",
107            (
108                "✓ Profiles exported successfully",
109                "✓ プロファイルのエクスポートが完了しました",
110            ),
111        );
112
113        messages.insert(
114            "success.import",
115            (
116                "✓ Profiles imported successfully",
117                "✓ プロファイルのインポートが完了しました",
118            ),
119        );
120
121        // Info messages
122        messages.insert(
123            "info.export_count",
124            (
125                "Exporting {count} profile(s)",
126                "{count} 件のプロファイルをエクスポート中",
127            ),
128        );
129
130        messages.insert(
131            "info.import_count",
132            (
133                "Importing {count} profile(s)",
134                "{count} 件のプロファイルをインポート中",
135            ),
136        );
137
138        messages.insert(
139            "info.export_summary",
140            (
141                "Export summary: {exported} profile(s) exported, {skipped} profile(s) skipped",
142                "エクスポート概要: {exported} 件のプロファイルをエクスポート、{skipped} 件をスキップ"
143            ),
144        );
145
146        Self { lang, messages }
147    }
148
149    pub fn get(&self, key: &str) -> &'static str {
150        match self.messages.get(key) {
151            Some(&(en, ja)) => match self.lang {
152                Language::English => en,
153                Language::Japanese => ja,
154            },
155            None => "",
156        }
157    }
158
159    #[allow(dead_code)]
160    pub fn format(&self, key: &str, replacements: &[(&str, &str)]) -> String {
161        let template = self.get(key);
162        let mut result = template.to_string();
163        for (placeholder, value) in replacements {
164            result = result.replace(&format!("{{{}}}", placeholder), value);
165        }
166        result
167    }
168}
169
170impl Default for Messages {
171    fn default() -> Self {
172        // Default to Japanese based on locale, or English if not detected
173        let lang = std::env::var("LANG")
174            .ok()
175            .map(|lang| {
176                if lang.starts_with("ja") {
177                    Language::Japanese
178                } else {
179                    Language::English
180                }
181            })
182            .unwrap_or(Language::English);
183
184        Self::new(lang)
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[test]
193    fn test_messages_english() {
194        let messages = Messages::new(Language::English);
195        assert!(messages.get("warn.export_sensitive").starts_with("WARNING"));
196        assert!(messages.get("success.export").contains("exported"));
197    }
198
199    #[test]
200    fn test_messages_japanese() {
201        let messages = Messages::new(Language::Japanese);
202        assert!(messages.get("warn.export_sensitive").starts_with("警告"));
203        assert!(messages.get("success.export").contains("エクスポート"));
204    }
205
206    #[test]
207    fn test_format_message() {
208        let messages = Messages::new(Language::English);
209        let formatted = messages.format("info.export_count", &[("count", "3")]);
210        assert!(formatted.contains("3"));
211        assert!(formatted.contains("profile"));
212    }
213
214    #[test]
215    fn test_language_from_code() {
216        assert_eq!(Language::from_code("en"), Some(Language::English));
217        assert_eq!(Language::from_code("ja"), Some(Language::Japanese));
218        assert_eq!(Language::from_code("EN"), Some(Language::English));
219        assert_eq!(Language::from_code("JA"), Some(Language::Japanese));
220        assert_eq!(Language::from_code("fr"), None);
221    }
222}