I'm trying to translate the language of my project using ABP into Vietnamese. When I add translated key-value pairs to the AbpLanguageTexts table with a null TenantId, the host accounts are successfully translated. However, when I add a new tenant, the new tenant's keys are not translated as expected. The ABP documentation mentions that if the new tenant doesn't have its own key, and there's no resource file (.vi, .en) in the code, it will query the key in the record with null tenantId. Is it possible that the key I want to translate is hardcoded in the code, so it will always translate to English without querying the record with null tenantId? How can I translate similar keys into Vietnamese and ensure that all newly created tenants will also have those keys translated?

7 Answer(s)
-
0
- In ABP, many UI texts (like
DisplayName:Abp.Identity.*,IdentitySettings*, etc.) come from module localization JSON files (embeddeden.json, etc.). When a text exists in those JSON resources, ABP can keep returning that value (English) unless your dynamic (DB) localization is configured to override it for the current tenant. - Your observation matches a known behavior: DB language texts saved on Host (TenantId = null) may not be visible for tenants, because
LanguageTextis a multi-tenant entity and the localization lookup/caching runs under the current tenant. So the query is filtered byTenantId = CurrentTenantId, and the Host records are not considered.
To make Host translations apply to all tenants (including newly created ones), replace the dynamic resource localizer so it reads localization texts under the Host context:
using Microsoft.Extensions.Caching.Distributed; using Volo.Abp.DependencyInjection; using Volo.Abp.Localization; using Volo.Abp.MultiTenancy; [Dependency(ReplaceServices = true)] [ExposeServices(typeof(DynamicResourceLocalizer), typeof(IDynamicResourceLocalizer))] public class MyDynamicResourceLocalizer : DynamicResourceLocalizer { private readonly ICurrentTenant _currentTenant; public MyDynamicResourceLocalizer( IServiceScopeFactory serviceScopeFactory, IDistributedCache<LanguageTextCacheItem> cache, ICurrentTenant currentTenant) : base(serviceScopeFactory, cache) { _currentTenant = currentTenant; } public override LocalizedString GetOrNull(LocalizationResourceBase resource, string cultureName, string name) { using (_currentTenant.Change(null)) { return base.GetOrNull(resource, cultureName, name); } } protected override LanguageTextCacheItem GetCacheItem(LocalizationResourceBase resource, string cultureName) { using (_currentTenant.Change(null)) { return base.GetCacheItem(resource, cultureName); } } protected override Task<LanguageTextCacheItem> GetCacheItemAsync(LocalizationResourceBase resource, string cultureName) { using (_currentTenant.Change(null)) { return base.GetCacheItemAsync(resource, cultureName); } } }And register it (e.g., in your
Domainmodule):public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddSingleton<IDynamicResourceLocalizer, MyDynamicResourceLocalizer>(); }With this change:
- You can keep inserting Vietnamese texts into the DB with
TenantId = null. - Tenants will also resolve those keys from the Host records (so new tenants automatically get the translations).
- It doesn’t matter if the original English text is defined in embedded JSON; the dynamic localizer will be able to supply the DB value instead.
Links (verified):
- https://abp.io/support/questions/6636
- https://abp.io/support/questions/8778
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) - In ABP, many UI texts (like
-
0
Hi,
LanguageText(theAbpLanguageTextsentity) isIMultiTenant, so when a tenant resolves a localized string, ABP applies the standardWHERE TenantId = CurrentTenantIdfilter. That filter doesn't fall back to host records — yourTenantId = nullrows are simply filtered out for tenant requests. The fallback described in the docs is about the embedded JSON resources (e.g. a missingvi.jsonfalling back toen.json), not a cross-tenant fallback on the DB side.To make the host-level texts apply to all tenants, override
DynamicResourceLocalizerand switch to the host context when loading the cache item:using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.LanguageManagement; using Volo.Abp.Localization; using Volo.Abp.MultiTenancy; namespace MyCompanyName.MyProjectName; [Dependency(ReplaceServices = true)] [ExposeServices(typeof(DynamicResourceLocalizer), typeof(IDynamicResourceLocalizer))] public class MyDynamicResourceLocalizer : DynamicResourceLocalizer { public MyDynamicResourceLocalizer( IServiceScopeFactory serviceScopeFactory, IDistributedCache<LanguageTextCacheItem> cache) : base(serviceScopeFactory, cache) { } protected override LanguageTextCacheItem CreateCacheItem(LocalizationResourceBase resource, string cultureName) { var cacheItem = new LanguageTextCacheItem(); using (var scope = ServiceScopeFactory.CreateScope()) { var currentTenant = scope.ServiceProvider.GetRequiredService<ICurrentTenant>(); using (currentTenant.Change(null)) { var texts = scope.ServiceProvider .GetRequiredService<ILanguageTextRepository>() .GetList(resource.ResourceName, cultureName); foreach (var text in texts) { cacheItem.Dictionary[text.Name] = text.Value; } } } return cacheItem; } protected override async Task<LanguageTextCacheItem> CreateCacheItemAsync(LocalizationResourceBase resource, string cultureName) { var cacheItem = new LanguageTextCacheItem(); using (var scope = ServiceScopeFactory.CreateScope()) { var currentTenant = scope.ServiceProvider.GetRequiredService<ICurrentTenant>(); using (currentTenant.Change(null)) { var texts = await scope.ServiceProvider .GetRequiredService<ILanguageTextRepository>() .GetListAsync(resource.ResourceName, cultureName); foreach (var text in texts) { cacheItem.Dictionary[text.Name] = text.Value; } } } return cacheItem; } }The
[Dependency(ReplaceServices = true)]+[ExposeServices(...)]attributes are enough — no extraAddSingletonregistration is needed. After restarting the app, every tenant (including newly created ones) will read the Vietnamese translations from yourTenantId = nullrows.Same approach we suggested earlier in https://abp.io/support/questions/1642 if you want more background.
Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
How can I configure the default ABP modules to use Vietnamese instead of English as they currently do?
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
- Add Vietnamese as a supported language.
- Set Vietnamese as the default language (so ABP modules/UI use
viwhen there is no user/tenant preference yet). - Provide Vietnamese translations for the module resources (either as embedded virtual JSON overriding module resources, or via Language Management DB + the “host fallback” dynamic-localizer approach you already use).
1) Set Vietnamese as the default language + add it to supported languages
In your
*.Domain.Shared(or the module where you configure localization), configureAbpLocalizationOptions:using Volo.Abp.Localization; Configure<AbpLocalizationOptions>(options => { // Make sure Vietnamese is available in the language list options.Languages.Add(new LanguageInfo( "vi", "vi", "Tiếng Việt", flagIcon: "vn" )); // Make Vietnamese the default when nothing else is selected options.DefaultLanguage = "vi"; });This makes newly created tenants/users start with Vietnamese as the default culture, but it does not automatically translate everything unless Vietnamese resources exist.
2) Provide Vietnamese translations for the ABP module resources (recommended for “default modules”)
ABP module texts come from their own localization resources (embedded JSON). To translate module strings to Vietnamese, you override/extend those resources by adding
vi.jsonunder your app and registering it viaAddVirtualJson(...)for each module resource you want to override.Example pattern (same idea as overriding
AbpTimingResourcein the official article):using Volo.Abp.Localization; using Volo.Abp.Timing.Localization; Configure<AbpLocalizationOptions>(options => { // Add your overrides for an ABP module resource: options.Resources .Get<AbpTimingResource>() .AddVirtualJson("/Localization/AbpTiming"); // contains vi.json });Then create:
YourProject.Domain.Shared/Localization/AbpTiming/vi.json
with contents like:
{ "culture": "vi", "texts": { "DisplayName:Abp.Timing.Timezone": "Múi giờ", "Description:Abp.Timing.Timezone": "Múi giờ của ứng dụng" } }Repeat this for other module resources you need (Identity, SettingManagement, FeatureManagement, etc.) by adding corresponding folders and
vi.jsonfiles and registering them with.AddVirtualJson(...).3) If you want DB-based translations to apply to all tenants
Keep using Language Management + your custom
DynamicResourceLocalizeroverride (host context) soTenantId = nulltranslations are visible to tenants. That ensures newly created tenants also see the translations without copying rows per tenant.
Sources:
- https://abp.io/docs/latest/framework/fundamentals/localization
- https://abp.io/community/articles/ba1oy03l
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
I couldn't get it to work with these configurations:
options.Resources .Get<SaasResource>() .AddVirtualJson("/Localization/Saas");
options.Resources .Get<PaymentResource>() .AddVirtualJson("/Localization/Payment");
options.Resources .Get<AbpIdentityResource>() .AddVirtualJson("/Localization/AbpIdentity");
options.Resources .Get<AbpOpenIddictResource>() .AddVirtualJson("/Localization/AbpOpenIddict");
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
AddVirtualJson(...)is correct, but it only works if your JSON files are actually available in ABP Virtual File System at runtime.- The most common reason it “works in Debug but not in your app / not at all” is that the
vi.jsonfiles are not embedded (or not added to VFS), so ABP can’t find them. Then ABP keeps using the module’s built-inen.json.
Use this checklist (it applies to
SaasResource,PaymentResource,AbpIdentityResource,AbpOpenIddictResource):1) Put the JSON files in the right project and embed them
Place the override files in your
*.Domain.Sharedproject (recommended), e.g.:MyProject.Domain.Shared /Localization /AbpIdentity vi.json /AbpOpenIddict vi.json /Saas vi.json /Payment vi.jsonThen embed them in
MyProject.Domain.Shared.csproj:<ItemGroup> <EmbeddedResource Include="Localization\**\*.json" /> <Content Remove="Localization\**\*.json" /> </ItemGroup> <ItemGroup> <PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="*" /> </ItemGroup>(Without embedding, the files may not be available in production and ABP won’t load them.)
2) Ensure your Domain.Shared module adds its embedded files to VFS
In your
MyProjectDomainSharedModule:using Volo.Abp.VirtualFileSystem; Configure<AbpVirtualFileSystemOptions>(options => { options.FileSets.AddEmbedded<MyProjectDomainSharedModule>(); });3) Ensure your JSON format is correct (case matters)
Use the standard ABP JSON schema:
{ "culture": "vi", "texts": { "SomeKey": "Some Vietnamese value" } }(
culture/textsshould be lowercase as above.)4) Register the virtual json paths (what you already did) in the SAME module that runs
In
MyProjectDomainSharedModule:Configure<AbpLocalizationOptions>(options => { options.Resources.Get<SaasResource>() .AddVirtualJson("/Localization/Saas"); options.Resources.Get<PaymentResource>() .AddVirtualJson("/Localization/Payment"); options.Resources.Get<AbpIdentityResource>() .AddVirtualJson("/Localization/AbpIdentity"); options.Resources.Get<AbpOpenIddictResource>() .AddVirtualJson("/Localization/AbpOpenIddict"); });Then restart the app (localization is cached).
5) Important: UI language must actually be
viEven if you set default language to
vi, if the current culture is stillen, you’ll see English. Make sure Vietnamese exists inAbpLocalizationOptions.Languagesand default isvi(or user/tenant setsvi).
If you still see English after doing the above
That typically means either:
- the keys you’re trying to override belong to a different resource than you think (e.g.,
AbpAccountResourcevsAbpIdentityResource), OR - you’re translating texts that are coming from DB dynamic localization (Language Management) rather than JSON resources, in which case this
AddVirtualJsonapproach won’t affect them.
But in your specific snippet (Identity/OpenIddict/SaaS/Payment module UI texts), the usual fix is embedding +
AddEmbedded<...>()as above.Sources:
- https://abp.io/docs/latest/framework/fundamentals/localization
- https://abp.io/support/questions/8730
- https://abp.io/community/articles/ba1oy03l
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi,
ABP picks the default culture from the
Abp.Localization.DefaultLanguagesetting (default value is"en"). For modules to land on Vietnamese by default, two things need to happen:Add
vi(orvi-VN) to the enabled languages from Language Management → Languages, with Enabled = true. Without this,viisn't inRequestLocalizationOptions.SupportedCulturesand the middleware won't accept it.Set the default to
vi. The simplest way isappsettings.json:
"Settings": { "Abp.Localization.DefaultLanguage": "vi" }Values under the
Settings:section are picked up by ABP'sConfigurationSettingValueProviderautomatically. If you prefer, you can change the same setting from Administration → Settings → General (host or per-tenant), or programmatically viaISettingManager.SetGlobalAsync(LocalizationSettingNames.DefaultLanguage, "vi").After that, requests without an explicit language preference land on
vi, ABP modules use Vietnamese, and theDynamicResourceLocalizeroverride from the previous reply resolves those keys from yourTenantId = nullrows.Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)