Appearance
Are you an LLM? You can read better optimized documentation at /features/internationalization.md for this page in Markdown format
Internationalization
This document provides a detailed technical overview of the internationalization (i18n) and localization architecture of The Example App. It covers how the application supports multiple languages for both static UI elements and dynamic content fetched from Contentful.
Supported Locales
The application is explicitly configured to support a set of locales for its static user interface strings (labels, buttons, etc.). This list is defined in Startup.cs.
csharp
// File: TheExampleApp/Startup.cs
public static List<CultureInfo> SupportedCultures = new List<CultureInfo>
{
//When adding supported locales make sure to also add a static translation files for the locale under /wwwroot/locales
new CultureInfo("en-US"),
new CultureInfo("de-DE"),
};1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Currently, the supported locales are:
en-US(U.S. English) - The default locale.de-DE(German, Germany)
Note: To add support for a new UI language, you must:
- Add the new
CultureInfoto theSupportedCultureslist inStartup.cs.- Create a corresponding translation file (e.g.,
fr-FR.json) in thewwwroot/locales/directory.
Locale Selection
Users can switch locales via a dropdown in the application's header. This functionality is managed by the LocalesViewComponent.
The component performs the following actions:
- Fetches the list of all available locales directly from the configured Contentful space using
_client.GetSpace(). This ensures the dropdown is always in sync with the content localization strategy in the CMS. If the API call fails (e.g., due to invalid credentials), it falls back to a default locale ofen-US. - Determines the currently selected locale by checking the user's session, falling back to the current request culture.
- Persists the selected locale code into the session using the
Startup.LOCALE_KEY. - Sets the
UsePreviewApiflag based on whether the Contentful client is configured to use the Preview API. - Renders a view that displays the locale dropdown. When a user selects a new locale, the page is reloaded with a
?locale={code}query string parameter, which triggers the culture selection logic on the server.
csharp
// File: TheExampleApp/ViewComponents/LocalesViewComponent.cs
public async Task<IViewComponentResult> InvokeAsync()
{
Space space = null;
try
{
// Try to get the space, if this fails we're probably in a state of bad credentials.
space = await _client.GetSpace();
}
catch
{
// Getting the space failed, set some default values.
space = new Space() {
Locales = new List<Locale>()
{
new Locale
{
Code = "en-US",
Default = true,
Name = "U.S. English"
}
}
};
}
// Read the selected locale from the session or the current culture
var selectedLocale = HttpContext.Session.GetString(Startup.LOCALE_KEY) ?? CultureInfo.CurrentCulture.ToString();
var localeInfo = new LocalesInfo
{
Locales = space.Locales,
SelectedLocale = space?.Locales.FirstOrDefault(c => c.Code == selectedLocale) ?? space?.Locales.Single(c => c.Default),
};
// Persist the selected locale back to the session
HttpContext.Session.SetString(Startup.LOCALE_KEY, localeInfo.SelectedLocale?.Code);
localeInfo.UsePreviewApi = _client.IsPreviewClient;
return View(localeInfo);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
For more information on View Components, see the View Components documentation.
LocalizationConfiguration
Instead of using standard .resx files, the application employs a custom JSON-based localization provider for static UI strings. The JsonViewLocalizer class implements ASP.NET Core's IViewLocalizer interface.
At startup, this class accepts an IFileProvider and scans the directory it points to (typically wwwroot/locales), reads each JSON file, and deserializes its contents into an in-memory dictionary. The locale code (e.g., "en-US") is extracted from each filename and used as the top-level dictionary key.
When a translation is requested in a Razor view (e.g., @Localizer["homeLabel"]), the JsonViewLocalizer uses CultureInfo.CurrentCulture to find the correct dictionary and then retrieves the string by its key. If the culture or key is not found, it returns a LocalizedString marked as not found with the key as the value.
This implementation is registered as a singleton in Startup.cs:
csharp
// File: TheExampleApp/Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddSingleton<IViewLocalizer, JsonViewLocalizer>();
// ...
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
csharp
// File: TheExampleApp/Configuration/LocalizationConfiguration.cs
public class JsonViewLocalizer : IViewLocalizer
{
private Dictionary<string, Dictionary<string,string>> _items = new Dictionary<string, Dictionary<string, string>>();
// The constructor loads all JSON files from the provider into memory.
public JsonViewLocalizer(IFileProvider provider)
{
var files = provider.GetDirectoryContents("");
foreach (IFileInfo file in files)
{
using ( var sr = new StreamReader(file.CreateReadStream()))
{
var fileContents = sr.ReadToEnd();
var dictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(fileContents);
var fileName = Path.GetFileNameWithoutExtension(file.Name);
_items.Add(fileName.Substring(fileName.LastIndexOf('.') + 1), dictionary);
}
}
}
// The indexer provides the localized string for the current culture.
public LocalizedHtmlString this[string name] => new LocalizedHtmlString(name, _items[CultureInfo.CurrentCulture.ToString()][name]);
// ... other IViewLocalizer members
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Locale JSON Files
Translation files for static UI strings are stored as simple JSON key-value pairs in the wwwroot/locales/ directory. The filename must match the culture code exactly (e.g., en-US.json).
This approach simplifies the process of adding or editing translations, as they can be managed in plain text files without requiring Visual Studio or a resource editor.
Example File Structure:
TheExampleApp/
└── wwwroot/
└── locales/
├── de-DE.json
└── en-US.json1
2
3
4
5
2
3
4
5
Example Content:
json
// File: TheExampleApp/wwwroot/locales/de-DE.json
{
"homeLabel": "Startseite",
"coursesLabel": "Kurse",
"settingsLabel": "Einstellungen",
"viewOnGithub": "Auf GitHub ansehen"
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Contentful Localization
The application's dynamic content (courses, lessons, etc.) is localized directly within Contentful. The list of available locales for content is managed in the Contentful space settings, not in the application's source code.
The application interacts with Contentful's localization features in two key ways:
- Locale Discovery: As shown in the
LocalesViewComponent, the app dynamically fetches the list of locales from the Contentful space to populate the language switcher. This means that adding a new language in Contentful will automatically make it selectable in the UI. - Content Fetching: The
contentful.aspnetcoreSDK automatically requests content for the currently active locale. The application ensures the correct culture is set on the request thread, so no special parameters are needed for individualGetEntriescalls.
For a deeper dive into how the application is configured to work with Contentful, please see the Contentful Integration documentation.
Locale Fallback Chain
The application features a sophisticated fallback mechanism to handle cases where a locale is available in Contentful but is not supported by the application's static UI translation files. This logic is defined in a CustomRequestCultureProvider within Startup.cs.
Scenario:
- A locale
es-ESis added to the Contentful space. - The fallback for
es-ESis set toen-USwithin Contentful's settings. - The application's
SupportedCultureslist only containsen-USandde-DE. - A user selects "Español" (
es-ES) from the locale dropdown.
Execution Flow:
- The
CustomRequestCultureProviderdetects the?locale=es-ESquery parameter. - It validates that
es-ESexists in the list of locales fetched from Contentful. - It checks if
es-ESexists in the application'sSupportedCultureslist. It does not. - The
GetNextFallbackLocalefunction is called. It recursively traverses theFallbackCodechain defined in Contentful until it finds a locale that is present inSupportedCultures. In this case, it findsen-US. - The provider then sets two distinct values in the session:
Session[LOCALE_KEY]is set toes-ES. This ensures that API calls to Contentful will request Spanish content.Session[FALLBACK_LOCALE_KEY]is set toen-US. This is used to set the request'sUICulture, ensuring the static UI renders in English.
This design allows the application to display content in any language defined in Contentful, while gracefully falling back to a supported UI language if a direct translation is not available.
csharp
// File: TheExampleApp/Startup.cs
// ... inside CustomRequestCultureProvider
if(SupportedCultures.Any(c => string.Equals(c.ToString(), locale, StringComparison.OrdinalIgnoreCase)) == false)
{
// The locale is supported in Contentful, but not by the application.
// Walk through the fallback chain to see if we find a supported locale there.
var fallback = GetNextFallbackLocale(contentfulLocales.FirstOrDefault(c => c.Code == locale));
if(fallback != null)
{
// We found a fallback, use that for static strings.
s.Session.SetString(LOCALE_KEY, locale);
s.Session.SetString(FALLBACK_LOCALE_KEY, fallback);
// Use the fallback for static strings
return await Task.FromResult(new ProviderCultureResult(fallback, fallback));
}
}
string GetNextFallbackLocale(Locale selectedLocale)
{
if(selectedLocale != null && selectedLocale.FallbackCode != null)
{
if(SupportedCultures.Any(c => string.Equals(c.ToString(), selectedLocale.FallbackCode)))
{
return selectedLocale.FallbackCode;
}
else
{
// Recursive call to traverse the fallback chain
return GetNextFallbackLocale(contentfulLocales.FirstOrDefault(c => c.Code == selectedLocale.FallbackCode));
}
}
return null;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Request Culture Providers
The application's culture is determined for each request by a pipeline of providers configured in Startup.cs. They are executed in the following order:
CustomRequestCultureProvider(for Query String):- Triggers only if the
localequery string parameter is present. - Contains the primary logic for validating the locale against Contentful and handling the fallback chain as described above.
- Saves the chosen locale(s) to the session.
- Returns
nullif the query parameter is not found, allowing the next provider to run.
- Triggers only if the
QueryStringRequestCultureProvider:- The standard ASP.NET Core provider. It is configured to look for the
localequery string key. This acts as a secondary handler if the custom provider's logic is ever bypassed.
- The standard ASP.NET Core provider. It is configured to look for the
CustomRequestCultureProvider(for Session):- This provider runs on every request and is responsible for persistence.
- It reads the
LOCALE_KEYandFALLBACK_LOCALE_KEYfrom the user's session. - If a
FALLBACK_LOCALE_KEYexists, it is given precedence and used to set theUICulture. - If not, the
LOCALE_KEYis used. - This ensures that once a user selects a language, their choice is remembered for subsequent navigation.
If none of these providers determine a culture, the application defaults to en-US, as defined in RequestLocalizationOptions.
Session Storage
The user's locale preference is persisted across requests using ASP.NET Core's session state. This avoids forcing the user to re-select their language on every page.
The following session keys are used:
| Key | Constant | File | Purpose |
|---|---|---|---|
locale | Startup.LOCALE_KEY | Startup.cs | Stores the primary locale code selected by the user (e.g., es-ES). This is used to query the Contentful API. |
fallback-locale | Startup.FALLBACK_LOCALE_KEY | Startup.cs | Stores the UI fallback locale (e.g., en-US) only when the primary locale is not supported by the app's static JSON files. |
The session is configured in Startup.ConfigureServices with a long idle timeout to meet the specific requirements of this example application.
csharp
// File: TheExampleApp/Startup.cs
services.AddSession(options => {
// IdleTimeout is set to a high value to confirm to requirements for this particular application.
// In your application you should use an IdleTimeout that suits your application needs or stick to the default of 20 minutes.
options.IdleTimeout = TimeSpan.FromDays(2);
});1
2
3
4
5
6
7
2
3
4
5
6
7