This commit is contained in:
CortexCore
2025-03-24 14:42:40 +08:00
parent 18239a5ae4
commit 9845d20f7f
99 changed files with 5418 additions and 5512 deletions

View 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);
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0a0edc8f5d75b3646a7b405ef830a52d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: baab5c8590ad6ee44acbc258eeacdc8b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: