feat(localization): module catalogs and localized messages (2/4) - #1361
Open
marcelo-maciel wants to merge 1 commit into
Open
feat(localization): module catalogs and localized messages (2/4)#1361marcelo-maciel wants to merge 1 commit into
marcelo-maciel wants to merge 1 commit into
Conversation
Module slice of the i18n work (split of fullstackhero#1344 as requested in review). Depends on the framework slice: it uses `SharedResources`, `SupportedCultures` and the `MessageKey` / `MessageArgs` / `ResourceSource` plumbing on `CustomException`, none of which exist on `main` yet. - One `{Module}Resources` catalog per module (en + pt-BR) for Auditing, Billing, Catalog, Chat, Files, Multitenancy, Notifications, Tickets and Webhooks, plus the Identity messages that were not part of the framework slice. - Domain and handler throws carry `MessageKey` and `ResourceSource` so the response body is localized while `Message` stays English for the logs. - Validators resolve their messages through `IStringLocalizer`, with message arguments kept culture-insensitive (`MaxWindowDays` as `int` rather than `MaxWindow.TotalDays` as `double`, which `string.Format` would render with a culture-dependent decimal separator). - `CatalogParityTests` pins key parity and placeholder parity across every catalog, so a missing or malformed translation fails the build rather than reaching a user. - `Tickets.Tests` joins the solution, which previously had no test project. Protected code (Golden Rule fullstackhero#4): this slice touches one file under `src/BuildingBlocks` — `Web/Validation/PagedQueryValidator.cs`. Its constructor now takes `IStringLocalizer<SharedResources>` so paging messages localize. All three subclasses live in modules (`GetAuditsQueryValidator`, `GetTenantsQueryValidator`, `SearchUsersQueryValidator`), so the base class travels with its callers rather than breaking them from another PR. Every other `BuildingBlocks` change is in the framework slice. The `SSH.NET` pin (`2026.0.0`) is byte-identical to fullstackhero#1333, so `dotnet restore` passes while that PR is open.
marcelo-maciel
marked this pull request as ready for review
August 17, 2026 04:02
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This was referenced Aug 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Module slice of the i18n work, split out of #1344. 229 files, and mechanical: one catalog per module plus the
MessageKey/ResourceSourcewiring at each throw and validation site.The split is four PRs rather than three, because these ~220 files fit none of your three and would have put the framework PR back at ~285.
clients/adminclients/dashboardsrc/BuildingBlocks(Golden Rule #4)One file:
Web/Validation/PagedQueryValidator.cs. Its constructor now takesIStringLocalizer<SharedResources>so the paging messages localize. All three subclasses are in modules —GetAuditsQueryValidator,GetTenantsQueryValidator,SearchUsersQueryValidator— so the base class travels with its callers. Leaving it in the framework PR would either break that PR's build (its subclasses onmaincall the old constructor) or dragAuditingResourcesandGetAuditsQueryHandlerin with it, for no review benefit. The other seventeenBuildingBlocksfiles are declared in the framework PR.What is in here
{Module}Resourcescatalog per module,en-USneutral plus*.pt-BR.resx, for Auditing, Billing, Catalog, Chat, Files, Multitenancy, Notifications, Tickets and Webhooks, plus the Identity messages outside theLocaleplumbing.MessageKeyandResourceSource.Messagestays English, so logs and audit records remain culture-independent while the response body localizes. This is where the 41Conflictthrows across Billing and Catalog live — the ones that#1344had briefly answering 409 with "an unexpected error occurred" untilTitleKeyForwas fixed in the framework slice.IStringLocalizer. Message arguments are kept culture-insensitive:GetAuditsQueryHandler.MaxWindowDaysis a newintconstant, replacingMaxWindow.TotalDaysas adoublein the two audit-window validators. The localizer formats withstring.FormatunderCurrentCulture, and adoublein a localized message is culture-sensitive by construction.CatalogParityTestsdiscovers every catalog by reflection and compares each culture's own key set withtryParents: false, plus the placeholder-index set per key.{1}present in one culture and not the other throwsFormatExceptionat render time, in that culture only. New module catalogs are covered without a new test — Notifications had shipped a catalog with no parity test and has no test project to hold one.Tickets.Testsjoins the solution, which had no test project for that module.Testing
Because this slice cannot build alone, the numbers below are framework + this slice, which is what will be on
mainonce both land.dotnet restore src/FSH.Starter.slnxwith the audit on: exit 0, noNU1903.dotnet build -warnaserror: exit 0.#1344on every path undersrc/,.agents/andAGENTS.md:git diffbetween them is empty there.The verdict above is aggregated per assembly rather than taken from the process exit code:
dotnet teston this solution has been observed exiting 0 while reporting failures, and zero assemblies reporting is itself treated as red.dotnet restorepasses with the audit on:src/Directory.Packages.propscarries theSSH.NETpin byte-identical to #1333, same as the other three slices. See the framework PR for why.Docs (Golden Rule #10)
fullstackhero/docs#238, kept as a single PR covering all four slices. The String resources section of
internationalization.mdxis this slice: the per-module{Module}Resourcescatalogs and how a localized message is wired at a throw or validation site. That PR should merge after the last of the four.