☰
Humanizer 的 IFormatter 接口:多语言数字、日期与时间单位格式化的本地化核心
2026/9/29 2:24:30 网站建设 项目流程
  • 开发工具

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

导读

IFormatter是 Humanizer 中负责本地化格式化的核心接口:当你的语言对数字、日期、时长和数据单位存在复杂的语法规则时(例如罗马尼亚语中 "5 days" 是 "5 zile" 而 "24 days" 是 "24 de zile",阿拉伯语中 "2 days" 是 "يومين" 而不是 "2 يوم"),就需要通过该接口获取符合目标语言习惯的字符串。阅读完本文,你将掌握IFormatter的全部 8 个方法签名与语义、Humanizer 内部的默认实现与注册机制,以及如何基于源码路径定位任意语言的格式化逻辑。

为什么需要 IFormatter:语言规则差异是本地化的真正难点

数字、日期和单位的本地化远不止"翻译单词"这么简单。以原文档给出的两个例子为证:

  • 罗马尼亚语:数字与名词之间存在介词de的插入规则,"5 days" 写作 "5 zile",而 "24 days" 写作 "24 de zile";
  • 阿拉伯语:名词存在单数、双数、复数等语法形态,"2 days" 使用双数形式 "يومين",而不是 "2 يوم"。

这类规则无法用简单的字符串替换表覆盖,因此 Humanizer 抽象出IFormatter接口,把"给定一个时间单位、时态或数据单位,返回目标语言对应的短语"这一职责统一封装,并由DefaultFormatter及基于声明式规则生成的ProfiledFormatter提供实现。

IFormatter 接口全貌

接口定义位于 src/Humanizer/Localisation/Formatters/IFormatter.cs,其注释明确说明其职责是"本地化 Humanizer 的数字、日期、时长和单位格式化"。完整方法如下:

方法签名用途关键参数
string DateHumanize_Now()返回"现在"的本地化文本无
string DateHumanize_Never()返回"从未发生"的本地化文本无
string DateHumanize(TimeUnit, Tense, int)返回相对日期短语(如 "2 days ago")时间单位、过去/未来时态、数量
string TimeSpanHumanize_Zero()返回零时长短语无
string TimeSpanHumanize(TimeUnit, int, bool toWords = false)返回时长短语时间单位、数量、是否用文字表达数字
string TimeSpanHumanize_Age()返回年龄后缀格式无
string DataUnitHumanize(DataUnit, double, bool toSymbol = true)返回数据单位(符号或全称)数据单位、数量、是否输出符号
string TimeUnitHumanize(TimeUnit)返回时间单位的符号时间单位

方法中涉及的枚举均定义在源码中:TimeUnit(Millisecond、Second、Minute、Hour、Day、Week、Month、Year)、Tense(Future、Past)、DataUnit(覆盖Bit、Byte到Exabyte、Pebibyte以及显式单位系统的DecimalKilobyte等 24 个取值)。

各方法详解

DateHumanize_Now / DateHumanize_Never:固定短语

这两个无参方法分别返回当前时刻与"永不"的本地化文本。在 DefaultFormatter 实现 中,它们直接读取生成好的短语表:

public virtual string DateHumanize_Now() => phraseTable.DateNow ?? "now"; public virtual string DateHumanize_Never() => phraseTable.DateNever ?? "never";

当某个语言未提供对应短语时,会回退到英文的"now"/"never"。从源码结构可以推断,短语数据来自按语言生成的LocalePhraseTable(由LocalePhraseTableCatalog.Resolve(culture)解析,见 DefaultFormatter.cs)。

DateHumanize(TimeUnit, Tense, int):相对日期短语

这是DateHumanize扩展方法(如DateTime.Humanize())最终调用的核心方法,负责把"2 天前"、"3 小时后"这类语义转成目标语言:

string DateHumanize(Humanizer.TimeUnit timeUnit, Humanizer.Tense timeUnitTense, int unit);

