2025-06-19 11:06:31 +00:00
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 ;
2025-06-14 06:32:00 +00:00
using System.IO ;
2025-04-05 15:02:28 +00:00
using System.Text ;
2025-06-14 06:32:00 +00:00
using System.Text.Json ;
2025-01-12 11:45:36 +00:00
using CommunityToolkit.Mvvm.DependencyInjection ;
2025-04-05 15:02:28 +00:00
using Flow.Launcher.Infrastructure.UserSettings ;
using ToolGood.Words.Pinyin ;
2025-06-14 06:32:00 +00:00
using Flow.Launcher.Infrastructure.Logger ;
2016-04-23 23:37:25 +00:00
2020-04-21 09:12:17 +00:00
namespace Flow.Launcher.Infrastructure
2016-04-23 23:37:25 +00:00
{
2020-11-01 10:07:35 +00:00
public class PinyinAlphabet : IAlphabet
2020-01-19 23:06:16 +00:00
{
2024-06-02 06:19:27 +00:00
private readonly ConcurrentDictionary < string , ( string translation , TranslationMapping map ) > _pinyinCache =
new ( ) ;
2020-12-22 13:53:59 +00:00
2025-04-05 15:01:09 +00:00
private readonly Settings _settings ;
2020-10-15 13:06:57 +00:00
2025-06-14 06:32:00 +00:00
private ReadOnlyDictionary < string , string > currentDoublePinyinTable ;
2025-06-19 11:06:31 +00:00
/// <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 ( )
2019-12-03 13:55:22 +00:00
{
2025-04-05 15:01:09 +00:00
_settings = Ioc . Default . GetRequiredService < Settings > ( ) ;
2025-06-14 06:32:00 +00:00
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 ( ) ;
2025-06-14 06:32:00 +00:00
}
} ;
}
2025-06-19 11:06:31 +00:00
/// <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 ( ) ;
}
2025-06-19 11:06:31 +00:00
/// <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 ) ;
}
2025-06-19 11:06:31 +00:00
/// <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>
2025-06-14 06:32:00 +00:00
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 ) ;
2025-06-14 06:32:00 +00:00
}
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
}
2025-06-19 11:06:31 +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>
2024-05-25 07:02:37 +00:00
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
}
2025-06-19 11:06:31 +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>
2020-12-22 13:53:59 +00:00
public ( string translation , TranslationMapping map ) Translate ( string content )
2016-04-23 23:37:25 +00:00
{
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
}
2020-12-12 06:55:41 +00:00
2025-06-19 11:06:31 +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 ) ;
2020-12-22 13:53:59 +00:00
2025-04-05 15:02:28 +00:00
var resultBuilder = new StringBuilder ( ) ;
var map = new TranslationMapping ( ) ;
2020-12-25 16:29:35 +00:00
2025-06-14 06:32:00 +00:00
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 ] ;
2025-06-14 06:32:00 +00:00
if ( previousIsChinese )
{
2025-06-14 08:33:48 +00:00
map . AddNewIndex ( i , resultBuilder . Length , translated . Length + 1 ) ;
2025-06-14 06:32:00 +00:00
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 ;
2025-06-14 06:32:00 +00:00
}
2024-06-02 07:18:24 +00:00
}
else
2022-11-16 15:32:27 +00:00
{
2025-06-14 06:32:00 +00:00
if ( previousIsChinese )
2022-11-16 15:32:27 +00:00
{
2025-06-14 06:32:00 +00:00
previousIsChinese = false ;
2022-11-16 15:32:27 +00:00
resultBuilder . Append ( ' ' ) ;
2019-12-03 13:55:22 +00:00
}
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 ) ;
2016-04-23 23:37:25 +00:00
}
2024-06-02 06:19:27 +00:00
#region Double Pinyin
2025-06-19 11:06:31 +00:00
/// <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>
2025-06-14 06:32:00 +00:00
private string ToDoublePin ( string fullPinyin )
2024-06-02 06:19:27 +00:00
{
2025-06-14 06:32:00 +00:00
if ( currentDoublePinyinTable . TryGetValue ( fullPinyin , out var doublePinyinValue ) )
2024-06-02 06:19:27 +00:00
{
2025-06-14 06:32:00 +00:00
return doublePinyinValue ;
2024-06-02 06:19:27 +00:00
}
2025-06-14 06:32:00 +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
2016-04-23 23:37:25 +00:00
}
2022-11-16 15:32:27 +00:00
}