Appearance
Are you an LLM? You can read better optimized documentation at /features/localization.md for this page in Markdown format
Localization and internationalization
This document provides a detailed technical overview of the localization (l10n) and internationalization (i18n) implementation in TheExampleApp. The application is designed to support multiple languages for both static UI elements and dynamic content fetched from the Contentful CMS.
Multi-language support
The application employs a hybrid strategy for multi-language support:
- Static UI Text: Interface elements like labels, buttons, and metadata are translated using simple JSON files. A custom ASP.NET Core
IViewLocalizerimplementation reads these files to provide translations at runtime. - Dynamic Content: All page content, such as course text, lesson details, and author information, is managed and localized directly within Contentful. The application fetches the appropriate language version of the content based on the user's selected locale.
This dual approach separates concerns, allowing UI text to be managed within the codebase while content remains fully decoupled in the headless CMS.
Supported locales
The application is pre-configured to support the following locales:
- English (United States):
en-US(Default) - German (Germany):
de-DE
These are defined in the Startup.cs file:
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
Adding new locales
To add support for a new language, two steps are required:
- Update
Startup.cs: Add a newCultureInfoobject to theSupportedCultureslist. - Create a JSON Translation File: Add a new JSON file in the
wwwroot/locales/directory. The filename must match the locale code exactly (e.g.,fr-FR.json). This file should contain key-value pairs for all static UI strings. You can usewwwroot/locales/en-US.jsonas a template.
json
// File: TheExampleApp/wwwroot/locales/en-US.json (Snippet)
{
"viewOnGithub": "View on GitHub",
"settingsLabel": "Settings",
"homeLabel": "Home",
"coursesLabel": "Courses",
"saveSettingsButtonLabel": "Save settings"
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Note: If a translation file is not provided for a supported culture, requests for that locale will fail when trying to render static text.
Locale detection
The application determines the active locale for each request using a prioritized chain of providers configured in Startup.cs. The culture is determined once per request by the RequestLocalizationMiddleware.
The detection process follows this order of precedence:
- Query String Parameter: A
?locale=<locale-code>parameter in the URL has the highest priority. This immediately sets the culture for the current request and persists it to the user's session for subsequent requests. - Session Value: If no query parameter is present, the application checks for a locale code stored in the user's session from a previous request. See Session Management for more details.
- Default Culture: If neither a query parameter nor a session value is found, the application falls back to the default culture, which is
en-US.
This logic is configured via a series of IRequestCultureProvider implementations:
csharp
// File: TheExampleApp/Startup.cs
app.UseRequestLocalization(new RequestLocalizationOptions
{
RequestCultureProviders = new List<IRequestCultureProvider>
{
// Custom provider to handle query string, session, and Contentful fallback logic.
new CustomRequestCultureProvider(async (s) => { /* ... */ }),
// Standard provider for the ?locale= query string.
new QueryStringRequestCultureProvider()
{
QueryStringKey = LOCALE_KEY
},
// Custom provider to read the locale from the session.
new CustomRequestCultureProvider(async (s) => { /* ... */ })
},
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = SupportedCultures,
SupportedUICultures = SupportedCultures
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Fallback Chain Handling
A sophisticated fallback mechanism is implemented for cases where a locale is supported by Contentful but not by the application's static JSON files (e.g., a request for es-ES when only en-US and de-DE JSON files exist).
The primary CustomRequestCultureProvider in Startup.cs contains this logic:
- It checks if the requested locale (from
?locale=...) is supported by the application's static files (SupportedCultures). - If not, it inspects the locale's fallback chain as defined in Contentful (e.g.,
en-GBmight fall back toen-US). - It traverses this chain until it finds a locale that is present in the application's
SupportedCultureslist. - If a suitable fallback is found:
- The original requested locale (e.g.,
en-GB) is stored in the session aslocale. This is used for API calls to Contentful to fetch the desired content. - The found fallback locale (e.g.,
en-US) is stored in the session asfallback-locale. This is used to set theCurrentCulturefor the request, ensuring that theJsonViewLocalizercan find and render the static UI text.
- The original requested locale (e.g.,
csharp
// File: TheExampleApp/Startup.cs (Helper function within the provider)
string GetNextFallbackLocale(Locale selectedLocale)
{
if(selectedLocale != null && selectedLocale.FallbackCode != null)
{
if(SupportedCultures.Any(c => string.Equals(c.ToString(), selectedLocale.FallbackCode)))
{
// A supported fallback was found.
return selectedLocale.FallbackCode;
}
else
{
// Recursively check the next locale in the 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
This ensures the application remains functional, displaying UI in a supported language while still attempting to fetch content in the user's preferred language.
Static translations
Static UI text is managed by a custom IViewLocalizer implementation that reads from JSON files.
JsonViewLocalizer Implementation
The TheExampleApp.Configuration.JsonViewLocalizer class provides a simple, file-based localization system. For more details on service registration, see Service Configuration.
Key characteristics:
- It is registered as a singleton service in
Startup.cs. - During application startup, its constructor scans the directory specified by its
IFileProvider(configured to bewwwroot/locales/), deserializes each JSON file, and loads the translations into an in-memoryDictionary. - The dictionary is keyed by locale code (e.g., "en-US"), with the value being another dictionary of translation keys and their corresponding strings.
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 provided file provider.
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);
// The locale code is parsed from the filename.
_items.Add(fileName.Substring(fileName.LastIndexOf('.') + 1), dictionary);
}
}
}
// The indexer provides access to the translations for the current request's 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
29
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
LocalesViewComponent
The UI for switching languages is rendered by the LocalesViewComponent. This component is responsible for:
- Fetching the list of all available locales from the Contentful space via the
IContentfulClient. If fetching fails (e.g., due to invalid credentials), it falls back to a default locale configuration withen-USas the only available locale. - Determining the currently selected locale from the session or request culture.
- Persisting the selected locale to the session for subsequent requests.
- Tracking whether the Preview API is being used.
- Rendering a dropdown menu that allows the user to select a different language. Selecting a language reloads the page with the appropriate
?locale=query parameter.
For more information on View Components in this project, see View Components.
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"
}
}
};
}
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),
};
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
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
Contentful localization
Localization of dynamic content is handled by Contentful and the contentful.aspnetcore SDK.
Fetching Locale-Specific Content
The ContentfulClient is locale-aware. When it makes a request to the Contentful Delivery API, it automatically includes a locale parameter in the query. The value of this parameter is determined by the CultureInfo.CurrentCulture of the current thread.
Because the RequestLocalizationMiddleware sets this culture at the beginning of each request based on the logic described in the Locale Detection section, the ContentfulClient seamlessly requests the correct language version of the content.
The key takeaway is that developers do not need to manually specify the locale when querying for entries. The framework handles it automatically. For more on how content is fetched and rendered, see Content Display.
Contentful Fallback Support
It is important to distinguish between the application's static file fallback and Contentful's native content fallback.
- Contentful's Fallback: Within your Contentful space, you can configure a fallback language for each locale (e.g.,
de-DEcan fall back toen-US). If you request an entry forde-DEand a specific field has no content, the Contentful API will automatically return the content from theen-USversion of that field. This behavior is transparent to the application. - Application's Fallback: As described earlier, the application has its own fallback logic for static UI strings only. This is to prevent errors when a user requests a language that has content in Contentful but no corresponding
.jsontranslation file in the application'swwwroot/localesdirectory.