You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/guides/localization-guide.md
+89-16Lines changed: 89 additions & 16 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -23,46 +23,51 @@ This approach ensures:
23
23
24
24
### Supported Languages
25
25
26
-
The API is configured using **ISO 639-1 language codes**. You can configure either generic codes (e.g., `en`, `pt`) or specific regional cultures (e.g., `en-US`, `pt-PT`).
26
+
The API is configured using **ISO 639-1 language codes**. You can configure either generic codes (e.g., `en`, `pt`) or specific regional cultures (e.g., `pt-PT`).
The `LocalizationOptions` class (`src/BookStore.ApiService/Infrastructure/LocalizationOptions.cs`) binds this section. The default/fallback list used when no configuration is present is `["en", "pt", "pt-PT", "es", "fr", "de"]` with `"en"` as the default culture.
39
+
40
+
The supported cultures are exposed at runtime via:
41
+
42
+
```
43
+
GET /api/config/localization
46
44
```
47
45
46
+
This returns a `LocalizationConfigDto` containing `DefaultCulture` and `SupportedCultures[]`. The Web frontend calls this endpoint at startup to configure its own `RequestLocalizationOptions` (with `"en"` as the fallback if the backend is unavailable).
47
+
48
48
### Cache Configuration
49
49
50
-
**Critical**: Localized content is cached using `HybridCache` with the `GetOrCreateLocalizedAsync` extension method, which automatically varies the cache by culture.
50
+
**Critical**: Localized content is cached using `HybridCache` with the `GetOrCreateLocalizedAsync` extension method (`src/BookStore.ApiService/Infrastructure/Extensions/HybridCacheExtensions.cs`), which automatically appends `|{CultureInfo.CurrentUICulture.Name}` to the cache key.
51
+
52
+
In practice, endpoints also include the culture explicitly in the base key alongside tenant and query parameters:
cacheKey, // GetOrCreateLocalizedAsync appends "|{culture}" to this
55
60
asynccancel=> { /* load from database */ },
56
61
options: newHybridCacheEntryOptions
57
62
{
58
63
Expiration=TimeSpan.FromMinutes(5),
59
64
LocalCacheExpiration=TimeSpan.FromMinutes(2)
60
65
},
61
-
tags: [$"category:{id}"],
66
+
tags: [CacheTags.CategoryList],
62
67
token: cancellationToken);
63
68
```
64
69
65
-
The cache key automatically becomes `category:{id}|{culture}` (e.g., `category:123|en-US`, `category:123|pt-PT`).
70
+
The final stored key is `categories:tenant=...:culture=en:...|en`. The culture appears twice (once in the explicit key for transparency, once appended by `GetOrCreateLocalizedAsync`), ensuring correct variation by tenant, culture, and query parameters.
@@ -218,6 +223,74 @@ public class CategoryProjection
218
223
}
219
224
```
220
225
226
+
## Web Frontend Localization
227
+
228
+
### How Culture Flows from Web to API
229
+
230
+
The `BookStoreHeaderHandler` (`src/BookStore.Client/Infrastructure/BookStoreHeaderHandler.cs`) is a `DelegatingHandler` applied to all Refit clients. It automatically adds the `Accept-Language` header when absent:
This means the Blazor circuit's current UI culture is propagated transparently to every API call.
244
+
245
+
### `RequestLocalizationOptions` in the Web
246
+
247
+
At startup, `BookStore.Web/Program.cs` calls `GET /api/config/localization` to fetch the supported cultures from the API and configures its own `RequestLocalizationOptions` accordingly:
If the backend is unreachable at startup, the Web falls back to `["en"]` with default culture `"en"`.
263
+
264
+
### `LanguageService`
265
+
266
+
`src/BookStore.Web/Services/LanguageService.cs` wraps the configuration endpoint with an in-memory cache:
267
+
268
+
-`GetSupportedLanguagesAsync()` — returns the supported culture codes (cached after first call, with fallback to `["en", "pt", "pt-PT", "es", "fr", "de"]` on error).
269
+
-`GetLanguagesWithDisplayNamesAsync()` — returns a `Dictionary<string, string>` mapping code → display name in the current UI language.
270
+
-`GetDefaultCultureAsync()` — returns the configured default culture (fallback: `"en"`).
271
+
-`GetDisplayName(string cultureCode)` — static helper converting a culture code to its .NET display name.
272
+
-`GetAllLanguages()` — enumerates all .NET cultures (used for selecting a book's primary written language, not the UI language).
273
+
274
+
### Language Selector Components
275
+
276
+
Two Blazor components provide language selection in the UI:
277
+
278
+
| Component | File | Purpose |
279
+
|---|---|---|
280
+
|`<LanguageSelector>`|`Components/Shared/LanguageSelector.razor`| Selects a UI/content language from the **supported** cultures returned by the API. Shows "(Default)" next to the configured default. |
281
+
|`<AllLanguageSelector>`|`Components/Shared/AllLanguageSelector.razor`| Selects from **all** .NET cultures. Used in admin dialogs to set a book's primary written language. |
The Web project uses standard ASP.NET Core resource-based localization for UI error messages:
286
+
287
+
-`AddLocalization(options => options.ResourcesPath = "Resources")` is registered in `Program.cs`.
288
+
-`src/BookStore.Web/Services/ErrorLocalizationService.cs` injects `IStringLocalizer<ErrorLocalizationService>` and looks up error codes from resource files.
289
+
- Default (English) strings live in `src/BookStore.Web/Resources/Services/ErrorLocalizationService.resx`.
290
+
- To add a translated version, add `ErrorLocalizationService.{culture}.resx` (e.g., `ErrorLocalizationService.pt.resx`) in the same folder.
291
+
292
+
This is distinct from the dictionary-based content localization used by the API — `.resx` files only cover UI error messages in the Web project.
0 commit comments