拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Humanizer 序数化(Ordinalize)扩展方法完全指南:从 1st/2nd/3rd 到多语言本地化

Humanizer 序数化(Ordinalize)扩展方法完全指南:从 1st/2nd/3rd 到多语言本地化
  • 开发工具

【免费下载链接】Humanizer

Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities

项目地址:https://gitcode.com/gh_mirrors/hu/Humanizer
点击查看免费下载

本篇技术指南以 Humanizer 库 OrdinalizeExtensions 的 API 参考文档为主体,系统讲解如何将数字转换为序数形式(如1st、2nd、3rd),深入剖析其在当前仓库中的源码实现、语法性别(GrammaticalGender)支持、区域性(CultureInfo)本地化机制以及 64 位整数扩展。读完本文,你将掌握Ordinalize全系列重载的用法、底层IOrdinalizer插件化架构,以及如何利用它输出符合巴西葡萄牙语、西班牙语等语言习惯的序数文本。

什么是序数化(Ordinalize)

序数(ordinal number)用于表示在有序序列中的位置,例如英文中的1st、2nd、3rd、4th。Humanizer 的OrdinalizeExtensions静态类为int、long和string类型提供了同名的扩展方法Ordinalize,用于"把一个数字变成序数字符串"。它是 Humanizer 众多人性化字符串功能(Humanize、Dehumanize、ToQuantity等)中的一员,位于 src/Humanizer/OrdinalizeExtensions.cs。

public static class OrdinalizeExtensions { public static string Ordinalize(this int number); public static string Ordinalize(this string numberString); // ... 更多重载 }

根据 src/Humanizer/OrdinalizeExtensions.cs 中的类注释,序数化只接受整数值;如果调用方持有小数,必须先自行选择并执行显式的取整与类型转换策略,再调用Ordinalize。

基础用法:int 与 string 两个入口

原 API 文档列出两类接收者(receiver)的Ordinalize重载:int与string。两者在语义上等价——将数字转为序数文本,区别仅在于输入类型。

方法签名参数说明返回
string Ordinalize(this int number)number:要被序数化的整数string
string Ordinalize(this string numberString)numberString:以字符串形式表示的数字string

从源码看,int重载的默认实现(OrdinalizeExtensions.cs)会委托给带CultureInfo的重载:

public static string Ordinalize(this int number) => number.Ordinalize(CultureInfo.CurrentCulture);

而string重载(OrdinalizeExtensions.cs)则直接调用全局默认序数化器:

public static string Ordinalize(this string numberString) => Configurator.Ordinalizer.Convert(int.Parse(numberString), NormalizeOrdinalNumberString(numberString));

即先通过int.Parse把字符串解析为整数,再交给配置好的IOrdinalizer完成转换。在英文环境下,实际输出由英语序数化器(基于ModuloSuffixOrdinalizer的取模后缀规则)决定:

1.Ordinalize(); // "1st" 2.Ordinalize(); // "2nd" 3.Ordinalize(); // "3rd" 4.Ordinalize(); // "4th" 11.Ordinalize(); // "11th" —— 注意不是 "11st" 21.Ordinalize(); // "21st" 101.Ordinalize(); // "101st"

这些结果与测试用例 tests/Humanizer.Tests/OrdinalizeTests.cs 中的[InlineData]断言完全一致,例如"1" → "1st"、"11" → "11th"、"21" → "21st"、"101" → "101st"。

语法性别(GrammaticalGender)参数

原文档明确指出,Ordinalize的多个重载接受Humanizer.GrammaticalGender参数,并特别强调其用于**巴西葡萄牙语(Brazilian Portuguese)**区域:

1.Ordinalize(GrammaticalGender.Masculine) -> "1º" 1.Ordinalize(GrammaticalGender.Feminine) -> "1ª"

GrammaticalGender是一个三值枚举(src/Humanizer/GrammaticalGender.cs):

