feat: Adds cliloc support and fixes valuestringbuilder ctor (#1013)
- [X] Adds cliloc support using the following API:
```cs
public static class Localization
{
string GetText(int number);
string GetText(int number, string lang);
string Format(int number, params object[] args);
string Format(int number, string lang, params object[] args);
string Format(int number, $"{arg1}{arg2}{arg3}");
string Format(int number, lang, $"{arg1}{arg2}{arg3}");
bool TryGetLocalization(int number, out LocalizationEntry entry);
bool TryGetLocalization(int number, string lang, out LocalizationEntry entry);
}
public class LocalizationEntry
{
string Language { get; }
string Number { get; }
string Text { get; }
string?[] TextSlices { get; } // Used for string building
string StringFormatter { get; }
string Format(params object[] args);
string Format($"{arg1}{arg2}{arg3}");
}
```
- [X] Fixes a bug with ValueStringBuilder and default initialization size
- [X] Optimizes ValueStringBuilder to use `ISpanFormattable`
- [X] Adds `Append<T>(T value);` support ValueStringBuilder
- [X] Adds `Append($"");` string interpolation support to ValueStringBuider
This commit is contained in:
parent
790cb5d733
commit
be7da3a3dc
8 changed files with 1046 additions and 156 deletions
201
Projects/Server/Localization/Localization.cs
Normal file
201
Projects/Server/Localization/Localization.cs
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2022 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: Localization.cs *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using Server.Buffers;
|
||||
|
||||
namespace Server;
|
||||
|
||||
public static class Localization
|
||||
{
|
||||
private const bool _loadLocalizationOnStartup = false;
|
||||
public const string FallbackLanguage = "enu";
|
||||
|
||||
private static Dictionary<int, LocalizationEntry> _fallbackEntries;
|
||||
private static Dictionary<string, Dictionary<int, LocalizationEntry>> _localizations = new();
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
if (_loadLocalizationOnStartup)
|
||||
{
|
||||
foreach (var file in Core.FindDataFileByPattern("cliloc.*"))
|
||||
{
|
||||
var fi = new FileInfo(file);
|
||||
LoadClilocs(fi.Extension.ToLowerInvariant(), file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Dictionary<int, LocalizationEntry> LoadClilocs(string lang) =>
|
||||
LoadClilocs(lang, Core.FindDataFile($"cliloc.{lang}", false));
|
||||
|
||||
private static Dictionary<int, LocalizationEntry> LoadClilocs(string lang, string file)
|
||||
{
|
||||
Dictionary<int, LocalizationEntry> entries = _localizations[lang] = new Dictionary<int, LocalizationEntry>();
|
||||
if (lang == FallbackLanguage)
|
||||
{
|
||||
_fallbackEntries = entries;
|
||||
}
|
||||
|
||||
if (File.Exists(file))
|
||||
{
|
||||
using var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
using var bin = new BinaryReader(fs);
|
||||
|
||||
bin.ReadInt32();
|
||||
bin.ReadInt16();
|
||||
|
||||
byte[] buffer = null;
|
||||
while (bin.BaseStream.Length != bin.BaseStream.Position)
|
||||
{
|
||||
var number = bin.ReadInt32();
|
||||
var flag = bin.ReadByte(); // Original, Custom, Modified
|
||||
var length = bin.ReadInt16();
|
||||
|
||||
if (buffer == null || buffer.Length < length)
|
||||
{
|
||||
buffer = GC.AllocateUninitializedArray<byte>(length);
|
||||
}
|
||||
|
||||
var bytesRead = bin.Read(buffer, 0, length);
|
||||
if (bytesRead != length)
|
||||
{
|
||||
throw new Exception($"Could not read enough bytes from {file}");
|
||||
}
|
||||
|
||||
var text = Encoding.UTF8.GetString(buffer.AsSpan(0, length));
|
||||
entries[number] = new LocalizationEntry(lang, number, text);
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the original text for a localization entry.
|
||||
/// </summary>
|
||||
/// <param name="number">Localization number</param>
|
||||
/// <param name="lang">Language in ISO 639‑2 format</param>
|
||||
/// <returns>Original text for the localizaton entry</returns>
|
||||
public static string GetText(int number, string lang = FallbackLanguage) =>
|
||||
TryGetLocalization(lang, number, out var entry) ? entry.Text : null;
|
||||
|
||||
public static string Format(int number, string lang = FallbackLanguage) => GetText(number, lang);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a formatted string of the localization entry using the <see cref="FallbackLanguage" />.
|
||||
/// Uses <see cref="string.Format"/> under the hood.
|
||||
/// Note: This method is not recommended since it uses almost double the memory and 50% more processing.
|
||||
/// Instead use Format with string interpolation.
|
||||
/// </summary>
|
||||
/// <param name="number">Localization number</param>
|
||||
/// <param name="args">An object array containing zero or more objects to format</param>
|
||||
/// <returns>A copy of the localization text where the placeholder arguments have been replaced with string representations of the provided arguments</returns>
|
||||
public static string Format(int number, params object[] args) =>
|
||||
!TryGetLocalization(number, out var entry) ? null : string.Format(entry.StringFormatter, args);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a formatted string of the localization entry using the specified language.
|
||||
/// Uses <see cref="string.Format"/> under the hood.
|
||||
/// Note: This method is not recommended since it uses almost double the memory and 50% more processing.
|
||||
/// Instead use Format with string interpolation.
|
||||
/// </summary>
|
||||
/// <param name="number">Localization number</param>
|
||||
/// <param name="lang">Language in ISO 639-2 format</param>
|
||||
/// <param name="args">An object array containing zero or more objects to format</param>
|
||||
/// <returns>A copy of the localization text where the placeholder arguments have been replaced with string representations of the provided arguments</returns>
|
||||
public static string Format(int number, string lang, params object[] args) =>
|
||||
!TryGetLocalization(lang, number, out var entry) ? null : string.Format(entry.StringFormatter, args);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a localization entry using the <see cref="FallbackLanguage" />.
|
||||
/// </summary>
|
||||
/// <param name="number">Localization number</param>
|
||||
/// <param name="entry">Localization entry retrieved</param>
|
||||
/// <returns>True if the entry exists, otherwise false.</returns>
|
||||
public static bool TryGetLocalization(int number, out LocalizationEntry entry) =>
|
||||
TryGetLocalization(FallbackLanguage, number, out entry);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a localization entry.
|
||||
/// </summary>
|
||||
/// <param name="lang">Language in ISO 639-2 format</param>
|
||||
/// <param name="number">Localization number</param>
|
||||
/// <param name="entry">Localization entry retrieved</param>
|
||||
/// <returns>True if the entry exists, otherwise false.</returns>
|
||||
public static bool TryGetLocalization(string lang, int number, out LocalizationEntry entry)
|
||||
{
|
||||
if (lang != FallbackLanguage)
|
||||
{
|
||||
if (!_localizations.TryGetValue(lang, out var entries))
|
||||
{
|
||||
entries = LoadClilocs(lang);
|
||||
}
|
||||
|
||||
if (entries.TryGetValue(number, out entry))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
_fallbackEntries ??= LoadClilocs(FallbackLanguage);
|
||||
return _fallbackEntries.TryGetValue(number, out entry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a formatted string of the localization entry using the specified language.
|
||||
/// Uses string interpolation under the hood. This method is preferably relative to the object array method signature.
|
||||
/// Example:
|
||||
/// Localization.Format(1073841, "jpn", $"{totalItems}{maxItems}{totalWeight}");
|
||||
/// </summary>
|
||||
/// <param name="lang">Language in ISO 639-2 format</param>
|
||||
/// <param name="number">Localization number</param>
|
||||
/// <param name="handler">interpolated string handler used by the compiler as a string builder during compilation</param>
|
||||
/// <returns>A copy of the localization text where the placeholder arguments have been replaced with string representations of the provided interpolation arguments</returns>
|
||||
public static PooledArraySpanFormattable Format(
|
||||
int number, string lang,
|
||||
[InterpolatedStringHandlerArgument("number", "lang")]
|
||||
ref LocalizationInterpolationHandler handler
|
||||
)
|
||||
{
|
||||
var chars = handler.ToPooledArray(out var length);
|
||||
handler = default; // Defensive clear
|
||||
return new PooledArraySpanFormattable(chars, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a formatted string of the localization entry using the <see cref="FallbackLanguage" />.
|
||||
/// Uses string interpolation under the hood. This method is preferably relative to the object array method signature.
|
||||
/// Example:
|
||||
/// Localization.Format(1073841, $"{totalItems}{maxItems}{totalWeight}");
|
||||
/// </summary>
|
||||
/// <param name="number">Localization number</param>
|
||||
/// <param name="handler">interpolated string handler used by the compiler as a string builder during compilation</param>
|
||||
/// <returns>A copy of the localization text where the placeholder arguments have been replaced with string representations of the provided interpolation arguments</returns>
|
||||
public static PooledArraySpanFormattable Format(
|
||||
int number,
|
||||
[InterpolatedStringHandlerArgument("number")]
|
||||
ref LocalizationInterpolationHandler handler
|
||||
)
|
||||
{
|
||||
var chars = handler.ToPooledArray(out var length);
|
||||
handler = default; // Defensive clear
|
||||
return new PooledArraySpanFormattable(chars, length);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue