diff --git a/.impeccable/design.json b/.impeccable/design.json
new file mode 100644
index 000000000..92cbe7de6
--- /dev/null
+++ b/.impeccable/design.json
@@ -0,0 +1,101 @@
+{
+ "schemaVersion": 2,
+ "title": "Design System: Humanizer Documentation",
+ "updatedAt": "2026-07-29T20:30:55Z",
+ "northStar": "The Working Proof Sheet: an editorial, code-forward documentation world where real C# input and output prove Humanizer.",
+ "breakpoints": {
+ "smallPhone": "360px",
+ "phone": "480px",
+ "taskGrid": "48rem",
+ "desktopMax": "996px",
+ "desktopMin": "997px"
+ },
+ "colors": {
+ "paper": "#f7f3e9",
+ "paperRaised": "#fffdf7",
+ "ink": "#172026",
+ "inkMuted": "#566269",
+ "rule": "#c9c4b8",
+ "accentTeal": "#056b84",
+ "accentRust": "#ad3e2a",
+ "proofText": "#edf3f2",
+ "proofOutput": "#91d9e8",
+ "promiseTeal": "#045b6e",
+ "white": "#fff",
+ "promiseTextMuted": "#d7f3f8",
+ "promiseTextSoft": "#e5f6f9"
+ },
+ "typography": {
+ "docsDisplay": "Iowan Old Style, Baskerville, Times New Roman, serif",
+ "homeDisplay": "Avenir Next, Segoe UI, Helvetica Neue, sans-serif",
+ "body": "Avenir Next, Segoe UI, Helvetica Neue, sans-serif",
+ "readingMeasure": "72ch",
+ "identity": "0.9rem",
+ "label": "0.78rem",
+ "lede": "clamp(1.1rem, 2vw, 1.35rem)",
+ "proofLabel": "0.72rem",
+ "proofCode": "clamp(0.76rem, 2.4vw, 0.95rem)",
+ "installCode": "clamp(0.85rem, 2vw, 1rem)",
+ "routeHeading": "clamp(1.4rem, 3vw, 2rem)",
+ "referenceHeading": "1.25rem",
+ "promiseCopy": "1.1rem"
+ },
+ "components": [
+ {
+ "name": "Primary Button",
+ "className": "ds-button-primary",
+ "css": "background: var(--ifm-font-color-base); color: var(--ifm-background-color); min-height: 2.75rem; border-radius: 0;"
+ },
+ {
+ "name": "Text Link",
+ "className": "ds-text-link",
+ "css": "color: var(--ifm-link-color); text-decoration: underline; text-underline-offset: 0.2em;"
+ },
+ {
+ "name": "Proof Sheet",
+ "className": "ds-proof-sheet",
+ "css": "background: #172026; color: #edf3f2; border-radius: 0; padding: clamp(1rem, 3vw, 2rem);"
+ },
+ {
+ "name": "Task Route",
+ "className": "ds-task-route",
+ "css": "border-top: 1px solid var(--ifm-color-emphasis-300); padding-block: 1.5rem;"
+ },
+ {
+ "name": "Language Promise",
+ "className": "ds-language-promise",
+ "css": "background: #045b6e; color: #fff; border-radius: 0; padding: clamp(2rem, 5vw, 4rem);"
+ },
+ {
+ "name": "Navigation",
+ "className": "ds-navigation",
+ "css": "border-bottom: 1px solid var(--ifm-color-emphasis-300); box-shadow: none;"
+ },
+ {
+ "name": "Supported Culture List",
+ "className": "ds-supported-culture-list",
+ "css": "display: grid; grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr)); gap: 0.75rem 1rem;"
+ }
+ ],
+ "effects": {
+ "shadow": "0 1px 0 var(--ifm-color-emphasis-300)",
+ "motion": "scroll-behavior: smooth"
+ },
+ "rules": [
+ "Teal signals navigation or capability; rust labels structure or focus.",
+ "Use ruled separation or a tone change instead of elevation.",
+ "Keep every claim version-correct and behaviorally verified.",
+ "Every listed culture supports every applicable localized feature."
+ ],
+ "dos": [
+ "Use deterministic runnable C#.",
+ "Reuse canonical assets.",
+ "Keep NuGet installation and proof in the first viewport.",
+ "Preserve visible focus and reduced-motion behavior."
+ ],
+ "donts": [
+ "Do not add cards, pills, gradients, decorative shadows, or invented imagery.",
+ "Do not invent metrics, customers, testimonials, or benchmarks.",
+ "Do not present partial-culture capability tiers."
+ ]
+}
diff --git a/DESIGN.md b/DESIGN.md
new file mode 100644
index 000000000..ea6dd0f74
--- /dev/null
+++ b/DESIGN.md
@@ -0,0 +1,176 @@
+---
+name: Humanizer Documentation
+description: An editorial working proof sheet for human-friendly .NET APIs.
+colors:
+ paper: "#f7f3e9"
+ paper-raised: "#fffdf7"
+ ink: "#172026"
+ ink-muted: "#566269"
+ rule: "#c9c4b8"
+ accent-teal: "#056b84"
+ accent-rust: "#ad3e2a"
+ proof-text: "#edf3f2"
+ proof-output: "#91d9e8"
+ promise-teal: "#045b6e"
+ white: "#fff"
+ promise-text-muted: "#d7f3f8"
+ promise-text-soft: "#e5f6f9"
+ dark-paper: "#11191c"
+ dark-paper-raised: "#192327"
+ dark-ink: "#edf3f2"
+ dark-ink-muted: "#a9b9b9"
+ dark-rule: "#3a494d"
+ dark-accent-teal: "#63c4d8"
+ dark-accent-rust: "#f28b73"
+typography:
+ docs-display:
+ fontFamily: "Iowan Old Style, Baskerville, Times New Roman, serif"
+ fontSize: "clamp(2.25rem, 7vw, 4.5rem)"
+ fontWeight: 600
+ home-display:
+ fontFamily: "Avenir Next, Segoe UI, Helvetica Neue, sans-serif"
+ fontSize: "clamp(3rem, 8vw, 6rem)"
+ fontWeight: 760
+ lineHeight: 0.98
+ letterSpacing: "-0.04em"
+ section-heading:
+ fontFamily: "Avenir Next, Segoe UI, Helvetica Neue, sans-serif"
+ fontSize: "clamp(2.3rem, 6vw, 4.75rem)"
+ fontWeight: 720
+ lineHeight: 1
+ letterSpacing: "-0.04em"
+ body:
+ fontFamily: "Avenir Next, Segoe UI, Helvetica Neue, sans-serif"
+ fontSize: "1rem"
+ lineHeight: 1.65
+ label:
+ fontFamily: "Avenir Next, Segoe UI, Helvetica Neue, sans-serif"
+ fontSize: "0.78rem"
+ fontWeight: 800
+ letterSpacing: "0.16em"
+ identity:
+ fontSize: "0.9rem"
+ lede:
+ fontSize: "clamp(1.1rem, 2vw, 1.35rem)"
+ proof-label:
+ fontSize: "0.72rem"
+ proof-code:
+ fontSize: "clamp(0.76rem, 2.4vw, 0.95rem)"
+ install-code:
+ fontSize: "clamp(0.85rem, 2vw, 1rem)"
+ route-heading:
+ fontSize: "clamp(1.4rem, 3vw, 2rem)"
+ reference-heading:
+ fontSize: "1.25rem"
+ promise-copy:
+ fontSize: "1.1rem"
+rounded:
+ square: "0"
+ focus: "0.15rem"
+spacing:
+ xs: "0.4rem"
+ sm: "0.75rem"
+ md: "1rem"
+ lg: "1.5rem"
+ touch-target: "2.75rem"
+ home-gutter: "clamp(1rem, 4vw, 4rem)"
+ section-block: "clamp(5rem, 10vw, 9rem)"
+components:
+ button-primary:
+ backgroundColor: "{colors.ink}"
+ textColor: "{colors.paper}"
+ typography: "{typography.body}"
+ rounded: "{rounded.square}"
+ padding: "0.65rem 1.1rem"
+ height: "{spacing.touch-target}"
+ button-primary-hover:
+ backgroundColor: "{colors.accent-teal}"
+ textColor: "#fff"
+ rounded: "{rounded.square}"
+ proof-sheet:
+ backgroundColor: "{colors.ink}"
+ textColor: "{colors.proof-text}"
+ rounded: "{rounded.square}"
+ padding: "clamp(1rem, 3vw, 2rem)"
+ language-promise:
+ backgroundColor: "{colors.promise-teal}"
+ textColor: "#fff"
+ rounded: "{rounded.square}"
+ padding: "clamp(2rem, 5vw, 4rem)"
+---
+
+# Design System: Humanizer Documentation
+
+## Overview
+
+The Working Proof Sheet is a cream-paper and ink editorial world where real C#
+input and output prove the product. The homepage uses forceful sans-serif type;
+documentation uses an old-style serif for long-form headings. The canonical
+logo, ruled composition, restrained teal and rust accents, and code-forward
+examples replace decorative imagery, invented statistics, and card grids.
+
+The system is task-first, editorial, flat, version-visible, and accessible.
+
+## Colors
+
+Paper and raised paper form the reading surface. Ink and muted ink carry the
+content hierarchy. Teal signals navigation or capability; rust labels structure
+or focus. Neither becomes ambient decoration. Dark mode uses the paired dark
+tokens rather than inverting arbitrary values.
+
+## Typography
+
+Documentation headings use the serif display stack. Body copy, navigation,
+labels, and the homepage use the sans-serif stack. Homepage display text is
+tight and heavy; labels are compact and letter-spaced. Reading copy is limited
+to 72 characters per line.
+
+The Proof-Sheet Split: editorial serif establishes the reference manual while
+precise sans-serif type identifies controls, tasks, and executable proof.
+
+## Layout
+
+The homepage is capped at 90rem with a fluid gutter and generous section
+spacing. Task routes become a grid at 48rem. Documentation text remains within
+72ch. The shell accounts for 360px and 480px phones and the 996px/997px
+navigation transition. Tables stay contained; historical language coverage
+keeps its bounded sticky table. Interactive targets are at least 2.75rem high.
+
+## Elevation and Depth
+
+The interface is flat: no cards, gradients, or shadows. Paper tones,
+one-pixel rules, and whitespace establish hierarchy. The navigation boundary is
+the sole persistent elevation cue.
+
+Ruled-Not-Raised: use a rule or a tone change before introducing visual depth.
+
+## Shapes
+
+Buttons, proof sheets, task routes, and promises are square. A 0.15rem radius is
+reserved for focus treatments and existing shell controls.
+
+## Components
+
+- Primary button: ink on paper, changing to teal on hover.
+- Text link: underlined or rule-led; never disguised as a pill.
+- Proof sheet: dark code surface with distinct output color.
+- Task route: ruled text destination with a clear action.
+- Language promise: teal full-width statement of the localization contract.
+- Navigation: compact, version-visible, and separated by one rule.
+- Supported culture list: readable code list, not a partial-capability matrix.
+
+## Do
+
+- Use deterministic, runnable C# examples.
+- Reuse the canonical Humanizer logo and existing documentation assets.
+- Put the product promise, NuGet install command, and proof in the first viewport.
+- Use one-pixel rules, visible focus, and reduced-motion support.
+- Keep tables contained and use familiar public API names.
+
+## Do Not
+
+- Add card grids, pills, gradients, decorative shadows, or invented imagery.
+- Invent metrics, customers, testimonials, or benchmarks.
+- Use nondeterministic examples or hide keyboard focus.
+- Permit horizontal page scrolling.
+- Present cultures as tiers of partial feature support.
diff --git a/PRODUCT.md b/PRODUCT.md
new file mode 100644
index 000000000..d37192139
--- /dev/null
+++ b/PRODUCT.md
@@ -0,0 +1,73 @@
+# Product
+
+
+
+## Platform
+
+web
+
+## Users
+
+The primary reader is a .NET developer who has a value to present and wants
+the shortest correct Humanizer call. Contributors are a secondary audience
+served by dedicated contribution references.
+
+## Product Purpose
+
+Humanizer is a mature .NET library that turns identifiers, dates, times,
+numbers, quantities, enums, and collections into text people can read.
+Humanizr.net is its versioned documentation and API reference. Success means a
+developer can identify the applicable API, install the matching NuGet package,
+and verify the call without learning Humanizer's internal localization model.
+
+## Positioning
+
+Humanizer exposes focused, familiar .NET extension methods such as
+`Humanize`, `Pluralize`, and `Singularize`. Localization is part of those
+operations rather than a separate formatting pass.
+
+## Operating Context
+
+Readers arrive with a typed .NET value and use the site to install Humanizer,
+find a task guide, copy a runnable example, or confirm an exact signature.
+Versioned guides, examples, search results, and API references must stay aligned
+with the selected NuGet package.
+
+## Capabilities and Constraints
+
+- Locale support is all-or-nothing: every listed culture supports every
+ applicable localized feature. Missing behavior or English fallback is a bug.
+- Current application documentation describes Humanizer 4 as a NuGet package.
+ Source checkout and build instructions belong only in contributor guides.
+- Historical documentation remains version-correct and must not inherit
+ unreleased APIs or current locale claims.
+- Application documentation uses public API language, not source-generator
+ ownership, locale inheritance, or linguistic implementation terminology.
+
+## Brand Commitments
+
+Preserve the Humanizer name, canonical logo, NuGet package identity, and
+direct developer-to-developer voice. Do not invent customers, testimonials,
+benchmarks, or product imagery.
+
+## Evidence on Hand
+
+- Canonical logo: `website/static/img/logo.png`
+- Runnable documentation examples: `website/docs/_examples`
+- Generated API reference: `website/docs/api`
+- Per-version manifest: `website/humanizer-versions.json`
+- Supported-culture inventory: `website/docs/languages/language-coverage.json`
+
+## Product Principles
+
+- Start from the programming task or value the reader already has.
+- Show runnable C# before detailed explanation.
+- Prefer familiar public API terms over internal taxonomy.
+- Keep every claim version-correct and behaviorally verified.
+- Treat accessibility as a release requirement.
+
+## Accessibility & Inclusion
+
+Meet WCAG AA contrast, preserve visible keyboard focus, support reduced motion,
+avoid horizontal page scrolling, and keep touch targets usable at narrow mobile
+widths.
diff --git a/tools/docs/generate-language-coverage.ps1 b/tools/docs/generate-language-coverage.ps1
index be3140c02..ba4bc17bd 100644
--- a/tools/docs/generate-language-coverage.ps1
+++ b/tools/docs/generate-language-coverage.ps1
@@ -329,7 +329,7 @@ function Get-CurrentEvidence {
return [ordered]@{
coverageKind = "yaml-capabilities"
versionKey = "current"
- generatedFor = "main/preview"
+ generatedFor = "4.0"
source = "src/Humanizer/Locales/*.yml"
localesPath = [System.IO.Path]::GetFullPath($LocalesPath)
package = [ordered]@{
diff --git a/tools/docs/snapshot.ps1 b/tools/docs/snapshot.ps1
index 933f9c5b5..31efd7312 100644
--- a/tools/docs/snapshot.ps1
+++ b/tools/docs/snapshot.ps1
@@ -359,7 +359,7 @@ function Add-ApiLanding {
)
$provenance = if ($Entry.version -eq "current") {
- "Generated from the ``main/preview`` ``$($Entry.apiPackage)`` checkout using the ``$($Entry.referenceTfm)`` reference assembly."
+ "Generated from the Humanizer 4 ``$($Entry.referenceTfm)`` reference assembly."
} else {
"Generated from ``$($Entry.apiPackage)`` ``$($Entry.source.packageVersion)`` using the ``$($Entry.referenceTfm)`` reference assembly."
}
diff --git a/website/docs/analyzer/index.mdx b/website/docs/analyzer/index.mdx
index 8c0cffca6..036444a0b 100644
--- a/website/docs/analyzer/index.mdx
+++ b/website/docs/analyzer/index.mdx
@@ -87,7 +87,7 @@ component selection.
| Duplicate analyzer/load behavior on `3.0.8` | Upgrade to `3.0.10` for the fallback-selection fix. |
| IDE and CI disagree | Compare SDK/MSBuild versions and the resolved package lock file, then restart the IDE after a clean restore. |
-The current preview also offers a separate words-to-number code fix on compiler
+Humanizer 4 also offers a separate words-to-number code fix on compiler
diagnostics `CS0029` and `CS0266`. It wraps recognized Humanizer calls in a
checked `int` conversion. That feature is not part of `HUMANIZER001` and is not
present in `3.0.1`, `3.0.8`, or `3.0.10`.
@@ -116,7 +116,7 @@ diagnostic is clean.
## Version notes
`HUMANIZER001` first appears in supported stable version `3.0.1` and retains
-default severity `error` through `3.0.10` and current preview. Analyzer loading
+default severity `error` through `3.0.10` and Humanizer 4. Analyzer loading
is materially more robust at the `3.0.8` and `3.0.10` boundaries.
## Related guides and API
diff --git a/website/docs/api/index.md b/website/docs/api/index.md
index 086a6b799..21f58332c 100644
--- a/website/docs/api/index.md
+++ b/website/docs/api/index.md
@@ -6,4 +6,4 @@ sidebar_position: 2
# Humanizer API reference
-Generated from the `main/preview` `Humanizer` checkout using the `net10.0` reference assembly.
+Generated from the Humanizer 4 `net10.0` reference assembly.
diff --git a/website/docs/concepts/culture-and-configuration.mdx b/website/docs/concepts/culture-and-configuration.mdx
index ababaaac8..558d775ef 100644
--- a/website/docs/concepts/culture-and-configuration.mdx
+++ b/website/docs/concepts/culture-and-configuration.mdx
@@ -17,7 +17,7 @@ Humanizer chooses language and grammar from one of three places:
1. A `CultureInfo` passed to the operation.
2. `CultureInfo.CurrentCulture` or `CurrentUICulture`, depending on the API.
-3. A configured registry fallback when the requested culture has no matching component.
+3. A configured registry fallback for a culture that is not listed as supported.
Per-call culture is the narrowest and safest choice. Ambient culture is useful when a request pipeline already establishes it. Global `Configurator` changes are process-wide policy.
diff --git a/website/docs/concepts/trimming-and-native-aot.mdx b/website/docs/concepts/trimming-and-native-aot.mdx
index 6cb362c1b..d800037ba 100644
--- a/website/docs/concepts/trimming-and-native-aot.mdx
+++ b/website/docs/concepts/trimming-and-native-aot.mdx
@@ -32,7 +32,10 @@ The repository verifier is:
pwsh tools/docs/verify-aot.ps1
```
-It tests Humanizer `3.0.10` and a freshly packed current checkout from an isolated package cache. The normal documentation test gate invokes it automatically. A supported host must complete both publish modes and run both binaries. An unsupported local OS/architecture is reported and skipped; CI can require a supported host with:
+It tests the selected Humanizer NuGet package from an isolated package cache.
+The normal documentation test gate invokes it automatically. A supported host
+must complete both publish modes and run both binaries. An unsupported local
+OS/architecture is reported and skipped; CI can require a supported host with:
```console
pwsh tools/docs/test.ps1 -RequireNativeAot
@@ -51,7 +54,7 @@ The generic parameter gives the trimmer and AOT compiler the enum’s public
fields. By contrast, `DehumanizeTo(string, Type, ...)` and the base-`Enum`
`Humanize(...)` overloads construct generic methods through reflection and are
annotated with both `RequiresUnreferencedCode` and `RequiresDynamicCode`. The
-source-selecting humanization overload is available on `main/preview`.
+source-selecting humanization overload is available on `Humanizer 4`.
### Read warnings as API feedback
@@ -67,7 +70,11 @@ Assembly compatibility metadata is not proof that every consumer call, dependenc
## Version notes
-This compatibility claim begins with Humanizer `3.0.1`; `2.x` snapshots must exclude this page. The current package emits `IsTrimmable` and, on current compatible TFMs, `IsAotCompatible` metadata. The publish verifier is the behavioral proof for `3.0.10` and current, rather than a promise about every historical target framework.
+This compatibility claim begins with Humanizer `3.0.1`; `2.x` snapshots must
+exclude this page. Humanizer 4 emits `IsTrimmable` and, on compatible TFMs,
+`IsAotCompatible` metadata. The publish verifier is the behavioral proof for
+`3.0.10` and Humanizer 4, rather than a promise about every historical target
+framework.
## Related guides and API
diff --git a/website/docs/contributing/index.mdx b/website/docs/contributing/index.mdx
index 9234a0136..e788c72cb 100644
--- a/website/docs/contributing/index.mdx
+++ b/website/docs/contributing/index.mdx
@@ -58,5 +58,5 @@ changes against the branch that owns that version.
- [Report a language issue](./report-language-issue.mdx)
- [Add or update a locale](./adding-or-updating-a-locale.mdx)
- [Choose or extend a locale engine](./choosing-or-extending-a-locale-engine.mdx)
-- [Supported cultures and capabilities](../languages/supported-cultures.mdx)
+- [Supported cultures](../languages/supported-cultures.mdx)
- [Selected-version API reference](../api/index.md)
diff --git a/website/docs/contributing/report-language-issue.mdx b/website/docs/contributing/report-language-issue.mdx
index 1223658e0..c574b8ea2 100644
--- a/website/docs/contributing/report-language-issue.mdx
+++ b/website/docs/contributing/report-language-issue.mdx
@@ -56,5 +56,5 @@ on the current branch or in historical documentation.
- [Add or update a locale](./adding-or-updating-a-locale.mdx)
- [Parity and preflight](./parity-and-preflight.mdx)
-- [Supported cultures and capabilities](../languages/supported-cultures.mdx)
+- [Supported cultures](../languages/supported-cultures.mdx)
- [Selected-version API reference](../api/index.md)
diff --git a/website/docs/index.md b/website/docs/index.md
index 02d79303f..d5b49d110 100644
--- a/website/docs/index.md
+++ b/website/docs/index.md
@@ -34,13 +34,13 @@ selector keeps guides, examples, and API pages in the same release.
## Use and improve languages
- [Languages and cultures](./languages/index.mdx) — choose a culture and understand the package boundary.
-- [Supported cultures and capabilities](./languages/supported-cultures.mdx) — inspect generated `main/preview` coverage.
+- [Supported cultures](./languages/supported-cultures.mdx) — find the culture codes supported by this version.
- [Report or correct a language issue](./contributing/report-language-issue.mdx) — provide the linguistic and platform context needed for a reliable fix.
- [Contribute to Humanizer](./contributing/index.mdx) — follow the locale, validation, and documentation workflows.
## Maintain a project
- [Plan an upgrade](./upgrading/index.mdx) — follow every compatibility boundary between two supported versions.
-- [Evaluate main/preview](./upgrading/main-preview.mdx) — review verified differences after `3.0.10` without guessing a release number.
+- [Upgrade to Humanizer 4](./upgrading/main-preview.mdx) — review verified differences from `3.0.10`.
- [Migrate namespaces with the analyzer](./analyzer/index.mdx) — configure and run the bundled analyzer locally and in CI.
- [Publish trimmed or Native AOT](./concepts/trimming-and-native-aot.mdx) — choose linker-safe enum APIs and run the publish proof.
diff --git a/website/docs/languages/custom-localization.mdx b/website/docs/languages/custom-localization.mdx
index a525a64ca..26ac24b0e 100644
--- a/website/docs/languages/custom-localization.mdx
+++ b/website/docs/languages/custom-localization.mdx
@@ -22,7 +22,7 @@ humanization strategies are separate process-wide properties.
## Example
-In `main/preview`, register before any code resolves the formatter registry:
+In `Humanizer 4`, register before any code resolves the formatter registry:
```csharp
using System.Globalization;
@@ -46,7 +46,7 @@ sealed class ProductFormatter(CultureInfo culture)
The result is `just now`. Register the narrowest component that owns the
behavior; do not replace a formatter merely to change number words.
-On `main/preview`, a custom formatter used by `HumanizeWithCase` must also
+In Humanizer 4, a custom formatter used by `HumanizeWithCase` must also
implement `IGrammaticalCaseTimeSpanFormatter`. A custom global time-span
strategy must implement `IGrammaticalCaseTimeSpanHumanizeStrategy`. Existing
`IFormatter` and `ITimeSpanHumanizeStrategy` implementations continue to work
@@ -55,7 +55,7 @@ for the existing duration APIs, but case-aware calls fail with
## Pitfall
-In Humanizer `3.x` and `main/preview`, a `LocaliserRegistry` freezes on first
+In Humanizer `3.x` and `Humanizer 4`, a `LocaliserRegistry` freezes on first
resolution. A later `Register` call throws. Configure registries once during
startup, before hosted services, request handlers, or static initializers can
call Humanizer.
@@ -70,10 +70,10 @@ If the built-in locale is wrong rather than product-specific, follow the
## Version notes
-Humanizer `2.x` registries remain mutable; `3.x` and `main/preview` freeze them
+Humanizer `2.x` registries remain mutable; `3.x` and `Humanizer 4` freeze them
after first use. Constructor shapes and available registries are
version-specific public API. Compile against the selected package and follow
-that snapshot's API reference rather than copying a `main/preview` extension
+that snapshot's API reference rather than copying a `Humanizer 4` extension
into a historical application.
## Related guides and API
diff --git a/website/docs/languages/grammar-and-platform-formatting.mdx b/website/docs/languages/grammar-and-platform-formatting.mdx
index 382403910..5d5b1385f 100644
--- a/website/docs/languages/grammar-and-platform-formatting.mdx
+++ b/website/docs/languages/grammar-and-platform-formatting.mdx
@@ -46,7 +46,7 @@ Pass grammar explicitly when the surrounding noun or sentence determines it.
The default gender or case is a locale convention, not a guess about your
domain text.
-On `main/preview`, `TimeSpan.HumanizeWithCase` applies case only to a bare
+In Humanizer 4, `TimeSpan.HumanizeWithCase` applies case only to a bare
duration phrase. It does not add a preposition and does not alter the separate
relative-date or date-ordinal grammar paths. Singular duration forms may be
complete locale-authored one-word or article phrases; counted forms may write
@@ -80,7 +80,7 @@ framework on which those types exist.
## Related guides and API
- [Choose a culture](./using-cultures.mdx)
-- [Supported cultures and capabilities](./supported-cultures.mdx)
+- [Supported cultures](./supported-cultures.mdx)
- [Format durations and ages](../scenarios/durations-and-ages.mdx)
- [Localization scenarios](../scenarios/localization-and-extensibility.mdx)
- [Selected-version API reference](../api/index.md)
diff --git a/website/docs/languages/index.mdx b/website/docs/languages/index.mdx
index 98b3eefea..b0c42be36 100644
--- a/website/docs/languages/index.mdx
+++ b/website/docs/languages/index.mdx
@@ -12,12 +12,13 @@ example: illustrative
## Orientation
Humanizer localizes behavior by culture, not by translating one final string.
-Choose a culture for the operation, then use the capability page to confirm
-which locale data is present in the selected documentation version.
+Choose a culture for the operation, then confirm that it is listed for the
+selected documentation version. Every listed culture supports every applicable
+localized feature.
The package and the culture are separate choices. Published releases through
`3.0.10` use the `Humanizer` metapackage to bring in `Humanizer.Core` and its
-locale packages. The current `main/preview` package is consolidated: one
+locale packages. Humanizer 4 is consolidated: one
`Humanizer` reference contains the runtime, generated locale data, and
analyzers. Installing a culture-specific package does not select that culture,
and selecting a culture does not install a missing historical package.
@@ -35,7 +36,7 @@ Console.WriteLine(42.ToWords(french));
```
Use [culture selection](./using-cultures.mdx) for per-call and ambient-culture
-patterns. Use [supported cultures and capabilities](./supported-cultures.mdx)
+patterns. Use [supported cultures](./supported-cultures.mdx)
for the selected-version inventory.
Use the [scenario finder](../scenarios/index.mdx) to reach the focused guide for
number words, dates, clock notation, collections, or another localized task.
@@ -46,17 +47,16 @@ or [open a prefilled locale issue](https://github.com/Humanizr/Humanizer/issues/
## Pitfall
-Do not infer capability coverage from a package name, a neutral-culture
-fallback, or one successful call. A regional culture can resolve locale data
-from a parent such as `fr`, but that resolution does not prove that every
-phrase is natural for every region. Report incorrect inherited behavior just
-as you would incorrect directly authored behavior.
+Do not infer support from a package name or English fallback. A regional
+culture can reuse a same-language parent such as `fr`; that is an
+implementation detail, not partial support. Report incorrect inherited
+behavior just as you would incorrect directly authored behavior.
## Version notes
The generated coverage page is scoped to the selected documentation version.
Stable snapshots use their own package and documentation evidence. Package
-consolidation is a current-preview behavior, while published versions through
+consolidation is Humanizer 4 behavior, while published versions through
`3.0.10` retain the metapackage and locale-package model.
## Related guides and API
diff --git a/website/docs/languages/language-coverage.json b/website/docs/languages/language-coverage.json
index a8c03ee04..ee8e018e4 100644
--- a/website/docs/languages/language-coverage.json
+++ b/website/docs/languages/language-coverage.json
@@ -2,7 +2,7 @@
"schemaVersion": 2,
"coverageKind": "yaml-capabilities",
"version": "current",
- "generatedFor": "main/preview",
+ "generatedFor": "4.0",
"source": "src/Humanizer/Locales/*.yml",
"package": {
"id": "Humanizer",
diff --git a/website/docs/languages/supported-cultures.mdx b/website/docs/languages/supported-cultures.mdx
index b1a56564c..1f71308bc 100644
--- a/website/docs/languages/supported-cultures.mdx
+++ b/website/docs/languages/supported-cultures.mdx
@@ -1,6 +1,6 @@
---
id: supported-cultures
-title: Supported cultures and capabilities
+title: Supported cultures
sidebar_position: 5
diataxis: reference
persona: application developer
@@ -9,38 +9,22 @@ example: illustrative
import coverage from './language-coverage.json';
-export function CoverageValue({status}) {
- if (status.kind === 'own') {
- return 'Own';
- }
- if (status.kind === 'via') {
- return Via {status.via};
- }
- return status.kind === 'platform'
- ? Platform
- : 'Missing';
-}
+export const supportedCultures =
+ coverage.locales?.map(({locale}) => locale) ?? coverage.cultures ?? [];
-# Supported cultures and capabilities
+# Supported cultures
## Orientation
-This inventory is generated for {coverage.generatedFor} from
-{coverage.source}. The verified
-{coverage.package.id} assets target
-{coverage.package.targetFrameworks.join(', ')}, and their locale
-evidence is {coverage.package.localeData}.
-
-`DateOnly` ordinals are present in the
-{coverage.package.apiAvailability.dateOnlyOrdinals.join(', ') || 'none'}
-assets. Clock notation is present in the
-{coverage.package.apiAvailability.clockNotation.join(', ') || 'none'}
-assets. Those lists come from exact members in each package XML documentation
-file, not from a version-number assumption.
+Humanizer 4 treats locale support as all-or-nothing. Every culture listed below
+supports every applicable localized feature. Regional cultures may reuse a
+same-language parent, and calendar or numeric formatting may use .NET
+globalization data when no Humanizer override is needed; neither represents
+partial support.
## Example
-Use the culture code in the first column with `CultureInfo`:
+Use a culture code from the list with `CultureInfo`:
```csharp
using System.Globalization;
@@ -50,101 +34,19 @@ var culture = CultureInfo.GetCultureInfo("pt-BR");
Console.WriteLine(123.ToWords(culture));
```
-{coverage.coverageKind === 'yaml-capabilities' ? (
- <>
-
- The table contains {coverage.locales.length} checked-in locale files.
- Own means that locale contributes at least one canonical
- block for the capability. Via … means the capability is
- fully inherited through the immediate same-language parent.
- Platform means an optional calendar or number-formatting
- override is absent, so Humanizer uses .NET globalization data.
-
-
- The canonical source-generator parser rejects invalid schemas, missing
- required capabilities, inheritance cycles, and cross-language parents
- before this output is written.
-
-
- Grammatical-case duration coverage is narrower than general locale
- coverage and is classified separately as distinct,
- invariant, or not-applicable. Case and unit
- realizations may be authored, evidenced as rendering the same as another
- case, or explicitly not applicable; release validation rejects
- unsupported applicable entries. An Own or
- Via … cell in this table does not imply that
- HumanizeWithCase supports every case or duration unit.
- See the duration guide for the runtime contract.
-
-
-
-
Current locale capability coverage
-
-
-
Culture
-
Parent
- {coverage.capabilities.map((capability) => (
-
{capability.label}
- ))}
-
-
-
- {coverage.locales.map((locale) => (
-
-
{locale.locale}
-
{locale.parent ? {locale.parent} : '—'}
- {coverage.capabilities.map((capability) => (
-
-
-
- ))}
-
- ))}
-
-
-
- >
-) : (
- <>
-
- This published release predates the current canonical YAML model. Its
- culture list comes from locale dependencies in the verified
- {coverage.package.id} metapackage, plus neutral
- en from the verified
- {coverage.package.apiPackage} package. It does not claim
- per-capability ownership that the packages cannot prove.
-
-
-
-
Published cultures for this version
-
-
Published culture
-
-
- {coverage.cultures.map((culture) => (
-
{culture}
- ))}
-
-
-
- >
-)}
+
This version supports {supportedCultures.length} cultures:
+
+
+ {supportedCultures.map((locale) => (
+
{locale}
+ ))}
+
## Pitfall
-For current YAML coverage, do not read `Platform` as unsupported. Calendar and
-numeric overrides exist only when Humanizer must replace platform-supplied
-values. Conversely, neither a current `Own`/`Via` cell nor a historical package
-dependency proves that one sample establishes complete linguistic correctness.
-If output is wrong, use the correction path.
+A listed culture is supported. Missing behavior, English fallback, or
+incorrect language in that culture is a bug. If output is wrong, use the
+correction path.
[Open the correction guide](../contributing/report-language-issue.mdx) or
[open a prefilled locale issue](https://github.com/Humanizr/Humanizer/issues/new?title=Locale%3A%20describe%20the%20language%20issue&body=Culture%3A%20%0AHumanizer%20version%3A%20%0AAPI%20call%3A%20%0AExpected%3A%20%0AActual%3A%20%0APlatform%2FTFM%3A%20).
@@ -152,14 +54,12 @@ If output is wrong, use the correction path.
## Version notes
The sibling `language-coverage.json` is generated separately for each docs
-version. Current coverage uses the canonical source tree. Published snapshots
-use their own verified NuGet packages; they never inherit the preview culture
-inventory.
+version. Published snapshots use their own verified NuGet packages and culture
+lists; they never inherit another version's inventory.
## Related guides and API
- [Choose a culture](./using-cultures.mdx)
- [Grammar and platform formatting](./grammar-and-platform-formatting.mdx)
-- [Format durations and ages](../scenarios/durations-and-ages.mdx)
- [Report or correct a language issue](../contributing/report-language-issue.mdx)
- [Selected-version API reference](../api/index.md)
diff --git a/website/docs/languages/using-cultures.mdx b/website/docs/languages/using-cultures.mdx
index f82dbc786..c7843a6c8 100644
--- a/website/docs/languages/using-cultures.mdx
+++ b/website/docs/languages/using-cultures.mdx
@@ -57,7 +57,7 @@ Parent fallback is resolution, not parity proof. If `fr-CA` reaches `fr`, that
only identifies where the data came from. It does not establish that French
Canadian wording matches every neutral-French choice.
-On `main/preview`, use `Configurator.IsCultureSupported(culture)` before
+On `Humanizer 4`, use `Configurator.IsCultureSupported(culture)` before
choosing an application fallback. It checks generated Humanizer locale data
through the named parent chain and does not return `true` merely because the
default English localizers could produce output:
@@ -77,14 +77,14 @@ If inherited or direct output is wrong, follow the
Explicit and ambient `CultureInfo` selection applies across the supported
documentation corpus. The locale set and package layout are versioned:
-`main/preview` has generated locale data in the consolidated `Humanizer`
+`Humanizer 4` has generated locale data in the consolidated `Humanizer`
package, while published versions through `3.0.10` use the metapackage and
locale-package graph documented in their snapshot.
-`Configurator.IsCultureSupported` is a `main/preview` API.
+`Configurator.IsCultureSupported` is a `Humanizer 4` API.
## Related guides and API
-- [Supported cultures and capabilities](./supported-cultures.mdx)
+- [Supported cultures](./supported-cultures.mdx)
- [Culture and global configuration](../concepts/culture-and-configuration.mdx)
- [Localization and extensibility](../scenarios/localization-and-extensibility.mdx)
- [Package selection](../start/package-selection.md)
diff --git a/website/docs/scenarios/byte-sizes-and-rates.mdx b/website/docs/scenarios/byte-sizes-and-rates.mdx
index eb83bb8b1..657dc7288 100644
--- a/website/docs/scenarios/byte-sizes-and-rates.mdx
+++ b/website/docs/scenarios/byte-sizes-and-rates.mdx
@@ -26,7 +26,7 @@ transfer rate:
### Create and format sizes
Use numeric extensions through `Terabytes`, or the corresponding
-`ByteSize.From...` factories. `main/preview` also exposes decimal petabyte and
+`ByteSize.From...` factories. `Humanizer 4` also exposes decimal petabyte and
exabyte values plus explicit binary `Kibibyte`, `Mebibyte`, `Gibibyte`,
`Tebibyte`, and `Pebibyte` factories, properties, format tokens, parsing
symbols, and `DataUnit` values. `Humanize` chooses the largest whole unit and
@@ -57,7 +57,7 @@ The choice is explicit and is not stored in `ByteSize`.
### Decompose exact quantities
-On `main/preview`, `HumanizeComposite` walks the existing units from exabytes
+On `Humanizer 4`, `HumanizeComposite` walks the existing units from exabytes
through bits and returns up to `precision` nonzero parts. It preserves exact
byte and bit residuals while parts remain available: for example, 81,937 bits
becomes `10 KB 2 B 1 b` at precision 3. Once the precision is reached, the
@@ -87,7 +87,7 @@ Use `HumanizeWithUnitSystem` on `ByteRate` for explicit SI or IEC rate output.
## Pitfall
Humanizer’s legacy `KB`, `MB`, `GB`, and `TB` values use binary multiples of
-1024, while `PB` and `EB` use decimal SI factors. On `main/preview`, prefer the
+1024, while `PB` and `EB` use decimal SI factors. On `Humanizer 4`, prefer the
explicit IEC symbols `KiB`, `MiB`, `GiB`, `TiB`, and `PiB` when the binary
meaning must be unambiguous. The symbols `b` and `B` are intentionally
case-sensitive: lowercase is bits, uppercase is bytes. Other recognized unit
@@ -109,7 +109,7 @@ Construct rates with a positive, nonzero interval. Keep `ByteSize` as the model
## Version notes
Core `ByteSize` creation and formatting span the documented corpus. Petabyte,
-exabyte, explicit IEC unit support, and `HumanizeComposite` are `main/preview`
+exabyte, explicit IEC unit support, and `HumanizeComposite` are `Humanizer 4`
additions. Culture-aware `ByteRate.Humanize` appears in `2.13.14`; earlier
snapshots need a reduced example or exclusion. The verified source runs
against the selected package.
diff --git a/website/docs/scenarios/durations-and-ages.mdx b/website/docs/scenarios/durations-and-ages.mdx
index 06a04a625..bf759783d 100644
--- a/website/docs/scenarios/durations-and-ages.mdx
+++ b/website/docs/scenarios/durations-and-ages.mdx
@@ -36,13 +36,13 @@ The default maximum unit is `Week`. Ask for `Month` or `Year` explicitly when th
### Use compact symbols
-On `main/preview`, `HumanizeToSymbols` applies the same precision, empty-unit,
+In Humanizer 4, `HumanizeToSymbols` applies the same precision, empty-unit,
range, culture, and separator controls but emits localized `TimeUnit` symbols
such as `1h, 3s, 1ms`.
### Keep sub-second precision in seconds
-On `main/preview`, use `HumanizeWithFractionalSeconds` or
+In Humanizer 4, use `HumanizeWithFractionalSeconds` or
`HumanizeToSymbolsWithFractionalSeconds` when milliseconds should appear as a
fraction of the seconds unit. These APIs always use `Second` as the inclusive
minimum unit. Supply the precision, maximum unit, maximum fractional digits,
@@ -61,7 +61,7 @@ from the configured formatter. A custom
### Apply grammatical case
-On `main/preview`, `HumanizeWithCase` selects a locale-authored grammatical
+In Humanizer 4, `HumanizeWithCase` selects a locale-authored grammatical
case form for each duration unit. A singular form may include a localized word
or article, and a locale may encode a count in the inflected unit form:
@@ -109,7 +109,7 @@ underlying locale-data model.
### Parse invariant durations
-On `main/preview`, `DehumanizeTimeSpan` and `TryDehumanizeTimeSpan` accept
+In Humanizer 4, `DehumanizeTimeSpan` and `TryDehumanizeTimeSpan` accept
standard invariant `TimeSpan` text or compact tokens using `ms`, `s`, `m`, `h`,
`d`, and `w`. Compact tokens may have one leading sign, optional whitespace,
fractional values that resolve to whole ticks, repeated units, and units in any
@@ -136,7 +136,7 @@ comma-decimal culture, while `1,5h`, `one hour`, and `1 hour` are rejected.
`TimeSpan.Humanize` is available across the documented corpus. `ToAge` begins
in `3.0.1`; `HumanizeWithCase`, `HumanizeToSymbols`, `DehumanizeTimeSpan`, and
-`TryDehumanizeTimeSpan` are `main/preview` APIs. The verified executable runs
+`TryDehumanizeTimeSpan` are `Humanizer 4` APIs. The verified executable runs
against the selected package; older snapshots replace or exclude calls they
do not support.
diff --git a/website/docs/scenarios/enums-and-flags.mdx b/website/docs/scenarios/enums-and-flags.mdx
index 8d6d26c15..1293940f8 100644
--- a/website/docs/scenarios/enums-and-flags.mdx
+++ b/website/docs/scenarios/enums-and-flags.mdx
@@ -13,7 +13,7 @@ import enumsFlags from '!!raw-loader!../_examples/scenarios-enums-flags/Program.
## Orientation
-Enum humanization resolves one display label for each member: supported description/display metadata when present, otherwise the humanized member name. On `main/preview`, dehumanization also recognizes the raw name, its humanized form, and authored display aliases. Flags enums are decomposed and joined through the active collection formatter.
+Enum humanization resolves one display label for each member: supported description/display metadata when present, otherwise the humanized member name. On `Humanizer 4`, dehumanization also recognizes the raw name, its humanized form, and authored display aliases. Flags enums are decomposed and joined through the active collection formatter.
## Example
@@ -28,7 +28,7 @@ and an unknown flag bit:
Humanizer prefers supported description/display metadata before deriving words
from the member name. That chosen value is also the member's dehumanization
-label. On `main/preview`, `LetterCasing` changes only labels derived from the
+label. On `Humanizer 4`, `LetterCasing` changes only labels derived from the
member name; authored metadata preserves its exact casing.
`Configurator.UseEnumDescriptionPropertyLocator` can select a custom
description-like property globally and must run before enum metadata is
@@ -36,7 +36,7 @@ cached.
### Select the humanization source
-On `main/preview`, pass both `LetterCasing` and `EnumHumanizeSource` when the
+On `Humanizer 4`, pass both `LetterCasing` and `EnumHumanizeSource` when the
default metadata precedence is not the desired presentation:
- `Default` preserves the existing precedence. A `DisplayAttribute` uses its
@@ -65,7 +65,7 @@ change dehumanization aliases or their collision rules.
### Parse accepted aliases
-On `main/preview`, `DehumanizeTo` recognizes the raw member name, its
+On `Humanizer 4`, `DehumanizeTo` recognizes the raw member name, its
humanized form, and `DisplayAttribute` name, description, and short-name
values. It also recognizes a configured description attribute value when that
value becomes the member's authored description. Matching is
@@ -82,7 +82,7 @@ Known nonzero flags are humanized and joined using the current UI culture. A def
## Pitfall
Aliases should be unambiguous even though matching is case-insensitive. On
-`main/preview`, a current humanized representation takes precedence over a
+`Humanizer 4`, a current humanized representation takes precedence over a
supplemental alias when labels collide; collisions among supplemental aliases
retain the later enum value in unsigned numeric order. Prefer the generic enum
overloads. Runtime enum humanization and the runtime `Type` dehumanization
@@ -97,9 +97,9 @@ constraints and metadata configuration. The flags example runs against the
selected package. Consult the selected API before migrating code that stores
values as the base `Enum` type. Metadata casing preservation when a
`LetterCasing` argument is supplied, and dehumanization through raw,
-humanized, and supplemental metadata aliases, are `main/preview` behavior.
+humanized, and supplemental metadata aliases, are `Humanizer 4` behavior.
`EnumHumanizeSource` and its two casing-plus-source overloads are also
-`main/preview` APIs.
+`Humanizer 4` APIs.
## Related guides and API
diff --git a/website/docs/scenarios/index.mdx b/website/docs/scenarios/index.mdx
index 46a2c6f16..b765e1d91 100644
--- a/website/docs/scenarios/index.mdx
+++ b/website/docs/scenarios/index.mdx
@@ -45,7 +45,7 @@ Humanized output is presentation text, not a stable storage or wire format. Keep
## Version notes
-The site covers selected releases from `2.10.1` through `3.0.10` plus `main/preview`. Use the version selector before copying a call. Focused pages state important API boundaries, and unavailable pages are removed or replaced in historical snapshots.
+The site covers selected releases from `2.10.1` through `3.0.10` plus `Humanizer 4`. Use the version selector before copying a call. Focused pages state important API boundaries, and unavailable pages are removed or replaced in historical snapshots.
## Related guides and API
diff --git a/website/docs/scenarios/inflection-and-quantities.mdx b/website/docs/scenarios/inflection-and-quantities.mdx
index 411af1e80..b797c0913 100644
--- a/website/docs/scenarios/inflection-and-quantities.mdx
+++ b/website/docs/scenarios/inflection-and-quantities.mdx
@@ -37,7 +37,7 @@ Numeric format strings and providers control the displayed number. They do not t
### Pluralize a localized noun for a count
-On `main/preview` for v4, `PluralizationForms.TryPluralize` selects a
+On `Humanizer 4`, `PluralizationForms.TryPluralize` selects a
caller-supplied form using the explicit culture's Unicode CLDR decimal cardinal
rule. Every supported culture supplies this rule. If the selected form was not
supplied, the operation returns `false`; it does not guess morphology or fall
@@ -59,14 +59,14 @@ and its [decimal operands](https://unicode.org/reports/tr35/tr35-numbers.html).
`Vocabularies.Default` supports `AddIrregular`, `AddUncountable`, `AddPlural`, and `AddSingular`. Prefer an irregular or uncountable entry over a broad regular expression. Set `matchEnding: false` when a rule must match the entire noun instead of every word ending with the same letters.
-On `main/preview`, `AddAcronym` uses the same vocabulary to preserve the
+In Humanizer 4, `AddAcronym` uses the same vocabulary to preserve the
canonical casing of a domain acronym during string humanization. Registration
is case-insensitive, but the registered spelling controls the output. Acronyms
must contain letters only.
## Pitfall
-Vocabulary changes, including acronym registrations on `main/preview`, are
+Vocabulary changes, including acronym registrations in Humanizer 4, are
process-global. Register domain words once during startup and test them in an
isolated process; do not add rules per request. `ToQuantity` treats exactly `1`
and `-1` as singular. Fractional values, `NaN`, and infinity use the plural
@@ -82,7 +82,7 @@ Core inflection is available across the documented release set. Individual
vocabulary rules and overloads have changed, and early Humanizer `3.0.x`
temporarily lacked quantity overloads restored by `3.0.8`. The verified source
runs against the selected package. `AddAcronym` is available on
-`main/preview`.
+`Humanizer 4`.
Culture-explicit `PluralizationForms` are new in v4.
## Related guides and API
diff --git a/website/docs/scenarios/localization-and-extensibility.mdx b/website/docs/scenarios/localization-and-extensibility.mdx
index e059ff9b1..a81b9abb9 100644
--- a/website/docs/scenarios/localization-and-extensibility.mdx
+++ b/website/docs/scenarios/localization-and-extensibility.mdx
@@ -39,7 +39,7 @@ Registries cover collection formatters, general formatters, number-word converte
### Set a global time-span policy
-On `main/preview`, assign `Configurator.TimeSpanHumanizeStrategy` once during
+On `Humanizer 4`, assign `Configurator.TimeSpanHumanizeStrategy` once during
application startup to route every `TimeSpan.Humanize` and
`HumanizeToSymbols` call through an application-wide policy. Fractional-second
calls use `IFractionalTimeSpanHumanizeStrategy` when the configured strategy
@@ -83,8 +83,8 @@ Parent-culture fallback identifies where behavior was resolved. It does not prov
The local transformer runs across the supported corpus. Registry immutability
and enum metadata configuration change in Humanizer 3.
`TimeSpanHumanizeStrategy`, `ITimeSpanHumanizeStrategy`, and
-`DefaultTimeSpanHumanizeStrategy` are `main/preview` APIs. Individual
-registries and locale capabilities are selected-version behavior; historical
+`DefaultTimeSpanHumanizeStrategy` are Humanizer 4 APIs. Individual registries
+and supported-culture inventories are selected-version behavior; historical
snapshots must not present current YAML/generator internals as old package
behavior.
diff --git a/website/docs/scenarios/numbers-words-ordinals-roman-bytes-and-quantities.mdx b/website/docs/scenarios/numbers-words-ordinals-roman-bytes-and-quantities.mdx
index a13180610..4b7e68c1f 100644
--- a/website/docs/scenarios/numbers-words-ordinals-roman-bytes-and-quantities.mdx
+++ b/website/docs/scenarios/numbers-words-ordinals-roman-bytes-and-quantities.mdx
@@ -40,7 +40,7 @@ Do not persist humanized number text as the numeric source of truth. `ByteSize`
The survey’s available calls compile and run against the selected package.
Number-word parsing begins in `3.0.1` and changes from `int` to `long` on
-`main/preview`. Culture-aware byte-rate formatting begins in `2.13.14`.
+`Humanizer 4`. Culture-aware byte-rate formatting begins in `2.13.14`.
Historical snapshots remove or replace unavailable focused pages.
## Related guides and API
diff --git a/website/docs/scenarios/strings-and-casing.mdx b/website/docs/scenarios/strings-and-casing.mdx
index cca4fb2b3..db8aa7242 100644
--- a/website/docs/scenarios/strings-and-casing.mdx
+++ b/website/docs/scenarios/strings-and-casing.mdx
@@ -25,14 +25,14 @@ This verified source covers readable labels, custom acronym casing, ampersands,
`Humanize` recognizes PascalCase, camelCase, underscores, dashes, numbers, and common acronym boundaries. Pass `LetterCasing.Sentence`, `Title`, `LowerCase`, or `AllCaps` when the label needs a known presentation case. All-capital input is treated as an acronym and can remain unchanged until a casing operation is requested.
-On `main/preview`, `Humanize` preserves ampersands as separate tokens instead
+On `Humanizer 4`, `Humanize` preserves ampersands as separate tokens instead
of discarding them with other punctuation. For example,
`"Research&Development".Humanize()` returns `"Research & development"`.
Other punctuation such as `+` and `/` is still removed.
### Preserve domain acronym casing
-On `main/preview`, call `Vocabularies.Default.AddAcronym` once during startup
+On `Humanizer 4`, call `Vocabularies.Default.AddAcronym` once during startup
when an acronym has required output casing. Registration matches
case-insensitively, while the registered spelling supplies the output:
registering `iOS` makes `"IOSSettings".Humanize()` return `"iOS settings"`.
@@ -55,7 +55,7 @@ tests that change it isolated.
`Dehumanize` is the direct readable-text-to-PascalCase operation. Use the named inflector transformations when the destination convention matters.
-`Underscore()` lowercases its output. On `main/preview`, pass
+`Underscore()` lowercases its output. On `Humanizer 4`, pass
`preserveCase: true` to retain the input casing while inserting boundaries:
`"HTMLParser".Underscore(preserveCase: true)` returns `"HTML_Parser"`.
@@ -74,7 +74,7 @@ Title and sentence casing use culture-sensitive text operations. Scope culture w
The verified executable compiles and runs against the selected package. Core
transformations also exist in `2.x`, but custom acronym registration,
ampersand preservation, and the case-preserving `Underscore` overload are
-`main/preview` behavior. Humanizer 3 also changes several delimiter and
+`Humanizer 4` behavior. Humanizer 3 also changes several delimiter and
no-letter edge cases. Use the selected snapshot when exact legacy behavior
matters.
diff --git a/website/docs/start/installation.mdx b/website/docs/start/installation.mdx
index 418d29872..7176abede 100644
--- a/website/docs/start/installation.mdx
+++ b/website/docs/start/installation.mdx
@@ -17,17 +17,19 @@ Install the `Humanizer` package in the project that produces user-facing text. S
## Example
-For the initial latest stable release:
+Install Humanizer 4 from NuGet:
```console
-dotnet add package Humanizer --version 3.0.10
+dotnet add package Humanizer --version 4.0.0
```
Then add `using Humanizer;` and run this verified program:
{quickStart}
-The project must target a framework supported by the selected package. Humanizer `3.0.10` ships `net10.0`, `net8.0`, `net48`, and `netstandard2.0` assets. The current checkout also targets `net11.0`.
+The project must target a framework supported by the selected package.
+Humanizer 4 ships `net11.0`, `net10.0`, `net8.0`, `net48`, and
+`netstandard2.0` assets.
## Confirm the reference
@@ -45,7 +47,10 @@ Do not assume that every framework capable of consuming `netstandard2.0` is supp
## Version notes
-The `3.0.10` `Humanizer` package is a metapackage over `Humanizer.Core` and locale packages. The current preview is consolidated into `Humanizer`. See [package selection](./package-selection.md) before replacing package references during an upgrade.
+Humanizer 4 is consolidated into the `Humanizer` package. Releases through
+`3.0.10` use a metapackage over `Humanizer.Core` and locale packages. See
+[package selection](./package-selection.md) before replacing package references
+during an upgrade.
## Related guides and API
diff --git a/website/docs/start/overview.mdx b/website/docs/start/overview.mdx
index ab8e403c2..5d875b048 100644
--- a/website/docs/start/overview.mdx
+++ b/website/docs/start/overview.mdx
@@ -19,7 +19,8 @@ Use Humanizer at display boundaries: view models, messages, reports, logs intend
## Example
-The same small console program is compiled against Humanizer `3.0.10` and the current checkout during documentation validation:
+The same small console program is compiled and run during documentation
+validation for every selected version:
{quickStart}
@@ -35,7 +36,12 @@ It prints `2 minutes`. The culture is explicit, so the result does not change wi
## Documentation and support
-The site covers Humanizer `2.10.1` and later. Published releases receive versioned snapshots; `main/preview` describes the current checkout and is not a stable package. Documentation availability is not a promise that an older release remains under active maintenance. Use the [issue tracker](https://github.com/Humanizr/Humanizer/issues) for confirmed bugs and support questions.
+The site covers Humanizer `2.10.1` and later. Published releases receive
+versioned snapshots, and the next-version documentation describes the upcoming
+Humanizer 4 NuGet package. Documentation availability is not a promise that an
+older release remains under active maintenance. Use the
+[issue tracker](https://github.com/Humanizr/Humanizer/issues) for confirmed bugs
+and support questions.
## Pitfall
diff --git a/website/docs/start/package-selection.md b/website/docs/start/package-selection.md
index d6abb5091..4ffcbb224 100644
--- a/website/docs/start/package-selection.md
+++ b/website/docs/start/package-selection.md
@@ -18,20 +18,20 @@ For ordinary applications, install `Humanizer`. That name is stable even though
| Documentation version | Normal install | Package shape |
| --- | --- | --- |
| `2.10.1` through `3.0.10` | `Humanizer` | Metapackage that brings in `Humanizer.Core` and locale packages |
-| `main/preview` | Build from the checkout | Consolidated `Humanizer` library with generated locale data |
+| `4.0` | `Humanizer` | Consolidated library with runtime, generated locale data, and analyzers |
The API reference for releases through `3.0.10` is generated from `Humanizer.Core`, because that is where the public implementation assembly lives. That does not change the normal install command.
## Example
-Install the selected stable version:
+Install Humanizer from NuGet:
```console
-dotnet add package Humanizer --version 3.0.10
+dotnet add package Humanizer --version 4.0.0
```
Then use the same `Humanizer` namespace regardless of package layout. This
-verified program is compiled against the selected package and current checkout:
+verified program is compiled against the selected package:
{quickStart}
@@ -39,7 +39,9 @@ Choose `Humanizer.Core` directly only when you deliberately want the older relea
## Pitfall
-Do not copy `Humanizer.Core.` references from a historical release into the current preview. Locale packaging is version-specific, and package names are not proof that an API or language surface is present in another release.
+Do not copy `Humanizer.Core.` references from a historical release into
+Humanizer 4. Locale packaging is version-specific, and package names are not
+proof that an API is present in another release.
## Version notes
diff --git a/website/docs/start/quick-start.mdx b/website/docs/start/quick-start.mdx
index 99a5da176..6b7c3e8a6 100644
--- a/website/docs/start/quick-start.mdx
+++ b/website/docs/start/quick-start.mdx
@@ -17,12 +17,12 @@ You will create a console app, install Humanizer, and produce one deterministic
## Example
-Create the project and add the initial latest stable package:
+Create the project and install Humanizer 4 from NuGet:
```console
dotnet new console --name HumanizerQuickStart
cd HumanizerQuickStart
-dotnet add package Humanizer --version 3.0.10
+dotnet add package Humanizer --version 4.0.0
```
Replace `Program.cs` with the verified source:
@@ -51,7 +51,8 @@ Many Humanizer operations use the current culture or UI culture when no culture
## Version notes
-This source is compiled and run against both Humanizer `3.0.10` and the current checkout. Historical snapshots retain the same source and resolve their declared package version.
+This source is compiled and run during documentation validation. Historical
+snapshots retain the same source and resolve their declared package version.
## Related guides and API
diff --git a/website/docs/start/troubleshooting.mdx b/website/docs/start/troubleshooting.mdx
index ad867b474..f5b5f91b3 100644
--- a/website/docs/start/troubleshooting.mdx
+++ b/website/docs/start/troubleshooting.mdx
@@ -34,7 +34,7 @@ Run `dotnet list package --include-transitive`, inspect the target in `project.a
| --- | --- | --- |
| API page or method is missing | Package version, selected docs version, TFM | Switch the site version; check the page’s version notes and selected-version API root. |
| `MissingMethodException` or `TypeLoadException` | All direct/transitive Humanizer versions | Align `Humanizer`, `Humanizer.Core`, and historical locale packages; clean `bin`/`obj`, restore, and rebuild. |
-| Output is English or the wrong regional form | `CurrentCulture`, `CurrentUICulture`, explicit argument, package version | Pass culture directly where possible; otherwise set both ambient cultures at the request/job boundary and check locale capability. |
+| Output is English or the wrong regional form | `CurrentCulture`, `CurrentUICulture`, explicit argument, package version | Pass culture directly where possible; otherwise set both ambient cultures at the request/job boundary. If the culture is listed as supported, the wrong language is a bug. |
| Output differs on Windows, Linux, or macOS | OS, TFM, culture, ICU/NLS mode | Compare .NET globalization data and Humanizer’s generated platform-formatting coverage; avoid asserting platform-owned text without an explicit override. |
| A selected-version parser rejects input | Exact text and culture/provider | Confirm the parser exists in the selected API, use its `Try` form when available, and inspect the first unrecognized token or unit. |
| Enum input throws `NoMatchFoundException` | Enum members/metadata and input | Use an unambiguous label; pass `OnNoMatch.ReturnsNull` when failure is expected. Matching is case-insensitive. |
@@ -45,7 +45,9 @@ Run `dotnet list package --include-transitive`, inspect the target in `project.a
### Reduce culture issues
-Set culture explicitly in a one-file reproduction. If the wrong result persists, compare the selected release’s capability page and note whether the culture resolves directly or through a parent. Include platform and TFM when reporting a locale issue.
+Set culture explicitly in a one-file reproduction. If the wrong result
+persists, confirm that the culture is listed for the selected release and
+report the bug. Include platform and TFM.
### Reduce package and analyzer issues
@@ -59,8 +61,7 @@ Do not “fix” a localized output difference by persisting the current humaniz
Package layout, target frameworks, analyzer assets, locale implementation, and
API routes all vary across the supported corpus. This page follows the selected
-documentation version; `main/preview` describes an unreleased checkout rather
-than a stable package.
+documentation version.
## Related guides and API
diff --git a/website/docs/upgrading/index.mdx b/website/docs/upgrading/index.mdx
index 874d4312f..4a1591f27 100644
--- a/website/docs/upgrading/index.mdx
+++ b/website/docs/upgrading/index.mdx
@@ -83,10 +83,8 @@ order. This is a compatibility path, not a list of every feature or fix.
introduced Roslyn-versioned analyzer assets that are absent from `3.0.1`;
`3.0.10` then corrected fallback selection for older build hosts.
-The chooser intentionally stops at latest stable `3.0.10`. If you are
-deliberately testing the repository checkout, review
-[3.0.10 to main/preview](./main-preview.mdx) separately. The preview has no
-guessed stable version or installation command.
+For Humanizer 4, continue with the
+[3.0.10 to Humanizer 4](./main-preview.mdx) migration guide.
## Example
@@ -120,6 +118,6 @@ major/minor line.
- [Humanizer 3 migration](./version-3-migration.mdx)
- [Humanizer 3 patch line](./version-3-patch-line.mdx)
-- [Prepare for main/preview](./main-preview.mdx)
+- [Upgrade from 3.0.10 to Humanizer 4](./main-preview.mdx)
- [Analyzer migration and CI](../analyzer/index.mdx)
- [Selected-version API reference](../api/index.md)
diff --git a/website/docs/upgrading/main-preview.mdx b/website/docs/upgrading/main-preview.mdx
index ac15f61c4..37707a9a5 100644
--- a/website/docs/upgrading/main-preview.mdx
+++ b/website/docs/upgrading/main-preview.mdx
@@ -1,83 +1,78 @@
---
id: main-preview
-title: Prepare for main/preview from 3.0.10
+title: Upgrade from 3.0.10 to Humanizer 4
sidebar_position: 5
diataxis: how-to
-persona: developer evaluating the current Humanizer preview
+persona: developer upgrading to Humanizer 4
example: illustrative
---
-# Prepare for main/preview from 3.0.10
+# Upgrade from 3.0.10 to Humanizer 4
## Orientation
-Use this page only when deliberately testing the current repository preview after a clean `3.0.10` build. `3.0.10` remains the latest stable documentation boundary and default upgrade target. The preview is named `main/preview` because no release authority in this repository has assigned it a public `3.5`, `4`, or other stable version.
+Use this page when upgrading an application from Humanizer `3.0.10` to
+Humanizer 4. Start from a clean `3.0.10` build, update the NuGet package, then
+work through each compatibility boundary below.
## Example
-Run every command in this section from the root of the application repository. The commands create a fresh Humanizer checkout in the sibling directory `../Humanizer` and keep the application change isolated:
+Create an application branch, update the package from NuGet, and validate the
+application before changing source:
```console
-git switch -c evaluate-humanizer-main-preview
-git clone https://github.com/Humanizr/Humanizer.git ../Humanizer
-git -C ../Humanizer switch main
-dotnet add reference ../Humanizer/src/Humanizer/Humanizer.csproj
+git switch -c upgrade-humanizer-4
+dotnet add package Humanizer --version 4.0.0
dotnet restore --force-evaluate
dotnet build
dotnet test
```
-Replace `` with the application project path relative to the repository root. This verifies source compatibility without inventing a package version or feed.
-
-To test package layout rather than a project reference, pack the same checkout into an isolated local feed:
-
-```console
-dotnet pack ../Humanizer/src/Humanizer/Humanizer.csproj \
- --configuration Release \
- --output ../humanizer-preview-feed
-dotnet remove reference ../Humanizer/src/Humanizer/Humanizer.csproj
-dotnet add package Humanizer \
- --prerelease \
- --source ../humanizer-preview-feed
-dotnet restore \
- --source ../humanizer-preview-feed \
- --source https://api.nuget.org/v3/index.json
-dotnet build
-dotnet test
-```
-
-The local feed contains the version recorded in the produced `Humanizer..nupkg`; `--prerelease` selects it without guessing that version. NuGet.org remains available only for dependencies. The repository AOT verifier follows this pattern and proves the restored archive is the one it just packed. Once a public preview is announced, replace this local path with the official version and feed coordinates.
+Replace `` with the application project path relative to
+the repository root.
### Consolidate package references
-Published releases through `3.0.10` use a `Humanizer` metapackage over `Humanizer.Core` and locale packages. The current preview consolidates runtime code, generated locale data, and analyzers in `Humanizer`. Remove direct locale-package references only as part of the preview migration and verify the resulting asset graph.
+Published releases through `3.0.10` use a `Humanizer` metapackage over
+`Humanizer.Core` and locale packages. Humanizer 4 consolidates runtime code,
+generated locale data, and analyzers in `Humanizer`. Remove direct
+locale-package references as part of the migration and verify the resulting
+asset graph.
-Supporting locale assemblies do not need new explicit references in the preview. Culture selection remains a runtime `CultureInfo` concern.
+Supporting locale assemblies do not need new explicit references. Culture
+selection remains a runtime `CultureInfo` concern.
### Widen number-word parser results
-`ToNumber` and `TryToNumber` return `int` through `3.0.10` and `long` on current. Use `var` when the wider result is acceptable. When the application contract remains 32-bit, use a checked conversion:
+`ToNumber` and `TryToNumber` return `int` through `3.0.10` and `long` in
+Humanizer 4. Use `var` when the wider result is acceptable. When the
+application contract remains 32-bit, use a checked conversion:
```csharp
var parsed = "one hundred".ToNumber(culture);
var legacyValue = checked((int)parsed);
```
-The current analyzer can offer that checked conversion for recognized assignments failing with `CS0029` or `CS0266`. It is separate from `HUMANIZER001` and is not present in `3.0.10`.
+The Humanizer 4 analyzer can offer that checked conversion for recognized
+assignments failing with `CS0029` or `CS0266`. It is separate from
+`HUMANIZER001` and is not present in `3.0.10`.
### Replace removed resource internals
-`Resources` and `ResourceKeys` remain public in `3.0.10` and are removed on current. Replace direct resource-key access with typed Humanizer APIs. Custom formatters and converters should implement the typed public interfaces rather than depending on resource names.
+`Resources` and `ResourceKeys` remain public in `3.0.10` and are removed in
+Humanizer 4. Replace direct resource-key access with typed Humanizer APIs.
+Custom formatters and converters should implement the typed public interfaces
+rather than depending on resource names.
### Recheck ampersands in humanized strings
-Current preserves `&` as a separate token when humanizing PascalCase or
+Humanizer 4 preserves `&` as a separate token when humanizing PascalCase or
camelCase text. `3.0.10` discarded it with other punctuation, so exact-output
tests and persisted display labels can change:
```csharp
// 3.0.10: "Research development"
-// main/preview: "Research & development"
+// Humanizer 4: "Research & development"
var label = "Research&Development".Humanize();
```
@@ -86,19 +81,24 @@ that contains ampersands before adopting the preview.
### Recheck frameworks and localization
-`3.0.10` ships `netstandard2.0`, `net48`, `net8.0`, and `net10.0` assets. Current also targets `net11.0`. The preview generates locale registries and formatting data from checked-in YAML instead of shipping the historical locale-assembly implementation. This is an implementation migration, so compare exact localized output your application tests or displays.
+`3.0.10` ships `netstandard2.0`, `net48`, `net8.0`, and `net10.0` assets.
+Humanizer 4 also targets `net11.0`. It generates locale registries and
+formatting data from reviewed locale definitions instead of shipping the
+historical locale-assembly implementation.
## Pitfall
-Do not infer a stable version number, installation command, or support promise from the current branch. Do not retain mixed preview and stable Humanizer assemblies. Package consolidation can make a restore appear simpler while stale lock files or direct `Humanizer.Core` references still select the old graph.
+Do not retain mixed Humanizer 3 and Humanizer 4 assemblies. Package
+consolidation can make a restore appear simpler while stale lock files or
+direct `Humanizer.Core` references still select the old graph.
## Version notes
-This page is current-only. The stable upgrade chooser intentionally ends at `3.0.10`; preview notes are linked separately. Before a stable release, reconcile this page with the assigned version, published packages, final target frameworks, analyzer contents, and release notes.
+This page is specific to the Humanizer 4 package.
## Related guides and API
-- [Plan a stable upgrade](./index.mdx)
+- [Plan an upgrade](./index.mdx)
- [Package selection](../start/package-selection.md)
- [Troubleshooting](../start/troubleshooting.mdx)
- [Selected-version API reference](../api/index.md)
diff --git a/website/docs/upgrading/version-3-migration.mdx b/website/docs/upgrading/version-3-migration.mdx
index 238a922c5..082cca54b 100644
--- a/website/docs/upgrading/version-3-migration.mdx
+++ b/website/docs/upgrading/version-3-migration.mdx
@@ -44,7 +44,7 @@ qualified names.
| Boolean `ToMetric` overloads | Pass `MetricNumeralFormats`. |
| `EnglishArticles` | Replace the enum-dependent logic in the application. |
| `Configurator.EnumDescriptionPropertyLocator` | Call `Configurator.UseEnumDescriptionPropertyLocator(...)` during startup, before enum humanization. |
-| `Resources` or `ResourceKeys` | These remain in stable Humanizer 3 releases through `3.0.10`; their removal is a future `main/preview` migration boundary. |
+| `Resources` or `ResourceKeys` | These remain in stable Humanizer 3 releases through `3.0.10`; their removal is a future `Humanizer 4` migration boundary. |
| String-keyed `DefaultFormatter` hooks | Use typed `IFormatter`/`DefaultFormatter` members. |
The string `ToQuantity(int, ...)` overloads were absent in early Humanizer 3
diff --git a/website/humanizer-versions.json b/website/humanizer-versions.json
index 9116e08c8..8831a715a 100644
--- a/website/humanizer-versions.json
+++ b/website/humanizer-versions.json
@@ -174,7 +174,7 @@
},
{
"version": "current",
- "label": "main/preview",
+ "label": "4.0 (next)",
"source": {
"kind": "checkout",
"tag": "main"
diff --git a/website/src/css/custom.css b/website/src/css/custom.css
index db004fb13..ac410cd0b 100644
--- a/website/src/css/custom.css
+++ b/website/src/css/custom.css
@@ -299,6 +299,20 @@ body:has(.navbar-sidebar--show) .humanizerAllVersionsShell {
z-index: 3;
}
+.supportedCultureList {
+ display: grid;
+ gap: 0.5rem 1rem;
+ grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
+ list-style: none;
+ margin: 1.5rem 0;
+ padding: 0;
+}
+
+.supportedCultureList li {
+ border-bottom: 1px solid var(--humanizer-rule);
+ padding-block: 0.4rem;
+}
+
.theme-doc-markdown :is(code, a) {
overflow-wrap: anywhere;
}
diff --git a/website/src/pages/index.module.css b/website/src/pages/index.module.css
index ae5f69331..f66f067fa 100644
--- a/website/src/pages/index.module.css
+++ b/website/src/pages/index.module.css
@@ -1,339 +1,326 @@
.home {
+ --home-gutter: clamp(1rem, 4vw, 4rem);
+
overflow: hidden;
}
-.homeHero,
-.homeStart,
-.homeScenarios,
-.homeRoutes,
-.homeIndex {
+.hero,
+.tasks,
+.languagePromise,
+.reference {
margin-inline: auto;
- max-width: 82rem;
- width: min(100% - 2rem, 82rem);
+ max-width: 90rem;
+ width: min(100% - (2 * var(--home-gutter)), 90rem);
}
-.homeHero {
+.hero {
display: grid;
- gap: 3rem;
- padding-block: clamp(4rem, 12vw, 9rem);
+ gap: clamp(3rem, 7vw, 7rem);
+ min-height: min(52rem, calc(100svh - var(--ifm-navbar-height)));
+ padding-block: clamp(3.5rem, 8vw, 7.5rem);
}
-.homeHero__logo {
- background: #fff;
- box-sizing: content-box;
- height: clamp(5.5rem, 12vw, 7.25rem);
- margin-bottom: 1.5rem;
- padding: 0.35rem;
- width: auto;
+.heroCopy {
+ align-self: center;
}
-.homeEyebrow {
- color: var(--humanizer-accent-warm);
- font-size: 0.78rem;
- font-weight: 800;
- letter-spacing: 0.16em;
- margin-bottom: 1rem;
- text-transform: uppercase;
+.identity {
+ align-items: center;
+ color: var(--humanizer-ink-muted);
+ display: flex;
+ font-size: 0.9rem;
+ font-weight: 750;
+ gap: 0.85rem;
+ margin-bottom: clamp(2rem, 5vw, 4rem);
}
-.homeHero h1 {
- font-size: clamp(3.4rem, 16vw, 8.5rem);
- font-weight: 500;
- line-height: 0.88;
- margin: 0;
- max-width: 9ch;
+.logo {
+ background: #fff;
+ box-sizing: content-box;
+ height: 3.5rem;
+ padding: 0.2rem;
+ width: 3.5rem;
}
-.homeHero h1 span {
- color: var(--humanizer-accent);
- display: block;
- font-style: italic;
+.hero h1 {
+ font-family: var(--humanizer-body);
+ font-size: clamp(3rem, 8vw, 6rem);
+ font-weight: 760;
+ letter-spacing: -0.04em;
+ line-height: 0.98;
+ margin: 0;
+ max-width: 12ch;
+ text-wrap: balance;
}
-.homeHero__lede {
+.lede {
color: var(--humanizer-ink-muted);
- font-size: clamp(1.05rem, 3vw, 1.35rem);
+ font-size: clamp(1.1rem, 2vw, 1.35rem);
margin-block: 2rem;
- max-width: 45rem;
+ max-width: 42rem;
}
-.homeHero__actions {
+.heroActions,
+.referenceLinks {
align-items: center;
display: flex;
flex-wrap: wrap;
gap: 1rem 1.5rem;
}
-.homeSpecimen {
- align-self: end;
- background: #172026;
- border-left: 0.35rem solid var(--humanizer-accent-warm);
- color: #edf3f2;
- min-width: 0;
- padding: 1.25rem;
+.textLink {
+ color: var(--humanizer-ink);
+ font-weight: 750;
+ text-decoration-color: var(--humanizer-accent);
+ text-decoration-thickness: 0.12em;
+ text-underline-offset: 0.3em;
}
-.homeSpecimen__label {
- align-items: center;
- border-bottom: 1px solid color-mix(in srgb, currentColor 25%, transparent);
- display: flex;
- font-family: var(--humanizer-body);
- font-size: 0.75rem;
- justify-content: space-between;
- letter-spacing: 0.08em;
- padding-bottom: 0.75rem;
- text-transform: uppercase;
+.textLink:hover {
+ color: var(--humanizer-accent);
}
-.home pre {
- overflow-x: auto;
- overscroll-behavior-inline: contain;
+.install {
+ border-block: 1px solid var(--humanizer-rule);
+ display: grid;
+ gap: 0.4rem;
+ margin-top: clamp(2.5rem, 6vw, 4rem);
+ padding-block: 1rem;
}
-.homeSpecimen pre {
- background: transparent;
- border: 0;
- font-size: clamp(0.76rem, 3.4vw, 1rem);
- margin: 0;
- padding: 2rem 0 0;
+.install span {
+ color: var(--humanizer-ink-muted);
+ font-size: 0.78rem;
+ font-weight: 750;
}
-.homeSpecimen code {
+.install code {
background: transparent;
- color: inherit;
+ color: var(--humanizer-ink);
+ font-size: clamp(0.85rem, 2vw, 1rem);
+ padding: 0;
}
-.homeCode__comment {
- color: #8ca0a4;
+.proof {
+ align-self: center;
+ background: #172026;
+ border-collapse: collapse;
+ color: #edf3f2;
+ min-width: 0;
+ padding: clamp(1rem, 3vw, 2rem);
+ table-layout: fixed;
+ width: 100%;
}
-.homeCode__string {
- color: #91d9e8;
+.proofCaption {
+ clip-path: inset(50%);
+ height: 1px;
+ overflow: hidden;
+ position: absolute;
+ white-space: nowrap;
+ width: 1px;
+}
+
+.proof th {
+ border-bottom: 1px solid color-mix(in srgb, currentcolor 25%, transparent);
+ color: color-mix(in srgb, currentcolor 72%, transparent);
+ font-size: 0.72rem;
+ font-weight: 750;
+ letter-spacing: 0.06em;
+ padding: clamp(1rem, 3vw, 2rem) clamp(0.4rem, 1.5vw, 1rem) 0.8rem;
+ text-align: left;
+ text-transform: uppercase;
}
-.homeCode__keyword {
- color: #f28b73;
+.proof th:first-child,
+.proof td:first-child {
+ padding-left: clamp(1rem, 3vw, 2rem);
}
-.homeCode__result {
- color: #f1c97a;
+.proof th:last-child,
+.proof td:last-child {
+ padding-right: clamp(1rem, 3vw, 2rem);
+ width: 38%;
}
-.homeIndex {
- border-block: 1px solid var(--humanizer-rule);
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
+.proof th:nth-child(2),
+.proof td:nth-child(2) {
+ text-align: center;
+ width: 2rem;
}
-.homeIndex a {
- align-items: center;
- color: var(--humanizer-ink);
- display: flex;
- font-size: 0.88rem;
- font-weight: 700;
- min-height: 3.25rem;
- padding-inline: 0.75rem;
+.proofRow {
+ border-bottom: 1px solid color-mix(in srgb, currentcolor 18%, transparent);
}
-.homeIndex a:hover {
- background: var(--humanizer-paper-raised);
- color: var(--humanizer-accent);
+.proofRow:last-child {
+ border-bottom: 0;
}
-.homeStart,
-.homeScenarios {
- padding-block: clamp(5rem, 12vw, 9rem);
+.proofRow td {
+ padding: clamp(1.2rem, 4vw, 2rem) clamp(0.4rem, 1.5vw, 1rem);
+ vertical-align: top;
}
-.homeSectionIntro {
- margin-bottom: clamp(2.5rem, 7vw, 5rem);
- max-width: 54rem;
+.proofRow code,
+.proofRow samp {
+ background: transparent;
+ color: inherit;
+ font-size: clamp(0.76rem, 2.4vw, 0.95rem);
+ overflow-wrap: anywhere;
+ padding: 0;
+ white-space: normal;
}
-.homeSectionIntro h2,
-.homeRoutes h2 {
- font-size: clamp(2.4rem, 8vw, 5.75rem);
- font-weight: 500;
- line-height: 0.98;
+.proofRow td:nth-child(2) {
+ color: var(--humanizer-accent-warm);
}
-.homeSectionIntro > p:last-child,
-.homeRoutes article > p:not(:global(.humanizerKicker)) {
- color: var(--humanizer-ink-muted);
- font-size: 1.05rem;
- max-width: 42rem;
+.proofRow samp {
+ color: #91d9e8;
}
-.homeStart__steps {
+.tasks {
border-top: 1px solid var(--humanizer-rule);
-}
-
-.homeStart__steps article {
- border-bottom: 1px solid var(--humanizer-rule);
display: grid;
- gap: 1rem;
- grid-template-columns: 2.5rem minmax(0, 1fr);
- padding-block: 2rem;
+ gap: clamp(2.5rem, 7vw, 6rem);
+ padding-block: clamp(5rem, 10vw, 9rem);
}
-.homeStart__steps h3,
-.homeScenarioList h3 {
- font-size: clamp(1.5rem, 5vw, 2.3rem);
- margin-bottom: 0.35rem;
-}
-
-.homeStepNumber,
-.homeScenarioList__number {
- color: var(--humanizer-accent-warm);
- font-size: 0.78rem;
- font-weight: 800;
- letter-spacing: 0.1em;
-}
-
-.homeStart__steps pre {
- background: #172026;
- border-radius: 0;
- color: #edf3f2;
- font-size: 0.78rem;
- margin-top: 1.25rem;
- max-width: 100%;
- padding: 1.25rem;
-}
-
-.homeExampleLabel {
+.sectionLead > p,
+.languagePromise > p,
+.reference > div:first-child > p {
color: var(--humanizer-accent-warm);
- display: block;
font-size: 0.78rem;
font-weight: 800;
- letter-spacing: 0.1em;
- margin-top: 1.25rem;
- text-transform: uppercase;
+ margin-bottom: 1rem;
}
-.homeScenarios {
- border-top: 1px solid var(--humanizer-rule);
+.sectionLead h2,
+.languagePromise h2,
+.reference h2 {
+ font-family: var(--humanizer-body);
+ font-size: clamp(2.3rem, 6vw, 4.75rem);
+ font-weight: 720;
+ letter-spacing: -0.04em;
+ line-height: 1;
+ margin: 0;
+ max-width: 13ch;
+ text-wrap: balance;
}
-.homeScenarioList {
+.taskList {
border-top: 1px solid var(--humanizer-rule);
}
-.homeScenarioList article {
- align-items: start;
+.task {
+ align-items: center;
border-bottom: 1px solid var(--humanizer-rule);
+ color: var(--humanizer-ink);
display: grid;
gap: 1rem;
- grid-template-columns: 2.5rem minmax(0, 1fr);
- padding-block: 1.75rem;
+ grid-template-columns: minmax(0, 1fr) auto;
+ padding-block: 1.5rem;
}
-.homeScenarioList p {
- color: var(--humanizer-ink-muted);
- margin: 0;
+.task:hover {
+ color: var(--humanizer-accent);
+ text-decoration: none;
}
-.homeScenarioList code {
- background: var(--humanizer-paper-raised);
- border: 1px solid var(--humanizer-rule);
- color: var(--humanizer-accent);
- font-size: 0.78rem;
- grid-column: 2;
- justify-self: start;
- max-width: 100%;
- overflow-wrap: anywhere;
- padding: 0.5rem 0.65rem;
+.task h3 {
+ font-family: var(--humanizer-body);
+ font-size: clamp(1.4rem, 3vw, 2rem);
+ margin: 0;
}
-.homeRoutes {
- border-top: 1px solid var(--humanizer-rule);
- display: grid;
- padding-bottom: clamp(5rem, 12vw, 9rem);
+.task p {
+ color: var(--humanizer-ink-muted);
+ margin: 0.2rem 0 0;
}
-.homeRoutes article {
- border-bottom: 1px solid var(--humanizer-rule);
- padding-block: 3rem;
+.task code {
+ background: transparent;
+ color: var(--humanizer-accent);
+ display: none;
+ padding: 0;
}
-.homeRoutes h2 {
- font-size: clamp(2rem, 7vw, 3.6rem);
+.task > span {
+ font-size: 1.25rem;
}
-@media (min-width: 48rem) {
- .homeHero {
- grid-template-columns: minmax(0, 1.35fr) minmax(18rem, 0.65fr);
- }
+.languagePromise {
+ background: #045b6e;
+ color: #fff;
+ display: grid;
+ gap: 2rem;
+ padding: clamp(2rem, 5vw, 4rem);
+}
- .homeIndex {
- grid-template-columns: repeat(6, minmax(0, 1fr));
- }
+.languagePromise > p {
+ color: #d7f3f8;
+ margin: 0;
+}
- .homeIndex a {
- border-right: 1px solid var(--humanizer-rule);
- justify-content: center;
- }
+.languagePromise h2 {
+ color: #fff;
+}
- .homeIndex a:last-child {
- border-right: 0;
- }
+.languagePromise div > p {
+ color: #e5f6f9;
+ font-size: 1.1rem;
+ margin: 1rem 0 0;
+ max-width: 44rem;
+}
- .homeStart__steps article {
- gap: 2rem;
- grid-template-columns: 4rem minmax(0, 1fr);
- }
+.languagePromise .textLink {
+ align-self: end;
+ color: #fff;
+ text-decoration-color: #fff;
+}
- .homeStart__steps article > div {
- display: grid;
- gap: 0 2rem;
- grid-template-columns: minmax(12rem, 0.6fr) minmax(18rem, 1.4fr);
- }
+.reference {
+ display: grid;
+ gap: 2rem;
+ padding-block: clamp(5rem, 10vw, 9rem);
+}
- .homeStart__steps h3 {
- grid-column: 1;
- }
+.reference > p {
+ color: var(--humanizer-ink-muted);
+ font-size: 1.1rem;
+ margin: 0;
+ max-width: 42rem;
+}
- .homeStart__steps p,
- .homeStart__steps pre,
- .homeStart__steps :global(.humanizerTextLink),
- .homeExampleLabel {
- grid-column: 2;
+@media (min-width: 48rem) {
+ .hero {
+ grid-template-columns: minmax(0, 1.08fr) minmax(24rem, 0.92fr);
}
- .homeStart__steps p {
- grid-row: 1 / span 2;
+ .tasks {
+ grid-template-columns: minmax(17rem, 0.65fr) minmax(0, 1.35fr);
}
- .homeScenarioList article {
- align-items: center;
+ .task {
gap: 2rem;
- grid-template-columns: 4rem minmax(0, 1fr) minmax(10rem, auto);
+ grid-template-columns: minmax(12rem, 0.7fr) minmax(16rem, 1fr) auto;
}
- .homeScenarioList code {
- grid-column: 3;
- justify-self: end;
+ .task code {
+ display: block;
}
- .homeRoutes {
- grid-template-columns: repeat(3, minmax(0, 1fr));
+ .languagePromise {
+ grid-template-columns: 0.35fr minmax(0, 1.2fr) auto;
}
- .homeRoutes article {
- border-right: 1px solid var(--humanizer-rule);
- padding: 3rem 2rem;
+ .reference {
+ align-items: end;
+ grid-template-columns: minmax(16rem, 0.9fr) minmax(18rem, 1.1fr);
}
- .homeRoutes article:last-child {
- border-right: 0;
- }
-}
-
-@media (min-width: 64rem) {
- .homeHero,
- .homeStart,
- .homeScenarios,
- .homeRoutes,
- .homeIndex {
- width: min(100% - 4rem, 82rem);
+ .referenceLinks {
+ grid-column: 2;
}
}
diff --git a/website/src/pages/index.tsx b/website/src/pages/index.tsx
index 4239954a8..fc3bc49fb 100644
--- a/website/src/pages/index.tsx
+++ b/website/src/pages/index.tsx
@@ -1,226 +1,198 @@
+/*
+ * THESIS: Humanizer is a small, direct transformation from program values to
+ * readable language.
+ * OWN-WORLD: A working proof sheet pairs real C# input with human output.
+ * STORY: Understand the library, install it, choose the value you have, then
+ * follow the version-correct guide.
+ * FIRST VIEWPORT: Canonical identity, promise, install command, first call, and
+ * output are all visible without scrolling on a typical laptop.
+ * FORM: One typographic sheet with ruled transformation rows; no card grid,
+ * decorative metrics, or invented product imagery.
+ */
import Link from '@docusaurus/Link';
+import {
+ useLatestVersion,
+ useVersions,
+} from '@docusaurus/plugin-content-docs/client';
import useBaseUrl from '@docusaurus/useBaseUrl';
import Layout from '@theme/Layout';
+import {useEffect, useState} from 'react';
import styles from './index.module.css';
-const scenarioGroups = [
+const transformations = [
{
- number: '01',
- title: 'Strings & casing',
- description:
- 'Humanize identifiers, transform casing, and reverse display text.',
- sample: '"PascalCaseInput".Humanize()',
+ input: '"PascalCaseInput".Humanize()',
+ output: '"Pascal case input"',
+ },
+ {
+ input: '"person".Pluralize()',
+ output: '"people"',
+ },
+ {
+ input: '42.ToWords(CultureInfo.GetCultureInfo("en"))',
+ output: '"forty-two"',
+ },
+ {
+ input: '"person".ToQuantity(2)',
+ output: '"2 people"',
+ },
+];
+
+const taskRoutes = [
+ {
+ title: 'Strings',
+ prompt: 'Identifiers, casing, pluralization',
+ sample: '.Humanize() .Pluralize() .Singularize()',
+ to: 'scenarios/strings-and-casing/',
},
{
- number: '02',
title: 'Dates & time',
- description: 'Describe moments, durations, ages, and calendar values naturally.',
- sample: 'date.Humanize()',
+ prompt: 'Relative time, durations, ages, clocks',
+ sample: '.Humanize() .ToAge() .ToClockNotation()',
+ to: 'scenarios/dates-times-durations-and-age/',
},
{
- number: '03',
- title: 'Numbers & quantities',
- description: 'Write numbers, ordinals, byte sizes, and quantities for people.',
- sample: '42.ToWords()',
+ title: 'Numbers',
+ prompt: 'Words, ordinals, quantities, byte sizes',
+ sample: '.ToWords() .Ordinalize() .Bytes()',
+ to: 'scenarios/numbers-words-ordinals-roman-bytes-and-quantities/',
},
{
- number: '04',
- title: 'Enums & collections',
- description: 'Turn application values and lists into readable interface copy.',
- sample: 'items.Humanize()',
+ title: 'Application values',
+ prompt: 'Enums, flags, collections',
+ sample: '.Humanize() .DehumanizeTo()',
+ to: 'scenarios/enums-and-collections/',
},
];
export default function Home(): React.JSX.Element {
+ const versions = useVersions(undefined);
+ const latestVersion = useLatestVersion(undefined);
+ const [docsVersion, setDocsVersion] = useState(latestVersion);
+
+ useEffect(() => {
+ const preferredVersionName = localStorage.getItem(
+ 'docs-preferred-version-default',
+ );
+ setDocsVersion(
+ versions.find(({name}) => name === preferredVersionName) ?? latestVersion,
+ );
+ }, [latestVersion, versions]);
+
+ const docsPath = (path: string) => `${docsVersion.path}/${path}`;
const logoUrl = useBaseUrl('/img/logo.png');
return (
+ description="Turn .NET strings, dates, times, numbers, quantities, enums, and collections into text people can read.">
-
-
-
-
Humanizer for .NET
-
- Make software
- sound like people.
-
-
- A mature .NET library for turning dates, times, numbers,
- quantities, strings, enums, and collections into language people
- understand.
+
+
+
+
+ Humanizer for .NET
+
+
Turn .NET values into words people understand.
+
+ Humanizer adds focused extension methods for readable strings,
+ dates, times, numbers, quantities, enums, and collections—across
+ every supported culture.
-
-
- Start with Humanizer
+
+
+ Run the five-minute example
-
- Browse the API ↗
+
+ Find an API by task →
-
-
-
- Illustrative C#
- input → output
+
+ Install from NuGet
+ dotnet add package Humanizer
-
-
-
- // less machine, more human
-
- {'\n'}
-
- "PascalCaseInput"
-
- .Humanize()
- {'\n'}
-
- → "Pascal case input"
-
-
-
-
-
-
-
-
-
Start
-
From package to useful text in minutes.
-
- Install the package, import the namespace, and call the extension
- that matches the value you want to present.
-
-
-
-
- 01
-
-
Install
-
Add the latest stable Humanizer package to your project.
-
- dotnet add package Humanizer
-
-
-
-
- 02
-
-
Humanize
-
- Use focused extension methods on the values you already have.
-
Move between releases with the boundaries in view.
-
- Use version-specific guidance for package, namespace, analyzer,
- and behavior changes instead of reconstructing history.
-
-
- Open upgrading guidance →
-
-
-
-
Reference
-
Exact signatures, in the version you selected.
+
+
Language support
+
+
Supported means supported.
- Generated API pages live beside the guides, share their version,
- and remain searchable as one documentation set.
+ Humanizer 4 makes one promise: every listed culture supports
+ every applicable localized feature. Missing behavior or English
+ fallback is a bug, not a capability tier.
-
- Browse the API →
+
+
+ See supported cultures →
+
+
+
+
+
+
Version-correct by design
+
Guides and signatures stay together.
+
+
+ Choose your Humanizer version once. The guides, runnable examples,
+ search results, and generated API reference follow that package.
+
+
+
+ Open the guides
-
-
-
Languages
-
Culture-aware output, with a direct correction path.
-
- Understand culture selection and locale behavior, then find the
- focused contribution guidance when language output needs work.
-
-
- Explore language support →
+
+ Browse exact signatures →
-
+
diff --git a/website/tests/e2e/accessibility.spec.ts b/website/tests/e2e/accessibility.spec.ts
index a88eff339..a6c1693ba 100644
--- a/website/tests/e2e/accessibility.spec.ts
+++ b/website/tests/e2e/accessibility.spec.ts
@@ -44,23 +44,16 @@ for (const colorScheme of ['light', 'dark'] as const) {
{name: 'desktop', width: 1280, height: 800},
{name: '320px', width: 320, height: 720},
]) {
- test(`supported cultures table is accessible at ${viewport.name} in ${colorScheme} mode`, async ({
+ test(`supported cultures list is accessible at ${viewport.name} in ${colorScheme} mode`, async ({
page,
}) => {
await page.setViewportSize(viewport);
await page.emulateMedia({colorScheme});
await page.goto('/docs/next/languages/supported-cultures/');
- const region = page.getByRole('region', {
- name: 'Current locale capability coverage',
- });
- await expect(region).toBeVisible();
- await expect(region).toHaveAttribute('tabindex', '0');
- await expect(
- region.getByText('Current locale capability coverage', {exact: true}),
- ).toBeVisible();
- await region.focus();
- await expect(region).toBeFocused();
+ const cultures = page.getByRole('list', {name: 'Supported cultures'});
+ await expect(cultures).toBeVisible();
+ await expect(cultures.getByText('en', {exact: true})).toBeVisible();
await expectNoSeriousAccessibilityViolations(page);
});
diff --git a/website/tests/e2e/production.spec.ts b/website/tests/e2e/production.spec.ts
index 25b209718..843854647 100644
--- a/website/tests/e2e/production.spec.ts
+++ b/website/tests/e2e/production.spec.ts
@@ -40,7 +40,9 @@ test('deployed site preserves reader, version, search, and legacy contracts', as
await page.goto('/');
await expect(
- page.getByRole('heading', {name: 'Make software sound like people.'}),
+ page.getByRole('heading', {
+ name: 'Turn .NET values into words people understand.',
+ }),
).toBeVisible();
await expect(page.getByRole('button', {name: 'All versions'})).toBeVisible();
diff --git a/website/tests/e2e/reader-shell.spec.ts b/website/tests/e2e/reader-shell.spec.ts
index 1551815b3..f331d7c89 100644
--- a/website/tests/e2e/reader-shell.spec.ts
+++ b/website/tests/e2e/reader-shell.spec.ts
@@ -25,10 +25,12 @@ test('navigation remains operable and unclipped at reader breakpoints', async ({
);
expect(pageWidth).toBeLessThanOrEqual(width);
await expect(
- page.getByRole('heading', {name: 'Make software sound like people.'}),
+ page.getByRole('heading', {
+ name: 'Turn .NET values into words people understand.',
+ }),
).toBeVisible();
await expect(
- page.getByRole('navigation', {name: 'Documentation sections'}),
+ page.getByRole('table', {name: 'Humanizer input and output examples'}),
).toBeVisible();
}
@@ -40,10 +42,12 @@ test('navigation remains operable and unclipped at reader breakpoints', async ({
await page.getByRole('button', {name: 'Toggle navigation bar'}).click();
const mobileSidebar = page.locator('.navbar-sidebar');
expect((await mobileSidebar.boundingBox())?.height).toBe(800);
- await expect(page.getByRole('link', {name: 'Guides'})).toBeVisible();
+ await expect(
+ page.getByRole('link', {name: 'Guides', exact: true}),
+ ).toBeVisible();
await expect(page.getByText('Versions', {exact: true})).toBeVisible();
await page.getByRole('button', {name: 'Expand the dropdown'}).click();
- await expect(page.getByRole('link', {name: 'main/preview'})).toBeVisible();
+ await expect(page.getByRole('link', {name: '4.0 (next)'})).toBeVisible();
await expect(
mobileSidebar
.getByRole('button', {name: /Switch between dark and light mode/}),
@@ -111,19 +115,19 @@ test('theme follows the system default and remains keyboard switchable', async (
await expect(page.locator('html')).toHaveAttribute('data-theme', 'light');
});
-test('homepage upgrade and language paths open their dedicated guides', async ({
+test('homepage quick-start and language paths open their dedicated guides', async ({
page,
}) => {
for (const destination of [
{
- heading: 'Plan an upgrade',
- link: 'Open upgrading guidance',
- path: '/docs/upgrading/',
+ heading: 'Five-minute quick start',
+ link: 'Run the five-minute example',
+ path: '/docs/start/quick-start/',
},
{
- heading: 'Languages and cultures',
- link: 'Explore language support',
- path: '/docs/languages/',
+ heading: 'Supported cultures',
+ link: 'See supported cultures',
+ path: '/docs/languages/supported-cultures/',
},
]) {
await page.goto('/');
@@ -166,7 +170,7 @@ test('version not found preserves the requested API path and target version', as
await page.goto('/docs/api/Humanizer.Resources/');
await page.locator('.humanizerVersionDropdown').focus();
await page.keyboard.press('Enter');
- await page.getByRole('link', {name: 'main/preview'}).click();
+ await page.getByRole('link', {name: '4.0 (next)'}).click();
await expect(page).toHaveURL(
/\/docs\/next\/api\/Humanizer\.Resources\/$/,
@@ -182,21 +186,32 @@ test('version not found preserves the requested API path and target version', as
page.getByRole('heading', {name: 'Not available in this version.'}),
).toBeVisible();
await expect(
- page.getByText('main/preview', {exact: true}).first(),
+ page.getByText('4.0 (next)', {exact: true}).first(),
).toBeVisible();
await expect(
- page.getByRole('link', {name: 'Browse main/preview docs'}),
+ page.getByRole('link', {name: 'Browse 4.0 (next) docs'}),
).toHaveAttribute('href', '/docs/next/');
await expect(
- page.getByRole('link', {name: 'Open main/preview API'}),
+ page.getByRole('link', {name: 'Open 4.0 (next) API'}),
).toHaveAttribute('href', '/docs/next/api/');
await page.goto('/');
await page.reload();
- await expect(page.getByRole('link', {name: 'Guides'})).toHaveAttribute(
- 'href',
- '/docs/next/',
- );
+ await expect(
+ page.getByRole('link', {name: 'Guides', exact: true}),
+ ).toHaveAttribute('href', '/docs/next/');
+ await expect(
+ page.getByRole('link', {name: 'Run the five-minute example'}),
+ ).toHaveAttribute('href', '/docs/next/start/quick-start/');
+ await expect(
+ page.getByRole('link', {name: 'See supported cultures'}),
+ ).toHaveAttribute('href', '/docs/next/languages/supported-cultures/');
+ await expect(
+ page.getByRole('link', {name: 'Open the guides'}),
+ ).toHaveAttribute('href', '/docs/next/');
+ await expect(
+ page.getByRole('link', {name: 'Browse exact signatures'}),
+ ).toHaveAttribute('href', '/docs/next/api/');
await page.goto('/docs/next/api/Humanizer.ByteSize/');
await page.locator('.humanizerVersionDropdown').focus();
diff --git a/website/tests/versionRoutes.test.mjs b/website/tests/versionRoutes.test.mjs
index 22eba7066..63120b4b3 100644
--- a/website/tests/versionRoutes.test.mjs
+++ b/website/tests/versionRoutes.test.mjs
@@ -9,7 +9,7 @@ import {
const versions = [
{label: '3.0.10 (latest)', name: '3.0.10', path: '/docs'},
{label: '2.10.1', name: '2.10.1', path: '/docs/2.10.1'},
- {label: 'main/preview', name: 'current', path: '/docs/next'},
+ {label: '4.0 (next)', name: 'current', path: '/docs/next'},
];
test('version context prefers the explicit preview route', () => {
@@ -18,7 +18,7 @@ test('version context prefers the explicit preview route', () => {
{
apiRoot: '/docs/next/api/',
docsRoot: '/docs/next/',
- label: 'main/preview',
+ label: '4.0 (next)',
path: '/docs/next',
relativePath: 'api/Missing/',
},
@@ -60,7 +60,7 @@ test('unsupported versions are limited to releases before 2.10.1', () => {
test('unsupported-version policy fails closed without published semver data', () => {
assert.equal(
getUnsupportedVersionContext('/docs/1.0/api/Missing/', [
- {label: 'main/preview', name: 'current', path: '/docs/next'},
+ {label: '4.0 (next)', name: 'current', path: '/docs/next'},
]),
undefined,
);
@@ -70,7 +70,7 @@ test('version switch preserves a path only when Docusaurus fell back to a root',
const items = [
{label: '3.0.10', to: '/docs/api/Humanizer.ByteSize/'},
{
- label: 'main/preview',
+ label: '4.0 (next)',
onClick: 'save preferred version',
to: '/docs/next?query=bytes#members',
},
@@ -86,7 +86,7 @@ test('version switch preserves a path only when Docusaurus fell back to a root',
{label: '3.0.10', to: '/docs/api/Humanizer.ByteSize/'},
{
'data-noBrokenLinkCheck': true,
- label: 'main/preview',
+ label: '4.0 (next)',
onClick: 'save preferred version',
to: '/docs/next/api/Humanizer.ByteSize/?query=bytes#members',
},
@@ -97,10 +97,10 @@ test('version switch preserves a path only when Docusaurus fell back to a root',
test('version switch leaves a version root unchanged from a version root', () => {
assert.deepEqual(
preserveUnavailableVersionPath(
- [{label: 'main/preview', to: '/docs/next/'}],
+ [{label: '4.0 (next)', to: '/docs/next/'}],
'/docs/',
versions,
),
- [{label: 'main/preview', to: '/docs/next/'}],
+ [{label: '4.0 (next)', to: '/docs/next/'}],
);
});