|
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