1use std::ffi::OsString;
2
3use serde::Deserialize;
4
5use clap::ValueEnum;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)]
8#[serde(rename_all = "lowercase")]
9pub enum Language {
10 En,
11 Zh,
12}
13
14impl Language {
15 pub fn detect(
16 explicit: Option<Language>,
17 lc_all: Option<&str>,
18 lc_messages: Option<&str>,
19 lang: Option<&str>,
20 ) -> Self {
21 explicit.unwrap_or_else(|| {
22 let locale = first_non_empty([lc_all, lc_messages, lang]);
23 if locale.map(locale_indicates_chinese).unwrap_or(false) {
24 Language::Zh
25 } else {
26 Language::En
27 }
28 })
29 }
30
31 pub fn is_chinese(self) -> bool {
32 matches!(self, Language::Zh)
33 }
34}
35
36pub fn detect_language_from_args_and_env(
37 args: &[OsString],
38 lc_all: Option<&str>,
39 lc_messages: Option<&str>,
40 lang: Option<&str>,
41) -> Language {
42 Language::detect(parse_language_override(args), lc_all, lc_messages, lang)
43}
44
45pub(crate) fn parse_language_override(args: &[OsString]) -> Option<Language> {
46 let mut iter = args.iter().skip(1);
47 while let Some(arg) = iter.next() {
48 let value = arg.to_string_lossy();
49 if let Some(raw) = value.strip_prefix("--lang=") {
50 return parse_language_value(raw);
51 }
52 if value == "--lang" {
53 if let Some(next) = iter.next() {
54 return parse_language_value(&next.to_string_lossy());
55 }
56 }
57 }
58 None
59}
60
61pub(crate) fn parse_language_value(value: &str) -> Option<Language> {
62 if value.eq_ignore_ascii_case("zh") {
63 Some(Language::Zh)
64 } else if value.eq_ignore_ascii_case("en") {
65 Some(Language::En)
66 } else {
67 None
68 }
69}
70
71fn first_non_empty<const N: usize>(values: [Option<&str>; N]) -> Option<&str> {
72 values
73 .into_iter()
74 .flatten()
75 .map(str::trim)
76 .find(|value| !value.is_empty())
77}
78
79fn locale_indicates_chinese(locale: &str) -> bool {
80 locale.to_ascii_lowercase().contains("zh")
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn language_defaults_to_english_when_locale_is_not_chinese() {
89 assert_eq!(
90 Language::detect(None, None, None, Some("en_US.UTF-8")),
91 Language::En
92 );
93 }
94
95 #[test]
96 fn language_switches_to_chinese_from_locale() {
97 assert_eq!(
98 Language::detect(None, None, None, Some("zh_CN.UTF-8")),
99 Language::Zh
100 );
101 }
102
103 #[test]
104 fn explicit_language_overrides_locale() {
105 assert_eq!(
106 Language::detect(Some(Language::En), Some("zh_CN.UTF-8"), None, None),
107 Language::En
108 );
109 }
110}