  • Masculine(阳性)
  • Feminine(阴性)
  • Neuter(中性)
// 巴西葡萄牙语 1.Ordinalize(GrammaticalGender.Masculine); // "1º" 1.Ordinalize(GrammaticalGender.Feminine); // "1ª" "1".Ordinalize(GrammaticalGender.Masculine); // "1º" "1".Ordinalize(GrammaticalGender.Feminine); // "1ª"

源码中性别相关的重载(OrdinalizeExtensions.cs)会将参数透传给IOrdinalizer.Convert(int, string, GrammaticalGender);而巴西葡萄牙语的序数化器正是基于 SuffixOrdinalizer 实现——该类接收三个性别后缀,并在Convert中按性别选择后缀(SuffixOrdinalizer.cs):

return numberString + gender switch { GrammaticalGender.Feminine => feminineSuffix, // "ª" GrammaticalGender.Neuter => neuterSuffix, _ => masculineSuffix // "º" };

CultureInfo 参数与本地化机制

原文档中多个重载接受System.Globalization.CultureInfo culture参数,其语义为:指定用于序数化的区域性;若传入null,则使用当前线程的 UI 文化(current thread's UI culture)。

从实现看,culture为null时实际回落逻辑(OrdinalizeExtensions.cs)为:

var resolvedCulture = culture ?? CultureInfo.CurrentCulture; return Configurator.Ordinalizers.ResolveForCulture(culture) .Convert(ParseOrdinalNumber(numberString, resolvedCulture), ...);

这里的关键是Configurator.Ordinalizers—— 一个LocaliserRegistry<IOrdinalizer>类型的注册表(src/Humanizer/Configuration/Configurator.cs)。它由 OrdinalizerRegistry 构建:默认回退到DefaultOrdinalizer(原样返回输入文本),随后通过源代码生成器(Source Generator)按 locale 注册各语言的专用序数化器。

DefaultOrdinalizer(src/Humanizer/Localisation/Ordinalizers/DefaultOrdinalizer.cs)本身是一个"什么都不做"的基类:

public virtual string Convert(long number, string numberString) => numberString;

各区域语言通过继承它来覆盖行为。从 OrdinalizerProfileCatalogInput.cs 可见,源生成器支持三种序数化器类型:

  • suffix:固定性别后缀(如巴西葡萄牙语的º/ª),对应SuffixOrdinalizer;
  • modulo-suffix:按数字取模规则选择后缀(如英语的st/nd/rd/th),对应 ModuloSuffixOrdinalizer;
  • number-word-suffix:基于数字单词的后缀形式。

以英语为例,ModuloSuffixOrdinalizer的后缀选择遵循严格优先级(ModuloSuffixOrdinalizer.cs):先检查最后两位数区间规则,再查精确数值映射,然后查后两位后缀表,最后回退到末位数字后缀表与默认后缀。这正是11th、12th、13th不同于1st、2nd、3rd的底层原因(11-13的末位1、2、3被精确映射覆盖为th)。

显式指定文化的示例

// 指定文化,与当前线程文化无关 1.Ordinalize(new System.Globalization.CultureInfo("pt-BR"), Humanizer.GrammaticalGender.Feminine); // "1ª" "1".Ordinalize(new System.Globalization.CultureInfo("en-US")); // "1st"

文化相关的数字格式化细节

Ordinalize在内部还会根据文化格式化数字本身(OrdinalizeExtensions.cs):对正数,若该文化的NumberFormat.NativeDigits是标准 ASCII 数字(UsesInvariantDigits为true),则用不变文化格式化,否则使用该文化自身的数字格式(例如某些语言的原生数字字符)。负数则额外比对文化的负号与NumberNegativePattern,以决定是否直接用不变文化输出。这些数字格式化配置按文化名缓存在ConcurrentDictionary<string, OrdinalNumberFormatting>中(OrdinalizeExtensions.cs),避免每次调用重复构建。

全系列重载一览

综合原 API 文档与 src/Humanizer/OrdinalizeExtensions.cs,Ordinalize的完整重载矩阵如下(均返回string):

接收者参数组合
int number()/(WordForm)/(CultureInfo?)/(CultureInfo?, WordForm)/(GrammaticalGender)/(GrammaticalGender, WordForm)/(GrammaticalGender, CultureInfo?)/(GrammaticalGender, CultureInfo?, WordForm)
string numberString同上 8 种组合
long number同上 8 种组合

其中WordForm用于区分缩写与完整词形。例如西班牙语(源码注释中的示例,OrdinalizeExtensions.cs):

"1".Ordinalize(WordForm.Abbreviation) -> 1.er // 如 "Vivo en el 1.er piso"(我住在一楼) "1".Ordinalize(WordForm.Normal) -> 1.º // 如 "Fui el 1º de mi promoción"(我是班里第一名) 1.Ordinalize(GrammaticalGender.Feminine, WordForm.Normal) -> 1.ª

注意:原 API 文档生成的版本中string接收者仅收录了int/string两类;long重载在源码中同样完整存在(OrdinalizeExtensions.cs),并通过ILongOrdinalizer接口提供原生 64 位支持(详见下文)。

底层架构:IOrdinalizer 与 64 位支持

Ordinalize之所以能做到多语言、多形式输出,是因为它完全委托给可插拔的序数化器接口(src/Humanizer/Localisation/Ordinalizers/IOrdinalizer.cs):

public interface IOrdinalizer { string Convert(int number, string numberString); string Convert(int number, string numberString, WordForm wordForm); string Convert(int number, string numberString, GrammaticalGender gender); string Convert(int number, string numberString, GrammaticalGender gender, WordForm wordForm); } public interface ILongOrdinalizer : IOrdinalizer { string Convert(long number, string numberString); // ... 对应的 WordForm / gender 重载 }

ILongOrdinalizer用于支持超出int范围的 64 位值。在long重载的ConvertOrdinalizer辅助方法中(OrdinalizeExtensions.cs),如果注册的序数化器实现了ILongOrdinalizer,则直接传入long;否则尝试把值安全收缩到int,若超出范围则抛出:

NotSupportedException: The registered ordinalizer '{type}' does not support 64-bit values.

这意味着:在默认的英语环境下,long.MaxValue.Ordinalize()可以正常工作(DefaultOrdinalizer及其派生类实现了ILongOrdinalizer);但若你通过 Configurator 注册了一个仅实现IOrdinalizer的自定义序数化器,超大long值会抛出上述异常——这是扩展自定义序数化器时需要注意的边界。

另外,string重载在解析与归一化上做了两层处理(OrdinalizeExtensions.cs):

  1. ParseOrdinalNumber:按指定文化解析(int.Parse(numberString, culture));
  2. NormalizeOrdinalNumberString:剔除字符串中的 Unicode格式类字符(UnicodeCategory.Format,如不可见的控制/格式符),保证传入IOrdinalizer的字符串干净一致。

测试验证与可靠性

仓库提供了详尽的测试用例来锁定Ordinalize行为。tests/Humanizer.Tests/OrdinalizeTests.cs 通过[Theory]+[InlineData]覆盖了大量输入-输出对,例如int系列(0→"0th"、1→"1st"、2→"2nd"、3→"3rd"、4→"4th"、11→"11th"、21→"21st"、101→"101st"等),以及string系列的同构断言。运行测试即可验证:

dotnet test tests/Humanizer.Tests/Humanizer.Tests.csproj --filter "FullyQualifiedName~Ordinalize"

实用注意事项小结

  1. 只接受整数:Ordinalize的语义限定于整数值(类注释明确说明,见 OrdinalizeExtensions.cs);小数需自行取整后再调用。
  2. culture为null时:按源码实现(而非原文档措辞)实际使用CultureInfo.CurrentCulture(当前线程文化)作为回落值。
  3. 默认文化行为:int/long的无参数重载均委托给CultureInfo.CurrentCulture版本,因此输出随线程当前文化变化。
  4. 性别只对特定语言生效:GrammaticalGender主要影响巴西葡萄牙语(1º/1ª)等区分性别的语言;对英语等无性别后缀的语言,性别参数通常被忽略(SuffixOrdinalizer按性别选择,而英语的ModuloSuffixOrdinalizer不感知性别)。
  5. 自定义序数化器:可通过Configurator.Ordinalizers注册新的IOrdinalizer/ILongOrdinalizer实现来扩展;若注册项仅实现 32 位接口,注意 64 位大数会抛NotSupportedException。

借助这套 API,你可以在极少的代码量下输出符合多种语言习惯的序数文本——从英文的1st/2nd/3rd/4th,到巴西葡萄牙语的1º/1ª,再到西班牙语的1.er/1.º,无需手写任何规则分支。

  • 开发工具

【免费下载链接】Humanizer

Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities

项目地址:https://gitcode.com/gh_mirrors/hu/Humanizer
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表