Flow.Launcher/Flow.Launcher.Infrastructure/PinyinAlphabet.cs

197 lines
8 KiB
C#
Raw Permalink Normal View History

using System;
2025-04-10 01:56:27 +00:00
using System.Collections.Concurrent;
2024-06-02 06:19:27 +00:00
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
2025-04-05 15:02:28 +00:00
using System.Text;
using System.Text.Json;
using CommunityToolkit.Mvvm.DependencyInjection;
2025-04-05 15:02:28 +00:00
using Flow.Launcher.Infrastructure.UserSettings;
using ToolGood.Words.Pinyin;
using Flow.Launcher.Infrastructure.Logger;
2020-04-21 09:12:17 +00:00
namespace Flow.Launcher.Infrastructure
{
public class PinyinAlphabet : IAlphabet
{
2024-06-02 06:19:27 +00:00
private readonly ConcurrentDictionary<string, (string translation, TranslationMapping map)> _pinyinCache =
new();
2025-04-05 15:01:09 +00:00
private readonly Settings _settings;
2020-10-15 13:06:57 +00:00
private ReadOnlyDictionary<string, string> currentDoublePinyinTable;
/// <summary>
/// Initializes a new instance of the <see cref="PinyinAlphabet"/> 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.
/// </summary>
2025-04-05 15:01:09 +00:00
public PinyinAlphabet()
{
2025-04-05 15:01:09 +00:00
_settings = Ioc.Default.GetRequiredService<Settings>();
LoadDoublePinyinTable();
_settings.PropertyChanged += (sender, e) =>
{
if (e.PropertyName == nameof(Settings.UseDoublePinyin) ||
e.PropertyName == nameof(Settings.DoublePinyinSchema))
{
2025-06-14 14:22:25 +00:00
Reload();
}
};
}
/// <summary>
/// Reloads the double Pinyin mapping table and clears the translation cache.
/// </summary>
2025-06-14 14:22:25 +00:00
public void Reload()
{
LoadDoublePinyinTable();
_pinyinCache.Clear();
}
/// <summary>
/// Loads the double Pinyin mapping table for the current schema from a JSON stream.
/// </summary>
/// <param name="jsonStream">A stream containing the double Pinyin tables in JSON format.</param>
/// <exception cref="InvalidOperationException">
/// Thrown if the current double Pinyin schema is invalid or the table is missing from the JSON.
/// </exception>
2025-06-14 14:22:25 +00:00
private void CreateDoublePinyinTableFromStream(Stream jsonStream)
{
Dictionary<string, Dictionary<string, string>> table = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, string>>>(jsonStream);
if (!table.TryGetValue(_settings.DoublePinyinSchema, out var value))
{
throw new InvalidOperationException("DoublePinyinSchema is invalid or double pinyin table is broken.");
}
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(value);
}
/// <summary>
/// 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.
/// </summary>
private void LoadDoublePinyinTable()
{
if (_settings.UseDoublePinyin)
{
var tablePath = Path.Join(AppContext.BaseDirectory, "Resources", "double_pinyin.json");
try
{
using var fs = File.OpenRead(tablePath);
2025-06-14 14:22:25 +00:00
CreateDoublePinyinTableFromStream(fs);
}
catch (System.Exception e)
{
Log.Exception(nameof(PinyinAlphabet), "Failed to load double pinyin table from file: " + tablePath, e);
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
}
}
else
{
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
}
2017-01-12 02:16:53 +00:00
}
/// <summary>
/// Determines whether the specified string should be translated based on current settings and its content.
/// </summary>
/// <param name="stringToTranslate">The string to evaluate for translation eligibility.</param>
/// <returns>
/// True if the string contains no Chinese characters and, when double Pinyin is enabled, has an even length; otherwise, false.
/// </returns>
public bool ShouldTranslate(string stringToTranslate)
2022-11-16 15:32:27 +00:00
{
2024-06-02 06:37:15 +00:00
return _settings.UseDoublePinyin ?
(!WordsHelper.HasChinese(stringToTranslate) && stringToTranslate.Length % 2 == 0) :
!WordsHelper.HasChinese(stringToTranslate);
2022-11-16 15:32:27 +00:00
}
/// <summary>
/// Translates the input string to Pinyin, returning the translated string and its mapping.
/// </summary>
/// <param name="content">The string to translate.</param>
/// <returns>
/// A tuple containing the translated Pinyin string and a <see cref="TranslationMapping"/> object.
/// If Pinyin translation is disabled or the input contains no Chinese characters, returns the original string and null mapping.
/// </returns>
public (string translation, TranslationMapping map) Translate(string content)
{
2025-06-14 14:22:25 +00:00
if (!_settings.ShouldUsePinyin || !WordsHelper.HasChinese(content))
2025-06-13 15:39:14 +00:00
return (content, null);
return _pinyinCache.TryGetValue(content, out var value)
? value
: BuildCacheFromContent(content);
2022-11-16 15:32:27 +00:00
}
/// <summary>
/// Generates the Pinyin or double Pinyin translation and mapping for the given content.
/// </summary>
/// <param name="content">The input string to translate.</param>
/// <returns>
/// A tuple containing the translated string and a <see cref="TranslationMapping"/> correlating original and translated indices.
/// </returns>
2022-11-16 15:32:27 +00:00
private (string translation, TranslationMapping map) BuildCacheFromContent(string content)
{
2024-06-02 07:18:24 +00:00
var resultList = WordsHelper.GetPinyinList(content);
2025-04-05 15:02:28 +00:00
var resultBuilder = new StringBuilder();
var map = new TranslationMapping();
var previousIsChinese = false;
2024-06-02 07:18:24 +00:00
2025-04-05 15:02:28 +00:00
for (var i = 0; i < resultList.Length; i++)
2024-06-02 07:18:24 +00:00
{
if (content[i] >= 0x3400 && content[i] <= 0x9FD5)
{
2025-06-14 08:33:48 +00:00
string translated = _settings.UseDoublePinyin ? ToDoublePin(resultList[i]) : resultList[i];
if (previousIsChinese)
{
2025-06-14 08:33:48 +00:00
map.AddNewIndex(i, resultBuilder.Length, translated.Length + 1);
resultBuilder.Append(' ');
2025-06-14 08:33:48 +00:00
resultBuilder.Append(translated);
}
else
{
map.AddNewIndex(i, resultBuilder.Length, translated.Length);
resultBuilder.Append(translated);
previousIsChinese = true;
}
2024-06-02 07:18:24 +00:00
}
else
2022-11-16 15:32:27 +00:00
{
if (previousIsChinese)
2022-11-16 15:32:27 +00:00
{
previousIsChinese = false;
2022-11-16 15:32:27 +00:00
resultBuilder.Append(' ');
}
2024-06-02 07:18:24 +00:00
resultBuilder.Append(resultList[i]);
2017-01-12 02:16:53 +00:00
}
2024-06-02 07:18:24 +00:00
}
2022-11-16 15:32:27 +00:00
2024-06-02 07:18:24 +00:00
map.endConstruct();
2022-11-16 15:32:27 +00:00
2024-06-02 07:18:24 +00:00
var key = resultBuilder.ToString();
2022-11-16 15:32:27 +00:00
2024-06-02 07:18:24 +00:00
return _pinyinCache[content] = (key, map);
}
2024-06-02 06:19:27 +00:00
#region Double Pinyin
/// <summary>
/// Converts a full Pinyin syllable to its double Pinyin equivalent using the current mapping table.
/// Returns the original Pinyin if no mapping exists.
/// </summary>
/// <param name="fullPinyin">The full Pinyin syllable to convert.</param>
/// <returns>The double Pinyin equivalent if found; otherwise, the original full Pinyin.</returns>
private string ToDoublePin(string fullPinyin)
2024-06-02 06:19:27 +00:00
{
if (currentDoublePinyinTable.TryGetValue(fullPinyin, out var doublePinyinValue))
2024-06-02 06:19:27 +00:00
{
return doublePinyinValue;
2024-06-02 06:19:27 +00:00
}
return fullPinyin;
2024-06-02 06:19:27 +00:00
}
2025-04-05 15:01:09 +00:00
2024-06-02 06:19:27 +00:00
#endregion
}
2022-11-16 15:32:27 +00:00
}