Humanizr/Humanizer · critical · InvalidOperationException
Locale '{localeCode}.surfaces.calendar.hijriMonths' must not
Error message
Locale '{localeCode}.surfaces.calendar.hijriMonths' must not contain directionality controls (U+200E, U+200F, U+061C). What it means
Thrown by AddCalendarFeatures when any Hijri month string contains a Unicode bidirectional-formatting control character: U+200E (LRM), U+200F (RLM), or U+061C (ALM). These invisible marks corrupt mixed-direction rendering and are easy to paste accidentally from RTL sources, so the canonical schema bans them at build time.
Source
Thrown at src/Humanizer.SourceGenerators/Common/CanonicalLocaleAuthoring.cs:512
if (calendarSurface.TryGetValue("hijriMonths", out var hijriMonthsValue))
{
if (hijriMonthsValue is not SimpleYamlSequence hijriSeq || hijriSeq.Items.Length != 12)
{
throw new InvalidOperationException(
$"Locale '{localeCode}.surfaces.calendar.hijriMonths' must be a sequence of exactly 12 strings.");
}
foreach (var item in hijriSeq.Items)
{
if (item is not SimpleYamlScalar scalar)
{
throw new InvalidOperationException(
$"Locale '{localeCode}.surfaces.calendar.hijriMonths' items must be scalar strings.");
}
if (scalar.Value.IndexOfAny(['\u200E', '\u200F', '\u061C']) >= 0)
{
throw new InvalidOperationException(
$"Locale '{localeCode}.surfaces.calendar.hijriMonths' must not contain directionality controls (U+200E, U+200F, U+061C).");
}
}
}
features["calendar"] = calendarSurface;
}
static string NormalizeCanonicalText(string value)
{
var normalized = value.Replace("\r\n", "\n");
normalized = normalized.TrimEnd('\n') + "\n";
return normalized;
}
static readonly HashSet<string> ExplicitDefaultEnginePaths =
[
"surfaces.ordinal.numeric",View on GitHub (pinned to ffc2b77c0f)
Solutions
- Strip U+200E, U+200F, and U+061C from every hijriMonths entry (e.g. regex replace or an editor that shows invisible characters).
- Re-type the affected month names from a trusted plain-text source rather than copy-pasting.
- Rebuild to confirm the directionality-control check passes.
Example fix
# before (the string contains an invisible U+200E before 'Muharram') calendar: hijriMonths: ["\u200EMuharram", Safar, ...] # after calendar: hijriMonths: [Muharram, Safar, Rabi al-Awwal, Rabi al-Thani, Jumada al-Awwal, Jumada al-Thani, Rajab, Shaban, Ramadan, Shawwal, Dhu al-Qidah, Dhu al-Hijjah]
Defensive patterns
Strategy: validation
Validate before calling
# Detect directionality controls in hijriMonths before building.
$controls = [char]0x200E, [char]0x200F, [char]0x061C
$yaml = Get-Content src/Humanizer/Locales/<code>.yml -Raw
foreach ($c in $controls) {
if ($yaml.Contains($c)) { Write-Error "Locale contains directionality control U+$('{0:X4}' -f [int]$c)" }
}
# Strip: $yaml = $yaml -replace '[\u200E\u200F\u061C]', '' Prevention
- Enable 'show invisible characters' in your editor when editing RTL month names.
- Re-type Hijri month names from a trusted plain-text source rather than copy-pasting from browsers.
- Run the pre-build control-stripping check above before 'dotnet build'.
When it happens
Trigger: A Hijri month string copied from a web page or another locale contains an embedded U+200E/U+200F/U+061C. The IndexOfAny check at CanonicalLocaleAuthoring.cs:509-513 detects the control and fails the build.
Common situations: Pasting Arabic month names from browsers/documents that wrap them in LRM/RLM marks; copying from a locale file that previously stored the controls; editors that auto-insert directionality marks in RTL paragraphs.
Related errors
- Locale '{localeCode}.surfaces.calendar.hijriMonths' must be
- Locale '{localeCode}.surfaces.calendar.hijriMonths' items mu
- Locale '{localeCode}.surfaces.calendar' defines unsupported
- Locale '{localeCode}.surfaces.calendar.months' must be a seq
- Locale '{localeCode}.surfaces.calendar.months' items must be
AI-assisted analysis of Humanizr/Humanizer@ffc2b77c0f (2026-08-13).
Data as JSON: /api/errors/0c0c4c2f555ea745.
Report an issue: GitHub.