参数含义:

  • timeUnit:描述的时间单位(天、小时、月……);
  • timeUnitTense:引用发生在过去(Past)还是未来(Future);
  • unit:单位的数量。

接口 XML 文档注明:当timeUnit不受支持或unit为负数时会抛出ArgumentOutOfRangeException。底层实现TryFormatDateFromPhraseTable会依次处理零值(输出 "now")、数量为 1 的单数形态、数量为 2 的精确双数模板,再按复数规则渲染多数量短语(见 DefaultFormatter.cs)。

TimeSpanHumanize(TimeUnit, int, bool toWords):时长短语

string TimeSpanHumanize(Humanizer.TimeUnit timeUnit, int unit, bool toWords=false);

toWords参数决定数字是否用文字输出:例如英文下TimeSpanHumanize(Day, 2)输出 "2 days",而toWords: true时输出 "two days"。实现中FormatCountValue会根据toWords选择走NumberToWords(数字转文字,且支持按单位性别调用number.ToWords(gender, culture),见 ProfiledFormatter.cs)还是直接输出数字字符串。数量为 1 时优先使用单数形态(toWords为真时还会优先选用专门的单数文字变体SingleWordsVariant),否则渲染复数/多数量形态(见 DefaultFormatter.cs)。

TimeSpanHumanize_Zero:零时长

返回零时长(0 秒)的本地化文本,默认实现为phraseTable.TimeSpanZero ?? "no time"(见 DefaultFormatter.cs)。仅在TimeSpanHumanize的toWords为真且数量为 0 时被使用。

TimeSpanHumanize_Age:年龄后缀

该方法返回把已 humanize 的时长字符串转换为年龄表达的格式。原文档给出的英文示例是:该格式添加 " old" 后缀,使 "40 years" 变成 "40 years old"。默认实现返回短语表中的TimeSpanAge,缺失时回退为"{0}"(即原样输出,见 DefaultFormatter.cs)。因此各语言可以定义自己的年龄后缀模板,例如英文"{0} old"。

DataUnitHumanize(DataUnit, double, bool toSymbol):数据单位

string DataUnitHumanize(Humanizer.DataUnit dataUnit, double count, bool toSymbol=true);

toSymbol决定输出符号(如KB)还是完整单词(如kilobyte)。该方法是ByteSize.ToString()等字节格式化功能的本地化出口——在 ByteSize.cs 中,字节对象会通过Configurator.GetFormatter(culture)拿到当前文化的 formatter,然后对Exabyte、Petabyte、Gigabyte……逐个调用DataUnitHumanize(DataUnit.XXX, xxxBytes, toSymbol)来拼接本地化字符串。默认实现中,若某语言缺少 Petabyte、Exabyte 或二进制单位(Kibibyte 等)的短语,会回退到英文 formatter 处理(见 DefaultFormatter.cs)。

TimeUnitHumanize(TimeUnit):时间单位符号

返回给定时间单位的本地化符号,例如英文的s、m、h、d。默认实现从短语表读取Symbol,缺失则抛出InvalidOperationException(见 DefaultFormatter.cs)。该符号常用于TimeSpanHumanizeWithFractionalSeconds这类需要紧凑输出(如 "1.5s")的场景。

注册机制:IFormatter 是如何被选中的

所有 formatter 通过FormatterRegistry注册到Configurator.Formatters(见 Configurator.cs)。默认注册逻辑见 FormatterRegistry.cs:

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

LocaliserRegistry<IFormatter>的默认工厂为所有文化创建DefaultFormatter,而FormatterRegistryRegistrations.Register(this)会为特殊语言覆盖更精确的实现(如ProfiledFormatter)。日常调用链是:DateHumanize/TimeSpanHumanize/ByteSize.ToString等扩展方法 →Configurator.GetFormatter(culture)(见 Configurator.cs)→ 按当前线程文化解析出IFormatter实例 → 调用对应方法。

