1use std::{error::Error, fmt, str::FromStr};
4
5#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
11#[repr(u8)]
12pub enum Language {
13 #[default]
15 Auto,
16 Arabic,
18 Chinese,
20 English,
22 Esperanto,
24 Finnish,
26 French,
28 German,
30 Greek,
32 Hebrew,
34 Hindi,
36 Indonesian,
38 Korean,
40 Portuguese,
42 Romanian,
44 Russian,
46 Spanish,
48 Tagalog,
50 Vietnamese,
52}
53
54impl Language {
55 pub const ALL: [Self; 19] = [
57 Self::Auto,
58 Self::Arabic,
59 Self::Chinese,
60 Self::English,
61 Self::Esperanto,
62 Self::Finnish,
63 Self::French,
64 Self::German,
65 Self::Greek,
66 Self::Hebrew,
67 Self::Hindi,
68 Self::Indonesian,
69 Self::Korean,
70 Self::Portuguese,
71 Self::Romanian,
72 Self::Russian,
73 Self::Spanish,
74 Self::Tagalog,
75 Self::Vietnamese,
76 ];
77
78 pub const SUPPORTED: [Self; 14] = [
80 Self::Auto,
81 Self::Arabic,
82 Self::Chinese,
83 Self::English,
84 Self::French,
85 Self::German,
86 Self::Hebrew,
87 Self::Hindi,
88 Self::Indonesian,
89 Self::Korean,
90 Self::Portuguese,
91 Self::Russian,
92 Self::Spanish,
93 Self::Tagalog,
94 ];
95
96 #[must_use]
98 pub const fn display_name(self) -> &'static str {
99 match self {
100 Self::Auto => "Auto",
101 Self::Arabic => "Arabic",
102 Self::Chinese => "Chinese",
103 Self::English => "English",
104 Self::Esperanto => "Esperanto",
105 Self::Finnish => "Finnish",
106 Self::French => "French",
107 Self::German => "German",
108 Self::Greek => "Greek",
109 Self::Hebrew => "Hebrew",
110 Self::Hindi => "Hindi",
111 Self::Indonesian => "Indonesian",
112 Self::Korean => "Korean",
113 Self::Portuguese => "Portuguese",
114 Self::Romanian => "Romanian",
115 Self::Russian => "Russian",
116 Self::Spanish => "Spanish",
117 Self::Tagalog => "Tagalog",
118 Self::Vietnamese => "Vietnamese",
119 }
120 }
121
122 #[must_use]
124 pub const fn code(self) -> &'static str {
125 match self {
126 Self::Auto => "auto",
127 Self::Arabic => "ar",
128 Self::Chinese => "zh",
129 Self::English => "en",
130 Self::Esperanto => "eo",
131 Self::Finnish => "fi",
132 Self::French => "fr",
133 Self::German => "de",
134 Self::Greek => "el",
135 Self::Hebrew => "he",
136 Self::Hindi => "hi",
137 Self::Indonesian => "id",
138 Self::Korean => "ko",
139 Self::Portuguese => "pt",
140 Self::Romanian => "ro",
141 Self::Russian => "ru",
142 Self::Spanish => "es",
143 Self::Tagalog => "tl",
144 Self::Vietnamese => "vi",
145 }
146 }
147
148 #[must_use]
150 pub const fn identifier_prefix(self) -> &'static str {
151 match self {
152 Self::Auto => "auto",
153 Self::Arabic => "arb",
154 Self::Chinese => "zho",
155 Self::English => "eng",
156 Self::Esperanto => "epo",
157 Self::Finnish => "fin",
158 Self::French => "fra",
159 Self::German => "deu",
160 Self::Greek => "ell",
161 Self::Hebrew => "heb",
162 Self::Hindi => "hin",
163 Self::Indonesian => "ind",
164 Self::Korean => "kor",
165 Self::Portuguese => "por",
166 Self::Romanian => "ron",
167 Self::Russian => "rus",
168 Self::Spanish => "spa",
169 Self::Tagalog => "tgl",
170 Self::Vietnamese => "vie",
171 }
172 }
173
174 #[must_use]
176 pub const fn aliases(self) -> &'static [&'static str] {
177 match self {
178 Self::Auto => &["all", "global"],
179 Self::Tagalog => &["fil"],
180 _ => &[],
181 }
182 }
183
184 pub fn all_aliases(self) -> impl Iterator<Item = &'static str> {
189 [
190 self.variant_name(),
191 self.display_name(),
192 self.code(),
193 self.identifier_prefix(),
194 ]
195 .into_iter()
196 .chain(self.aliases().iter().copied())
197 }
198
199 #[must_use]
201 pub const fn is_parsing_supported(self) -> bool {
202 matches!(
203 self,
204 Self::Auto
205 | Self::Arabic
206 | Self::Chinese
207 | Self::English
208 | Self::French
209 | Self::German
210 | Self::Hebrew
211 | Self::Hindi
212 | Self::Indonesian
213 | Self::Korean
214 | Self::Portuguese
215 | Self::Russian
216 | Self::Spanish
217 | Self::Tagalog
218 )
219 }
220
221 #[must_use]
223 pub const fn is_auto(self) -> bool {
224 matches!(self, Self::Auto)
225 }
226
227 const fn variant_name(self) -> &'static str {
228 match self {
229 Self::Auto => "auto",
230 Self::Arabic => "arabic",
231 Self::Chinese => "chinese",
232 Self::English => "english",
233 Self::Esperanto => "esperanto",
234 Self::Finnish => "finnish",
235 Self::French => "french",
236 Self::German => "german",
237 Self::Greek => "greek",
238 Self::Hebrew => "hebrew",
239 Self::Hindi => "hindi",
240 Self::Indonesian => "indonesian",
241 Self::Korean => "korean",
242 Self::Portuguese => "portuguese",
243 Self::Romanian => "romanian",
244 Self::Russian => "russian",
245 Self::Spanish => "spanish",
246 Self::Tagalog => "tagalog",
247 Self::Vietnamese => "vietnamese",
248 }
249 }
250
251 fn matches_identifier(self, identifier: &str) -> bool {
252 self.all_aliases()
253 .any(|alias| alias.eq_ignore_ascii_case(identifier))
254 }
255}
256
257impl fmt::Display for Language {
258 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
259 formatter.write_str(self.display_name())
260 }
261}
262
263impl FromStr for Language {
264 type Err = ParseLanguageError;
265
266 fn from_str(value: &str) -> Result<Self, Self::Err> {
267 let normalized = value.trim();
268 if normalized.is_empty() {
269 return Err(ParseLanguageError::new(value));
270 }
271
272 let prefix = normalized
273 .split(['-', '_'])
274 .next()
275 .expect("a non-empty string always has a first segment");
276
277 Self::ALL
278 .into_iter()
279 .find(|language| {
280 language.matches_identifier(normalized) || language.matches_identifier(prefix)
281 })
282 .ok_or_else(|| ParseLanguageError::new(value))
283 }
284}
285
286impl TryFrom<&str> for Language {
287 type Error = ParseLanguageError;
288
289 fn try_from(value: &str) -> Result<Self, Self::Error> {
290 value.parse()
291 }
292}
293
294impl AsRef<str> for Language {
295 fn as_ref(&self) -> &str {
296 self.code()
297 }
298}
299
300#[derive(Clone, Debug, Eq, PartialEq)]
302pub struct ParseLanguageError {
303 input: String,
304}
305
306impl ParseLanguageError {
307 fn new(input: &str) -> Self {
308 Self {
309 input: input.to_owned(),
310 }
311 }
312
313 #[must_use]
315 pub fn input(&self) -> &str {
316 &self.input
317 }
318}
319
320impl fmt::Display for ParseLanguageError {
321 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
322 if self.input.trim().is_empty() {
323 formatter.write_str("language must be a non-empty string")
324 } else {
325 write!(formatter, "unknown language: {}", self.input)
326 }
327 }
328}
329
330impl Error for ParseLanguageError {}
331
332#[cfg(test)]
333#[path = "../tests/unit/language.rs"]
334mod tests;