- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
导读:本文围绕 Humanizer 公开 API 中的GrammaticalCase枚举展开,说明它如何在日期序数词(ToOrdinalWords)与时长文本(HumanizeWithCase)两类场景中按语法格位(格)选择正确的词形。读完本文,你将掌握该枚举的全部成员语义、Humanizer 对其的校验与异常规则、以及它在俄语、丹麦语、罗马尼亚语、印地语、阿塞拜疆语等文化下的实际输出形态与适用边界。
一、什么是 GrammaticalCase:从 API 文档到源码定义
在 version-3.0.1 的 API 文档 中,GrammaticalCase被定义为:
Options for specifying the desired grammatical case for the output words
即“用于指定输出词汇所需语法格位的选项”。它是 Humanizer 中一个public enum,作用是告诉本地化层:当某门语言的名词、形容词、序数词会随句子成分(主语、宾语、领属者、介词宾语等)发生**词形变化(变格)**时,应该渲染成哪一种形态。对于俄语、波兰语、捷克语、德语、芬兰语等拥有格系统的语言,这一参数直接决定输出词的正确写法;而对于英语这类基本不发生名词变格的语言,该参数通常不产生任何效果。
当前仓库中的实际定义位于 src/Humanizer/GrammaticalCase.cs,其完整枚举成员为28 个(自动按序编号 0~29,Causal为最后一个)。而 v3.0.1 版本 API 文档只收录了前 6 个印欧语系核心格位(编号 0~5),后续版本在此基础上扩展出了更多区域性语言(如芬兰-乌戈尔语系、达罗毗荼语系)所需的格位。
二、全部枚举成员与语义对照表
以下表格整合了 API 文档 中记载的 6 个核心成员与 源码 中扩展的其余成员及其 XML 注释语义:
| 成员 | 隐含数值 | 语义(对应源码 XML 注释) | 典型使用语言(推断) |
|---|---|---|---|
Nominative | 0 | 表示限定动词的主语(subject of a finite verb) | 俄语、德语、拉丁语等 |
Genitive | 1 | 表示另一名词的领属者(possessor of another noun) | 俄语、德语、丹麦语 |
Dative | 2 | 表示动词的间接宾语(indirect object of a verb) | 俄语、德语 |
Accusative | 3 | 表示动词的直接宾语(direct object of a verb) | 俄语、德语 |
Instrumental | 4 | 表示执行动作时使用的工具(object used in performing an action) | 俄语、波兰语 |
Prepositional | 5 | 表示介词宾语(object of a preposition) | 俄语 |
Ablative | 6 | 表示离开某名词的运动(motion away from a noun) | 拉丁语、芬兰语 |
Comitative | 7 | 表示伴随(accompaniment) | 芬兰语、爱沙尼亚语 |
Ergative | 8 | 表示作格结构中及物动词的施事(agent of a transitive verb in an ergative construction) | 巴斯克语 |
Locative | 9 | 表示位置(location) | 波兰语、捷克语 |
Oblique | 10 | 表示用在格标记后置词或后缀之前的形态(form used before a case-marking postposition or suffix) | 印地语、旁遮普语 |
Partitive | 11 | 表示部分或不定量(partial or indefinite quantity) | 芬兰语、爱沙尼亚语 |
Vocative | 12 | 表示直接称呼(direct address) | 波兰语、捷克语、乌克兰语 |
Elative | 13 | 表示从内部离开的运动(motion out of or away from within) | 芬兰语、爱沙尼亚语 |
Illative | 14 | 表示进入内部的运动(motion into) | 芬兰语、爱沙尼亚语 |
Sociative | 15 | 表示马拉雅拉姆语社会性格的关联或伴随(association or accompaniment) | 马拉雅拉姆语 |
Terminative | 16 | 表示端点或界限(endpoint or limit) | 爱沙尼亚语 |
Translative | 17 | 表示转变进入某种状态(transition into a state) | 芬兰语、爱沙尼亚语 |
Absolutive | 18 | 表示不及物动词的无标记主目或及物动词宾语(unmarked argument of an intransitive verb or object of a transitive verb) | 巴斯克语 |
Additive | 19 | 表示爱沙尼亚语短入格(Estonian short illative form) | 爱沙尼亚语 |
Inessive | 20 | 表示位于内部(location within) | 芬兰语、爱沙尼亚语 |
Allative | 21 | 表示朝上/朝某处的运动(motion onto or toward) | 芬兰语、爱沙尼亚语 |
Adessive | 22 | 表示位于某处/附着(location on or at) | 芬兰语、爱沙尼亚语 |
Essive | 23 | 表示临时状态或角色(temporary state or role) | 芬兰语、爱沙尼亚语 |
Abessive | 24 | 表示缺失或“没有”(absence or being without) | 芬兰语、爱沙尼亚语 |
Equative | 25 | 表示比较或等同(comparison or equivalence) | 格鲁吉亚语 |
Directive | 26 | 表示朝某方向的运动或趋向(motion or direction toward) | 部分高加索语言 |
Lative | 27 | 表示朝目的地的方向(direction toward a destination) | 芬兰语系部分语言 |
Benefactive | 28 | 表示预期受益者(intended beneficiary) | 部分语言 |
Causal | 29 | 表示原因或理由(cause or reason) | 芬兰语等 |
说明:数值为源码中按声明顺序的隐含编号,前 6 项与 v3.0.1 API 文档 中标注的显式值(Nominative=0 … Prepositional=5)完全一致。
三、GrammaticalCase 的两大消费入口
Humanizer 中实际接收GrammaticalCase参数的公开 API 主要有两组,均定义在扩展方法中,可通过 Public API 快照 中的ToOrdinalWords(...grammaticalCase)与HumanizeWithCase(...grammaticalCase)签名得到印证。
3.1 日期序数词:ToOrdinalWords(input, GrammaticalCase)
定义于 src/Humanizer/DateToOrdinalWordsExtensions.cs:
DateTime.ToOrdinalWords(this DateTime input, GrammaticalCase grammaticalCase);DateOnly.ToOrdinalWords(this DateOnly input, GrammaticalCase grammaticalCase)(仅 .NET 6.0 及以上)。
其实现直接委托给Configurator注册的IDateToOrdinalWordConverter/IDateOnlyToOrdinalWordConverter。从 DefaultDateToOrdinalWordConverter.cs 可以看到默认转换器的行为:
- 英语文化下输出形如
22nd of December, 2020,且Convert(DateTime date, GrammaticalCase grammaticalCase)直接忽略传入的格位参数并调用无格位版本——这正是“英语没有变格,参数无效”的源码证据; - 非英语文化则使用当前文化的短日期格式
date.ToString("d", culture),并剥离希伯来/阿拉伯日历等输出中可能嵌入的方向性标记(U+200E、U+200F、U+061C),保证嵌入更大序数短语时文本可读。
源码目录src/Humanizer/Localisation/DateToOrdinalWords/下存放了 8 个区域性转换器实现,可以推断俄语、波兰语等带格系统的语言各自实现了格位感知的序数词转换;而 CoverageGapTests.cs 中Assert.Equal("February 22nd, 2024", date.ToOrdinalWords(GrammaticalCase.Genitive))的用例则从测试侧再次确认:英语文化下无论传入哪个格位,输出保持一致。
3.2 时长文本:HumanizeWithCase(TimeSpan, GrammaticalCase, …)
定义于 src/Humanizer/TimeSpanHumanizeExtensions.cs,提供两个重载:
public static string HumanizeWithCase( this TimeSpan timeSpan, GrammaticalCase grammaticalCase, int precision = 1, CultureInfo? culture = null, TimeUnit maxUnit = TimeUnit.Week, TimeUnit minUnit = TimeUnit.Millisecond, string? collectionSeparator = ", ") public static string HumanizeWithCase( this TimeSpan timeSpan, GrammaticalCase grammaticalCase, int precision, bool countEmptyUnits, CultureInfo? culture = null, TimeUnit maxUnit = TimeUnit.Week, TimeUnit minUnit = TimeUnit.Millisecond, string? collectionSeparator = ", ")参数说明(依据源码 XML 注释):
grammaticalCase:用于选择每个时间单位短语的格位;precision:最多返回的时间单位数量(默认 1);countEmptyUnits:空时间单位是否计入precision(前导空单位永不计数);culture:使用的文化,null时取当前线程文化;maxUnit/minUnit:输出的最大/最小时间单位(默认Week/Millisecond);collectionSeparator:组合各时间分段的连接符;传null时使用文化的默认集合格式化器。
它内部先调用ValidateGrammaticalCase做参数校验,再要求当前配置的ITimeSpanHumanizeStrategy必须实现IGrammaticalCaseTimeSpanHumanizeStrategy,否则抛出NotSupportedException。默认策略 DefaultTimeSpanHumanizeStrategy.cs 实现了该接口,并经由IGrammaticalCaseTimeSpanFormatter接口把格位请求转发给 DefaultFormatter.cs 中的TimeSpanHumanize(TimeUnit, int, GrammaticalCase)显式接口实现。
四、源码级校验与异常语义
Humanizer 对GrammaticalCase的校验横跨扩展方法与格式化器两层,理解这些规则有助于规避运行时异常:
4.1 范围校验:仅允许 ≤ Causal 的枚举值
在 TimeSpanHumanizeExtensions.cs 的ValidateGrammaticalCase与 DefaultFormatter.cs 的接口实现中,均采用无符号比较:
if ((uint)grammaticalCase > (uint)GrammaticalCase.Causal) { throw new ArgumentOutOfRangeException( nameof(grammaticalCase), grammaticalCase, "Unsupported grammatical case."); }即:只要是枚举定义内的值(0~29)就通过校验;若传入未定义的整数值(如(GrammaticalCase)99),则抛出ArgumentOutOfRangeException。
4.2 文化级分类:NotApplicable / Unsupported
DefaultFormatter的格位感知路径依赖源码生成器产出的LocaleDurationCaseTableCatalog(生成输入定义见 src/Humanizer.SourceGenerators/Generators/ProfileCatalogs/LocaleDurationCaseTableCatalogInput.cs),按LocaleDurationCaseClassification分三类处理:
Unsupported:该文化具备格位系统,但尚无经核验的时长短语形态——抛出NotSupportedException;NotApplicable:该文化不支持格位时长短语(如英语)——抛出NotSupportedException;- 正常分类:通过
table.TryGetCase(grammaticalCase, out var caseOverlay)查询,若文化不支持所请求的特定格位,同样抛出NotSupportedException。
此外,格位叠加层还按单位区分SameAsNominative(退回主格形态)、NotApplicable/Unsupported(拒绝)与常规短语渲染三种路径,保证“某个格位对该单位不适用”时给出清晰失败信息而非错误词形。
五、格位感知输出的真实形态:固定用例验证
测试 tests/Humanizer.Tests/Localisation/GeneratedLocaleData/CldrDurationCaseTests.cs 提供了大量“钉死”(pinned)的格位输出样例,可直接作为行为参照:
| 文化 | 格位 | 单位 | 数量 | 期望输出 |
|---|---|---|---|---|
da-DK | Genitive | Day | 1 | 1 dags |
sv-SE | Genitive | Day | 1 | 1 dygns |
nn-NO | Genitive | Week | 1 | 1 vekes |
nb-NO | Genitive | Week | 1 | 1 ukes |
ro-RO | Genitive | Day | 1 | unei zile |
am-ET | Accusative | Day | 1 | አንድ ቀን |
hi-IN | Oblique | Day | 1 | 1 दिन |
pa-IN | Oblique | Week | 1 | 1 ਹਫ਼ਤੇ |
az | Dative | Day | 1 | 1 günə |
可见:丹麦语、瑞典语、挪威语的领属格会给名词加词尾(如dag → dags、dygn → dygns);罗马尼亚语属格甚至将单位渲染成独立的属格短语unei zile;印地语与旁遮普语则使用Oblique形态(后置词前的变格)。同一测试文件还验证了阿拉伯语(ar)在Nominative/Genitive/Accusative下小时与天的双数、复数形态差异,例如ساعة واحدة(1 小时)、ساعتان(2 小时,主格)、ساعتين(2 小时,属格/宾格)。
测试中还包含“上下文相关失败”用例:索马里语(so)对Nominative抛出包含"does not apply"的NotSupportedException,验证了 4.2 节所述的单位级格位适用性检查。
六、实战示例与注意事项
using Humanizer; using System.Globalization; // 1) 日期序数词:英语下格位参数被忽略 var en = new DateTime(2024, 2, 22) .ToOrdinalWords(GrammaticalCase.Genitive); // "February 22nd, 2024"(与 Nominative 相同) // 2) 时长短语:丹麦语属格 var danish = TimeSpan.FromDays(1).HumanizeWithCase( GrammaticalCase.Genitive, culture: new CultureInfo("da-DK"), maxUnit: Humanizer.TimeUnit.Day, minUnit: Humanizer.TimeUnit.Day); // => "1 dags" // 3) 时长短语:阿塞拜疆语与格 var azeri = TimeSpan.FromDays(1).HumanizeWithCase( GrammaticalCase.Dative, culture: new CultureInfo("az"), maxUnit: Humanizer.TimeUnit.Day, minUnit: Humanizer.TimeUnit.Day); // => "1 günə"使用要点:
- 先确认文化支持:对无格系统的文化(如
en-US、zh-CN)调用HumanizeWithCase会因LocaleDurationCaseClassification.NotApplicable抛出NotSupportedException,并非所有语言都适用格位参数; - 异常面较广:除
ArgumentOutOfRangeException(非法枚举值)外,还可能在“文化无格位时长表”“文化不支持该格位”“该格位不适用于该单位”三种情况下抛出NotSupportedException,生产代码应做好捕获或先探测支持范围; - 默认参数即可满足多数场景:
precision默认 1、maxUnit默认Week、minUnit默认Millisecond,与普通Humanize的默认行为一致,通常只需显式传入grammaticalCase与culture; - DateOnly 重载仅限 .NET 6+:
DateOnly版本的ToOrdinalWords(input, grammaticalCase)带有#if NET6_0_OR_GREATER条件编译,面向旧框架时只有DateTime重载可用。
七、总结
GrammaticalCase是 Humanizer 多语言本地化能力在“屈折语言”方向上的关键抽象:它以 28 个成员覆盖印欧、乌拉尔、达罗毗荼、闪含等多个语系的格位系统,通过ToOrdinalWords与HumanizeWithCase两个入口暴露给开发者,底层则由源码生成器按文化产出格位时长表、由DefaultFormatter统一调度。理解其成员语义、文化分类与异常路径,是正确使用 Humanizer 处理俄语、丹麦语、印地语等语言文本输出的前提。
- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
相关推荐
Humanizer 的 GrammaticalCase 枚举:为多语言屈折输出精确指定语法格
Humanizer 的 GrammaticalCase 枚举:为多语言屈折输出精确指定语法格 本篇技术指南围绕 Humanizer https://link.g
开发工具Humanizer GrammaticalCase 枚举参考:语法格的完整定义与格感知时长输出实现
Humanizer GrammaticalCase 枚举参考:语法格的完整定义与格感知时长输出实现 本文以 Humanizer 2.13.14 版本文档站中的
开发工具Humanizer 的 GrammaticalCase 枚举:为多语言输出选择正确的语法格
Humanizer 的 GrammaticalCase 枚举:为多语言输出选择正确的语法格 GrammaticalCase 是 Humanizer 库中用于指定
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考