Add grammatical-case-aware TimeSpan humanization - #1879
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis change adds grammatical-case-aware ChangesGrammatical-case duration formatting
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant TimeSpanHumanizeExtensions
participant HumanizeStrategy
participant DefaultFormatter
participant LocaleDurationCaseTableCatalog
Caller->>TimeSpanHumanizeExtensions: HumanizeWithCase(timeSpan, grammaticalCase)
TimeSpanHumanizeExtensions->>HumanizeStrategy: Humanize(..., grammaticalCase)
HumanizeStrategy->>DefaultFormatter: TimeSpanHumanize(timeUnit, count, grammaticalCase)
DefaultFormatter->>LocaleDurationCaseTableCatalog: Resolve(culture)
LocaleDurationCaseTableCatalog-->>DefaultFormatter: LocaleDurationCaseTable
DefaultFormatter-->>HumanizeStrategy: Locale-authored duration phrase
HumanizeStrategy-->>Caller: Bare grammatical-case duration
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a987414834
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Adds a new, opt-in grammatical-case-aware TimeSpan humanization API (HumanizeWithCase) to Humanizer, along with generated locale coverage tables, new capability interfaces for custom strategies/formatters, tests, and documentation so callers can request cased duration phrases without changing existing Humanize contracts.
Changes:
- Introduces
TimeSpan.HumanizeWithCase(...)plus new optional capability interfaces (IGrammaticalCaseTimeSpanHumanizeStrategy,IGrammaticalCaseTimeSpanFormatter) implemented by the defaults. - Extends locale YAML + source generator pipeline to classify and (when available) provide per-unit case overlays with explicit unsupported/not-applicable behavior.
- Adds runtime + generator tests, public API approvals, and documentation updates covering the new API and authoring surface.
Reviewed changes
Copilot reviewed 135 out of 135 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| website/scenario-api-contract.json | Adds the new capability interface docs to scenario contract validation. |
| website/docs/scenarios/localization-and-extensibility.mdx | Documents how case-aware duration behavior is wired via optional strategy/formatter capabilities. |
| website/docs/scenarios/durations-and-ages.mdx | Adds scenario documentation and example usage for HumanizeWithCase. |
| website/docs/scenarios/dates-times-durations-and-age.mdx | Links new case-aware duration APIs from the overview scenario page. |
| website/docs/languages/supported-cultures.mdx | Clarifies that duration-case coverage is separate from general locale coverage. |
| website/docs/languages/grammar-and-platform-formatting.mdx | Notes scope/constraints of case-aware duration formatting. |
| website/docs/languages/custom-localization.mdx | Documents custom formatter/strategy requirements for HumanizeWithCase. |
| website/docs/contributing/locale-yaml-reference.mdx | Documents the new surfaces.durationCases authoring surface and runtime contract. |
| website/docs/api/Humanizer.TimeSpanHumanizeExtensions.md | Adds API reference entries for the two HumanizeWithCase overloads. |
| website/docs/api/Humanizer.md | Adds the two new capability interfaces to the API index. |
| website/docs/api/Humanizer.ITimeSpanHumanizeStrategy.md | Adds the new derived interface to the ITimeSpanHumanizeStrategy page. |
| website/docs/api/Humanizer.IGrammaticalCaseTimeSpanHumanizeStrategy.md | New API doc page for case-aware time-span strategy capability. |
| website/docs/api/Humanizer.IGrammaticalCaseTimeSpanFormatter.md | New API doc page for case-aware formatter capability. |
| website/docs/api/Humanizer.IFormatter.md | Adds the new derived formatter interface to the IFormatter page. |
| website/docs/api/Humanizer.GrammaticalCase.md | Updates doc output for the expanded GrammaticalCase enum. |
| website/docs/api/Humanizer.DefaultTimeSpanHumanizeStrategy.md | Documents DefaultTimeSpanHumanizeStrategy implementing the new capability method. |
| website/docs/api/Humanizer.DefaultFormatter.md | Documents DefaultFormatter implementing the new case-aware TimeSpan formatting method. |
| tests/Humanizer.Tests/TimeSpanHumanizeTests.cs | Adds unit tests for HumanizeWithCase, invalid inputs, and legacy custom component behavior. |
| tests/Humanizer.Tests/Localisation/ml/MalayalamDurationCaseTests.cs | Adds Malayalam case-aware duration assertions and an explicit unsupported-unit test. |
| tests/Humanizer.Tests/Localisation/hu/HungarianDurationCaseTests.cs | Adds Hungarian case-aware duration assertions. |
| tests/Humanizer.Tests/Localisation/GeneratedLocaleData/CldrDurationCaseTests.cs | Pins selected CLDR-derived case forms + verifies “unsupported” locale behavior throws clearly. |
| tests/Humanizer.Tests/Localisation/fi/FinnishDurationCaseTests.cs | Adds Finnish case-aware duration assertions. |
| tests/Humanizer.Tests/Localisation/de/GermanDurationCaseTests.cs | Adds German case-aware singular/multiple expectations (original motivating example). |
| tests/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.Net4_8.verified.txt | Approves new public API surface for net48. |
| tests/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet8_0.verified.txt | Approves new public API surface for net8. |
| tests/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet10_0.verified.txt | Approves new public API surface for net10. |
| tests/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet11_0.verified.txt | Approves new public API surface for net11. |
| tests/Humanizer.SourceGenerators.Tests/SourceGenerators/LocalePhraseNormalizationTests.cs | Adds generator tests ensuring duration-case overlays are complete and validated. |
| tests/Humanizer.SourceGenerators.Tests/SourceGenerators/CanonicalLocaleSchemaTests.cs | Adds generator tests verifying every checked-in locale is duration-case-classified with expected counts. |
| src/Humanizer/TimeSpanHumanizeStrategy/IGrammaticalCaseTimeSpanHumanizeStrategy.cs | Introduces the new optional strategy capability interface. |
| src/Humanizer/TimeSpanHumanizeStrategy/DefaultTimeSpanHumanizeStrategy.cs | Implements the capability method and routes it through DefaultHumanize. |
| src/Humanizer/TimeSpanHumanizeExtensions.cs | Adds HumanizeWithCase overloads and threads an optional GrammaticalCase through formatting pipeline. |
| src/Humanizer/Localisation/Formatters/ProfiledFormatter.cs | Refactors unit-gender lookup to a virtual hook for reuse/consistency. |
| src/Humanizer/Localisation/Formatters/LocaleDurationCaseTable.cs | Adds runtime structures + culture-resolution for generated duration-case tables. |
| src/Humanizer/Localisation/Formatters/IGrammaticalCaseTimeSpanFormatter.cs | Introduces the new optional formatter capability interface. |
| src/Humanizer/Localisation/Formatters/DefaultFormatter.cs | Implements case-aware duration formatting and consults generated duration-case tables. |
| src/Humanizer/Locales/af.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/am.yml | Adds durationCases classification metadata and overlays (where applicable). |
| src/Humanizer/Locales/ar.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/as.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/az.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/be.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/bg.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/bn.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/bs.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/ca.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/cy.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/da.yml | Adds durationCases overlays for distinct case support. |
| src/Humanizer/Locales/de.yml | Adds durationCases overlays for German case support. |
| src/Humanizer/Locales/el.yml | Adds durationCases overlays for distinct case support. |
| src/Humanizer/Locales/en.yml | Adds durationCases classification metadata (not-applicable). |
| src/Humanizer/Locales/es.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/et.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/eu.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/fa.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/fi.yml | Adds durationCases overlays for distinct case support. |
| src/Humanizer/Locales/fil.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/fr.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/ga.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/gl.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/gu.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/ha.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/he.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/hi.yml | Adds durationCases overlays for distinct case support. |
| src/Humanizer/Locales/hr.yml | Adds durationCases overlays for distinct case support. |
| src/Humanizer/Locales/hu.yml | Adds durationCases overlays for distinct case support. |
| src/Humanizer/Locales/id.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/ig.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/is.yml | Adds durationCases overlays for distinct case support. |
| src/Humanizer/Locales/it.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/ja.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/ka.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/kk.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/km.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/ko.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/kok.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/ku.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/ky.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/lb.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/lo.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/lv.yml | Adds durationCases overlays for distinct case support. |
| src/Humanizer/Locales/mk.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/mn.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/ms.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/mt.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/my.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/nb.yml | Adds durationCases overlays for distinct case support. |
| src/Humanizer/Locales/ne.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/nl.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/nn.yml | Adds durationCases overlays for distinct case support. |
| src/Humanizer/Locales/or.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/pa.yml | Adds durationCases overlays for distinct case support. |
| src/Humanizer/Locales/pa-Arab.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/ps.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/pt.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/ro.yml | Adds durationCases overlays for distinct case support. |
| src/Humanizer/Locales/si.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/so.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/sq.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/sr.yml | Adds durationCases overlays for distinct case support. |
| src/Humanizer/Locales/sr-Latn.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/sv.yml | Adds durationCases overlays for distinct case support. |
| src/Humanizer/Locales/sw.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/ta.yml | Adds durationCases overlays for distinct case support. |
| src/Humanizer/Locales/te.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/th.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/tk.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/tr.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/ur.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/uz-Cyrl-UZ.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/uz-Latn-UZ.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/vi.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/yo.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/zh-Hans.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/zh-Hant.yml | Adds durationCases classification metadata. |
| src/Humanizer/Locales/zu-ZA.yml | Adds durationCases classification metadata. |
| src/Humanizer/GrammaticalCase.cs | Expands GrammaticalCase enum to include additional CLDR-relevant cases. |
| src/Humanizer.SourceGenerators/HumanizerSourceGenerator.cs | Wires new duration-case table generation into the generator pipeline. |
| src/Humanizer.SourceGenerators/Generators/ProfileCatalogs/LocaleDurationCaseTableCatalogInput.cs | Generates the runtime duration-case lookup tables per locale. |
| src/Humanizer.SourceGenerators/Common/LocaleYamlCatalog.cs | Adds durationCases to the list of required locale surfaces. |
| src/Humanizer.SourceGenerators/Common/LocalePhraseNormalization.cs | Exposes phrase parsing helper for duration-case normalization. |
| src/Humanizer.SourceGenerators/Common/DurationCaseModels.cs | Adds parsing/validation models for the durationCases YAML surface + coverage generation. |
| src/Humanizer.SourceGenerators/Common/CanonicalLocaleAuthoring.cs | Includes durationCases in canonical surface extraction. |
| readme.md | Adds a quick-start example and contract notes for HumanizeWithCase. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
a987414 to
a8944c7
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a8944c787d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 137 out of 137 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
src/Humanizer/Localisation/Formatters/LocaleDurationCaseTable.cs:34
- The primary-constructor parameter 'cases' is immediately shadowed by a field with the same name (
readonly ... cases = cases;). Even if it compiles, it’s easy to misread and makes later edits error-prone. Use a distinct field name (e.g._cases).
src/Humanizer/TimeSpanHumanizeExtensions.cs:482 - This NotSupportedException message falls back to CultureInfo.CurrentUICulture when no culture is provided, but Configurator.GetFormatter(null) resolves using CultureInfo.CurrentCulture. The message can therefore report the wrong culture name.
There was a problem hiding this comment.
Actionable comments posted: 19
🧹 Nitpick comments (5)
src/Humanizer/TimeSpanHumanizeExtensions.cs (1)
176-196: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAmbiguous-by-reading overload: the 8th positional
boolchanges meaning.In the existing
DefaultHumanizethe 8th parameter istoWords; in this new overload it istoSymbols. The compiler distinguishes them by the trailingGrammaticalCase, but a caller passingtruepositionally gets a silently different behavior. Consider renaming this entry point (e.g.DefaultHumanizeWithCase) to keep the internal contract unmistakable.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Humanizer/TimeSpanHumanizeExtensions.cs` around lines 176 - 196, Rename the new DefaultHumanize overload that accepts GrammaticalCase to an unambiguous name such as DefaultHumanizeWithCase, and update all callers accordingly. Keep the existing DefaultHumanize overload and its toWords positional contract unchanged.src/Humanizer/Localisation/Formatters/DefaultFormatter.cs (1)
66-120: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCase-aware formatting is not overridable by derived formatters.
Every other formatting member on this public type is
virtual, but this is an explicit interface implementation, so subclasses (including third-party formatters deriving fromDefaultFormatter) can neither override nor call it. If the intent is that locale formatters can specialize case handling, expose aprotected virtualcore method that the explicit implementation delegates to; retrofitting that after release is harder than doing it now.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Humanizer/Localisation/Formatters/DefaultFormatter.cs` around lines 66 - 120, The explicit IGrammaticalCaseTimeSpanFormatter.TimeSpanHumanize implementation prevents derived formatters from specializing case-aware formatting. Extract its validation and formatting logic into a protected virtual core method on DefaultFormatter, then have the explicit interface member delegate to that method so subclasses can override the behavior.src/Humanizer.SourceGenerators/Common/DurationCaseModels.cs (1)
50-92: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSurface authoring errors as generator diagnostics instead of thrown exceptions.
Createthrows for both non-empty diagnostics and for any locale missingdurationCases. SinceLocaleDurationCaseTableCatalogInput.Create(src/Humanizer.SourceGenerators/Generators/ProfileCatalogs/LocaleDurationCaseTableCatalogInput.cs, Line 49-58) only guardsDiagnostics.IsEmpty, a locale YAML file that parses cleanly but omits the surface will crash the generator instead of producing theInvalidLocaleDefinitiondiagnostic used by every other locale path inLocaleCatalogInput.Create. Consider returning diagnostics from this layer (or catching in the caller) so authors get an actionable error.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Humanizer.SourceGenerators/Common/DurationCaseModels.cs` around lines 50 - 92, Update DurationCaseCoverageInput.Create to report missing durationCases classifications through the generator’s existing InvalidLocaleDefinition diagnostic flow instead of throwing InvalidOperationException. Reuse the established diagnostic-returning pattern used by LocaleCatalogInput.Create and LocaleDurationCaseTableCatalogInput.Create, while preserving successful catalog and coverage-row construction for valid locales.src/Humanizer.SourceGenerators/Generators/ProfileCatalogs/LocaleDurationCaseTableCatalogInput.cs (2)
25-45: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winEmitted case array and runtime index share one unenforced contract with
GrammaticalCase. The generator emits a fixed-orderLocalizedDurationCase?[]fromCaseOrder, and the runtime looks entries up by raw enum ordinal with no length or order validation anywhere; adding an enum member without touching the generator silently misresolves cases or throwsIndexOutOfRangeException.
src/Humanizer.SourceGenerators/Generators/ProfileCatalogs/LocaleDurationCaseTableCatalogInput.cs#L25-L45: derive/validateCaseOrderagainst theGrammaticalCasemember list (or emit a compile-time assertion on the final member) so a mismatch fails the build.src/Humanizer/Localisation/Formatters/LocaleDurationCaseTable.cs#L32-L43: bounds-check the index inTryGetCaseand returnfalsefor out-of-range values instead of relying on every caller pre-validating.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Humanizer.SourceGenerators/Generators/ProfileCatalogs/LocaleDurationCaseTableCatalogInput.cs` around lines 25 - 45, The CaseOrder contract with GrammaticalCase is unenforced: update LocaleDurationCaseTableCatalogInput.cs lines 25-45 to derive or validate the emitted order against the enum members, or add a compile-time assertion that fails when the final member changes; update LocaleDurationCaseTable.cs lines 32-43 in TryGetCase to bounds-check the raw enum index and return false for out-of-range values.
25-45: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPositional coupling between
CaseOrderand theGrammaticalCaseenum is unenforced.The emitted
LocalizedDurationCase?[]is indexed at runtime by(int)grammaticalCase(src/Humanizer/Localisation/Formatters/LocaleDurationCaseTable.cs, Line 34). Today both lists have 18 entries in the same order, but nothing fails the build if a future enum member is added without updating this array — the failure would be a silent wrong-case lookup or anIndexOutOfRangeException. Consider emitting a compile-time guard (e.g. generate the array sized/ordered from a shared name list and emit a static assertion onGrammaticalCase.Translative) or a generator-side length check.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Humanizer.SourceGenerators/Generators/ProfileCatalogs/LocaleDurationCaseTableCatalogInput.cs` around lines 25 - 45, Add generator-side validation in the catalog generation flow that compares CaseOrder against the GrammaticalCase enum’s expected member count and ordering, failing generation when they diverge. Anchor the check to CaseOrder and the emitted LocalizedDurationCase?[] so runtime indexing by (int)grammaticalCase remains safe; preserve the existing case order when validation succeeds.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/Humanizer/Locales/cs.yml`:
- Around line 108-123: Update the plural entries in the Czech duration-case
overlays in cs.yml, replacing incorrect forms with the proper plural ending for
each grammatical case: dative sekundám, instrumental sekundami, and locative
sekundách. Apply this consistently to every affected duration unit and overlay
range, including the visible second forms, while leaving singular, dual, paucal,
and numeric entries unchanged.
In `@src/Humanizer/Locales/fi.yml`:
- Around line 304-314: Update illative.units.minute.multiple.forms in the
Finnish locale so the default, dual, paucal, and plural forms use minuuttiin
instead of minuutista, matching the existing singular illative form and
neighboring units.
- Around line 183-189: Update the year forms under countPlacement in the Finnish
locale so default, dual, paucal, and plural use the partitive form “{count}
vuotta” instead of “{count} vuoteen”; preserve the existing singular form.
In `@src/Humanizer/Locales/hr.yml`:
- Around line 154-243: Update the instrumental duration templates under
instrumental.units so every count-bearing multiple form uses valid Croatian
instrumental plural forms, including sekundama, minutama, satima, danima,
tjednima, mjesecima, and godinama. Replace the existing count-bearing forms
across default, singular, dual, paucal, and plural entries while preserving the
single numeric forms and template structure.
In `@src/Humanizer/Locales/hu.yml`:
- Around line 10-22: Update the accusative millisecond default form under
units.millisecond.multiple.forms to use the accusative “-et” ending, matching
the singular form and other accusative categories; leave the surrounding locale
entries unchanged.
In `@src/Humanizer/Locales/is.yml`:
- Around line 238-248: Update the multiple week duration forms in the week
localization block so every count-based form renders “{count} viku” instead of
“{count} vikna”, while preserving the existing “vikur” forms elsewhere in the
block.
In `@src/Humanizer/Locales/kn.yml`:
- Around line 400-405: Update the vocative `year` locale forms so `default`,
`dual`, `paucal`, and `plural` use the correct counted plural morphology,
matching the established Kannada plural form for non-singular counts. Preserve
`singular` as `{count} ವರ್ಷ`; if Kannada intentionally treats `ವರ್ಷ` as
invariant, verify and retain that behavior explicitly.
In `@src/Humanizer/Locales/ml.yml`:
- Around line 253-333: The Malayalam instrumental duration forms in the
instrumental.units entries currently use the generic postposition “കൊണ്ട്”;
replace each unit’s single numeric and multiple plural-form values with the
corresponding CLDR 48 “-നാൽ” inflected forms, including second, minute, hour,
day, week, month, and year, while preserving the existing structure and count
placement.
In `@src/Humanizer/Locales/nb.yml`:
- Around line 7-8: Fix the shared durationCases YAML nesting so it is a child of
inflection, allowing the generator to read surfaces.inflection.durationCases.
Apply the indentation change in src/Humanizer/Locales/nb.yml lines 7-8,
src/Humanizer/Locales/nn.yml lines 8-9, src/Humanizer/Locales/my.yml lines 7-8,
src/Humanizer/Locales/ne.yml lines 7-8, and src/Humanizer/Locales/nl.yml lines
7-8; indent each block’s classification content beneath durationCases as needed.
In `@src/Humanizer/Locales/pl.yml`:
- Around line 7-99: Update the dual entries under durationCases.units for
second, minute, hour, week, month, and year across the genitive, dative,
instrumental, locative, and vocative case sections in pl.yml. Replace reused
singular or genitive text with the correct Polish dual form for each declared
case, including dative -om, instrumental -ami, locative -ach, and vocative
forms, while preserving the existing single, default, singular, paucal, and
plural values.
In `@src/Humanizer/Locales/ro.yml`:
- Around line 7-99: Update the Romanian genitive duration entries under
durationCases to preserve numeric counts in every multiple form: make the
default form retain the {count} placeholder for fallback counts such as 21, and
configure the dual form as the bare counted phrase without de for 2. Keep
single.numeric unchanged for exact one-unit phrases and apply the same pattern
consistently across all listed units.
In `@src/Humanizer/Locales/sk.yml`:
- Around line 18-22: Update the Slovak locale plural forms for each duration
unit in the genitive, dative, accusative, instrumental, and locative case
sections, replacing nominative-style values with grammatically correct
case-specific plural forms. Preserve the existing default, singular, dual, and
paucal values unless they also require correction for the same case-specific
rule.
In `@src/Humanizer/Locales/sr.yml`:
- Around line 100-103: Update the Serbian locale’s accusative millisecond entry
under the duration case table to use an explicit accusative translation of
“милисекунде” instead of sameAsNominative. Follow the existing explicit
case-mapping structure in sr.yml.
In `@src/Humanizer/Locales/ta.yml`:
- Around line 45-55: Update the multiple-count hour forms in all three hour
entries to use plural Tamil மணிநேரங்… templates for default, dual, paucal, and
plural, while preserving the singular form unchanged. Apply the appropriate
plural base for each context, such as மணிநேரங்களுக்கு, மணிநேரங்களை, or
மணிநேரங்களில், so counts of two or more no longer use singular-hour wording.
- Around line 146-153: Update the Tamil durationCases.accusative.day singular
configuration so one-day accusative durations use a count-bearing phrase with
the {count} placeholder, replacing the relative-date “நாளை” value while
preserving the existing multiple/default form structure.
In `@src/Humanizer/Localisation/Formatters/DefaultFormatter.cs`:
- Around line 339-358: Update FormatCaseAwareTimeSpanPhrase so count == 1 always
returns the numeric singular phrase via phrase.Single, rather than preferring
SingleWordsVariant. Preserve the existing fallback/error behavior and add
coverage for a locale defining both singular variants to verify HumanizeWithCase
uses the numeric form.
In `@src/Humanizer/TimeSpanHumanizeExtensions.cs`:
- Around line 467-482: The NotSupportedException in FormatTimePart reports
CurrentUICulture even though the formatter fallback uses CurrentCulture. Update
the fallback culture reference in the exception message to CurrentCulture while
preserving the existing explicit culture-name behavior.
In `@website/docs/api/Humanizer.DefaultFormatter.md`:
- Around line 305-314: Use sequential heading levels for the generated API
sections by changing the Parameters heading to h3 in
website/docs/api/Humanizer.DefaultFormatter.md (lines 305-314) and
website/docs/api/Humanizer.DefaultTimeSpanHumanizeStrategy.md (lines 84-93); no
other content changes are needed.
In `@website/docs/api/Humanizer.IGrammaticalCaseTimeSpanFormatter.md`:
- Around line 24-48: Normalize the heading hierarchy by changing the Parameters,
Returns, and Exceptions headings to ### under the method headings in
website/docs/api/Humanizer.IGrammaticalCaseTimeSpanFormatter.md:24-48,
website/docs/api/Humanizer.IGrammaticalCaseTimeSpanHumanizeStrategy.md:26-86,
and both overload sections in
website/docs/api/Humanizer.TimeSpanHumanizeExtensions.md:242-296 and :314-362.
---
Nitpick comments:
In `@src/Humanizer.SourceGenerators/Common/DurationCaseModels.cs`:
- Around line 50-92: Update DurationCaseCoverageInput.Create to report missing
durationCases classifications through the generator’s existing
InvalidLocaleDefinition diagnostic flow instead of throwing
InvalidOperationException. Reuse the established diagnostic-returning pattern
used by LocaleCatalogInput.Create and
LocaleDurationCaseTableCatalogInput.Create, while preserving successful catalog
and coverage-row construction for valid locales.
In
`@src/Humanizer.SourceGenerators/Generators/ProfileCatalogs/LocaleDurationCaseTableCatalogInput.cs`:
- Around line 25-45: The CaseOrder contract with GrammaticalCase is unenforced:
update LocaleDurationCaseTableCatalogInput.cs lines 25-45 to derive or validate
the emitted order against the enum members, or add a compile-time assertion that
fails when the final member changes; update LocaleDurationCaseTable.cs lines
32-43 in TryGetCase to bounds-check the raw enum index and return false for
out-of-range values.
- Around line 25-45: Add generator-side validation in the catalog generation
flow that compares CaseOrder against the GrammaticalCase enum’s expected member
count and ordering, failing generation when they diverge. Anchor the check to
CaseOrder and the emitted LocalizedDurationCase?[] so runtime indexing by
(int)grammaticalCase remains safe; preserve the existing case order when
validation succeeds.
In `@src/Humanizer/Localisation/Formatters/DefaultFormatter.cs`:
- Around line 66-120: The explicit
IGrammaticalCaseTimeSpanFormatter.TimeSpanHumanize implementation prevents
derived formatters from specializing case-aware formatting. Extract its
validation and formatting logic into a protected virtual core method on
DefaultFormatter, then have the explicit interface member delegate to that
method so subclasses can override the behavior.
In `@src/Humanizer/TimeSpanHumanizeExtensions.cs`:
- Around line 176-196: Rename the new DefaultHumanize overload that accepts
GrammaticalCase to an unambiguous name such as DefaultHumanizeWithCase, and
update all callers accordingly. Keep the existing DefaultHumanize overload and
its toWords positional contract unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: f5004c69-fc1d-456f-b1ac-30e14f90f668
📒 Files selected for processing (137)
readme.mdsrc/Humanizer.SourceGenerators/Common/CanonicalLocaleAuthoring.cssrc/Humanizer.SourceGenerators/Common/DurationCaseModels.cssrc/Humanizer.SourceGenerators/Common/LocalePhraseNormalization.cssrc/Humanizer.SourceGenerators/Common/LocaleYamlCatalog.cssrc/Humanizer.SourceGenerators/Generators/ProfileCatalogs/LocaleDurationCaseTableCatalogInput.cssrc/Humanizer.SourceGenerators/HumanizerSourceGenerator.cssrc/Humanizer/GrammaticalCase.cssrc/Humanizer/Locales/af.ymlsrc/Humanizer/Locales/am.ymlsrc/Humanizer/Locales/ar.ymlsrc/Humanizer/Locales/as.ymlsrc/Humanizer/Locales/az.ymlsrc/Humanizer/Locales/be.ymlsrc/Humanizer/Locales/bg.ymlsrc/Humanizer/Locales/bn.ymlsrc/Humanizer/Locales/bs.ymlsrc/Humanizer/Locales/ca.ymlsrc/Humanizer/Locales/cs.ymlsrc/Humanizer/Locales/cy.ymlsrc/Humanizer/Locales/da.ymlsrc/Humanizer/Locales/de.ymlsrc/Humanizer/Locales/el.ymlsrc/Humanizer/Locales/en.ymlsrc/Humanizer/Locales/es.ymlsrc/Humanizer/Locales/et.ymlsrc/Humanizer/Locales/eu.ymlsrc/Humanizer/Locales/fa.ymlsrc/Humanizer/Locales/fi.ymlsrc/Humanizer/Locales/fil.ymlsrc/Humanizer/Locales/fr.ymlsrc/Humanizer/Locales/ga.ymlsrc/Humanizer/Locales/gl.ymlsrc/Humanizer/Locales/gu.ymlsrc/Humanizer/Locales/ha.ymlsrc/Humanizer/Locales/he.ymlsrc/Humanizer/Locales/hi.ymlsrc/Humanizer/Locales/hr.ymlsrc/Humanizer/Locales/hu.ymlsrc/Humanizer/Locales/hy.ymlsrc/Humanizer/Locales/id.ymlsrc/Humanizer/Locales/ig.ymlsrc/Humanizer/Locales/is.ymlsrc/Humanizer/Locales/it.ymlsrc/Humanizer/Locales/ja.ymlsrc/Humanizer/Locales/ka.ymlsrc/Humanizer/Locales/kk.ymlsrc/Humanizer/Locales/km.ymlsrc/Humanizer/Locales/kn.ymlsrc/Humanizer/Locales/ko.ymlsrc/Humanizer/Locales/kok.ymlsrc/Humanizer/Locales/ku.ymlsrc/Humanizer/Locales/ky.ymlsrc/Humanizer/Locales/lb.ymlsrc/Humanizer/Locales/lo.ymlsrc/Humanizer/Locales/lt.ymlsrc/Humanizer/Locales/lv.ymlsrc/Humanizer/Locales/mk.ymlsrc/Humanizer/Locales/ml.ymlsrc/Humanizer/Locales/mn.ymlsrc/Humanizer/Locales/mr.ymlsrc/Humanizer/Locales/ms.ymlsrc/Humanizer/Locales/mt.ymlsrc/Humanizer/Locales/my.ymlsrc/Humanizer/Locales/nb.ymlsrc/Humanizer/Locales/ne.ymlsrc/Humanizer/Locales/nl.ymlsrc/Humanizer/Locales/nn.ymlsrc/Humanizer/Locales/or.ymlsrc/Humanizer/Locales/pa-Arab.ymlsrc/Humanizer/Locales/pa.ymlsrc/Humanizer/Locales/pl.ymlsrc/Humanizer/Locales/ps.ymlsrc/Humanizer/Locales/pt.ymlsrc/Humanizer/Locales/ro.ymlsrc/Humanizer/Locales/ru.ymlsrc/Humanizer/Locales/si.ymlsrc/Humanizer/Locales/sk.ymlsrc/Humanizer/Locales/sl.ymlsrc/Humanizer/Locales/so.ymlsrc/Humanizer/Locales/sq.ymlsrc/Humanizer/Locales/sr-Latn.ymlsrc/Humanizer/Locales/sr.ymlsrc/Humanizer/Locales/sv.ymlsrc/Humanizer/Locales/sw.ymlsrc/Humanizer/Locales/ta.ymlsrc/Humanizer/Locales/te.ymlsrc/Humanizer/Locales/th.ymlsrc/Humanizer/Locales/tk.ymlsrc/Humanizer/Locales/tr.ymlsrc/Humanizer/Locales/uk.ymlsrc/Humanizer/Locales/ur.ymlsrc/Humanizer/Locales/uz-Cyrl-UZ.ymlsrc/Humanizer/Locales/uz-Latn-UZ.ymlsrc/Humanizer/Locales/vi.ymlsrc/Humanizer/Locales/yo.ymlsrc/Humanizer/Locales/zh-Hans.ymlsrc/Humanizer/Locales/zh-Hant.ymlsrc/Humanizer/Locales/zu-ZA.ymlsrc/Humanizer/Localisation/Formatters/DefaultFormatter.cssrc/Humanizer/Localisation/Formatters/IGrammaticalCaseTimeSpanFormatter.cssrc/Humanizer/Localisation/Formatters/LocaleDurationCaseTable.cssrc/Humanizer/Localisation/Formatters/ProfiledFormatter.cssrc/Humanizer/TimeSpanHumanizeExtensions.cssrc/Humanizer/TimeSpanHumanizeStrategy/DefaultTimeSpanHumanizeStrategy.cssrc/Humanizer/TimeSpanHumanizeStrategy/IGrammaticalCaseTimeSpanHumanizeStrategy.cstests/Humanizer.SourceGenerators.Tests/SourceGenerators/CanonicalLocaleSchemaTests.cstests/Humanizer.SourceGenerators.Tests/SourceGenerators/HumanizerSourceGeneratorTests.CanonicalSchema.cstests/Humanizer.SourceGenerators.Tests/SourceGenerators/HumanizerSourceGeneratorTests.cstests/Humanizer.SourceGenerators.Tests/SourceGenerators/LocalePhraseNormalizationTests.cstests/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet10_0.verified.txttests/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet11_0.verified.txttests/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet8_0.verified.txttests/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.Net4_8.verified.txttests/Humanizer.Tests/Localisation/GeneratedLocaleData/CldrDurationCaseTests.cstests/Humanizer.Tests/Localisation/de/GermanDurationCaseTests.cstests/Humanizer.Tests/Localisation/fi/FinnishDurationCaseTests.cstests/Humanizer.Tests/Localisation/hu/HungarianDurationCaseTests.cstests/Humanizer.Tests/Localisation/ml/MalayalamDurationCaseTests.cstests/Humanizer.Tests/TimeSpanHumanizeTests.cswebsite/docs/api/Humanizer.DefaultFormatter.mdwebsite/docs/api/Humanizer.DefaultTimeSpanHumanizeStrategy.mdwebsite/docs/api/Humanizer.GrammaticalCase.mdwebsite/docs/api/Humanizer.IFormatter.mdwebsite/docs/api/Humanizer.IGrammaticalCaseTimeSpanFormatter.mdwebsite/docs/api/Humanizer.IGrammaticalCaseTimeSpanHumanizeStrategy.mdwebsite/docs/api/Humanizer.ITimeSpanHumanizeStrategy.mdwebsite/docs/api/Humanizer.TimeSpanHumanizeExtensions.mdwebsite/docs/api/Humanizer.mdwebsite/docs/contributing/locale-yaml-reference.mdxwebsite/docs/languages/custom-localization.mdxwebsite/docs/languages/grammar-and-platform-formatting.mdxwebsite/docs/languages/supported-cultures.mdxwebsite/docs/scenarios/dates-times-durations-and-age.mdxwebsite/docs/scenarios/durations-and-ages.mdxwebsite/docs/scenarios/localization-and-extensibility.mdxwebsite/scenario-api-contract.json
a8944c7 to
cf10904
Compare
There was a problem hiding this comment.
devskim found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
There was a problem hiding this comment.
Actionable comments posted: 11
🧹 Nitpick comments (7)
tests/Humanizer.SourceGenerators.Tests/SourceGenerators/LocalePhraseNormalizationTests.cs (1)
1123-1150: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReuse
CompleteCitationDurationPhraseshere.This inline
phrasesTextis a verbatim copy of the constant declared at Line 1177, minus the two-space indent difference.♻️ Proposed refactor
var catalog = HumanizerSourceGenerator.DurationCaseNormalization.ParseForTests( "zz", InvariantCitationDurationCases, - phrasesText: - """ - duration: - millisecond: - ... - """); + phrasesText: CompleteCitationDurationPhrases);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/Humanizer.SourceGenerators.Tests/SourceGenerators/LocalePhraseNormalizationTests.cs` around lines 1123 - 1150, Replace the duplicated inline phrasesText YAML in the affected test with the existing CompleteCitationDurationPhrases constant, preserving the test’s behavior and assertion setup.tests/Humanizer.SourceGenerators.Tests/SourceGenerators/HumanizerSourceGeneratorTests.cs (1)
2141-2169: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueSilent no-op if the marker text ever drifts.
The match is byte-exact on indentation and quote style. If canonicalization emits
"not-applicable"or different indentation, evidence injection quietly does nothing and the resulting failure points at schema validation rather than the fixture helper. Consider making the miss explicit for fixtures that do declaredurationCases.♻️ Optional guard
var marker = $" durationCases:{lineEnding} classification: 'not-applicable'{lineEnding}"; if (!candidateText.Contains(marker, StringComparison.Ordinal)) { + // No not-applicable durationCases block to enrich; fixtures that intentionally + // omit or customise the surface are left untouched. return candidateText; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/Humanizer.SourceGenerators.Tests/SourceGenerators/HumanizerSourceGeneratorTests.cs` around lines 2141 - 2169, Update AddDefaultDurationEvidence to detect when enabled fixtures declare durationCases but the expected marker is not found, and fail explicitly instead of silently returning the unchanged candidateText. Preserve the current evidence replacement behavior when the marker matches and the no-op behavior when the feature is disabled.tests/Humanizer.Tests/Localisation/GeneratedLocaleData/CldrDurationCaseTests.cs (2)
273-274: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
"one "is a fragile English-fallback probe.Any locale whose authored form legitimately contains the substring (romanized data, Latin-script variants) will trip this. Asserting the result differs from the English table's rendering for that unit/case would be more precise, though the current heuristic is acceptable for now.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/Humanizer.Tests/Localisation/GeneratedLocaleData/CldrDurationCaseTests.cs` around lines 273 - 274, Update the assertion in the locale test around the non-English branch to compare the generated result with the English table’s rendering for the same duration unit and case, rather than checking for the fragile substring "one ". Preserve the case-insensitive locale handling and ensure the assertion verifies that non-English output differs from the English fallback.
25-27: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDuration construction only handles the units currently in the data rows.
unit == TimeUnit.Week ? days*7 : FromDays(count)(and the Hour/Day variant at Line 67) silently produces the wrongTimeSpanif a Month/Year/Minute row is ever added. A smallswitchoverTimeUnitwould keep the theory extensible.Also applies to: 67-69
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/Humanizer.Tests/Localisation/GeneratedLocaleData/CldrDurationCaseTests.cs` around lines 25 - 27, Update the duration construction in both test cases around the existing conditional expressions to use a switch over TimeUnit, mapping each supported unit—including minute, hour, day, week, month, and year—to its appropriate TimeSpan conversion. Preserve the existing count-based duration behavior while ensuring newly added data rows cannot silently use an incorrect fallback.tests/Humanizer.Tests/TimeSpanHumanizeTests.cs (2)
700-748: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueThese two tests differ only by the requested case.
NominativeCaseRejectsLegacyCustomStrategyandNonNominativeCaseRejectsLegacyCustomStrategyare otherwise identical; a[Theory]overGrammaticalCase.Nominative/GrammaticalCase.Dativeremoves ~25 duplicated lines.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/Humanizer.Tests/TimeSpanHumanizeTests.cs` around lines 700 - 748, Combine NominativeCaseRejectsLegacyCustomStrategy and NonNominativeCaseRejectsLegacyCustomStrategy into one parameterized theory with GrammaticalCase.Nominative and GrammaticalCase.Dative as inputs. Preserve the shared strategy setup, exception assertions, CallCount check, and configuration restoration for both cases.
789-814: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse
DistinctCultureSwapfor the divergent-culture setup.This test depends on
CurrentCultureandCurrentUICulturediffering, and it mutates/restores only the UI culture by hand. Based on learnings, prefer theDistinctCultureSwaphelper (already used at Line 576 in this file) so both properties are set and restored together.♻️ Proposed refactor
- var originalUiCulture = CultureInfo.CurrentUICulture; - try - { - CultureInfo.CurrentUICulture = new("fr-FR"); - - var exception = Assert.Throws<NotSupportedException>( + using var _ = new Humanizer.Tests.Localisation.DistinctCultureSwap(new("en-US"), new("fr-FR")); + + var exception = Assert.Throws<NotSupportedException>( () => TimeSpanHumanizeExtensions.FormatTimePart( new LegacyFormatter(), TimeUnit.Day, 1, null, false, false, GrammaticalCase.Dative)); - - Assert.Contains(CultureInfo.CurrentCulture.Name, exception.Message); - Assert.DoesNotContain(CultureInfo.CurrentUICulture.Name, exception.Message); - } - finally - { - CultureInfo.CurrentUICulture = originalUiCulture; - } + + Assert.Contains(CultureInfo.CurrentCulture.Name, exception.Message); + Assert.DoesNotContain(CultureInfo.CurrentUICulture.Name, exception.Message);Based on learnings: "when validating behavior that depends on divergent culture settings … prefer the
DistinctCultureSwaphelper to set distinct values for both."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/Humanizer.Tests/TimeSpanHumanizeTests.cs` around lines 789 - 814, Update UnsupportedFormatterReportsTheFormattingCulture to use the existing DistinctCultureSwap helper for the divergent-culture setup, assigning distinct cultures through the helper and disposing it for cleanup. Remove the manual CurrentUICulture capture, mutation, and finally restoration while preserving the assertions that the exception uses CurrentCulture rather than CurrentUICulture.Source: Learnings
src/Humanizer.SourceGenerators/Common/LocaleYamlCatalog.cs (1)
374-387: 📐 Maintainability & Code Quality | 🔵 TrivialHardcoded
zh-CN/zh-Hansscript-compatibility exception.
HasCompatibleScriptspecial-cases exactly one locale pair instead of a general rule for "no explicit script subtag ⇒ inherits the parent's default script." Any future locale needing the same accommodation (e.g., anotherzh-*region variant, or a similar pattern for another language) will require another hardcoded branch here.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Humanizer.SourceGenerators/Common/LocaleYamlCatalog.cs` around lines 374 - 387, Generalize HasCompatibleScript so locales without explicit script subtags are compatible when the locale’s language/region default script matches the inherited locale’s effective script, rather than hardcoding the zh-CN/zh-Hans pair. Reuse the catalog’s existing locale/script-resolution data or helper, preserve exact-script comparison when both subtags exist, and retain incompatibility for unresolved mismatches.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/Humanizer/Locales/cs.yml`:
- Around line 143-259: Correct the Czech plural values in the affected case
overlays for the authored unit entries, including millisecond, second, minute,
hour, day, week, month, and year. Replace singular or nominative-looking plural
forms with the proper case-specific forms, such as dative sekundám, instrumental
sekundami, locative sekundách, and genitive týdnů and měsíců. Apply the same
corrections across all indicated overlay sections while preserving values that
are already correct.
In `@src/Humanizer/Locales/kn.yml`:
- Around line 497-500: Update the Kannada duration configuration under
durationCases.vocative so it explicitly models the CLDR-supported vocative
unit/count combinations, including one-year and multiple-month durations, or
encode the partial coverage as an intentional unsupported state recognized by
the schema/runtime; do not leave notApplicable implying that all Kannada
durations are unsupported.
In `@src/Humanizer/Locales/lv.yml`:
- Around line 93-110: Add singular nominative forms to the multiple.forms
mappings for the month and year locale entries: set month’s singular form to
“mēnesis” and year’s to “gads”, while preserving their existing default forms.
In `@src/Humanizer/Locales/pl.yml`:
- Around line 157-252: Correct the dual form values for the Polish duration
entries under second, minute, hour, day, week, month, and year, including the
corresponding entries in the other affected duration blocks. Use the
grammatically correct Polish forms for each declared case, such as dative -om,
instrumental -ami, and locative -ach, rather than reusing singular, nominative,
or genitive text.
In `@src/Humanizer/Locales/ps.yml`:
- Around line 30-41: Rename one of the duplicate corpus-source keys in the
sources mapping, such as changing the UD Pashto Prince entry to treebank, and
update every matching provenance reference to use the new identifier while
preserving both source definitions.
In `@src/Humanizer/Locales/ro.yml`:
- Around line 46-53: Update the Romanian locale duration entries in the
`multiple.forms` definitions so each `dual` template uses a bare count and unit
without `de`, including the other genitive duration units. Keep the `plural`
templates unchanged so fallback plural counts continue using `de`.
In `@src/Humanizer/Locales/ru.yml`:
- Around line 148-264: Update the dual forms for every duration unit in the
dative, instrumental, prepositional, and locative sections, including the later
corresponding sections, to use the case-specific CLDR form rather than genitive
singular. Ensure each unit’s dual ending matches its case, such as dative
“{count} …м”, instrumental “{count} …ми”, and prepositional/locative “{count}
…х”, while leaving other plural forms unchanged.
In `@src/Humanizer/Locales/sk.yml`:
- Around line 152-157: Update the affected Slovak unit entries in the locale
plural-form overlays so each genitive, dative, instrumental, and locative plural
uses its case-specific form, including “sekúnd”, “sekundami”, and “sekundách”
where applicable. Leave already-correct values and nominative-delegated
accusative entries unchanged.
In `@src/Humanizer/Locales/sv.yml`:
- Around line 166-177: Update the Swedish day duration mapping in the day phrase
configuration to use the genitive of dag: set the singular numeric form to “1
dags” and every count-based form to “{count} dagars”, replacing the current
dygns values.
In
`@tests/Humanizer.SourceGenerators.Tests/SourceGenerators/LocalePhraseNormalizationTests.cs`:
- Around line 1206-1210: Update ParseDurationCases to normalize raw literal line
endings before splitting, matching the approach used by
ReleaseDurationCasesRequireEveryReachablePluralForm, so every line receives the
intended indentation regardless of checkout platform. Also update
NonDistinctDurationClassificationsRejectCaseData to join lines with "\n" instead
of Environment.NewLine.
In `@website/docs/languages/supported-cultures.mdx`:
- Around line 73-77: Clarify the supported-cultures documentation near the
duration disposition guidance by distinguishing release-validation rejection of
Unsupported grammar entries from runtime coverage gaps that legitimately produce
NotSupportedException. State that Unsupported entries remain part of the
parser/normalizer representation, while runtime tables may intentionally lack
specific case or duration-unit support.
---
Nitpick comments:
In `@src/Humanizer.SourceGenerators/Common/LocaleYamlCatalog.cs`:
- Around line 374-387: Generalize HasCompatibleScript so locales without
explicit script subtags are compatible when the locale’s language/region default
script matches the inherited locale’s effective script, rather than hardcoding
the zh-CN/zh-Hans pair. Reuse the catalog’s existing locale/script-resolution
data or helper, preserve exact-script comparison when both subtags exist, and
retain incompatibility for unresolved mismatches.
In
`@tests/Humanizer.SourceGenerators.Tests/SourceGenerators/HumanizerSourceGeneratorTests.cs`:
- Around line 2141-2169: Update AddDefaultDurationEvidence to detect when
enabled fixtures declare durationCases but the expected marker is not found, and
fail explicitly instead of silently returning the unchanged candidateText.
Preserve the current evidence replacement behavior when the marker matches and
the no-op behavior when the feature is disabled.
In
`@tests/Humanizer.SourceGenerators.Tests/SourceGenerators/LocalePhraseNormalizationTests.cs`:
- Around line 1123-1150: Replace the duplicated inline phrasesText YAML in the
affected test with the existing CompleteCitationDurationPhrases constant,
preserving the test’s behavior and assertion setup.
In
`@tests/Humanizer.Tests/Localisation/GeneratedLocaleData/CldrDurationCaseTests.cs`:
- Around line 273-274: Update the assertion in the locale test around the
non-English branch to compare the generated result with the English table’s
rendering for the same duration unit and case, rather than checking for the
fragile substring "one ". Preserve the case-insensitive locale handling and
ensure the assertion verifies that non-English output differs from the English
fallback.
- Around line 25-27: Update the duration construction in both test cases around
the existing conditional expressions to use a switch over TimeUnit, mapping each
supported unit—including minute, hour, day, week, month, and year—to its
appropriate TimeSpan conversion. Preserve the existing count-based duration
behavior while ensuring newly added data rows cannot silently use an incorrect
fallback.
In `@tests/Humanizer.Tests/TimeSpanHumanizeTests.cs`:
- Around line 700-748: Combine NominativeCaseRejectsLegacyCustomStrategy and
NonNominativeCaseRejectsLegacyCustomStrategy into one parameterized theory with
GrammaticalCase.Nominative and GrammaticalCase.Dative as inputs. Preserve the
shared strategy setup, exception assertions, CallCount check, and configuration
restoration for both cases.
- Around line 789-814: Update UnsupportedFormatterReportsTheFormattingCulture to
use the existing DistinctCultureSwap helper for the divergent-culture setup,
assigning distinct cultures through the helper and disposing it for cleanup.
Remove the manual CurrentUICulture capture, mutation, and finally restoration
while preserving the assertions that the exception uses CurrentCulture rather
than CurrentUICulture.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 62d30703-cb9f-4ef5-aa66-6cf74b0ddf8b
📒 Files selected for processing (143)
readme.mdsrc/Humanizer.SourceGenerators/Common/CanonicalLocaleAuthoring.cssrc/Humanizer.SourceGenerators/Common/DurationCaseModels.cssrc/Humanizer.SourceGenerators/Common/LocalePhraseModels.cssrc/Humanizer.SourceGenerators/Common/LocalePhraseNormalization.cssrc/Humanizer.SourceGenerators/Common/LocaleYamlCatalog.cssrc/Humanizer.SourceGenerators/Generators/ProfileCatalogs/FormatterProfileCatalogInput.cssrc/Humanizer.SourceGenerators/Generators/ProfileCatalogs/LocaleDurationCaseTableCatalogInput.cssrc/Humanizer.SourceGenerators/Generators/ProfileCatalogs/LocalePhraseTableCatalogInput.cssrc/Humanizer.SourceGenerators/HumanizerSourceGenerator.cssrc/Humanizer/GrammaticalCase.cssrc/Humanizer/Locales/af.ymlsrc/Humanizer/Locales/am.ymlsrc/Humanizer/Locales/ar.ymlsrc/Humanizer/Locales/as.ymlsrc/Humanizer/Locales/az.ymlsrc/Humanizer/Locales/be.ymlsrc/Humanizer/Locales/bg.ymlsrc/Humanizer/Locales/bn.ymlsrc/Humanizer/Locales/bs.ymlsrc/Humanizer/Locales/ca.ymlsrc/Humanizer/Locales/cs.ymlsrc/Humanizer/Locales/cy.ymlsrc/Humanizer/Locales/da.ymlsrc/Humanizer/Locales/de.ymlsrc/Humanizer/Locales/el.ymlsrc/Humanizer/Locales/en.ymlsrc/Humanizer/Locales/es.ymlsrc/Humanizer/Locales/et.ymlsrc/Humanizer/Locales/eu.ymlsrc/Humanizer/Locales/fa.ymlsrc/Humanizer/Locales/fi.ymlsrc/Humanizer/Locales/fil.ymlsrc/Humanizer/Locales/fr.ymlsrc/Humanizer/Locales/ga.ymlsrc/Humanizer/Locales/gl.ymlsrc/Humanizer/Locales/gu.ymlsrc/Humanizer/Locales/ha.ymlsrc/Humanizer/Locales/he.ymlsrc/Humanizer/Locales/hi.ymlsrc/Humanizer/Locales/hr.ymlsrc/Humanizer/Locales/hu.ymlsrc/Humanizer/Locales/hy.ymlsrc/Humanizer/Locales/id.ymlsrc/Humanizer/Locales/ig.ymlsrc/Humanizer/Locales/is.ymlsrc/Humanizer/Locales/it.ymlsrc/Humanizer/Locales/ja.ymlsrc/Humanizer/Locales/ka.ymlsrc/Humanizer/Locales/kk.ymlsrc/Humanizer/Locales/km.ymlsrc/Humanizer/Locales/kn.ymlsrc/Humanizer/Locales/ko.ymlsrc/Humanizer/Locales/kok.ymlsrc/Humanizer/Locales/ku.ymlsrc/Humanizer/Locales/ky.ymlsrc/Humanizer/Locales/lb.ymlsrc/Humanizer/Locales/lo.ymlsrc/Humanizer/Locales/lt.ymlsrc/Humanizer/Locales/lv.ymlsrc/Humanizer/Locales/mk.ymlsrc/Humanizer/Locales/ml.ymlsrc/Humanizer/Locales/mn.ymlsrc/Humanizer/Locales/mr.ymlsrc/Humanizer/Locales/ms.ymlsrc/Humanizer/Locales/mt.ymlsrc/Humanizer/Locales/my.ymlsrc/Humanizer/Locales/nb.ymlsrc/Humanizer/Locales/ne.ymlsrc/Humanizer/Locales/nl.ymlsrc/Humanizer/Locales/nn.ymlsrc/Humanizer/Locales/or.ymlsrc/Humanizer/Locales/pa-Arab.ymlsrc/Humanizer/Locales/pa.ymlsrc/Humanizer/Locales/pl.ymlsrc/Humanizer/Locales/ps.ymlsrc/Humanizer/Locales/pt.ymlsrc/Humanizer/Locales/ro.ymlsrc/Humanizer/Locales/ru.ymlsrc/Humanizer/Locales/si.ymlsrc/Humanizer/Locales/sk.ymlsrc/Humanizer/Locales/sl.ymlsrc/Humanizer/Locales/so.ymlsrc/Humanizer/Locales/sq.ymlsrc/Humanizer/Locales/sr-Latn.ymlsrc/Humanizer/Locales/sr.ymlsrc/Humanizer/Locales/sv.ymlsrc/Humanizer/Locales/sw.ymlsrc/Humanizer/Locales/ta.ymlsrc/Humanizer/Locales/te.ymlsrc/Humanizer/Locales/th.ymlsrc/Humanizer/Locales/tk.ymlsrc/Humanizer/Locales/tr.ymlsrc/Humanizer/Locales/uk.ymlsrc/Humanizer/Locales/ur.ymlsrc/Humanizer/Locales/uz-Cyrl-UZ.ymlsrc/Humanizer/Locales/uz-Latn-UZ.ymlsrc/Humanizer/Locales/vi.ymlsrc/Humanizer/Locales/yo.ymlsrc/Humanizer/Locales/zh-Hans.ymlsrc/Humanizer/Locales/zh-Hant.ymlsrc/Humanizer/Locales/zu-ZA.ymlsrc/Humanizer/Localisation/Formatters/DefaultFormatter.cssrc/Humanizer/Localisation/Formatters/IGrammaticalCaseTimeSpanFormatter.cssrc/Humanizer/Localisation/Formatters/LocaleDurationCaseTable.cssrc/Humanizer/Localisation/Formatters/LocalePhraseTable.cssrc/Humanizer/Localisation/Formatters/ProfiledFormatter.cssrc/Humanizer/TimeSpanHumanizeExtensions.cssrc/Humanizer/TimeSpanHumanizeStrategy/DefaultTimeSpanHumanizeStrategy.cssrc/Humanizer/TimeSpanHumanizeStrategy/IGrammaticalCaseTimeSpanHumanizeStrategy.cstests/Humanizer.SourceGenerators.Tests/SourceGenerators/CanonicalLocaleSchemaTests.cstests/Humanizer.SourceGenerators.Tests/SourceGenerators/HumanizerSourceGeneratorTests.CanonicalSchema.cstests/Humanizer.SourceGenerators.Tests/SourceGenerators/HumanizerSourceGeneratorTests.cstests/Humanizer.SourceGenerators.Tests/SourceGenerators/LocalePhraseNormalizationTests.cstests/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet10_0.verified.txttests/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet11_0.verified.txttests/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet8_0.verified.txttests/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.Net4_8.verified.txttests/Humanizer.Tests/CoverageGapTests.cstests/Humanizer.Tests/Localisation/GeneratedLocaleData/CldrDurationCaseTests.cstests/Humanizer.Tests/Localisation/de/GermanDurationCaseTests.cstests/Humanizer.Tests/Localisation/fi/FinnishDurationCaseTests.cstests/Humanizer.Tests/Localisation/hu/HungarianDurationCaseTests.cstests/Humanizer.Tests/Localisation/ml/MalayalamDurationCaseTests.cstests/Humanizer.Tests/TimeSpanHumanizeTests.cstools/docs/generate-language-coverage.ps1website/docs/api/Humanizer.DefaultFormatter.mdwebsite/docs/api/Humanizer.DefaultTimeSpanHumanizeStrategy.mdwebsite/docs/api/Humanizer.GrammaticalCase.mdwebsite/docs/api/Humanizer.IFormatter.mdwebsite/docs/api/Humanizer.IGrammaticalCaseTimeSpanFormatter.mdwebsite/docs/api/Humanizer.IGrammaticalCaseTimeSpanHumanizeStrategy.mdwebsite/docs/api/Humanizer.ITimeSpanHumanizeStrategy.mdwebsite/docs/api/Humanizer.TimeSpanHumanizeExtensions.mdwebsite/docs/api/Humanizer.mdwebsite/docs/contributing/locale-yaml-reference.mdxwebsite/docs/languages/custom-localization.mdxwebsite/docs/languages/grammar-and-platform-formatting.mdxwebsite/docs/languages/supported-cultures.mdxwebsite/docs/scenarios/dates-times-durations-and-age.mdxwebsite/docs/scenarios/durations-and-ages.mdxwebsite/docs/scenarios/localization-and-extensibility.mdxwebsite/scenario-api-contract.json
🚧 Files skipped from review as they are similar to previous changes (23)
- tests/Humanizer.Tests/Localisation/hu/HungarianDurationCaseTests.cs
- tests/Humanizer.Tests/Localisation/fi/FinnishDurationCaseTests.cs
- tests/Humanizer.Tests/Localisation/de/GermanDurationCaseTests.cs
- website/scenario-api-contract.json
- src/Humanizer/Localisation/Formatters/IGrammaticalCaseTimeSpanFormatter.cs
- website/docs/api/Humanizer.md
- src/Humanizer/TimeSpanHumanizeStrategy/IGrammaticalCaseTimeSpanHumanizeStrategy.cs
- website/docs/scenarios/dates-times-durations-and-age.mdx
- tests/Humanizer.Tests/Localisation/ml/MalayalamDurationCaseTests.cs
- readme.md
- website/docs/api/Humanizer.ITimeSpanHumanizeStrategy.md
- website/docs/languages/grammar-and-platform-formatting.mdx
- src/Humanizer/Localisation/Formatters/LocaleDurationCaseTable.cs
- src/Humanizer.SourceGenerators/HumanizerSourceGenerator.cs
- src/Humanizer/GrammaticalCase.cs
- src/Humanizer.SourceGenerators/Common/CanonicalLocaleAuthoring.cs
- website/docs/scenarios/durations-and-ages.mdx
- website/docs/scenarios/localization-and-extensibility.mdx
- tests/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.Net4_8.verified.txt
- tests/Humanizer.SourceGenerators.Tests/SourceGenerators/CanonicalLocaleSchemaTests.cs
- tests/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet10_0.verified.txt
- src/Humanizer/Localisation/Formatters/DefaultFormatter.cs
- src/Humanizer/TimeSpanHumanizeExtensions.cs
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/Humanizer/Locales/ro.yml (1)
37-37: 🎯 Functional Correctness | 🟠 MajorUse bare Romanian dual genitive forms.
The
dualtemplates still render2 de <unit>(for example,2 de secunde). Use{count} <unit>for the dual form across all units, while retainingdein the fallback plural/default forms.Also applies to: 51-51, 65-65, 79-79, 93-93, 107-107, 121-121, 135-135
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Humanizer/Locales/ro.yml` at line 37, Update the Romanian locale’s dual templates for all affected time units to use the bare form “{count} <unit>” without “de”; retain “de” in the fallback plural/default templates.
♻️ Duplicate comments (1)
src/Humanizer/Locales/ru.yml (1)
148-264: 🎯 Functional Correctness | 🟠 MajorUse case-specific Russian dual forms.
The
dualtemplates in the dative, instrumental, prepositional, and locative sections still use genitive-singular forms. Replace them with the corresponding case forms for every unit—for example,{count} дням,{count} днями, and{count} днях—without changing the other plural forms.Also applies to: 342-458, 459-575, 576-692
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Humanizer/Locales/ru.yml` around lines 148 - 264, Update the dual templates under the Russian locale’s dative, instrumental, prepositional, and locative sections for every time unit so they use the corresponding case-specific dual forms rather than genitive-singular forms. For example, use dative “{count} дням,” instrumental “{count} днями,” and prepositional/locative “{count} днях,” while preserving all existing single, default, singular, paucal, and plural templates.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/Humanizer/Locales/ro.yml`:
- Line 37: Update the Romanian locale’s dual templates for all affected time
units to use the bare form “{count} <unit>” without “de”; retain “de” in the
fallback plural/default templates.
---
Duplicate comments:
In `@src/Humanizer/Locales/ru.yml`:
- Around line 148-264: Update the dual templates under the Russian locale’s
dative, instrumental, prepositional, and locative sections for every time unit
so they use the corresponding case-specific dual forms rather than
genitive-singular forms. For example, use dative “{count} дням,” instrumental
“{count} днями,” and prepositional/locative “{count} днях,” while preserving all
existing single, default, singular, paucal, and plural templates.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 24e6a394-01cc-4119-852a-374c80117cdf
📒 Files selected for processing (90)
src/Humanizer.SourceGenerators/Generators/ProfileCatalogs/LocaleDurationCaseTableCatalogInput.cssrc/Humanizer/Locales/af.ymlsrc/Humanizer/Locales/am.ymlsrc/Humanizer/Locales/ar.ymlsrc/Humanizer/Locales/as.ymlsrc/Humanizer/Locales/be.ymlsrc/Humanizer/Locales/bg.ymlsrc/Humanizer/Locales/bn.ymlsrc/Humanizer/Locales/ca.ymlsrc/Humanizer/Locales/cs.ymlsrc/Humanizer/Locales/cy.ymlsrc/Humanizer/Locales/da.ymlsrc/Humanizer/Locales/de.ymlsrc/Humanizer/Locales/el.ymlsrc/Humanizer/Locales/en.ymlsrc/Humanizer/Locales/es.ymlsrc/Humanizer/Locales/et.ymlsrc/Humanizer/Locales/eu.ymlsrc/Humanizer/Locales/fa.ymlsrc/Humanizer/Locales/fi.ymlsrc/Humanizer/Locales/fil.ymlsrc/Humanizer/Locales/fr.ymlsrc/Humanizer/Locales/ga.ymlsrc/Humanizer/Locales/gl.ymlsrc/Humanizer/Locales/gu.ymlsrc/Humanizer/Locales/ha.ymlsrc/Humanizer/Locales/he.ymlsrc/Humanizer/Locales/hi.ymlsrc/Humanizer/Locales/hr.ymlsrc/Humanizer/Locales/hu.ymlsrc/Humanizer/Locales/hy.ymlsrc/Humanizer/Locales/id.ymlsrc/Humanizer/Locales/ig.ymlsrc/Humanizer/Locales/is.ymlsrc/Humanizer/Locales/it.ymlsrc/Humanizer/Locales/ja.ymlsrc/Humanizer/Locales/ka.ymlsrc/Humanizer/Locales/km.ymlsrc/Humanizer/Locales/kn.ymlsrc/Humanizer/Locales/ko.ymlsrc/Humanizer/Locales/kok.ymlsrc/Humanizer/Locales/ku.ymlsrc/Humanizer/Locales/lb.ymlsrc/Humanizer/Locales/lo.ymlsrc/Humanizer/Locales/lt.ymlsrc/Humanizer/Locales/lv.ymlsrc/Humanizer/Locales/mk.ymlsrc/Humanizer/Locales/ml.ymlsrc/Humanizer/Locales/mr.ymlsrc/Humanizer/Locales/ms.ymlsrc/Humanizer/Locales/mt.ymlsrc/Humanizer/Locales/my.ymlsrc/Humanizer/Locales/nb.ymlsrc/Humanizer/Locales/ne.ymlsrc/Humanizer/Locales/nl.ymlsrc/Humanizer/Locales/nn.ymlsrc/Humanizer/Locales/pa-Arab.ymlsrc/Humanizer/Locales/pa.ymlsrc/Humanizer/Locales/pl.ymlsrc/Humanizer/Locales/ps.ymlsrc/Humanizer/Locales/pt.ymlsrc/Humanizer/Locales/ro.ymlsrc/Humanizer/Locales/ru.ymlsrc/Humanizer/Locales/sk.ymlsrc/Humanizer/Locales/sl.ymlsrc/Humanizer/Locales/sq.ymlsrc/Humanizer/Locales/sr-Latn.ymlsrc/Humanizer/Locales/sr.ymlsrc/Humanizer/Locales/sv.ymlsrc/Humanizer/Locales/sw.ymlsrc/Humanizer/Locales/ta.ymlsrc/Humanizer/Locales/te.ymlsrc/Humanizer/Locales/th.ymlsrc/Humanizer/Locales/tr.ymlsrc/Humanizer/Locales/uk.ymlsrc/Humanizer/Locales/ur.ymlsrc/Humanizer/Locales/vi.ymlsrc/Humanizer/Locales/yo.ymlsrc/Humanizer/Locales/zh-Hans.ymlsrc/Humanizer/Locales/zh-Hant.ymlsrc/Humanizer/Locales/zu-ZA.ymlsrc/Humanizer/Localisation/Formatters/DefaultFormatter.cssrc/Humanizer/Localisation/Formatters/LocaleDurationCaseTable.cssrc/Humanizer/Localisation/Formatters/ProfiledFormatter.cssrc/Humanizer/TimeSpanHumanizeExtensions.cstests/Humanizer.SourceGenerators.Tests/SourceGenerators/HumanizerSourceGeneratorTests.CanonicalSchema.cstests/Humanizer.Tests/Localisation/GeneratedLocaleData/CldrDurationCaseTests.cstests/Humanizer.Tests/TimeSpanHumanizeTests.cswebsite/docs/contributing/locale-yaml-reference.mdxwebsite/docs/scenarios/durations-and-ages.mdx
🚧 Files skipped from review as they are similar to previous changes (75)
- src/Humanizer/Locales/ms.yml
- src/Humanizer/Locales/ko.yml
- src/Humanizer/Locales/gl.yml
- src/Humanizer/Locales/he.yml
- src/Humanizer/Locales/fr.yml
- src/Humanizer/Locales/es.yml
- src/Humanizer/Locales/sq.yml
- src/Humanizer/Locales/zh-Hans.yml
- src/Humanizer/Locales/zu-ZA.yml
- src/Humanizer/Locales/ha.yml
- src/Humanizer/Locales/ku.yml
- src/Humanizer/Locales/mt.yml
- src/Humanizer/Locales/cy.yml
- src/Humanizer/Locales/ja.yml
- src/Humanizer/Locales/id.yml
- src/Humanizer/Locales/en.yml
- src/Humanizer/Locales/fa.yml
- src/Humanizer/Locales/am.yml
- src/Humanizer/Locales/bg.yml
- src/Humanizer/Locales/el.yml
- src/Humanizer/Locales/lo.yml
- src/Humanizer/Locales/ne.yml
- src/Humanizer/Locales/zh-Hant.yml
- src/Humanizer/Locales/as.yml
- src/Humanizer/Locales/ca.yml
- src/Humanizer/Locales/vi.yml
- src/Humanizer/Locales/yo.yml
- src/Humanizer/Locales/sw.yml
- src/Humanizer/Locales/my.yml
- src/Humanizer/Locales/km.yml
- src/Humanizer/Localisation/Formatters/LocaleDurationCaseTable.cs
- src/Humanizer/Locales/pt.yml
- src/Humanizer/Locales/mk.yml
- src/Humanizer/Locales/te.yml
- src/Humanizer/Locales/hy.yml
- src/Humanizer/Locales/nb.yml
- src/Humanizer/Locales/th.yml
- src/Humanizer/Locales/af.yml
- src/Humanizer/Locales/sv.yml
- src/Humanizer/Locales/nl.yml
- src/Humanizer/Locales/sl.yml
- src/Humanizer/Locales/sr-Latn.yml
- src/Humanizer/Locales/lt.yml
- src/Humanizer/Locales/cs.yml
- src/Humanizer/Locales/sk.yml
- src/Humanizer/Locales/ga.yml
- src/Humanizer/Locales/da.yml
- src/Humanizer/Locales/lv.yml
- src/Humanizer/Locales/fi.yml
- src/Humanizer/Locales/pa.yml
- src/Humanizer/Locales/hr.yml
- src/Humanizer/Locales/hi.yml
- src/Humanizer/Locales/mr.yml
- src/Humanizer/Locales/ta.yml
- src/Humanizer/Locales/lb.yml
- src/Humanizer/Locales/ar.yml
- src/Humanizer/Locales/fil.yml
- src/Humanizer/Locales/gu.yml
- src/Humanizer/Locales/ur.yml
- website/docs/scenarios/durations-and-ages.mdx
- src/Humanizer/Locales/bn.yml
- src/Humanizer/Locales/et.yml
- src/Humanizer/Locales/ps.yml
- src/Humanizer/Localisation/Formatters/ProfiledFormatter.cs
- src/Humanizer/Locales/pa-Arab.yml
- src/Humanizer/Localisation/Formatters/DefaultFormatter.cs
- website/docs/contributing/locale-yaml-reference.mdx
- src/Humanizer/Locales/ml.yml
- src/Humanizer/Locales/eu.yml
- tests/Humanizer.SourceGenerators.Tests/SourceGenerators/HumanizerSourceGeneratorTests.CanonicalSchema.cs
- src/Humanizer/Locales/uk.yml
- src/Humanizer/TimeSpanHumanizeExtensions.cs
- src/Humanizer/Locales/kn.yml
- src/Humanizer.SourceGenerators/Generators/ProfileCatalogs/LocaleDurationCaseTableCatalogInput.cs
- tests/Humanizer.Tests/TimeSpanHumanizeTests.cs
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: df4e9ac643
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Summary
TimeSpancan now produce a bare duration phrase in an explicitly requested grammatical case throughHumanizeWithCase, without changing any existingHumanize, formatter, or strategy member or output.The case-aware path uses explicit generated locale data, applies one case across multipart durations, keeps multi-count output numeric, and fails clearly when a locale, unit, or configured custom component cannot support the request. All 102 locale files are accounted for through 57 distinct roots, 34 not-applicable roots, and 11 same-language inherited children, with zero unsupported applicable roots, cases, or units and no English or invented-form fallback.
Compatibility
Existing
Humanize,IFormatter, andITimeSpanHumanizeStrategycontracts remain unchanged. Case support is exposed only through optional capability interfaces, including explicit implementations on the default concrete formatter and strategy so legacy calls usingdefaultremain source-compatible.Custom formatter and strategy subclasses must explicitly opt into the new capabilities. The new dispatch also composes with the existing fractional-duration capability and preserves its exact-type opt-in and fail-closed behavior.
Legacy duration, zero, age, and
toWordslocale surfaces are byte-identical to the semantic base. Native citation phrases that differ are isolated insidedurationCases.Locale data and credit
Every root declares an explicit inventory, citation case, evidence, and per-case realization. Authored children replace the inherited discriminated union; omitted data inherits only through same-language, same-script parents.
sameRenderedAsaliases are cycle checked and flattened to an authored terminal.Explicit case-number contracts preserve every reachable named form even when its rendered text equals the default form. This keeps South Slavic singular, paucal, and plural selection correct for compound counts while leaving ordinary formatter fallback and duplicate collapsing unchanged.
Duration patterns use Unicode CLDR 48 at commit
acd6d88ae493633240e19a87a721076a8a75c310where available, plus immutable per-locale grammar, corpus, dictionary, and native-review sources recorded in the YAML. Thanks to @JFBM for reporting the original German use case in #686, and to the source authors and language-family reviewers credited in the locale data.Validation
Submission checklist
mainbaselineFixes #686