using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Text; using System.Text.Json; using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Infrastructure.UserSettings; using ToolGood.Words.Pinyin; using Flow.Launcher.Infrastructure.Logger; namespace Flow.Launcher.Infrastructure { public class PinyinAlphabet : IAlphabet { private readonly ConcurrentDictionary _pinyinCache = new(); private readonly Settings _settings; private ReadOnlyDictionary currentDoublePinyinTable; /// /// Initializes a new instance of the class, loading the double Pinyin table based on current settings and subscribing to setting changes to reload the table and clear the cache as needed. /// public PinyinAlphabet() { _settings = Ioc.Default.GetRequiredService(); LoadDoublePinyinTable(); _settings.PropertyChanged += (sender, e) => { if (e.PropertyName == nameof(Settings.UseDoublePinyin) || e.PropertyName == nameof(Settings.DoublePinyinSchema)) { Reload(); } }; } /// /// Reloads the double Pinyin mapping table and clears the translation cache. /// public void Reload() { LoadDoublePinyinTable(); _pinyinCache.Clear(); } /// /// Loads the double Pinyin mapping table for the current schema from a JSON stream. /// /// A stream containing the double Pinyin tables in JSON format. /// /// Thrown if the current double Pinyin schema is invalid or the table is missing from the JSON. /// private void CreateDoublePinyinTableFromStream(Stream jsonStream) { Dictionary> table = JsonSerializer.Deserialize>>(jsonStream); if (!table.TryGetValue(_settings.DoublePinyinSchema, out var value)) { throw new InvalidOperationException("DoublePinyinSchema is invalid or double pinyin table is broken."); } currentDoublePinyinTable = new ReadOnlyDictionary(value); } /// /// Loads the double Pinyin mapping table from a JSON file if enabled in settings; sets an empty table if loading fails or double Pinyin is disabled. /// private void LoadDoublePinyinTable() { if (_settings.UseDoublePinyin) { var tablePath = Path.Join(AppContext.BaseDirectory, "Resources", "double_pinyin.json"); try { using var fs = File.OpenRead(tablePath); CreateDoublePinyinTableFromStream(fs); } catch (System.Exception e) { Log.Exception(nameof(PinyinAlphabet), "Failed to load double pinyin table from file: " + tablePath, e); currentDoublePinyinTable = new ReadOnlyDictionary(new Dictionary()); } } else { currentDoublePinyinTable = new ReadOnlyDictionary(new Dictionary()); } } /// /// Determines whether the specified string should be translated based on current settings and its content. /// /// The string to evaluate for translation eligibility. /// /// True if the string contains no Chinese characters and, when double Pinyin is enabled, has an even length; otherwise, false. /// public bool ShouldTranslate(string stringToTranslate) { return _settings.UseDoublePinyin ? (!WordsHelper.HasChinese(stringToTranslate) && stringToTranslate.Length % 2 == 0) : !WordsHelper.HasChinese(stringToTranslate); } /// /// Translates the input string to Pinyin, returning the translated string and its mapping. /// /// The string to translate. /// /// A tuple containing the translated Pinyin string and a object. /// If Pinyin translation is disabled or the input contains no Chinese characters, returns the original string and null mapping. /// public (string translation, TranslationMapping map) Translate(string content) { if (!_settings.ShouldUsePinyin || !WordsHelper.HasChinese(content)) return (content, null); return _pinyinCache.TryGetValue(content, out var value) ? value : BuildCacheFromContent(content); } /// /// Generates the Pinyin or double Pinyin translation and mapping for the given content. /// /// The input string to translate. /// /// A tuple containing the translated string and a correlating original and translated indices. /// private (string translation, TranslationMapping map) BuildCacheFromContent(string content) { var resultList = WordsHelper.GetPinyinList(content); var resultBuilder = new StringBuilder(); var map = new TranslationMapping(); var previousIsChinese = false; for (var i = 0; i < resultList.Length; i++) { if (content[i] >= 0x3400 && content[i] <= 0x9FD5) { string translated = _settings.UseDoublePinyin ? ToDoublePin(resultList[i]) : resultList[i]; if (previousIsChinese) { map.AddNewIndex(i, resultBuilder.Length, translated.Length + 1); resultBuilder.Append(' '); resultBuilder.Append(translated); } else { map.AddNewIndex(i, resultBuilder.Length, translated.Length); resultBuilder.Append(translated); previousIsChinese = true; } } else { if (previousIsChinese) { previousIsChinese = false; resultBuilder.Append(' '); } resultBuilder.Append(resultList[i]); } } map.endConstruct(); var key = resultBuilder.ToString(); return _pinyinCache[content] = (key, map); } #region Double Pinyin /// /// Converts a full Pinyin syllable to its double Pinyin equivalent using the current mapping table. /// Returns the original Pinyin if no mapping exists. /// /// The full Pinyin syllable to convert. /// The double Pinyin equivalent if found; otherwise, the original full Pinyin. private string ToDoublePin(string fullPinyin) { if (currentDoublePinyinTable.TryGetValue(fullPinyin, out var doublePinyinValue)) { return doublePinyinValue; } return fullPinyin; } #endregion } }