☰
Humanizer IFormatter 接口深度解析:构建多语言本地化格式化器的核心契约
2026/9/28 2:42:35 网站建设 项目流程
  • 开发工具

【免费下载链接】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 3.x 版本 API 文档中的IFormatter接口(原文档)为主体,结合仓库内src/Humanizer的源码实现,系统讲解该接口的完整方法契约、参数语义、默认实现原理,以及如何通过Configurator与LocaliserRegistry注册自定义格式化器。读完本文,你将掌握 Humanizer 本地化格式化体系的内部结构,并能独立为缺少内置支持的语种编写、注册与测试IFormatter实现。

为什么需要 IFormatter:复数与语法规则差异的抽象

Humanizer 负责把数字、日期、时间跨度、数据单位等值转换为人类可读的字符串。不同语言处理“数量 + 单位”的方式差异巨大,远不止加不加s这么简单:

  • 罗马尼亚语中 “5 days” 是 “5 zile”,而 “24 days” 是 “24 de zile”,需要根据数值决定是否插入介词de;
  • 阿拉伯语中 “2 days” 是双数形式يومين,而不是2 يوم;
  • 俄语、波兰语、斯洛文尼亚语等语言还存在 paucal(少数)、dual(双数)、many(多数)等额外的语法数范畴。

IFormatter正是为此设计的抽象层。接口的定义位于 src/Humanizer/Localisation/Formatters/IFormatter.cs,其职责注释明确写着:

Localizes Humanizer's number, date, duration, and unit formatting.

它把“如何用某种语言表达某个时间单位/数据单位”的规则全部封装为一个契约,使上层扩展方法(如Humanize、TimeSpanHumanize、ToSymbol)不关心具体语言的语法细节,只面向接口编程。

接口的派生类型为DefaultFormatter,完整实现见 src/Humanizer/Localisation/Formatters/DefaultFormatter.cs,它是所有语言格式化器的基类,下面逐一拆解其成员。

接口成员全解

IFormatter共声明 8 个方法,覆盖四类场景:相对日期、时间跨度(时长)、数据单位、时间单位符号。接口定义如下:

public interface IFormatter { string DateHumanize_Now(); string DateHumanize_Never(); string DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit); string TimeSpanHumanize_Zero(); string TimeSpanHumanize(TimeUnit timeUnit, int unit, bool toWords = false); string TimeSpanHumanize_Age(); string DataUnitHumanize(DataUnit dataUnit, double count, bool toSymbol = true); string TimeUnitHumanize(TimeUnit timeUnit); }

所有方法返回string,即某种语言下的最终可读文本。方法命名中的_Now、_Never、_Zero、_Age等后缀用于区分类似但语义不同的短语(如“现在”与“从未”、“0 秒”与“年龄后缀”),避免与带参数的DateHumanize/TimeSpanHumanize重载冲突。

相对日期短语:DateHumanize 家族

方法签名语义
DateHumanize_Nowstring DateHumanize_Now()返回“此刻/现在”的本地化文本,如英文now
DateHumanize_Neverstring DateHumanize_Never()返回“从未发生”的本地化文本,如英文never
DateHumanizestring DateHumanize(TimeUnit timeUnit, Tense timeUnitTense, int unit)返回指定时间单位、时态与数量的相对日期短语

其中DateHumanize的三个参数含义如下:

  • timeUnit:TimeUnit枚举(src/Humanizer/Localisation/TimeUnit.cs),取值包括Millisecond、Second、Minute、Hour、Day、Week、Month、Year;
  • timeUnitTense:Tense枚举(src/Humanizer/Localisation/Tense.cs),Future表示“in 2 days”这类未来表达,Past表示“2 days ago”这类过去表达;
  • unit:单位数量,用于驱动单数/双数/复数等语法形态的选择。

从源码看,DateHumanize_Never被空值日期的Humanize扩展直接调用。在 src/Humanizer/DateHumanizeExtensions.cs 中,DateTime?为null时通过Configurator.GetFormatter(culture).DateHumanize_Never()返回never或对应语言的等价文本。

DateHumanize的默认实现会先在短语表中查“count == 0 即 now”的情况,再处理single(数量为 1)、two模板(阿拉伯语“前天/后天”类特殊双数表达)与multiple(其余数量)分支,详见 DefaultFormatter.cs。

时间跨度短语:TimeSpanHumanize 家族

方法签名语义
TimeSpanHumanize_Zerostring TimeSpanHumanize_Zero()返回零时长文本,如英文0 seconds
TimeSpanHumanizestring TimeSpanHumanize(TimeUnit timeUnit, int unit, bool toWords = false)返回时长短语;toWords为true时数字转英文单词
TimeSpanHumanize_Agestring TimeSpanHumanize_Age()返回“年龄后缀”格式字符串,英文为{0} old

