fix(localization): 修复紧凑数字格式化器处理未知选项的行为

- 修改 CompactNumberLocalizationFormatter 中 TryApplyOption 方法的返回逻辑
- 当键名未知时现在返回 true 而不是 false,允许忽略未知选项
- 更新 NumericDisplayFormatter 使用 ResolveRule 方法来确定格式化规则
- 添加对不同数值显示风格的支持和验证
- 在集成测试中添加未知选项的测试用例以验证正确行为
- 改进 NumericSuffixFormatRule 的文档注释以更清楚地描述功能
This commit is contained in:
GeWuYou 2026-03-21 15:01:47 +08:00
parent 5996ecf5f3
commit fca3808657
4 changed files with 47 additions and 12 deletions

View File

@ -52,6 +52,7 @@ public class LocalizationIntegrationTests
"status.health": "Health: {current}/{max}", "status.health": "Health: {current}/{max}",
"status.gold": "Gold: {gold:compact}", "status.gold": "Gold: {gold:compact}",
"status.damage": "Damage: {damage:compact:maxDecimals=2}", "status.damage": "Damage: {damage:compact:maxDecimals=2}",
"status.unknownCompact": "Gold: {gold:compact:maxDecimalss=2}",
"status.invalidCompact": "Gold: {gold:compact:maxDecimals=abc}" "status.invalidCompact": "Gold: {gold:compact:maxDecimals=abc}"
} }
"""); """);
@ -63,6 +64,7 @@ public class LocalizationIntegrationTests
"status.health": "生命值: {current}/{max}", "status.health": "生命值: {current}/{max}",
"status.gold": "金币: {gold:compact}", "status.gold": "金币: {gold:compact}",
"status.damage": "伤害: {damage:compact:maxDecimals=2}", "status.damage": "伤害: {damage:compact:maxDecimals=2}",
"status.unknownCompact": "金币: {gold:compact:maxDecimalss=2}",
"status.invalidCompact": "金币: {gold:compact:maxDecimals=abc}" "status.invalidCompact": "金币: {gold:compact:maxDecimals=abc}"
} }
"""); """);
@ -134,6 +136,16 @@ public class LocalizationIntegrationTests
Assert.That(damage, Is.EqualTo("Damage: 1.23K")); Assert.That(damage, Is.EqualTo("Damage: 1.23K"));
} }
[Test]
public void GetString_WithUnknownCompactFormatterArgs_ShouldIgnoreUnknownOptions()
{
var gold = _manager!.GetString("common", "status.unknownCompact")
.WithVariable("gold", 1_250)
.Format();
Assert.That(gold, Is.EqualTo("Gold: 1.3K"));
}
[Test] [Test]
public void GetString_WithInvalidCompactFormatterArgs_ShouldFallbackToDefaultFormatting() public void GetString_WithInvalidCompactFormatterArgs_ShouldFallbackToDefaultFormatting()
{ {

View File

@ -146,7 +146,7 @@ public sealed class CompactNumberLocalizationFormatter : ILocalizationFormatter
/// <param name="minDecimalPlaces">最小小数位数的引用参数</param> /// <param name="minDecimalPlaces">最小小数位数的引用参数</param>
/// <param name="trimTrailingZeros">是否去除尾随零的引用参数</param> /// <param name="trimTrailingZeros">是否去除尾随零的引用参数</param>
/// <param name="useGroupingBelowThreshold">是否在阈值以下使用分组的引用参数</param> /// <param name="useGroupingBelowThreshold">是否在阈值以下使用分组的引用参数</param>
/// <returns>如果键名有效且值成功解析则返回true如果键名无效或值解析失败则返回false</returns> /// <returns>如果值成功解析或键名未知则返回true如果键名已知但值解析失败则返回false</returns>
private static bool TryApplyOption( private static bool TryApplyOption(
string key, string key,
string value, string value,
@ -161,7 +161,7 @@ public sealed class CompactNumberLocalizationFormatter : ILocalizationFormatter
"minDecimals" => int.TryParse(value, out minDecimalPlaces), "minDecimals" => int.TryParse(value, out minDecimalPlaces),
"trimZeros" => bool.TryParse(value, out trimTrailingZeros), "trimZeros" => bool.TryParse(value, out trimTrailingZeros),
"grouping" => bool.TryParse(value, out useGroupingBelowThreshold), "grouping" => bool.TryParse(value, out useGroupingBelowThreshold),
_ => false _ => true
}; };
} }
} }

