1
This commit is contained in:
92
Src/Core/Localization/CsvLocalizationService.cs
Normal file
92
Src/Core/Localization/CsvLocalizationService.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using BITKit;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Net.BITKit.Localization
|
||||
{
|
||||
public class CsvLocalizationService
|
||||
{
|
||||
public string Url { get; set; } =
|
||||
@"http://server.bitfall.icu:21982/com.project.b/net.project.b.localization.csv";
|
||||
|
||||
private readonly ILogger<CsvLocalizationService> _logger;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
|
||||
public CsvLocalizationService(ILocalizationService localizationService, ILogger<CsvLocalizationService> logger)
|
||||
{
|
||||
_localizationService = localizationService;
|
||||
_logger = logger;
|
||||
|
||||
_localizationService.OnLanguageChangeAsync += OnLanguageChangeAsync;
|
||||
}
|
||||
|
||||
public static Dictionary<string, Dictionary<string, string>> ParseCsvToDictionary(string csvData)
|
||||
{
|
||||
var dict = new Dictionary<string, Dictionary<string, string>>();
|
||||
using var reader = new StringReader(csvData);
|
||||
|
||||
var headerLine = reader.ReadLine();
|
||||
if (headerLine == null) return dict;
|
||||
|
||||
var headers = headerLine.Split(',');
|
||||
|
||||
for (var i = 1; i < headers.Length; i++) // 跳过 "Key" 列
|
||||
{
|
||||
var header = headers[i];
|
||||
if (dict.ContainsKey(header) is false)
|
||||
{
|
||||
dict.Add(header, new Dictionary<string, string>());
|
||||
}
|
||||
}
|
||||
|
||||
string? line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
var columns = line.Split(',');
|
||||
if (columns.Length < 2) continue;
|
||||
|
||||
var key = columns[0]; // 取 Key 值
|
||||
for (var i = 1; i < columns.Length; i++)
|
||||
{
|
||||
if(i>headers.Length-1)continue;
|
||||
var header = headers[i];
|
||||
if (dict.TryGetValue(header, out var d) is false)
|
||||
{
|
||||
d = new Dictionary<string, string>();
|
||||
dict.Add(header,d);
|
||||
}
|
||||
d.Set(key,columns[i]);
|
||||
//dict[headers[i]][key] = columns[i]; // 填充语言数据
|
||||
}
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
private async UniTask OnLanguageChangeAsync(string arg1, string arg2)
|
||||
{
|
||||
var csv = await new HttpClient().GetStringAsync(Url+$"?{DateTime.Now.Ticks}");
|
||||
|
||||
_logger.LogInformation($"下载完成:\n{csv}");
|
||||
|
||||
foreach (var (lang, dictionary) in ParseCsvToDictionary(csv))
|
||||
{
|
||||
if (_localizationService.LocalizedStrings.TryGetValue(lang, out var currentDictionary) is false)
|
||||
{
|
||||
currentDictionary = new Dictionary<string, string>();
|
||||
_localizationService.LocalizedStrings.Add(lang,currentDictionary);
|
||||
}
|
||||
foreach (var (key, value) in dictionary)
|
||||
{
|
||||
currentDictionary.Set(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
11
Src/Core/Localization/CsvLocalizationService.cs.meta
Normal file
11
Src/Core/Localization/CsvLocalizationService.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a0edc8f5d75b3646a7b405ef830a52d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
113
Src/Core/Localization/ILocalizationService.cs
Normal file
113
Src/Core/Localization/ILocalizationService.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using BITKit;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Net.BITKit.Localization
|
||||
{
|
||||
/// <summary>
|
||||
/// 本地化
|
||||
/// </summary>
|
||||
public interface ILocalizationService
|
||||
{
|
||||
public string Prefix { get; }
|
||||
/// <summary>
|
||||
/// 当前语言
|
||||
/// </summary>
|
||||
public string CurrentLanguage { get; }
|
||||
/// <summary>
|
||||
/// 更改回调,通常在此检查并下载语言包
|
||||
/// </summary>
|
||||
public event Func<string,string,UniTask> OnLanguageChangeAsync;
|
||||
/// <summary>
|
||||
/// 语言更改完成回调,UI和World执行更新
|
||||
/// </summary>
|
||||
public event Action<string,string> OnLanguageChanged;
|
||||
/// <summary>
|
||||
/// 获取翻译文本,返回无复制的引用
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="language">语言,默认为当前CultureInfo.Name</param>
|
||||
/// <returns></returns>
|
||||
public string GetLocalizedString(string key,string language=null);
|
||||
public UniTask ChangeLanguageAsync(string newLanguage);
|
||||
public IDictionary<string, IDictionary<string, string>> LocalizedStrings { get; }
|
||||
}
|
||||
|
||||
public class LocalizationService : ILocalizationService,IDisposable
|
||||
{
|
||||
private static LocalizationService _singleton;
|
||||
public string Prefix => "#";
|
||||
private char _prefix = '#';
|
||||
public string CurrentLanguage { get; private set; }
|
||||
|
||||
public event Func<string, string, UniTask> OnLanguageChangeAsync;
|
||||
public event Action<string, string> OnLanguageChanged;
|
||||
|
||||
private readonly ILogger<LocalizationService> _logger;
|
||||
|
||||
public IDictionary<string, IDictionary<string, string>> LocalizedStrings { get; } =
|
||||
new Dictionary<string, IDictionary<string, string>>();
|
||||
|
||||
private readonly ValidHandle _isBusy = new();
|
||||
|
||||
private readonly HashSet<string> _untranslatedKeys = new();
|
||||
|
||||
public LocalizationService(ILogger<LocalizationService> logger)
|
||||
{
|
||||
if (_singleton is not null)
|
||||
{
|
||||
logger.LogError("LocalizationService can only be one singleton");
|
||||
return;
|
||||
}
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
||||
public string GetLocalizedString(string key, string language = null)
|
||||
{
|
||||
language ??= CurrentLanguage; // 默认使用当前语言
|
||||
|
||||
if (key[0] != _prefix)
|
||||
{
|
||||
key = _prefix + key;
|
||||
}
|
||||
if (LocalizedStrings.TryGetValue(language, out var langDict) && langDict.TryGetValue(key, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
_untranslatedKeys.Add(key);
|
||||
return key.Replace(Prefix,string.Empty).Replace("_"," "); // 如果找不到翻译,就返回 key 本身(常见策略)
|
||||
}
|
||||
public async UniTask ChangeLanguageAsync(string newLanguage)
|
||||
{
|
||||
if (CurrentLanguage == newLanguage) return;
|
||||
var oldLanguage = CurrentLanguage;
|
||||
CurrentLanguage = newLanguage;
|
||||
|
||||
await _isBusy;
|
||||
using var _ = _isBusy.GetHandle();
|
||||
|
||||
#if UNITY_5_3_OR_NEWER
|
||||
await UniTask.SwitchToMainThread();
|
||||
#endif
|
||||
|
||||
await OnLanguageChangeAsync.UniTaskFunc(oldLanguage, newLanguage);
|
||||
|
||||
// 触发同步事件(例如更新 UI)
|
||||
OnLanguageChanged?.Invoke(oldLanguage, newLanguage);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_isBusy?.Dispose();
|
||||
_singleton = null;
|
||||
_logger.LogInformation("Untranslated keys:\n"+string.Join("\n", _untranslatedKeys));
|
||||
_untranslatedKeys.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
11
Src/Core/Localization/ILocalizationService.cs.meta
Normal file
11
Src/Core/Localization/ILocalizationService.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: baab5c8590ad6ee44acbc258eeacdc8b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user