TimeSpanHumanize的参数中,toWords控制数字的呈现形式。默认实现中,toWords为真时会优先选择SingleWordsVariant/MultipleWordsVariant词形变体,并把计数交给NumberToWords(unit, number, Culture)转成单词(DefaultFormatter.cs)。

TimeSpanHumanize_Age的设计很巧妙:它返回一个带占位符的格式模板,而不是完整句子。英文返回"{0} old",于是 “40 years” 可以被拼装成 “40 years old”。源码实现为phraseTable.TimeSpanAge ?? "{0}"(DefaultFormatter.cs),默认回退为原样输出。

数据单位:DataUnitHumanize

string DataUnitHumanize(Humanizer.DataUnit dataUnit, double count, bool toSymbol = true);
  • dataUnit:DataUnit枚举(src/Humanizer/Localisation/DataUnit.cs),涵盖Bit、Byte、Kilobyte…Exabyte等十进制单位,Pebibyte、Kibibyte…Tebibyte等二进制 IEC 单位,以及DecimalKilobyte…DecimalExabyte、BinaryKibibyte…BinaryPebibyte等显式单位系统取值;
  • count:单位数量,用于调整单复数形态;
  • toSymbol:true时返回符号(如KB、MiB),false时返回完整单词(如kilobyte)。

该方法是数据大小格式化(ByteSize系列)多语言化的关键入口。默认实现会优先查短语表;当目标语言缺少Petabyte、Exabyte及各种二进制单位短语时,会回退到内置英文DefaultFormatter(DefaultFormatter.cs)。

时间单位符号:TimeUnitHumanize

string TimeUnitHumanize(Humanizer.TimeUnit timeUnit);

返回给定时间单位的本地化符号,如英文的ms、s、min、h、d、w、M、y。它是TimeSpanHumanize中toSymbols场景(秒的符号化表达)的底层支撑,同时被TimeUnitToSymbolExtensions使用。

默认实现剖析:DefaultFormatter 与短语表机制

DefaultFormatter是IFormatter的唯一直接派生类型(Formatters 目录)。它的构造器接受CultureInfo或 localeCode 字符串,并解析生成好的LocalePhraseTable(DefaultFormatter.cs):

public DefaultFormatter(CultureInfo culture) { Culture = culture; phraseTable = LocalePhraseTableCatalog.Resolve(culture) ?? throw new InvalidOperationException("The generated locale phrase tables are missing the required English fallback."); } public DefaultFormatter(string localeCode) : this(new CultureInfo(localeCode)) { }

