Troubleshooting
Practical problem/solution guide for Pragmatic.Internationalization. Each section covers a common issue, the likely causes, and the fix.
Formatting Uses Wrong Culture
Section titled “Formatting Uses Wrong Culture”Numbers, dates, or money display in the server’s default locale instead of the user’s preferred culture.
Checklist
Section titled “Checklist”-
Did you call
UsePragmaticInternationalization()in Program.cs? The middleware setsI18NContextfrom the request. Without it, formatting falls back toCultureInfo.CurrentCulture.app.UsePragmaticInternationalization(); // Before UseRouting() -
Is the middleware registered early enough? It must run before any code that reads
I18NContext. Place it beforeUseRouting()andMapPragmaticEndpoints(). -
Is the culture in the supported list? If the requested culture is not in
I18NOptions.SupportedCultures, the resolver falls back to the default. Check that the culture is listed:options.SupportedCultures = [CultureCode.English, CultureCode.Italian, CultureCode.German]; -
Is the client sending the culture? Check the request for:
- Query string:
?culture=de-DE Accept-Languageheader:Accept-Language: de-DE,de;q=0.9,en;q=0.8
- Query string:
-
For console or background services: Set the culture manually since there is no HTTP middleware:
I18NContext.SetCulture("de-DE");
T Class Has No Properties
Section titled “T Class Has No Properties”The [TranslationKeys] attribute is present but the generated T class is empty or does not exist.
Checklist
Section titled “Checklist”-
Are translation files listed as
AdditionalFiles? The SG only sees files explicitly included with this item type:<ItemGroup><AdditionalFiles Include="translations/*.json" /></ItemGroup>Files included as
Content,None, orEmbeddedResourceare invisible to the source generator. -
Are the files in a recognized folder? The SG recognizes:
translations,i18n,locales,lang. Files matching*.translations.jsonor*.i18n.jsonare also included regardless of folder name. -
Is the JSON valid? Diagnostic PRAG1800 fires when a translation file cannot be parsed. Check for syntax errors (trailing commas, missing quotes, BOM characters).
-
Do the files contain string values? Diagnostic PRAG1803 fires when a file parses successfully but contains no string leaf values. Nested objects without string leaves are skipped:
// This file triggers PRAG1803 -- no string values{"section": {"subsection": {}}} -
Is the source generator referenced? In your
.csproj, the SG must be referenced as an analyzer:<ProjectReference Include="..\Pragmatic.Internationalization.SourceGenerator\..."OutputItemType="Analyzer"ReferenceOutputAssembly="false" />
Missing Translation Warning (PRAG1802)
Section titled “Missing Translation Warning (PRAG1802)”The build shows PRAG1802: Translation key 'X' exists in 'en' but is missing in 'it'.
What it means
Section titled “What it means”A key is defined in the default culture file but not in another culture file. At runtime, LocalizedString.Value will fall back to the default culture for that key.
Add the missing key to the incomplete translation file. If the fallback behavior is intentional (e.g., the key is English-only by design), you can suppress the warning:
<PropertyGroup> <NoWarn>$(NoWarn);PRAG1802</NoWarn></PropertyGroup>Or suppress it for a specific file in .editorconfig:
[translations/it.json]dotnet_diagnostic.PRAG1802.severity = noneDuplicate Key Warning (PRAG1801)
Section titled “Duplicate Key Warning (PRAG1801)”The build shows PRAG1801: Translation key 'welcome' is defined in multiple files for culture 'en'.
What it means
Section titled “What it means”The same key appears in two or more JSON files for the same culture. The SG uses the last definition it encounters. This is usually unintentional — you may have copied a file and forgotten to remove duplicate keys.
Consolidate the duplicate key into a single file. If you use the folder-per-culture structure, each file (e.g., common.json vs errors.json) creates a separate namespace, so keys only conflict when they appear in the same logical file.
Money Formatting Shows Wrong Symbol or Position
Section titled “Money Formatting Shows Wrong Symbol or Position”Money.Format() shows the currency symbol in the wrong position (e.g., $107.49 instead of 107,49 EUR).
Possible causes
Section titled “Possible causes”-
Wrong culture context. The symbol position and decimal separator come from the culture, not the currency:
I18NContext.SetCulture("en-US");money.Format(); // "$107.49" -- symbol before, dot separatorI18NContext.SetCulture("de-DE");money.Format(); // "107,49 $" -- symbol after, comma separator -
No
I18NContextset. Without middleware, formatting usesCultureInfo.CurrentCulture. See “Formatting Uses Wrong Culture” above. -
Using
Amount.ToString("C")directly instead ofmoney.Format(). TheToString("C")method formats using the culture’s default currency, not theMoney.Currency. A EUR amount formatted withen-USculture viaToString("C")produces$107.49(USD symbol), notEUR 107.49.
LocalizedString.Value Returns Wrong Language
Section titled “LocalizedString.Value Returns Wrong Language”product.Name.Value returns the English text instead of the user’s language.
Checklist
Section titled “Checklist”-
Is
I18NContextset? TheValueproperty readsI18NContext.Current.CultureCode. If no context is set, it falls back toCultureInfo.CurrentCulture. -
Does the
LocalizedStringcontain the requested culture? Checkproduct.Name.Culturesto see which cultures are stored. The fallback chain is: exact match (de-AT) -> base language (de) -> default culture (en). -
Is the culture code format correct?
LocalizedStringuses the culture code string for lookup. If you stored"de"but the context is"de-DE", the fallback chain will try"de-DE"first (miss), then"de"(match). This is correct behavior. But if you stored"de-DE"and the context is"de", the exact match fails and it falls to"en". -
For Data vs UI culture: Use
.UIValuefor display and.DataValuefor storage/API output:// UI culture: it-IT, Data culture: en-USproduct.Name.UIValue; // Italian textproduct.Name.DataValue; // English textproduct.Name.Value; // Italian text (same as UIValue, uses CultureCode)
EF Core: CurrencyCode or Money Column Errors
Section titled “EF Core: CurrencyCode or Money Column Errors”Database migration or query errors related to CurrencyCode or Money columns.
Checklist
Section titled “Checklist”-
Did you call
ApplyPragmaticInternationalization()in OnModelCreating?protected override void OnModelCreating(ModelBuilder modelBuilder){modelBuilder.ApplyPragmaticInternationalization();} -
Or the convention-based alternative?
protected override void ConfigureConventions(ModelConfigurationBuilder builder){builder.ApplyPragmaticInternationalizationConventions();} -
Is the
Pragmatic.Internationalization.EFCorepackage referenced? The value converters live in this separate package. -
Column type for CurrencyCode: Maps to
varchar(3). If your existing column is a different type, you need a migration to change it.
JSON Serialization Not Working
Section titled “JSON Serialization Not Working”Money or CurrencyCode values serialize as empty objects or throw serialization errors.
Checklist
Section titled “Checklist”-
Did you call
AddPragmaticInternationalization()? This registers JSON converters forMoney,CurrencyCode,CultureCode,LanguageCode, andCountryCode:builder.Services.AddPragmaticInternationalization(); -
Are you using a custom
JsonSerializerOptionsinstance? The converters are registered on the HTTP JSON options. If you create a separateJsonSerializerOptionsfor manual serialization, add the converters manually:var options = new JsonSerializerOptions();options.AddPragmaticInternationalization(); -
Expected JSON format for Money:
{ "amount": 99.99, "currency": "EUR" }If you see
{ "amount": 0, "currency": null }, the converter is not registered.
Diagnostics Reference
Section titled “Diagnostics Reference”| ID | Severity | Description | Fix |
|---|---|---|---|
| PRAG1800 | Error | Translation JSON file could not be parsed | Fix JSON syntax errors (trailing commas, missing quotes, BOM) |
| PRAG1801 | Warning | Duplicate translation key for same culture | Consolidate duplicate keys into a single file per culture |
| PRAG1802 | Warning | Translation key exists in default culture but missing in another | Add the missing key, or suppress if fallback is intentional |
| PRAG1803 | Info | Translation file contains no valid translation keys | Add string key-value pairs, or remove the empty file |
Viewing diagnostics
Section titled “Viewing diagnostics”- Visual Studio: Error List window, filter by “PRAG18”
- CLI:
dotnet buildoutput includes diagnostic messages with file and line information - Rider: Problems tool window
What happens when a translation key is missing?
Section titled “What happens when a translation key is missing?”LocalizedString.Value uses a fallback chain: exact culture (de-AT) -> base language (de) -> default culture (typically en). If no translation exists in any fallback, it returns an empty string.
For T class keys, missing translations are caught at build time by PRAG1802. At runtime, the key always has the embedded values from all cultures present at compile time.
Can I use both LocalizedString and T class in the same project?
Section titled “Can I use both LocalizedString and T class in the same project?”Yes. This is the recommended approach. Use T class for static application strings (error messages, UI labels) and LocalizedString for dynamic per-entity content (product names, descriptions).
How do I add a new language?
Section titled “How do I add a new language?”For the T class: add a new JSON file for the culture (e.g., translations/fr.json), populate all keys, and rebuild. PRAG1802 warnings will flag any missing keys.
For LocalizedString: add translations at runtime via the entity’s API (product.Name.Set("fr", "Widget")).
Does I18NContext work with background services?
Section titled “Does I18NContext work with background services?”Yes, but you must set the culture manually since there is no HTTP middleware:
I18NContext.SetCulture("en-US");await ProcessBackgroundJobAsync();I18NContext.Clear(); // Clean up when doneCan I override the query string parameter name?
Section titled “Can I override the query string parameter name?”The middleware reads from ?culture=xx by default. To change this, configure the culture resolution at the provider level.
How do plurals work with the T class?
Section titled “How do plurals work with the T class?”Define plural forms as nested objects in JSON:
{ "items": { "one": "1 item", "other": "{count} items" }}The SG recognizes objects whose keys are CLDR category names (zero, one, two, few, many, other) and generates a PluralString instead of a LocalizedString.
Does the system support RTL languages?
Section titled “Does the system support RTL languages?”The formatting system uses .NET’s CultureInfo which handles RTL number formatting (Arabic, Hebrew). The translation system stores and retrieves strings without directional assumptions. RTL layout is a UI concern handled by your frontend framework.
Getting Help
Section titled “Getting Help”- GitHub Issues: github.com/nicola-pragmatic/Pragmatic.Design/issues
- Showcase Examples: See the
Showcaseproject for working i18n usage across entities and endpoints. - Getting Started: See getting-started.md for initial setup.
- Concepts: See concepts.md for architecture and design decisions.
- Translation Keys: See translation-keys.md for SG configuration and folder structures.