diff --git a/Flow.Launcher.Infrastructure/IAlphabet.cs b/Flow.Launcher.Infrastructure/IAlphabet.cs
new file mode 100644
index 00000000000..e79ec0c6d6f
--- /dev/null
+++ b/Flow.Launcher.Infrastructure/IAlphabet.cs
@@ -0,0 +1,22 @@
+namespace Flow.Launcher.Infrastructure
+{
+ ///
+ /// Translate a language to English letters using a given rule.
+ ///
+ public interface IAlphabet
+ {
+ ///
+ /// Translate a string to English letters, using a given rule.
+ ///
+ /// String to translate.
+ ///
+ public (string translation, TranslationMapping map) Translate(string stringToTranslate);
+
+ ///
+ /// Determine if a string can be translated to English letter with this Alphabet.
+ ///
+ /// String to translate.
+ ///
+ public bool ShouldTranslate(string stringToTranslate);
+ }
+}
diff --git a/Flow.Launcher.Infrastructure/PinyinAlphabet.cs b/Flow.Launcher.Infrastructure/PinyinAlphabet.cs
index 7d72359684d..10799c676f1 100644
--- a/Flow.Launcher.Infrastructure/PinyinAlphabet.cs
+++ b/Flow.Launcher.Infrastructure/PinyinAlphabet.cs
@@ -1,203 +1,196 @@
using System;
using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
using System.Text;
using JetBrains.Annotations;
using Flow.Launcher.Infrastructure.UserSettings;
using ToolGood.Words.Pinyin;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
namespace Flow.Launcher.Infrastructure
{
- public class TranslationMapping
+ public class PinyinAlphabet : IAlphabet
{
- private bool constructed;
+ private readonly ConcurrentDictionary _pinyinCache =
+ new();
- private List originalIndexs = new List();
- private List translatedIndexs = new List();
- private int translatedLength = 0;
-
- public string key { get; private set; }
+ private Settings _settings;
- public void setKey(string key)
+ public void Initialize([NotNull] Settings settings)
{
- this.key = key;
+ _settings = settings ?? throw new ArgumentNullException(nameof(settings));
}
- public void AddNewIndex(int originalIndex, int translatedIndex, int length)
+ public bool ShouldTranslate(string stringToTranslate)
{
- if (constructed)
- throw new InvalidOperationException("Mapping shouldn't be changed after constructed");
-
- originalIndexs.Add(originalIndex);
- translatedIndexs.Add(translatedIndex);
- translatedIndexs.Add(translatedIndex + length);
- translatedLength += length - 1;
+ return _settings.UseDoublePinyin ?
+ (!WordsHelper.HasChinese(stringToTranslate) && stringToTranslate.Length % 2 == 0) :
+ !WordsHelper.HasChinese(stringToTranslate);
}
- public int MapToOriginalIndex(int translatedIndex)
+ public (string translation, TranslationMapping map) Translate(string content)
{
- if (translatedIndex > translatedIndexs.Last())
- return translatedIndex - translatedLength - 1;
-
- int lowerBound = 0;
- int upperBound = originalIndexs.Count - 1;
-
- int count = 0;
-
- // Corner case handle
- if (translatedIndex < translatedIndexs[0])
- return translatedIndex;
- if (translatedIndex > translatedIndexs.Last())
+ if (_settings.ShouldUsePinyin)
{
- int indexDef = 0;
- for (int k = 0; k < originalIndexs.Count; k++)
+ if (!_pinyinCache.TryGetValue(content, out var value))
+ {
+ return BuildCacheFromContent(content);
+ }
+ else
{
- indexDef += translatedIndexs[k * 2 + 1] - translatedIndexs[k * 2];
+ return value;
}
+ }
+ return (content, null);
+ }
- return translatedIndex - indexDef - 1;
+ private (string translation, TranslationMapping map) BuildCacheFromContent(string content)
+ {
+ if (!WordsHelper.HasChinese(content))
+ {
+ return (content, null);
}
- // Binary Search with Range
- for (int i = originalIndexs.Count / 2;; count++)
+ var resultList = WordsHelper.GetPinyinList(content);
+
+ StringBuilder resultBuilder = new StringBuilder();
+ TranslationMapping map = new TranslationMapping();
+
+ bool pre = false;
+
+ for (int i = 0; i < resultList.Length; i++)
{
- if (translatedIndex < translatedIndexs[i * 2])
- {
- // move to lower middle
- upperBound = i;
- i = (i + lowerBound) / 2;
- }
- else if (translatedIndex > translatedIndexs[i * 2 + 1] - 1)
+ if (content[i] >= 0x3400 && content[i] <= 0x9FD5)
{
- lowerBound = i;
- // move to upper middle
- // due to floor of integer division, move one up on corner case
- i = (i + upperBound + 1) / 2;
+ string dp = _settings.UseDoublePinyin ? ToDoublePin(resultList[i]) : resultList[i];
+ map.AddNewIndex(i, resultBuilder.Length, dp.Length + 1);
+ resultBuilder.Append(' ');
+ resultBuilder.Append(dp);
+ pre = true;
}
else
- return originalIndexs[i];
-
- if (upperBound - lowerBound <= 1 &&
- translatedIndex > translatedIndexs[lowerBound * 2 + 1] &&
- translatedIndex < translatedIndexs[upperBound * 2])
{
- int indexDef = 0;
-
- for (int j = 0; j < upperBound; j++)
+ if (pre)
{
- indexDef += translatedIndexs[j * 2 + 1] - translatedIndexs[j * 2];
+ pre = false;
+ resultBuilder.Append(' ');
}
- return translatedIndex - indexDef - 1;
+ resultBuilder.Append(resultList[i]);
}
}
- }
- public void endConstruct()
- {
- if (constructed)
- throw new InvalidOperationException("Mapping has already been constructed");
- constructed = true;
+ map.endConstruct();
+
+ var key = resultBuilder.ToString();
+
+ return _pinyinCache[content] = (key, map);
}
- }
- ///
- /// Translate a language to English letters using a given rule.
- ///
- public interface IAlphabet
- {
- ///
- /// Translate a string to English letters, using a given rule.
- ///
- /// String to translate.
- ///
- public (string translation, TranslationMapping map) Translate(string stringToTranslate);
-
- ///
- /// Determine if a string can be translated to English letter with this Alphabet.
- ///
- /// String to translate.
- ///
- public bool CanBeTranslated(string stringToTranslate);
- }
+ #region Double Pinyin
- public class PinyinAlphabet : IAlphabet
- {
- private ConcurrentDictionary _pinyinCache =
- new ConcurrentDictionary();
+ private static readonly ReadOnlyDictionary special = new(new Dictionary(){
+ {"A", "aa"},
+ {"Ai", "ai"},
+ {"An", "an"},
+ {"Ang", "ah"},
+ {"Ao", "ao"},
+ {"E", "ee"},
+ {"Ei", "ei"},
+ {"En", "en"},
+ {"Er", "er"},
+ {"O", "oo"},
+ {"Ou", "ou"}
+ });
- private Settings _settings;
- public void Initialize([NotNull] Settings settings)
- {
- _settings = settings ?? throw new ArgumentNullException(nameof(settings));
- }
+ private static readonly ReadOnlyDictionary first = new(new Dictionary(){
+ {"Ch", "i"},
+ {"Sh", "u"},
+ {"Zh", "v"}
+ });
- public bool CanBeTranslated(string stringToTranslate)
- {
- return WordsHelper.HasChinese(stringToTranslate);
- }
- public (string translation, TranslationMapping map) Translate(string content)
+ private static readonly ReadOnlyDictionary second = new(new Dictionary()
{
- if (_settings.ShouldUsePinyin)
+ {"ua", "x"},
+ {"ei", "w"},
+ {"e", "e"},
+ {"ou", "z"},
+ {"iu", "q"},
+ {"ve", "t"},
+ {"ue", "t"},
+ {"u", "u"},
+ {"i", "i"},
+ {"o", "o"},
+ {"uo", "o"},
+ {"ie", "p"},
+ {"a", "a"},
+ {"ong", "s"},
+ {"iong", "s"},
+ {"ai", "d"},
+ {"ing", "k"},
+ {"uai", "k"},
+ {"ang", "h"},
+ {"uan", "r"},
+ {"an", "j"},
+ {"en", "f"},
+ {"ia", "x"},
+ {"iang", "l"},
+ {"uang", "l"},
+ {"eng", "g"},
+ {"in", "b"},
+ {"ao", "c"},
+ {"v", "v"},
+ {"ui", "v"},
+ {"un", "y"},
+ {"iao", "n"},
+ {"ian", "m"}
+ });
+
+ private static string ToDoublePin(string fullPinyin)
+ {
+ // Assuming s is valid
+ StringBuilder doublePin = new StringBuilder();
+
+ if (fullPinyin.Length <= 3 && (fullPinyin[0] == 'a' || fullPinyin[0] == 'e' || fullPinyin[0] == 'o'))
{
- if (!_pinyinCache.ContainsKey(content))
+ if (special.TryGetValue(fullPinyin, out var value))
{
- return BuildCacheFromContent(content);
- }
- else
- {
- return _pinyinCache[content];
+ return value;
}
}
- return (content, null);
- }
- private (string translation, TranslationMapping map) BuildCacheFromContent(string content)
- {
- if (WordsHelper.HasChinese(content))
+ // zh, ch, sh
+ if (fullPinyin.Length >= 2 && first.ContainsKey(fullPinyin[..2]))
{
- var resultList = WordsHelper.GetPinyinList(content);
-
- StringBuilder resultBuilder = new StringBuilder();
- TranslationMapping map = new TranslationMapping();
-
- bool pre = false;
+ doublePin.Append(first[fullPinyin[..2]]);
- for (int i = 0; i < resultList.Length; i++)
+ if (second.TryGetValue(fullPinyin[2..], out string tmp))
{
- if (content[i] >= 0x3400 && content[i] <= 0x9FD5)
- {
- map.AddNewIndex(i, resultBuilder.Length, resultList[i].Length + 1);
- resultBuilder.Append(' ');
- resultBuilder.Append(resultList[i]);
- pre = true;
- }
- else
- {
- if (pre)
- {
- pre = false;
- resultBuilder.Append(' ');
- }
-
- resultBuilder.Append(resultList[i]);
- }
+ doublePin.Append(tmp);
+ }
+ else
+ {
+ doublePin.Append(fullPinyin[2..]);
}
-
- map.endConstruct();
-
- var key = resultBuilder.ToString();
- map.setKey(key);
-
- return _pinyinCache[content] = (key, map);
}
else
{
- return (content, null);
+ doublePin.Append(fullPinyin[0]);
+
+ if (second.TryGetValue(fullPinyin[1..], out string tmp))
+ {
+ doublePin.Append(tmp);
+ }
+ else
+ {
+ doublePin.Append(fullPinyin[1..]);
+ }
}
+
+ return doublePin.ToString();
}
+ #endregion
}
}
diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs
index bd5dbdda9be..4929e4cd2cf 100644
--- a/Flow.Launcher.Infrastructure/StringMatcher.cs
+++ b/Flow.Launcher.Infrastructure/StringMatcher.cs
@@ -61,7 +61,7 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption
query = query.Trim();
TranslationMapping translationMapping = null;
- if (_alphabet is not null && !_alphabet.CanBeTranslated(query))
+ if (_alphabet is not null && _alphabet.ShouldTranslate(query))
{
// We assume that if a query can be translated (containing characters of a language, like Chinese)
// it actually means user doesn't want it to be translated to English letters.
diff --git a/Flow.Launcher.Infrastructure/TranslationMapping.cs b/Flow.Launcher.Infrastructure/TranslationMapping.cs
new file mode 100644
index 00000000000..c976fc522d6
--- /dev/null
+++ b/Flow.Launcher.Infrastructure/TranslationMapping.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Flow.Launcher.Infrastructure
+{
+ public class TranslationMapping
+ {
+ private bool constructed;
+
+ private List originalIndexs = new List();
+ private List translatedIndexs = new List();
+ private int translatedLength = 0;
+
+ public void AddNewIndex(int originalIndex, int translatedIndex, int length)
+ {
+ if (constructed)
+ throw new InvalidOperationException("Mapping shouldn't be changed after constructed");
+
+ originalIndexs.Add(originalIndex);
+ translatedIndexs.Add(translatedIndex);
+ translatedIndexs.Add(translatedIndex + length);
+ translatedLength += length - 1;
+ }
+
+ public int MapToOriginalIndex(int translatedIndex)
+ {
+ if (translatedIndex > translatedIndexs.Last())
+ return translatedIndex - translatedLength - 1;
+
+ int lowerBound = 0;
+ int upperBound = originalIndexs.Count - 1;
+
+ int count = 0;
+
+ // Corner case handle
+ if (translatedIndex < translatedIndexs[0])
+ return translatedIndex;
+ if (translatedIndex > translatedIndexs.Last())
+ {
+ int indexDef = 0;
+ for (int k = 0; k < originalIndexs.Count; k++)
+ {
+ indexDef += translatedIndexs[k * 2 + 1] - translatedIndexs[k * 2];
+ }
+
+ return translatedIndex - indexDef - 1;
+ }
+
+ // Binary Search with Range
+ for (int i = originalIndexs.Count / 2;; count++)
+ {
+ if (translatedIndex < translatedIndexs[i * 2])
+ {
+ // move to lower middle
+ upperBound = i;
+ i = (i + lowerBound) / 2;
+ }
+ else if (translatedIndex > translatedIndexs[i * 2 + 1] - 1)
+ {
+ lowerBound = i;
+ // move to upper middle
+ // due to floor of integer division, move one up on corner case
+ i = (i + upperBound + 1) / 2;
+ }
+ else
+ return originalIndexs[i];
+
+ if (upperBound - lowerBound <= 1 &&
+ translatedIndex > translatedIndexs[lowerBound * 2 + 1] &&
+ translatedIndex < translatedIndexs[upperBound * 2])
+ {
+ int indexDef = 0;
+
+ for (int j = 0; j < upperBound; j++)
+ {
+ indexDef += translatedIndexs[j * 2 + 1] - translatedIndexs[j * 2];
+ }
+
+ return translatedIndex - indexDef - 1;
+ }
+ }
+ }
+
+ public void endConstruct()
+ {
+ if (constructed)
+ throw new InvalidOperationException("Mapping has already been constructed");
+ constructed = true;
+ }
+ }
+}
diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
index 4588466652e..30e3b77beee 100644
--- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
+++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
@@ -185,6 +185,9 @@ public CustomBrowserViewModel CustomBrowser
/// when false Alphabet static service will always return empty results
///
public bool ShouldUsePinyin { get; set; } = false;
+
+ public bool UseDoublePinyin { get; set; } = true; //For developing
+
public bool AlwaysPreview { get; set; } = false;
public bool AlwaysStartEn { get; set; } = false;
diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs
index 765a1a5593a..83870837a0b 100644
--- a/Flow.Launcher/App.xaml.cs
+++ b/Flow.Launcher/App.xaml.cs
@@ -74,7 +74,7 @@ private async void OnStartupAsync(object sender, StartupEventArgs e)
PluginManager.LoadPlugins(_settings.PluginSettings);
_mainVM = new MainViewModel(_settings);
- API = new PublicAPIInstance(_settingsVM, _mainVM, _alphabet);
+ API = new PublicAPIInstance(_settingsVM, _mainVM);
Http.API = API;
Http.Proxy = _settings.Proxy;
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index b49bf39d3c5..e14d692cd65 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -32,15 +32,13 @@ public class PublicAPIInstance : IPublicAPI
{
private readonly SettingWindowViewModel _settingsVM;
private readonly MainViewModel _mainVM;
- private readonly PinyinAlphabet _alphabet;
#region Constructor
- public PublicAPIInstance(SettingWindowViewModel settingsVM, MainViewModel mainVM, PinyinAlphabet alphabet)
+ public PublicAPIInstance(SettingWindowViewModel settingsVM, MainViewModel mainVM)
{
_settingsVM = settingsVM;
_mainVM = mainVM;
- _alphabet = alphabet;
GlobalHotkey.hookedKeyboardCallback = KListener_hookedKeyboardCallback;
WebRequest.RegisterPrefix("data", new DataWebRequestFactory());
}