关键点:

  • 短语表由源码生成器生成:各语言短语来自src/Humanizer/Locales/*.yml(如 ar.yml、en.yml),经Humanizer.SourceGenerators编译为LocalePhraseTable,运行时“分支少、无需解析”;
  • 每个短语含多种形态:single、dual、paucal、plural、default等LocalizedPhraseForms字段,以及countPlacement(数字放在单位前/后/不出现)、beforeCountText/afterCountText、template(如阿拉伯语“two”模板)等结构;
  • 兜底英文:即使某个语种缺短语,也会回退英文(EnglishFallback,见 DefaultFormatter.cs)。

以阿拉伯语为例,ar.yml 中relativeDate.past.second定义了:

second: single: 'منذ ثانية واحدة' multiple: countPlacement: none forms: default: 'منذ {count} ثانية' singular: 'منذ ثانيتين' dual: 'منذ ثانيتين' paucal: 'منذ ثانيتين' plural: 'منذ {count} ثوان'

可见阿拉伯语的“2 秒”直接使用双数短语منذ ثانيتين,而非拼接数字,这正是IFormatter要抽象出的语言差异。

ProfiledFormatter:声明式规则内核

仓库中还存在一个DefaultFormatter的变体ProfiledFormatter(src/Humanizer/Localisation/Formatters/ProfiledFormatter.cs),它从 YAML 中的formatter段读取声明式规则,支持:

  • 多种复数检测器:FormatterNumberDetectorKind枚举定义了SingularPlural、ArabicLike、ArabicCardinal、Between2And4Paucal、Polish、SouthSlavic、Slovenian、Russian、Lithuanian等形态检测算法(ProfiledFormatter.cs);
  • 精确数字覆盖规则:FormatterDateFormRule/FormatterTimeSpanFormRule允许为特定数字指定形态(如俄语21, 31…单数、2-4, 22-24…paucal);
  • 语言特殊处理:罗马尼亚语de介词插入(ShouldUseRomanianPreposition,当numeral % 100为 0 或大于 19 时插入)、卢森堡语 Eifeler 规则后缀n、拉脱维亚语回退变形等;
  • 单位性别:UnitGenders使数字转单词时带上语法性别。

YAML 中的 formatter 配置形如(ar.yml):

formatter: engine: 'profiled' dataUnitFallbackTransform: 'trim-trailing-s' pluralRule: 'arabic-like' casePluralRule: 'arabic-cardinal'

由此可推断:ProfiledFormatter是DefaultFormatter针对“可由声明式规则表达”的语言提供的统一实现,新增语种通常只需在 YAML 中补充短语与规则,无需手写 C# 类。

注册与解析机制:Configurator + LocaliserRegistry

IFormatter的实例由Configurator.Formatters注册表统一管理(src/Humanizer/Configuration/Configurator.cs):

public static LocaliserRegistry<IFormatter> Formatters { get; } = new FormatterRegistry();

FormatterRegistry(src/Humanizer/Configuration/FormatterRegistry.cs)以DefaultFormatter为默认工厂,然后通过源码生成器批量注册各语种的实现:

class FormatterRegistry : LocaliserRegistry<IFormatter> { public FormatterRegistry() : base(c => new DefaultFormatter(c)) => FormatterRegistryRegistrations.Register(this); }

LocaliserRegistry<TLocaliser>(src/Humanizer/Configuration/LocaliserRegistry.cs)提供了解析与注册能力:

  • ResolveForCulture(CultureInfo? culture):按当前线程文化或指定文化解析格式化器;首次使用时将注册表“冻结”为FrozenDictionary以获得更好的读性能,并用ConditionalWeakTable做按文化的实例缓存(LocaliserRegistry.cs);
  • Register(localeCode, localiser)/Register(localeCode, factory):注册自定义格式化器。注意:注册表被使用(冻结)后不能再注册,会抛出InvalidOperationException(LocaliserRegistry.cs);
  • 解析时按culture → culture.Parent链向上回退,最后落到默认格式化器(LocaliserRegistry.cs)。

上层扩展方法通过Configurator.GetFormatter(culture)获取当前文化的IFormatter(Configurator.cs),culture为null时使用当前线程文化。DateHumanizeExtensions中的调用链即为典型示例:

// DateHumanizeExtensions.cs return Configurator .GetFormatter(culture) .DateHumanize_Never();

自定义 IFormatter 实战指南

当目标语言无法用 YAML 声明式规则表达,或需要完全自控输出时,可自行实现IFormatter:

1. 定义实现类:继承DefaultFormatter(推荐,可复用短语表机制与英文回退)或直接实现IFormatter全部 8 个方法。若希望支持语法格(grammatical case)感知的时长短语,还应实现可选接口IGrammaticalCaseTimeSpanFormatter(src/Humanizer/Localisation/Formatters/IGrammaticalCaseTimeSpanFormatter.cs),它额外声明:

string TimeSpanHumanize(TimeUnit timeUnit, int unit, GrammaticalCase grammaticalCase);

从DefaultFormatter的显式实现可以看出,自定义类型若要启用语法格时长,必须显式实现该接口,否则会抛出NotSupportedException(DefaultFormatter.cs)。

2. 在应用启动时注册:由于注册表一旦使用即被冻结,注册必须发生在任何 Humanizer 调用之前(应用启动阶段或ModuleInitializer中):

Configurator.Formatters.Register("xx-YY", new MyFormatter(new CultureInfo("xx-YY")));

3. 验证解析:通过Configurator.Formatters.ResolveForCulture(new CultureInfo("xx-YY"))确认返回的是自定义实例;未注册的文化会沿父文化链回退到DefaultFormatter。

测试与验证

仓库测试目录 tests/Humanizer.Tests 中,与IFormatter直接相关的验证包括:

  • FormatterExactOutputTests.cs:针对各语种格式化输出做精确断言;
  • Localisation子目录下每个语种文件夹的测试类(如ar/、ru/),逐一验证该语言DateHumanize、TimeSpanHumanize等方法的输出;
  • LocaliserRegistryTests.cs:验证注册表解析、回退与注册机制。

编写自定义IFormatter时,可参照这些测试的模式,用UseCulture特性(tests/Humanizer.Tests/UseCultureAttribute.cs)切换线程文化后断言输出文本。

小结

IFormatter是 Humanizer 本地化体系的枢纽契约:它以 8 个方法覆盖相对日期、时长、年龄后缀、数据单位与时间单位符号五类输出,DefaultFormatter提供了基于生成短语表的默认实现,ProfiledFormatter进一步以声明式规则表达各语言的复数、介词、性别等特殊语法,而Configurator.Formatters注册表则负责按文化解析与自定义扩展。理解这一契约,即可把 Humanizer 的本地化能力延伸到任意语言场景。

  • 开发工具

【免费下载链接】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
点击查看免费下载

相关推荐

上一篇:vit_large_patch16_384.augreg_in21k_ft_in1k源码解析:从论文到PyTorch实现的完整解读
下一篇:Pinpoint 的 Reactor Netty 插件:Netty 响应式 HTTP 服务端与客户端全链路追踪实战

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询