1#[derive(Copy, Clone, Debug, Eq, PartialEq)]
4pub enum ConversionDirection {
5 RuToEn,
6 EnToRu,
7}
8
9fn is_latin_letter(ch: char) -> bool {
10 ch.is_ascii_alphabetic()
11}
12
13fn is_cyrillic_letter(ch: char) -> bool {
14 matches!(ch, 'А'..='Я' | 'а'..='я' | 'Ё' | 'ё')
15}
16
17fn map_ru_to_en(ch: char) -> char {
18 match ch {
19 ',' => '?',
21 '.' => '/',
22 '?' => '&',
23
24 '"' => '@', '№' => '#', ';' => '$', ':' => '^', 'й' => 'q',
31 'ц' => 'w',
32 'у' => 'e',
33 'к' => 'r',
34 'е' => 't',
35 'н' => 'y',
36 'г' => 'u',
37 'ш' => 'i',
38 'щ' => 'o',
39 'з' => 'p',
40 'х' => '[',
41 'ъ' => ']',
42 'ф' => 'a',
43 'ы' => 's',
44 'в' => 'd',
45 'а' => 'f',
46 'п' => 'g',
47 'р' => 'h',
48 'о' => 'j',
49 'л' => 'k',
50 'д' => 'l',
51 'ж' => ';',
52 'э' => '\'',
53 'я' => 'z',
54 'ч' => 'x',
55 'с' => 'c',
56 'м' => 'v',
57 'и' => 'b',
58 'т' => 'n',
59 'ь' => 'm',
60 'б' => ',',
61 'ю' => '.',
62 'ё' => '`',
63
64 'Й' => 'Q',
65 'Ц' => 'W',
66 'У' => 'E',
67 'К' => 'R',
68 'Е' => 'T',
69 'Н' => 'Y',
70 'Г' => 'U',
71 'Ш' => 'I',
72 'Щ' => 'O',
73 'З' => 'P',
74 'Х' => '{',
75 'Ъ' => '}',
76 'Ф' => 'A',
77 'Ы' => 'S',
78 'В' => 'D',
79 'А' => 'F',
80 'П' => 'G',
81 'Р' => 'H',
82 'О' => 'J',
83 'Л' => 'K',
84 'Д' => 'L',
85 'Ж' => ':',
86 'Э' => '"',
87 'Я' => 'Z',
88 'Ч' => 'X',
89 'С' => 'C',
90 'М' => 'V',
91 'И' => 'B',
92 'Т' => 'N',
93 'Ь' => 'M',
94 'Б' => '<',
95 'Ю' => '>',
96 'Ё' => '~',
97 _ => ch,
98 }
99}
100
101fn map_en_to_ru(ch: char) -> char {
102 match ch {
103 'q' => 'й',
105 'w' => 'ц',
106 'e' => 'у',
107 'r' => 'к',
108 't' => 'е',
109 'y' => 'н',
110 'u' => 'г',
111 'i' => 'ш',
112 'o' => 'щ',
113 'p' => 'з',
114 '[' => 'х',
115 ']' => 'ъ',
116 'a' => 'ф',
117 's' => 'ы',
118 'd' => 'в',
119 'f' => 'а',
120 'g' => 'п',
121 'h' => 'р',
122 'j' => 'о',
123 'k' => 'л',
124 'l' => 'д',
125 ';' => 'ж',
126 '\'' => 'э',
127 'z' => 'я',
128 'x' => 'ч',
129 'c' => 'с',
130 'v' => 'м',
131 'b' => 'и',
132 'n' => 'т',
133 'm' => 'ь',
134 ',' => 'б',
135 '.' => 'ю',
136 '`' => 'ё',
137
138 '?' => ',',
140 '/' => '.',
141 '&' => '?',
142
143 '@' => '"', '#' => '№', '$' => ';', '^' => ':', 'Q' => 'Й',
152 'W' => 'Ц',
153 'E' => 'У',
154 'R' => 'К',
155 'T' => 'Е',
156 'Y' => 'Н',
157 'U' => 'Г',
158 'I' => 'Ш',
159 'O' => 'Щ',
160 'P' => 'З',
161 '{' => 'Х',
162 '}' => 'Ъ',
163 'A' => 'Ф',
164 'S' => 'Ы',
165 'D' => 'В',
166 'F' => 'А',
167 'G' => 'П',
168 'H' => 'Р',
169 'J' => 'О',
170 'K' => 'Л',
171 'L' => 'Д',
172 ':' => 'Ж',
173 '"' => 'Э',
174 'Z' => 'Я',
175 'X' => 'Ч',
176 'C' => 'С',
177 'V' => 'М',
178 'B' => 'И',
179 'N' => 'Т',
180 'M' => 'Ь',
181 '<' => 'Б',
182 '>' => 'Ю',
183 '~' => 'Ё',
184 _ => ch,
185 }
186}
187
188fn letter_counts(text: &str) -> (usize, usize) {
189 let mut cyr = 0usize;
190 let mut lat = 0usize;
191 for ch in text.chars() {
192 if is_cyrillic_letter(ch) {
193 cyr += 1;
194 } else if is_latin_letter(ch) {
195 lat += 1;
196 }
197 }
198 (cyr, lat)
199}
200
201pub fn conversion_direction_for_text(text: &str) -> Option<ConversionDirection> {
205 let (cyr, lat) = letter_counts(text);
206 match cyr.cmp(&lat) {
207 std::cmp::Ordering::Greater => Some(ConversionDirection::RuToEn),
208 std::cmp::Ordering::Less => Some(ConversionDirection::EnToRu),
209 std::cmp::Ordering::Equal => None,
210 }
211}
212
213pub fn convert_ru_en_with_direction(text: &str, direction: ConversionDirection) -> String {
215 let mut out = String::with_capacity(text.len());
216 match direction {
217 ConversionDirection::RuToEn => {
218 for ch in text.chars() {
219 out.push(map_ru_to_en(ch));
220 }
221 }
222 ConversionDirection::EnToRu => {
223 for ch in text.chars() {
224 out.push(map_en_to_ru(ch));
225 }
226 }
227 }
228 out
229}
230
231pub fn convert_ru_en_bidirectional(text: &str) -> String {
234 let direction = conversion_direction_for_text(text).unwrap_or(ConversionDirection::RuToEn);
235 convert_ru_en_with_direction(text, direction)
236}