Localization
Smartstore is an application where different languages can be easily downloaded and activated in the admin area. Over 30 languages are available for download in the backend via Configuration > Regional Settings > Languages. Activated languages are displayed in the frontend as well as in the backend. Administrators can edit translations in the back office or import and export XML packages.
Overview
Smartstore ships with a database-driven localization system that covers UI strings and entity properties. Each request is assigned a working language determined from the user preference, current store or route culture.
Languages
ILanguageService
manages configured languages. The current language is available from the work context:
var language = _workContext.WorkingLanguage;
Key members of ILanguageService
include:
IsMultiLanguageEnvironment(storeId)
– detect if a store has more than one published language.GetAllLanguages(includeHidden, storeId, tracked)
/GetAllLanguagesAsync(...)
– load all languages, optionally scoped to a store and including hidden ones.IsPublishedLanguage(idOrSeoCode, storeId)
/IsPublishedLanguageAsync(...)
– check whether a language with the given ID or SEO code is published.GetMasterLanguageSeoCode(storeId)
/GetMasterLanguageSeoCodeAsync(...)
– return the SEO code of the first active language.GetMasterLanguageId(storeId)
/GetMasterLanguageIdAsync(...)
– return the ID of the first active language.
Resource strings
Text resources live in the LocaleStringResource
table and can be retrieved through ILocalizationService
or the T
helper in controllers and views:
var greeting = await _localizationService.GetResourceAsync("Common.Welcome");
@T("Common.Welcome")
Annotating models
Attach the LocalizedDisplay
attribute to model properties so labels pull their text from the resource table. By convention the resource key matches the property name:
[LocalizedDisplay("Admin.Domain.Fields.MySetting")]
public bool MySetting { get; set; }
Decorating the class with LocalizedDisplay
lets you provide a prefix so the properties can use shorter keys:
[LocalizedDisplay("Admin.Domain.Fields.")]
public class ConfigurationModel : ModelBase
{
[LocalizedDisplay("*MySetting")]
public bool MySetting { get; set; }
}
Display hints
To show a tooltip next to a label, create a second resource with the .Hint
suffix or specify it inline:
<LocaleResource Name="Admin.Domain.Fields.MySetting.Hint">
<Value>My setting does cool things</Value>
</LocaleResource>
<smart-label asp-for="MySetting" />
<smart-label asp-for="MySetting" sm-hint="Alternative way to set an explanation hint." />
Enumerations
Values of enums are localized under the Enums
namespace. Use Html.GetLocalizedEnumSelectList
to populate a <select>
element:
public enum MyEnum
{
Value1,
Value2
}
<LocaleResource Name="Enums" AppendRootKey="false">
<Children>
<LocaleResource Name="MyEnum.Value1">
<Value>Value 1</Value>
</LocaleResource>
<LocaleResource Name="MyEnum.Value2">
<Value>Value 2</Value>
</LocaleResource>
</Children>
</LocaleResource>
<select asp-for="MyEnumSetting" asp-items="Html.GetLocalizedEnumSelectList(typeof(MyEnum))"></select>
Localizing entities
Entity types that support translations implement ILocalizedEntity
. Each property that should be translated must be annotated with [LocalizedProperty]
so the framework can persist and display language-specific values:
public class Product : BaseEntity, ILocalizedEntity
{
[LocalizedProperty]
public string Name { get; set; }
[LocalizedProperty]
public string ShortDescription { get; set; }
}
LocalizedPropertyAttribute
marks a property as translatable. The system scans these attributes to render language tabs in the admin UI, include fields in import/export packages, and detect changes for cache invalidation. Setting Translatable = false
keeps the property in the metadata but hides it from the translation interface.
Entities can additionally be decorated with LocalizedEntityAttribute
to provide a custom key group or filter predicate for tooling and import/export routines.
Use ILocalizedEntityService
to read and store per-language values:
var localizedName = await _localizedEntityService.GetLocalizedAsync(product, p => p.Name, language.Id);
await _localizedEntityService.SaveLocalizedValueAsync(product, p => p.Name, "Localized name", language.Id);
Descriptors and batch loading
A plugin for translations can gather text without querying individual modules. The ILocalizedEntityDescriptorProvider
exposes metadata for every type that implements ILocalizedEntity
. Each descriptor lists the entity's key group and all properties marked with [LocalizedProperty]
or contributed by LocalizedSettingsLoader
and LocalizedCookieInfoLoader
.
ILocalizedEntityLoader
uses these descriptors to load only the localizable properties for a given entity type. It supports paging so large data sets can be processed in small batches:
var pager = _loader.LoadGroupPaged(descriptor, pageSize: 128);
while ((await pager.ReadNextPageAsync()).Out(out var batch))
{
// process up to 128 entities with only their localizable fields populated
}
LocalizedProperty metadata
The LocalizedProperty
table now carries audit information and translation metadata:
IsHidden
Marks master-language records that should not appear in the UI; translation plugins set this to true
to hide redundant source text.
CreatedOnUtc
, CreatedBy
, UpdatedOnUtc
, UpdatedBy
Filled by audit hooks; plugins that update records should set UpdatedOnUtc
and UpdatedBy
to keep their author information.
TranslatedOnUtc
Timestamp of the last translation. Entries with a null
or older value can be picked up in the next translation run.
MasterChecksum
Hash of the source text used for the translation. If the hash changes the target value must be retranslated.
Tracking changes
To keep master texts in sync, a translation plugin can register an AsyncDbSaveHook<ILocalizedEntity>
. The hook watches for changes to localized properties and either inserts missing LocalizedProperty
rows or clears TranslatedOnUtc
when a value is modified. This allows incremental translation sessions that only handle entities which actually changed.
Process large translation jobs in batches, detach entities between saves and use async
/await
for all I/O operations to reduce memory pressure and improve throughput.
Module localization
Modules bundle resource files and register them during installation. See the localizing modules guide for details.
Adding resources via migrations
When the core or a module introduces new text resources, they can be added through a FluentMigrator migration. Implement ILocaleResourcesProvider
and IDataSeeder<SmartDbContext>
on the migration, call MigrateLocaleResourcesAsync
in SeedAsync
, and use LocaleResourcesBuilder
to insert or update keys:
[MigrationVersion("2024-03-29 18:00:00", "Core: my feature migration")]
internal class MyFeatureMigration : Migration, ILocaleResourcesProvider, IDataSeeder<SmartDbContext>
{
public async Task SeedAsync(SmartDbContext context, CancellationToken ct = default)
=> await context.MigrateLocaleResourcesAsync(MigrateLocaleResources);
public void MigrateLocaleResources(LocaleResourcesBuilder builder)
{
builder.AddOrUpdate("ShoppingCart.SelectAllProducts",
"Select all products",
"Alle Artikel auswählen");
}
}
During migrations these resource entries are merged into LocaleStringResource
for all languages.
Last updated
Was this helpful?