1use 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
21pub 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 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 messages.insert(
54 "prompt.passphrase",
55 ("Enter passphrase: ", "パスフレーズを入力してください: "),
56 );
57
58 messages.insert(
59 "prompt.passphrase_confirm",
60 ("Confirm passphrase: ", "パスフレーズを再入力してください: "),
61 );
62
63 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 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 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 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}