using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Threading;
namespace NewLife;
/// <summary>多语言本地化。英文原文为键,缺翻译回退英文;默认跟随当前 UI 语言,可用 Culture/LogCulture 强制</summary>
public static partial class Localization
{
#region 属性
/// <summary>目标语言。默认 null,表示跟随 <see cref="CultureInfo.CurrentUICulture"/></summary>
public static CultureInfo? Culture { get; set; }
/// <summary>日志与配置注释的语言。默认 null,表示跟随 <see cref="Culture"/></summary>
public static CultureInfo? LogCulture { get; set; }
private static readonly ConcurrentDictionary<String, IReadOnlyDictionary<String, String>> _overrides = new(StringComparer.Ordinal);
private static readonly ConcurrentDictionary<String, String?> _cultureKeys = new(StringComparer.Ordinal);
private static readonly ConcurrentDictionary<String, Dictionary<String, String>> _tables = new(StringComparer.Ordinal);
private static readonly Dictionary<String, String> _emptyTable = new(StringComparer.Ordinal);
private static Int32 _overrideCount;
#endregion
#region 注册
/// <summary>注册本地化覆盖表。默认不覆盖已有译文,可同时注册新语言</summary>
public static void Register(CultureInfo culture, IDictionary<String, String> table, Boolean overwrite = false)
{
if (culture == null) throw new ArgumentNullException(nameof(culture));
if (table == null || table.Count == 0) return;
var key = GetCultureKey(culture);
if (key == null) return;
_overrides.AddOrUpdate(
key,
_ => Merge(key, table, overwrite),
(_, existing) => Merge(key, table, overwrite, existing));
Volatile.Write(ref _overrideCount, _overrides.Count);
// 运行时新增的语言同时进入语言清单,供管理端下拉选择与配置归一化;内置语言无需登记
if (!IsBuiltinLanguage(culture.Name)) _extraLanguages.TryAdd(key, LanguageInfo.FromCulture(culture));
}
/// <summary>移除指定语言的覆盖表</summary>
public static Boolean Unregister(CultureInfo culture)
{
if (culture == null) return false;
var key = GetCultureKey(culture);
if (key == null || !_overrides.TryRemove(key, out _)) return false;
Volatile.Write(ref _overrideCount, _overrides.Count);
_extraLanguages.TryRemove(key, out _);
return true;
}
private static IReadOnlyDictionary<String, String> Merge(String cultureKey, IDictionary<String, String> table, Boolean overwrite, IReadOnlyDictionary<String, String>? existing = null)
{
var result = new Dictionary<String, String>(StringComparer.Ordinal);
if (existing != null)
{
foreach (var item in existing) result[item.Key] = item.Value;
}
var builtin = GetTable(cultureKey);
foreach (var item in table)
{
if (item.Key.IsNullOrEmpty()) continue;
if (!overwrite && (result.ContainsKey(item.Key) || (builtin != null && builtin.ContainsKey(item.Key)))) continue;
result[item.Key] = item.Value ?? String.Empty;
}
return result;
}
#endregion
#region 方法
/// <summary>翻译指定文本,缺少翻译时返回原文</summary>
public static String Translate(String template) => Translate(null, template);
/// <summary>翻译并格式化指定文本,失败时回退英文</summary>
public static String Translate(String template, params Object?[]? args) => Translate(null, template, args);
/// <summary>按日志语言翻译指定文本</summary>
public static String TranslateForLog(String template) => Translate(LogCulture ?? Culture, template);
/// <summary>按日志语言翻译并格式化指定文本</summary>
public static String TranslateForLog(String template, params Object?[]? args) => Translate(LogCulture ?? Culture, template, args);
/// <summary>判断指定区域是否有可用的翻译表;英文始终为 true</summary>
public static Boolean IsSupported(CultureInfo culture)
{
if (culture == null) return false;
var key = GetCultureKeyCached(culture);
if (key == null) return false;
if (key == "en") return true;
// 运行时注册的覆盖表可能包含内置语言之外的新语言
if (Volatile.Read(ref _overrideCount) != 0 && _overrides.ContainsKey(key)) return true;
return GetTable(key) != null;
}
internal static String Translate(CultureInfo? culture, String template)
{
if (template.IsNullOrEmpty()) return template;
culture ??= Culture ?? CultureInfo.CurrentUICulture;
var key = GetCultureKeyCached(culture);
if (key == null) return template;
// 无覆盖表时跳过覆盖查找
if (Volatile.Read(ref _overrideCount) != 0 && _overrides.TryGetValue(key, out var overlay) && overlay.TryGetValue(template, out var text)) return text;
// en 也可能带词表:元数据源文为其它语言时,用 en.yml 提供英文译文
var table = GetTable(key);
if (table != null && table.TryGetValue(template, out text)) return text;
return template;
}
private static Dictionary<String, String>? GetTable(String key)
{
if (_tables.TryGetValue(key, out var table)) return table.Count == 0 ? null : table;
table = CreateTable(key);
if (table == null)
{
_tables.TryAdd(key, _emptyTable);
return null;
}
_tables.TryAdd(key, table);
return table;
}
private static Dictionary<String, String>? DecodeTable(String[] keys, Byte[] data)
{
if (keys == null || keys.Length == 0 || data == null || data.Length == 0) return null;
using var input = new MemoryStream(data);
using var deflate = new DeflateStream(input, CompressionMode.Decompress);
using var output = new MemoryStream();
deflate.CopyTo(output);
var buffer = output.ToArray();
if (buffer.Length < keys.Length * 4) return null;
var table = new Dictionary<String, String>(keys.Length, StringComparer.Ordinal);
var offset = keys.Length * 4;
for (var i = 0; i < keys.Length; i++)
{
var length = buffer[i * 4] | buffer[i * 4 + 1] << 8 | buffer[i * 4 + 2] << 16 | buffer[i * 4 + 3] << 24;
if (length < 0) continue;
if (length > buffer.Length - offset) return null;
table[keys[i]] = Encoding.UTF8.GetString(buffer, offset, length);
offset += length;
}
return table;
}
private static String? GetCultureKeyCached(CultureInfo culture)
{
var name = culture.Name;
if (name.Length == 0) return null;
if (_cultureKeys.TryGetValue(name, out var key)) return key;
key = GetCultureKey(culture);
_cultureKeys.TryAdd(name, key);
return key;
}
internal static String? GetCultureKeyOf(String name)
{
if (name.IsNullOrEmpty()) return null;
if (_cultureKeys.TryGetValue(name, out var key)) return key;
try
{
key = GetCultureKey(CultureInfo.GetCultureInfo(name));
}
catch (Exception ex) when (ex is CultureNotFoundException || ex is ArgumentException)
{
return null;
}
_cultureKeys.TryAdd(name, key);
return key;
}
internal static String Translate(CultureInfo? culture, String template, params Object?[]? args)
{
culture ??= Culture ?? CultureInfo.CurrentUICulture;
var text = Translate(culture, template);
if (args == null || args.Length == 0) return text;
try
{
return String.Format(culture, text, args);
}
catch (FormatException) when (!ReferenceEquals(text, template))
{
// 译文占位符不合法时回退英文,避免翻译问题演变成新的异常
return String.Format(culture, template, args);
}
}
#endregion
}
|