Open Closed

Language management #10740


User avatar
0
vietdoan08032001 created

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?

Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

7 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • In ABP, many UI texts (like DisplayName:Abp.Identity.*, IdentitySettings*, etc.) come from module localization JSON files (embedded en.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 LanguageText is a multi-tenant entity and the localization lookup/caching runs under the current tenant. So the query is filtered by TenantId = 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 Domain module):

    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)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    LanguageText (the AbpLanguageTexts entity) is IMultiTenant, so when a tenant resolves a localized string, ABP applies the standard WHERE TenantId = CurrentTenantId filter. That filter doesn't fall back to host records — your TenantId = null rows are simply filtered out for tenant requests. The fallback described in the docs is about the embedded JSON resources (e.g. a missing vi.json falling back to en.json), not a cross-tenant fallback on the DB side.

    To make the host-level texts apply to all tenants, override DynamicResourceLocalizer and 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 extra AddSingleton registration is needed. After restarting the app, every tenant (including newly created ones) will read the Vietnamese translations from your TenantId = null rows.

    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)
  • User Avatar
    0
    vietdoan08032001 created

    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)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • Add Vietnamese as a supported language.
    • Set Vietnamese as the default language (so ABP modules/UI use vi when 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), configure AbpLocalizationOptions:

    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.json under your app and registering it via AddVirtualJson(...) for each module resource you want to override.

    Example pattern (same idea as overriding AbpTimingResource in 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.json files and registering them with .AddVirtualJson(...).

    3) If you want DB-based translations to apply to all tenants

    Keep using Language Management + your custom DynamicResourceLocalizer override (host context) so TenantId = null translations 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)
  • User Avatar
    0
    vietdoan08032001 created

    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)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • 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.json files are not embedded (or not added to VFS), so ABP can’t find them. Then ABP keeps using the module’s built-in en.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.Shared project (recommended), e.g.:

    MyProject.Domain.Shared
      /Localization
        /AbpIdentity
          vi.json
        /AbpOpenIddict
          vi.json
        /Saas
          vi.json
        /Payment
          vi.json
    

    Then 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 / texts should 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 vi

    Even if you set default language to vi, if the current culture is still en, you’ll see English. Make sure Vietnamese exists in AbpLocalizationOptions.Languages and default is vi (or user/tenant sets vi).


    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., AbpAccountResource vs AbpIdentityResource), OR
    • you’re translating texts that are coming from DB dynamic localization (Language Management) rather than JSON resources, in which case this AddVirtualJson approach 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)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    ABP picks the default culture from the Abp.Localization.DefaultLanguage setting (default value is "en"). For modules to land on Vietnamese by default, two things need to happen:

    1. Add vi (or vi-VN) to the enabled languages from Language Management → Languages, with Enabled = true. Without this, vi isn't in RequestLocalizationOptions.SupportedCultures and the middleware won't accept it.

    2. Set the default to vi. The simplest way is appsettings.json:

    "Settings": {
      "Abp.Localization.DefaultLanguage": "vi"
    }
    

    Values under the Settings: section are picked up by ABP's ConfigurationSettingValueProvider automatically. If you prefer, you can change the same setting from Administration → Settings → General (host or per-tenant), or programmatically via ISettingManager.SetGlobalAsync(LocalizationSettingNames.DefaultLanguage, "vi").

    After that, requests without an explicit language preference land on vi, ABP modules use Vietnamese, and the DynamicResourceLocalizer override from the previous reply resolves those keys from your TenantId = null rows.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on September 24, 2026, 12:09
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.