View File

@ -37,7 +37,7 @@ public sealed class NumericDisplayFormatter : INumericDisplayFormatter
} }
var resolvedOptions = NormalizeOptions(options); var resolvedOptions = NormalizeOptions(options);
var rule = resolvedOptions.Rule ?? _defaultRule; var rule = ResolveRule(resolvedOptions);
if (rule.TryFormat(value, resolvedOptions, out var result)) if (rule.TryFormat(value, resolvedOptions, out var result))
{ {
@ -113,6 +113,22 @@ public sealed class NumericDisplayFormatter : INumericDisplayFormatter
return resolved; return resolved;
} }
private INumericFormatRule ResolveRule(NumericFormatOptions options)
{
ArgumentNullException.ThrowIfNull(options);
if (options.Rule is not null)
{
return options.Rule;
}
return options.Style switch
{
NumericDisplayStyle.Compact => _defaultRule,
_ => throw new ArgumentOutOfRangeException(nameof(options), options.Style, "不支持的数值显示风格。")
};
}
private static string FormatFallback(object value, IFormatProvider? provider) private static string FormatFallback(object value, IFormatProvider? provider)
{ {
return value switch return value switch

View File

@ -33,7 +33,7 @@ public sealed class NumericSuffixFormatRule : INumericFormatRule
} }
/// <summary> /// <summary>
/// 默认国际缩写规则 /// 默认国际缩写规则使用标准的K、M、B、T后缀表示千、百万、十亿、万亿
/// </summary> /// </summary>
public static NumericSuffixFormatRule InternationalCompact { get; } = new( public static NumericSuffixFormatRule InternationalCompact { get; } = new(
"compact", "compact",
@ -44,10 +44,19 @@ public sealed class NumericSuffixFormatRule : INumericFormatRule
new NumericSuffixThreshold(1_000_000_000_000m, "T") new NumericSuffixThreshold(1_000_000_000_000m, "T")
]); ]);
/// <inheritdoc/> /// <summary>
/// 获取此格式化规则的名称。
/// </summary>
public string Name { get; } public string Name { get; }
/// <inheritdoc/> /// <summary>
/// 尝试将指定的数值按照当前规则进行格式化。
/// </summary>
/// <typeparam name="T">数值的类型</typeparam>
/// <param name="value">要格式化的数值</param>
/// <param name="options">格式化选项,包含小数位数、舍入模式等设置</param>
/// <param name="result">格式化后的字符串结果</param>
/// <returns>如果格式化成功则返回true如果输入无效或格式化失败则返回false</returns>
public bool TryFormat<T>(T value, NumericFormatOptions options, out string result) public bool TryFormat<T>(T value, NumericFormatOptions options, out string result)
{ {
ArgumentNullException.ThrowIfNull(options); ArgumentNullException.ThrowIfNull(options);
@ -164,15 +173,13 @@ public sealed class NumericSuffixFormatRule : INumericFormatRule
} }
catch (OverflowException) catch (OverflowException)
{ {
try var doubleValue = (double)value;
if (TryFormatSpecialFloatingPoint(doubleValue, options.FormatProvider, out result))
{ {
return TryFormatDouble((double)value, options, out result);
}
catch (OverflowException)
{
result = value.ToString(options.FormatProvider ?? CultureInfo.CurrentCulture);
return true; return true;
} }
return TryFormatDouble(doubleValue, options, out result);
} }
} }