I have already asked a similar question before: #6471
And this is now a continue because I noticed that something is wrong and I need help. I noticed that MyDynamicResourceLocalizer does not translate all the translations for the tenants and that it translates differently for each tenant. The Switch button on the login page is mostly not translated. In the pictures you will see that for Host everything is translated in Language Text, and when I create tenant, MyDynamicResourceLocalizer it doesn't translate everything. This is for Abp framework translation that dosen exist in Srebian language - not lozalization that we create in our app.
Host:
Video Switch button when i change tenant: https://streamable.com/9fa6hw
This is our MyDynamicResourceLocalizer:
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 Horizont.Web
{
[Dependency(ReplaceServices = true)]
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;
}
}
}
"Culture": "sr-Latn",
Did I do it right or do I need to adjust something else? Thank you.
6 Answer(s)
-
0
To achieve platform-wide (host-level) translations for all tenants in ABP Framework, especially for built-in ABP resources that do not have a Serbian translation, you need to ensure that your dynamic localization implementation always falls back to the host's language texts when a tenant-specific translation is missing.
Your current MyDynamicResourceLocalizer implementation always loads language texts from the host (TenantId = null) by using:
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; } }
This is correct for host-level translations, but it means that tenant-specific overrides will never be used. However, if your tenants are not seeing all translations, it is likely because:
- The Serbian translation for ABP's built-in resources does not exist in the ABP source or in your database.
- The LanguageText table for tenants is empty or incomplete for Serbian, so only host-level texts are available.
- The Language Management UI only shows texts that exist in the database, not those from embedded JSON files or ABP's default resources.
Key Points & Solution:
- ABP's built-in resources (e.g., AbpUi, AbpAccount, etc.) do not have a Serbian translation by default. If you want to provide a Serbian translation, you must add the translations yourself, either via the Language Management UI or by providing your own JSON files.
- Your MyDynamicResourceLocalizer is already set up to always use host-level translations, which is the correct approach for platform-wide language texts.
- If you want to allow tenants to override translations, you should first check for a tenant-specific translation, and if not found, fall back to the host translation.
Recommended Implementation:
To support both host-wide and tenant-specific translations, update your MyDynamicResourceLocalizer like this:
[Dependency(ReplaceServices = true)] public class MyDynamicResourceLocalizer : DynamicResourceLocalizer { private readonly ICurrentTenant _currentTenant; public MyDynamicResourceLocalizer( IServiceScopeFactory serviceScopeFactory, IDistributedCache<LanguageTextCacheItem> cache, ICurrentTenant currentTenant) : base(serviceScopeFactory, cache) { _currentTenant = currentTenant; } protected override LanguageTextCacheItem CreateCacheItem(LocalizationResourceBase resource, string cultureName) { var cacheItem = new LanguageTextCacheItem(); using (var scope = ServiceScopeFactory.CreateScope()) { var languageTextRepository = scope.ServiceProvider.GetRequiredService<ILanguageTextRepository>(); // 1. Load tenant-specific translations (if any) var tenantTexts = languageTextRepository.GetList(resource.ResourceName, cultureName); foreach (var text in tenantTexts) { cacheItem.Dictionary[text.Name] = text.Value; } // 2. Load host translations for missing keys using (_currentTenant.Change(null)) { var hostTexts = languageTextRepository.GetList(resource.ResourceName, cultureName); foreach (var text in hostTexts) { if (!cacheItem.Dictionary.ContainsKey(text.Name)) { cacheItem.Dictionary[text.Name] = text.Value; } } } } return cacheItem; } }
- This ensures that tenant-specific translations override host translations, but host translations are used as a fallback.
- If you want only host-level translations (no tenant overrides), your original implementation is correct.
For ABP built-in resources in Serbian:
- You must provide the Serbian translations yourself, either by importing them into the Language Management module or by adding custom JSON files for each resource (e.g.,
/Localization/AbpUi/sr-Latn.json
).
Summary:
- Your approach is correct for host-wide translations.
- If you want fallback logic (tenant override, then host), use the code above.
- For ABP built-in resources, you must provide the Serbian translations yourself.
Sources:
- https://abp.io/support/questions/6636
- https://abp.io/support/questions/8778
- https://abp.io/docs/9.0/framework/fundamentals/localization#extending-existing-resource
- https://abp.io/community/articles/g1a6lgdl
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.
-
0
hi
Why did you add the
MyDynamicResourceLocalizer
?What is its purpose?
Thanks.
-
0
hi
The purpose is to have a translation in Serbian for all our users (tenants) for abp resources. For example the Switch button on the login page.
This is two tables in Host: In AbpLanguages i have this two:
In AbpLanguageTexts I translated them directly in the app as a Host using Language Management
AbpLanguageTexts for tenants are empty.
I understood that MyDynamicResourceLocalizer will translate everything from host to tenants and that when i login as a tenant we have it translated from host AbpLanguageTexts and this works but it doesn't translate everything using MyDynamicResourceLocalizer.
You can test like this:
Create new app in studio Multitenant, separate database, public web to use redis, add two language english and serbian latin
Login as host, translate switch button using Language Management Create two tenants, and check if switch button is translated.
Ok i hope i describe it now good :-)
Thx.
-
0
-
0
Ah, I see what I did wrong, thank you for the screenshot! I’d set the base language to English and the target to Serbian, so i was trying to translate from English into Serbian instead of the other way around. I’ve now corrected the base to Serbian, and it is working. Thanks for help, Maliming.
-
0
Great 👍