ASP.NET Core Localization Tutorial (.resx, IStringLocalizer)
The complete ASP.NET Core localization picture, executed against a real app on SDK 10.0.400 and re-run on 9.0.317 with identical output — HTTP probes, satellite assemblies on disk, crash traces and all. Built on the observation that IStringLocalizer is designed never to fail: a missing key returns the key, an untranslated string returns English, an unsupported culture returns English, and none of it logs anything. Includes the flag that looks like a coverage signal and is not — ResourceNotFound reads false when the neutral resource answered, so it can tell you a key exists nowhere but never that a German page is showing English. Then the traps. Both resx naming conventions compile to the identical manifest resource name, so the folder layout is cosmetic and the fix for silent key-echoing is dumping GetManifestResourceNames rather than rearranging directories. A neutral culture in SupportedCultures turns every price into ¤1,234.50, because a language without a country has no currency — the two culture lists exist for different jobs. string.Format runs only when you pass arguments, so a doubled brace shows up literally the day a message loses its last parameter, while a translator renumbering {0} to {1} throws FormatException in production on one language only. .resx has no plural support whatsoever, and English is already wrong at n=1. Plus globalization invariant mode, where the crash is loud but the widely-copied PredefinedCulturesOnly=false fix is far worse — translations stay perfect while every date and price silently collapses to invariant — with a one-line detection check, the request-localization provider order proved by curl, DataAnnotations messages and their undocumented placeholder slots, an executed resx2po/po2resx round trip, and a build gate that renders every translated format string and exits 1.