Tutorials
Skim Introduction, Configuration, and Services first so the moving parts feel familiar. On this page you will learn how to set up and tune your app’s localization—appsettings, injected settings, resources, APIs, and UI—based on what you actually need, whether that is one market or many. The sections below are organized as practical guides; their titles and order may evolve as the docs grow.
Dynamic per-language content in the Admin Panel
Not everything belongs in appsettings or .resx. DevKit core modules can persist language-specific values (payment copy, plan text, identity or email-related options, and similar) so operators can change them without a release.
Typical workflow for staff
- Open the Admin Panel and use the language picker to select the culture you are editing (for example English, then Spanish).
- Open the module screen you need (Pricing, Settings, or another module that supports per-language rows) and create or update the content while that language is active.
- Switch the picker to another language and repeat so each supported culture has the rows you care about.
What end users see
When someone browses the website or user panel, they already have their own chosen language; the app loads the module data that was saved for that culture, so marketing pages, checkout hints, and account-facing text stay aligned with their locale.
For module-specific behavior and fields, start with the Pricing and Settings introductions; they document how those areas tie into your product beyond static localization files.
Choose language on API calls
When a client calls your HTTP API (mobile app, SPA, Postman, another service), DevKit sets the thread’s CultureInfo.CurrentCulture and CultureInfo.CurrentUICulture from the standard Accept-Language header—the same header browsers use for language negotiation.
- Send a culture your app actually supports, for example
Accept-Language: faorAccept-Language: en-US,en;q=0.9. Only the first segment before the first comma is used. - If the client omits the header, behavior falls back to the default language from your
Localizationsection (the same default you marked withIsDefaultinappsettings).
Practical effect: IStringLocalizer, localized validation messages, and anything else that reads CultureInfo.CurrentUICulture follow the language the caller asked for. Keep header values aligned with Localization:Languages Code entries and your .resx culture names.
The snippet below only shows the Localization:Languages part that defines which culture is the default when no header is sent (trim or extend the list in your real file as needed):
{
"Localization": {
"Languages": [
{
"Name": "English",
"Code": "en",
"ShortName": "En",
"Direction": "ltr",
"Font": "system-ui, sans-serif",
"IsDefault": true
},
{
"Name": "Persian",
"Code": "fa",
"ShortName": "Fa",
"Direction": "rtl",
"Font": "Tahoma, sans-serif",
"IsDefault": false
}
]
}
}
Ship in only one language
- Under
Localization:Languages, keep a single language object and setIsDefault": trueon it. - Remove any other language objects from that array.
- Optional: keep only
Application:Defaultand dropApplication:Localizationsuntil you need per-culture overrides.
The built-in language switcher hides itself when there is only one language (LocalizationSettings.HasMoreThanOneLanguage()), so you usually do not need extra UI work.
Culture codes in .resx files and in Application:Localizations should still match that one Code (for example en everywhere).
Example—only the multilingual slice you are shrinking to one language:
{
"Localization": {
"CookieName": "MyProduct.WebApp.Culture",
"Languages": [
{
"Name": "English",
"Code": "en",
"ShortName": "En",
"Direction": "ltr",
"Font": "system-ui, sans-serif",
"IsDefault": true
}
]
}
}
Pick typefaces with the Font field
Hosts apply the per-language Font string as a CSS font-family value (for example on <html> or <body>). Define any custom face yourself, ship the files under your site’s static files, then use the same name in Font as in your @font-face rule.
1) Register the font in CSS (paths and file names are yours to choose):
@font-face {
font-family: "ContosoDisplay";
src: url("/fonts/ContosoDisplay.woff2") format("woff2");
font-weight: normal;
font-style: normal;
}
2) Point Font at that family (here you can use a stack so there is always a fallback):
{
"Localization": {
"Languages": [
{
"Name": "English",
"Code": "en",
"ShortName": "En",
"Direction": "ltr",
"Font": "\"ContosoDisplay\", system-ui, sans-serif",
"IsDefault": true
}
]
}
}
Escape inner double quotes in JSON as \" when you need them inside the string.
3) System fonts only — you can skip @font-face and put a full stack in Font alone:
{
"Localization": {
"Languages": [
{
"Name": "English",
"Code": "en",
"ShortName": "En",
"Direction": "ltr",
"Font": "system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif",
"IsDefault": true
}
]
}
}
Different app name, description, or support email per language
- Fill
Application:Defaultwith your neutral branding. - Under
Application:Localizations, add one object per cultureCode; only include properties that differ fromDefault.
In Razor or Blazor, inject ApplicationSettings and bind @ApplicationSettings.Localized.Name (and description, email, URLs the same way). When the user switches language, the merged values update.
Example—only Application with partial overrides:
{
"Application": {
"Default": {
"Name": "Contoso",
"Description": "Default marketing line",
"Url": "https://contoso.example/",
"SupportEmail": "[email protected]"
},
"Localizations": {
"es": {
"Name": "Contoso ES",
"SupportEmail": "[email protected]"
}
}
}
}
Different logo per language
- Put image files under your client’s wwwroot (or CDN) and use stable URLs.
- Set
LogoUrlonApplication:Defaultfor the usual logo. - Under
Application:Localizations, setLogoUrlonly for cultures that need a different asset.
Point your layout or splash at ApplicationSettings.Localized.LogoUrl instead of a hard-coded path, and keep alt tied to ApplicationSettings.Localized.Name.
Note: Whether a logo appears on some screens can still depend on identity/settings (for example “show logo on sign-in”).
LogoUrlpicks which image when the UI does show one.
Example—only logo-related Application fields:
{
"Application": {
"Default": {
"LogoUrl": "/images/logos/logo-en.svg"
},
"Localizations": {
"ar": {
"LogoUrl": "/images/logos/logo-ar.svg"
}
}
}
}
Where the template keeps longer text (.resx)
Settings are great for global branding. Longer copy, labels, and validation messages usually live in .resx resource files:
- Shared strings —
Core/ResourceswithSharedResource.resxplus satellites likeSharedResource.fa.resx. - Strings for one host only — under that client’s Resources folder (for example WebApp or AdminPanel).
After editing .resx files, rebuild so generated code and satellite assemblies stay aligned.
Inject settings in a Razor page
Useful for footers, legal pages, or any page that needs the merged Application values plus current language metadata:
@using CodeBlock.DevKit.Contracts.Models
@using CodeBlock.DevKit.Web.Localization
@inject ApplicationSettings ApplicationSettings
@inject LocalizationSettings LocalizationSettings
<h1>@ApplicationSettings.Localized.Name</h1>
<p>Support: @ApplicationSettings.Localized.SupportEmail</p>
@if (!string.IsNullOrEmpty(ApplicationSettings.Localized.LogoUrl))
{
<img src="@ApplicationSettings.Localized.LogoUrl" alt="@ApplicationSettings.Localized.Name" />
}
<p>Language: @LocalizationSettings.GetCurrentLanguageCode()</p>
<p>Direction: @LocalizationSettings.GetCurrentLanguageDirection()</p>
The template’s Privacy.cshtml is a real example of reading support email from settings.
Use IStringLocalizer in a use case
using CanBeYours.Core.Resources;
using Microsoft.Extensions.Localization;
internal class ExampleUseCase
{
private readonly IStringLocalizer<SharedResource> _localizer;
public ExampleUseCase(IStringLocalizer<SharedResource> localizer)
{
_localizer = localizer;
}
public string GetDemoSectionTitle()
{
return _localizer[SharedResource.DemoThing];
}
}
Replace CanBeYours.Core.Resources with your Core project’s namespace for SharedResource.
Use IStringLocalizer in a Blazor component
@using CanBeYours.Core.Resources
@inject IStringLocalizer<SharedResource> Localizer
<h3>@Localizer[SharedResource.DemoThing]</h3>
Validation messages tied to resources
Data annotations can pull error text from SharedResource (or another .resx type) using ErrorMessageResourceName and ErrorMessageResourceType. When you add a new spoken language, add the same resource keys to that culture’s satellite .resx so validation messages read naturally.