Skip to main content

dodo_zh/
variant.rs

1use crate::cedict::{Dictionary, Item};
2use crate::Error;
3use std::collections::HashMap;
4use std::path::PathBuf;
5use std::sync::OnceLock;
6
7// Static variable to handle the different versions of the dictionaries.
8pub(crate) static SIMPLIFIED: OnceLock<HashMap<String, Item>> = OnceLock::new();
9pub(crate) static TRADTIONAL: OnceLock<HashMap<String, Item>> = OnceLock::new();
10
11/// KeyVariant handle the different supported version of chinese.
12#[derive(Debug, PartialEq, Clone)]
13pub enum KeyVariant {
14    Simplified,
15    Traditional,
16}
17
18impl Default for KeyVariant {
19    fn default() -> Self {
20        Self::Simplified
21    }
22}
23
24/// Initialize Dictionaries for simplified & traditional chinese based on the given cedict file path
25///
26/// # Arguments
27///
28/// * `path` - &PathBuf
29pub(crate) fn initialize_dictionaries(path: &PathBuf) -> Result<(), Error> {
30    let simplified = Dictionary::new(path, KeyVariant::Simplified)?;
31    let traditional = Dictionary::new(path, KeyVariant::Traditional)?;
32
33    SIMPLIFIED.get_or_init(|| simplified.items);
34    TRADTIONAL.get_or_init(|| traditional.items);
35
36    Ok(())
37}
38
39impl KeyVariant {
40    /// Convert a text to a desired variant e.g: simplified -> traditional
41    ///
42    /// # Arguments
43    ///
44    /// * `text` - S
45    /// * `input_variant` - KeyVariant
46    /// * `target_variant`- KeyVariant
47    pub(crate) fn convert_text_to_desired_variant<S: AsRef<str>>(
48        text: S,
49        input_variant: Self,
50        target_variant: Self,
51    ) -> Option<String> {
52        // Split the content as a list of character
53        let chars = text.as_ref().chars();
54
55        // Use the variant that needed for the conversion
56        let dictionary = match input_variant {
57            KeyVariant::Simplified => SIMPLIFIED.get()?,
58            KeyVariant::Traditional => TRADTIONAL.get()?,
59        };
60
61        let mut rebuild_content: Vec<String> = Vec::new();
62        // Rebuild the list of character into the target variant
63        for c in chars {
64            match dictionary.get(&c.to_string()) {
65                Some(character) => {
66                    rebuild_content.push(character.get_character_for_key_variant(&target_variant))
67                }
68                // We assume that this may be a non chinese character e.g: space or number.
69                None => rebuild_content.push(c.to_string()),
70            }
71        }
72
73        Some(rebuild_content.join(""))
74    }
75
76    /// Which variant returns the variant of chinese that the text has been written on
77    ///
78    /// # Arguments
79    ///
80    /// * `text` - S
81    pub(crate) fn which_variant<S: AsRef<str>>(text: S) -> Option<Self> {
82        // Get the simplified dictionary
83        let (simplified_dict, traditional_dict) = SIMPLIFIED.get().zip(TRADTIONAL.get())?;
84
85        let characters = text.as_ref().chars();
86        for ch in characters {
87            let str_char = ch.to_string();
88            // Once we found that the variant is traditional. We directly returns the new variant.
89            if simplified_dict.get(&str_char).is_none() && traditional_dict.get(&str_char).is_some()
90            {
91                return Some(Self::Traditional);
92            }
93        }
94
95        Some(Self::Simplified)
96    }
97
98    /// Detect the chinese character variant by using Unicode
99    ///
100    /// # Arguments
101    ///
102    /// * `content` - S
103    pub(crate) fn detect_variant_with_unicode<S: AsRef<str>>(content: S) -> Self {
104        let characters = content.as_ref().chars();
105
106        for c in characters {
107            match c {
108                '\u{3400}'..='\u{4DBF}' => return Self::Traditional,
109                '\u{20000}'..='\u{2A6DF}' => return Self::Traditional,
110                '\u{2A700}'..='\u{2B73F}' => return Self::Traditional,
111                '\u{2B740}'..='\u{2B81F}' => return Self::Traditional,
112                '\u{2B820}'..='\u{2CEAF}' => return Self::Traditional,
113                '\u{2CEB0}'..='\u{2EBEF}' => return Self::Traditional,
114                _ => {}
115            }
116        }
117
118        Self::default()
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use std::path::PathBuf;
126
127    #[test]
128    fn expect_to_transform_traditional_to_simplified() {
129        let text = "她是我的最好摯友";
130
131        let res = super::initialize_dictionaries(&PathBuf::from("../static/cedict_sample_ts.u8"));
132        assert!(res.is_ok());
133
134        let converted = super::KeyVariant::convert_text_to_desired_variant(
135            text,
136            KeyVariant::Traditional,
137            KeyVariant::Simplified,
138        )
139        .unwrap();
140
141        assert_eq!(converted, "她是我的最好挚友");
142    }
143
144    #[test]
145    fn expect_to_convert_simplified_into_traditional() {
146        let text = "她是我的最好挚友";
147
148        let res = super::initialize_dictionaries(&PathBuf::from("../static/cedict_sample_ts.u8"));
149        assert!(res.is_ok());
150
151        let converted = super::KeyVariant::convert_text_to_desired_variant(
152            text,
153            KeyVariant::Simplified,
154            KeyVariant::Traditional,
155        )
156        .unwrap();
157
158        assert_eq!(converted, "她是我的最好摯友");
159    }
160
161    #[test]
162    fn expect_to_detect_traditional() {
163        let res = initialize_dictionaries(&PathBuf::from("../static/cedict_sample_ts.u8"));
164        assert!(res.is_ok());
165
166        let res = super::KeyVariant::which_variant("她是我的最好摯友");
167        assert_eq!(res.unwrap(), KeyVariant::Traditional);
168    }
169
170    #[test]
171    fn expect_to_detect_simplified() {
172        let res = initialize_dictionaries(&PathBuf::from("../static/cedict_sample_ts.u8"));
173        assert!(res.is_ok());
174
175        let res = super::KeyVariant::which_variant("她是我的最好挚友");
176        assert_eq!(res.unwrap(), KeyVariant::Simplified);
177    }
178
179    #[test]
180    fn expect_to_detect_traditional_with_unicode() {
181        let res = super::KeyVariant::detect_variant_with_unicode("這個牛肉的顏色是𫞩的");
182        assert_eq!(res, KeyVariant::Traditional);
183    }
184}