|
1 | 1 | using System; |
2 | 2 | using System.Collections.Concurrent; |
3 | 3 | using System.Collections.Generic; |
4 | | -using System.Linq; |
| 4 | +using System.Collections.ObjectModel; |
| 5 | +using System.IO; |
5 | 6 | using System.Text; |
6 | | -using JetBrains.Annotations; |
| 7 | +using System.Text.Json; |
| 8 | +using CommunityToolkit.Mvvm.DependencyInjection; |
7 | 9 | using Flow.Launcher.Infrastructure.UserSettings; |
8 | 10 | using ToolGood.Words.Pinyin; |
9 | | -using CommunityToolkit.Mvvm.DependencyInjection; |
| 11 | +using Flow.Launcher.Infrastructure.Logger; |
10 | 12 |
|
11 | 13 | namespace Flow.Launcher.Infrastructure |
12 | 14 | { |
13 | | - public class TranslationMapping |
| 15 | + public class PinyinAlphabet : IAlphabet |
14 | 16 | { |
15 | | - private bool constructed; |
| 17 | + private ConcurrentDictionary<string, (string translation, TranslationMapping map)> _pinyinCache = |
| 18 | + new(); |
16 | 19 |
|
17 | | - private List<int> originalIndexs = new List<int>(); |
18 | | - private List<int> translatedIndexs = new List<int>(); |
19 | | - private int translatedLength = 0; |
| 20 | + private readonly Settings _settings; |
20 | 21 |
|
21 | | - public string key { get; private set; } |
| 22 | + private ReadOnlyDictionary<string, string> currentDoublePinyinTable; |
22 | 23 |
|
23 | | - public void setKey(string key) |
| 24 | + public PinyinAlphabet() |
24 | 25 | { |
25 | | - this.key = key; |
| 26 | + _settings = Ioc.Default.GetRequiredService<Settings>(); |
| 27 | + LoadDoublePinyinTable(); |
| 28 | + |
| 29 | + _settings.PropertyChanged += (sender, e) => |
| 30 | + { |
| 31 | + if (e.PropertyName == nameof(Settings.UseDoublePinyin) || |
| 32 | + e.PropertyName == nameof(Settings.DoublePinyinSchema)) |
| 33 | + { |
| 34 | + Reload(); |
| 35 | + } |
| 36 | + }; |
26 | 37 | } |
27 | 38 |
|
28 | | - public void AddNewIndex(int originalIndex, int translatedIndex, int length) |
| 39 | + public void Reload() |
29 | 40 | { |
30 | | - if (constructed) |
31 | | - throw new InvalidOperationException("Mapping shouldn't be changed after constructed"); |
32 | | - |
33 | | - originalIndexs.Add(originalIndex); |
34 | | - translatedIndexs.Add(translatedIndex); |
35 | | - translatedIndexs.Add(translatedIndex + length); |
36 | | - translatedLength += length - 1; |
| 41 | + LoadDoublePinyinTable(); |
| 42 | + _pinyinCache.Clear(); |
37 | 43 | } |
38 | 44 |
|
39 | | - public int MapToOriginalIndex(int translatedIndex) |
| 45 | + private void CreateDoublePinyinTableFromStream(Stream jsonStream) |
40 | 46 | { |
41 | | - if (translatedIndex > translatedIndexs.Last()) |
42 | | - return translatedIndex - translatedLength - 1; |
43 | | - |
44 | | - int lowerBound = 0; |
45 | | - int upperBound = originalIndexs.Count - 1; |
46 | | - |
47 | | - int count = 0; |
48 | | - |
49 | | - // Corner case handle |
50 | | - if (translatedIndex < translatedIndexs[0]) |
51 | | - return translatedIndex; |
52 | | - if (translatedIndex > translatedIndexs.Last()) |
| 47 | + Dictionary<string, Dictionary<string, string>> table = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, string>>>(jsonStream); |
| 48 | + string schemaKey = _settings.DoublePinyinSchema.ToString(); // Convert enum to string |
| 49 | + if (!table.TryGetValue(schemaKey, out var value)) |
53 | 50 | { |
54 | | - int indexDef = 0; |
55 | | - for (int k = 0; k < originalIndexs.Count; k++) |
56 | | - { |
57 | | - indexDef += translatedIndexs[k * 2 + 1] - translatedIndexs[k * 2]; |
58 | | - } |
59 | | - |
60 | | - return translatedIndex - indexDef - 1; |
| 51 | + throw new ArgumentException("DoublePinyinSchema is invalid or double pinyin table is broken."); |
61 | 52 | } |
| 53 | + currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(value); |
| 54 | + } |
62 | 55 |
|
63 | | - // Binary Search with Range |
64 | | - for (int i = originalIndexs.Count / 2;; count++) |
| 56 | + private void LoadDoublePinyinTable() |
| 57 | + { |
| 58 | + if (_settings.UseDoublePinyin) |
65 | 59 | { |
66 | | - if (translatedIndex < translatedIndexs[i * 2]) |
| 60 | + var tablePath = Path.Join(AppContext.BaseDirectory, "Resources", "double_pinyin.json"); |
| 61 | + try |
67 | 62 | { |
68 | | - // move to lower middle |
69 | | - upperBound = i; |
70 | | - i = (i + lowerBound) / 2; |
| 63 | + using var fs = File.OpenRead(tablePath); |
| 64 | + CreateDoublePinyinTableFromStream(fs); |
71 | 65 | } |
72 | | - else if (translatedIndex > translatedIndexs[i * 2 + 1] - 1) |
| 66 | + catch (System.Exception e) |
73 | 67 | { |
74 | | - lowerBound = i; |
75 | | - // move to upper middle |
76 | | - // due to floor of integer division, move one up on corner case |
77 | | - i = (i + upperBound + 1) / 2; |
78 | | - } |
79 | | - else |
80 | | - return originalIndexs[i]; |
81 | | - |
82 | | - if (upperBound - lowerBound <= 1 && |
83 | | - translatedIndex > translatedIndexs[lowerBound * 2 + 1] && |
84 | | - translatedIndex < translatedIndexs[upperBound * 2]) |
85 | | - { |
86 | | - int indexDef = 0; |
87 | | - |
88 | | - for (int j = 0; j < upperBound; j++) |
89 | | - { |
90 | | - indexDef += translatedIndexs[j * 2 + 1] - translatedIndexs[j * 2]; |
91 | | - } |
92 | | - |
93 | | - return translatedIndex - indexDef - 1; |
| 68 | + Log.Exception(nameof(PinyinAlphabet), "Failed to load double pinyin table from file: " + tablePath, e); |
| 69 | + currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>()); |
94 | 70 | } |
95 | 71 | } |
| 72 | + else |
| 73 | + { |
| 74 | + currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>()); |
| 75 | + } |
96 | 76 | } |
97 | 77 |
|
98 | | - public void endConstruct() |
99 | | - { |
100 | | - if (constructed) |
101 | | - throw new InvalidOperationException("Mapping has already been constructed"); |
102 | | - constructed = true; |
103 | | - } |
104 | | - } |
105 | | - |
106 | | - /// <summary> |
107 | | - /// Translate a language to English letters using a given rule. |
108 | | - /// </summary> |
109 | | - public interface IAlphabet |
110 | | - { |
111 | | - /// <summary> |
112 | | - /// Translate a string to English letters, using a given rule. |
113 | | - /// </summary> |
114 | | - /// <param name="stringToTranslate">String to translate.</param> |
115 | | - /// <returns></returns> |
116 | | - public (string translation, TranslationMapping map) Translate(string stringToTranslate); |
117 | | - |
118 | | - /// <summary> |
119 | | - /// Determine if a string can be translated to English letter with this Alphabet. |
120 | | - /// </summary> |
121 | | - /// <param name="stringToTranslate">String to translate.</param> |
122 | | - /// <returns></returns> |
123 | | - public bool CanBeTranslated(string stringToTranslate); |
124 | | - } |
125 | | - |
126 | | - public class PinyinAlphabet : IAlphabet |
127 | | - { |
128 | | - private ConcurrentDictionary<string, (string translation, TranslationMapping map)> _pinyinCache = |
129 | | - new ConcurrentDictionary<string, (string translation, TranslationMapping map)>(); |
130 | | - |
131 | | - private Settings _settings; |
132 | | - |
133 | | - public PinyinAlphabet() |
134 | | - { |
135 | | - Initialize(Ioc.Default.GetRequiredService<Settings>()); |
136 | | - } |
137 | | - |
138 | | - private void Initialize([NotNull] Settings settings) |
139 | | - { |
140 | | - _settings = settings ?? throw new ArgumentNullException(nameof(settings)); |
141 | | - } |
142 | | - |
143 | | - public bool CanBeTranslated(string stringToTranslate) |
| 78 | + public bool ShouldTranslate(string stringToTranslate) |
144 | 79 | { |
145 | | - return WordsHelper.HasChinese(stringToTranslate); |
| 80 | + // If a string has Chinese characters, we don't need to translate it to pinyin. |
| 81 | + return _settings.ShouldUsePinyin && !WordsHelper.HasChinese(stringToTranslate); |
146 | 82 | } |
147 | 83 |
|
148 | 84 | public (string translation, TranslationMapping map) Translate(string content) |
149 | 85 | { |
150 | | - if (_settings.ShouldUsePinyin) |
151 | | - { |
152 | | - if (!_pinyinCache.ContainsKey(content)) |
153 | | - { |
154 | | - return BuildCacheFromContent(content); |
155 | | - } |
156 | | - else |
157 | | - { |
158 | | - return _pinyinCache[content]; |
159 | | - } |
160 | | - } |
161 | | - return (content, null); |
| 86 | + if (!_settings.ShouldUsePinyin || !WordsHelper.HasChinese(content)) |
| 87 | + return (content, null); |
| 88 | + |
| 89 | + return _pinyinCache.TryGetValue(content, out var value) |
| 90 | + ? value |
| 91 | + : BuildCacheFromContent(content); |
162 | 92 | } |
163 | 93 |
|
164 | 94 | private (string translation, TranslationMapping map) BuildCacheFromContent(string content) |
165 | 95 | { |
166 | | - if (WordsHelper.HasChinese(content)) |
167 | | - { |
168 | | - var resultList = WordsHelper.GetPinyinList(content); |
| 96 | + var resultList = WordsHelper.GetPinyinList(content); |
169 | 97 |
|
170 | | - StringBuilder resultBuilder = new StringBuilder(); |
171 | | - TranslationMapping map = new TranslationMapping(); |
| 98 | + var resultBuilder = new StringBuilder(); |
| 99 | + var map = new TranslationMapping(); |
172 | 100 |
|
173 | | - bool pre = false; |
| 101 | + var previousIsChinese = false; |
174 | 102 |
|
175 | | - for (int i = 0; i < resultList.Length; i++) |
| 103 | + for (var i = 0; i < resultList.Length; i++) |
| 104 | + { |
| 105 | + if (content[i] >= 0x3400 && content[i] <= 0x9FD5) |
176 | 106 | { |
177 | | - if (content[i] >= 0x3400 && content[i] <= 0x9FD5) |
| 107 | + string translated = _settings.UseDoublePinyin ? ToDoublePin(resultList[i]) : resultList[i]; |
| 108 | + if (i > 0) |
178 | 109 | { |
179 | | - map.AddNewIndex(i, resultBuilder.Length, resultList[i].Length + 1); |
180 | 110 | resultBuilder.Append(' '); |
181 | | - resultBuilder.Append(resultList[i]); |
182 | | - pre = true; |
183 | 111 | } |
184 | | - else |
| 112 | + map.AddNewIndex(resultBuilder.Length, translated.Length); |
| 113 | + resultBuilder.Append(translated); |
| 114 | + previousIsChinese = true; |
| 115 | + } |
| 116 | + else |
| 117 | + { |
| 118 | + if (previousIsChinese) |
185 | 119 | { |
186 | | - if (pre) |
187 | | - { |
188 | | - pre = false; |
189 | | - resultBuilder.Append(' '); |
190 | | - } |
191 | | - |
192 | | - resultBuilder.Append(resultList[i]); |
| 120 | + previousIsChinese = false; |
| 121 | + resultBuilder.Append(' '); |
193 | 122 | } |
| 123 | + map.AddNewIndex(resultBuilder.Length, resultList[i].Length); |
| 124 | + resultBuilder.Append(resultList[i]); |
194 | 125 | } |
| 126 | + } |
195 | 127 |
|
196 | | - map.endConstruct(); |
| 128 | + map.endConstruct(); |
197 | 129 |
|
198 | | - var key = resultBuilder.ToString(); |
199 | | - map.setKey(key); |
| 130 | + var key = resultBuilder.ToString(); |
200 | 131 |
|
201 | | - return _pinyinCache[content] = (key, map); |
202 | | - } |
203 | | - else |
| 132 | + return _pinyinCache[content] = (key, map); |
| 133 | + } |
| 134 | + |
| 135 | + #region Double Pinyin |
| 136 | + |
| 137 | + private string ToDoublePin(string fullPinyin) |
| 138 | + { |
| 139 | + if (currentDoublePinyinTable.TryGetValue(fullPinyin, out var doublePinyinValue)) |
204 | 140 | { |
205 | | - return (content, null); |
| 141 | + return doublePinyinValue; |
206 | 142 | } |
| 143 | + return fullPinyin; |
207 | 144 | } |
| 145 | + |
| 146 | + #endregion |
208 | 147 | } |
209 | 148 | } |
0 commit comments