DefaultFormatter支持两种构造方式:传入CultureInfo或直接传入 locale 代码字符串(如"en"),后者内部通过new CultureInfo(localeCode)构造(见 DefaultFormatter.cs)。

语言特异性规则的源码级原理

复数形态检测器(ProfiledFormatter)

对于阿拉伯语、俄语、波兰语、斯洛文尼亚语等复数规则复杂的语言,ProfiledFormatter根据 YAML locale 数据生成的FormatterProfile选择不同的FormatterNumberDetectorKind检测器(见 ProfiledFormatter.cs):

  • SingularPlural:1 用单数,其余用复数;
  • ArabicLike:1 单数、2 双数、3–10 复数、其余默认;
  • ArabicCardinal:按% 100区分 few(3–10)与 many(11–99)形态;
  • Between2And4Paucal:2–4 使用少量数(paucal)形态;
  • Polish:以 2–4 结尾(12–14 除外)用 paucal;
  • Slovenian:2 用双数、3–4 用 paucal;
  • Russian/Lithuanian:使用各自的语法数字检测器。

形态枚举FormatterNumberForm提供Zero、Singular、Dual、Paucal、Plural、Many、Default七种形态(见 ProfiledFormatter.cs),短语表据此解析出正确的词形。

罗马尼亚语 de 介词

罗马尼亚语的de介词规则在ShouldUseRomanianPreposition中实现:当数字模 100 小于 1 或大于 19 时(即非 1–19 的整数范围)需要插入de(见 ProfiledFormatter.cs),因此才有 "5 zile" 与 "24 de zile" 的区别。

卢森堡语的 Eifeler 规则

卢森堡语则应用 EifelerRule.cs 判断数字文字后的辅音是否触发n后缀省略,由FormatterSecondaryPlaceholderMode.LuxembourgishEifelerN模式驱动(见 ProfiledFormatter.cs)。

性别感知的数字转文字

某些语言的时间单位有语法性别。ProfiledFormatter通过UnitGenders字典把TimeUnit映射到GrammaticalGender,转换数字文字时调用number.ToWords(gender, culture)(见 ProfiledFormatter.cs)。

如何自定义实现 IFormatter

当目标语言无法用声明式规则覆盖时,可以实现IFormatter的 8 个方法并将其注册进Configurator.Formatters。从DefaultFormatter的结构看(DefaultFormatter.cs),它同时实现了IGrammaticalCaseTimeSpanFormatter以支持带语法格的时长短语(俄语、立陶宛语等),且该接口对派生自DefaultFormatter的自定义类有显式实现要求:GetType().Assembly与DefaultFormatter程序集不一致时会抛出NotSupportedException(见 DefaultFormatter.cs)。这意味着如果需要语法格支持,自定义类必须显式实现IGrammaticalCaseTimeSpanFormatter,而不能仅依赖基类。

建议的自定义路径:优先继承DefaultFormatter或直接注册基于 YAML 生成的ProfiledFormatter(新增语言通常只需添加 locale 数据,见 ProfiledFormatter.cs 的注释),只有规则极其特殊时才手写IFormatter实现。

小结

IFormatter是 Humanizer 本地化能力的抽象边界:8 个方法分别覆盖相对日期、时长、年龄、数据单位与时间单位符号;DefaultFormatter提供基于短语表的通用实现,ProfiledFormatter通过声明式 profile 承载各语言的复数、介词、性别与词形规则,而Configurator.Formatters注册表负责按文化解析出正确的实例。理解这一接口,你就掌握了 Humanizer 所有 Humanize 系列扩展方法(DateTime.Humanize()、TimeSpan.Humanize()、ByteSize.ToString()等)最终输出多语言文本的完整链路。

  • 开发工具

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

相关推荐

上一篇:QtScrcpy:安卓投屏与键鼠映射,10分钟上手
下一篇:Audacity 免费音频编辑完整指南:5 步从录音到成品

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

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

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

立即咨询