Skip to main content

Localization

:::info Status Outline — expand with a walkthrough of adding a new language and a new localized string to a configuration. :::

The stacked-module chain

LocalizationChain lets multiple localization modules stack, with the most-recently registered module winning per key and falling through to earlier modules for anything it doesn't define. Register your configuration's module with services.AddLocalizationModule<T>().

Ordering rule: services.AddLocalization() must be called before services.AddLocalizationModule(). The composite localizer registration must use Replace, not TryAdd, or it silently loses to the framework default. Both of these bit the platform once historically — there's a regression test (LocalizationChainTests) covering it now.

This site vs. application localization

This documentation site's own multilingual setup (Docusaurus i18n, EN/UK) is a separate concern from the application's runtime localization described above — see the repo root README.md's "Maintaining translations" section for how this site's translation tracking works.

Time zones (adjacent, often confused with localization)

The server stores/emits DateTime in UTC everywhere. Conversion to local time happens on both sides: client-side for display (a registered JsonConverter<DateTime>), server-side for PDF/CSV export (Kandra.Printing's PrintValueFormatter). The client detects its IANA time zone natively (TimeZoneInfo.Local + TryConvertWindowsIdToIanaId, no JS interop needed on .NET 6+ WASM) and sends it as an X-Time-Zone header on every request.

Two gotchas worth knowing:

  • A [Format]-less DateTime column can silently skip conversion — this shipped as a real bug once, worth checking for on any new print-form date column.
  • For a calendar-day identity field (not an instant), use DateOnly, not DateTime — a DTO field that's really "just a date" will otherwise get silently shifted a day by the client's UtcToLocalDateTimeJsonConverter when the local-midnight pick crosses a UTC day boundary. For an effective-instant field where every reader's local "today" genuinely can't agree (e.g. "constant becomes effective at..."), be precise about the editing user's own offset and show both UTC and local in the preview — never add a per-reader timezone selector to try to make everyone see the same instant as "their" midnight, because no instant is everyone's midnight at once.

See also

  • Platform doc: src/KandraCore/CLAUDE.md's "Time zone handling" section (full design + the historical bug).