Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/content/docs/changelog/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Notable changes to the kit, newest first.

## 2026-11-03

- **Frontends: i18n follow-ups (fix).** The admin language switcher now saves with `If-Match`, so a profile edit made in between is no longer silently overwritten (it retries once on `412`). The dashboard container now honours `FSH_DEFAULT_LANGUAGE` like the admin one; before, a containerized dashboard always started in `en-US`. `deploy/docker` and Terraform pass the setting to both apps. The dashboard no longer matches the English text of the deactivated-tenant error, because the API always sends its `code` now. See [Internationalization](/docs/cross-cutting-concerns/internationalization/#per-deployment-default) and [#1459](https://github.com/fullstackhero/dotnet-starter-kit/pull/1459).
- **Identity: a role can no longer hold the same permission twice (fix, migration).** `identity.RoleClaims` had no uniqueness, so two API instances starting at once after a release that adds a permission, or the tenant seeder overlapping the startup sync, could both insert the same grant and leave a duplicate row behind with nothing in the logs. Authorization was not affected, but the table drifted. The new `AddRoleClaimsUniqueIndex` Identity migration first deletes existing duplicates (keeping the lowest `Id` per `(RoleId, ClaimType, ClaimValue)`) and then adds a unique index on those columns, which replaces the `RoleId`-only index. `RolePermissionSyncer` and `IdentityDbInitializer` treat a unique violation on that index as another writer having inserted the grant first and log it at Information instead of failing, and `PUT /api/v1/identity/{id}/permissions` collapses a permission repeated in the request. Run the DbMigrator `apply` as usual; claims with a `NULL` value are not affected. See [Authorization](/docs/security/authorization/#the-role-permission-syncer), [#1435](https://github.com/fullstackhero/dotnet-starter-kit/issues/1435) and [#1458](https://github.com/fullstackhero/dotnet-starter-kit/pull/1458).
- **Admin & dashboard: sign-in now asks for the authenticator code when two-factor is on (fix).** Both apps let a user enable two-factor authentication under Settings > Security, but neither login page could send the code, so the next sign-in failed with `401 two_factor_required` and locked the user out. When `POST /api/v1/identity/token/issue` answers with the ProblemDetails `code` `Identity.TwoFactorRequired`, the login card now switches to a code step (six digits, `autocomplete="one-time-code"` so the browser or password manager can fill it), then repeats the call with `twoFactorCode`. A wrong code (`Identity.TwoFactorInvalid`) shows an inline error and keeps the user on the step; "Back to sign in" returns to the password step. The password is held only in the login page's memory for the retry, never in browser storage, and is dropped on back, success or leaving the page. The clients branch on the `code` extension, not the localized `detail` text. Recovery codes are still not supported. See [Two-factor authentication](/docs/security/two-factor/#signing-in-from-the-react-apps) and [#1446](https://github.com/fullstackhero/dotnet-starter-kit/issues/1446).
- **Eventing: one failing integration-event handler no longer blocks the others (fix).** `InMemoryEventBus` ran an event's handlers in order and stopped at the first exception, so the rest waited for the outbox retry, and if the failing one kept failing until the row dead-lettered they never ran. For `FileStorageKeyChangedIntegrationEvent` that meant a store rejecting Files' superseded-object delete also skipped Catalog's product-image URL rewrite. The bus now runs every handler, then rethrows (the single exception, or an `AggregateException` when several fail) so the outbox still retries; the inbox already records each handler that succeeded, so a retry re-runs only the failed ones. See [Eventing](/docs/building-blocks/eventing/#event-bus-implementations) and [#1423](https://github.com/fullstackhero/dotnet-starter-kit/issues/1423).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Internationalization (i18n)
lastUpdated: 2026-10-02
lastUpdated: 2026-11-03
description: End-to-end localization across the backend and both React apps - en-US as default and fallback, pt-BR included, with per-user language, IStringLocalizer, RequestLocalization, and react-i18next.
sidebar:
label: Internationalization
Expand Down Expand Up @@ -157,7 +157,7 @@ Dates, numbers, and currency are formatted in the browser with `Intl` under the

### Per-deployment default

The default language for a deployment is set at runtime through the app's `config.json`, via the `defaultLanguage` key - consistent with how the front-ends take the rest of their environment configuration at runtime rather than from `VITE_*` build-time variables.
The default language for a deployment is set at runtime through the app's `config.json`, via the `defaultLanguage` key - consistent with how the front-ends take the rest of their environment configuration at runtime rather than from `VITE_*` build-time variables. In the container images of both apps, set `FSH_DEFAULT_LANGUAGE` (for example `pt-BR`); leaving it unset gives `en-US`. `deploy/docker` passes it through from `.env`, and Terraform sets it on both static sites from `frontend_default_language`.

## Adding a language

Expand Down