ABP vNext 多语言与本地化:动态切换、资源继承与热更新

发布于:2025-06-15 ⋅ 阅读:(17) ⋅ 点赞:(0)

🚀 ABP vNext 多语言与本地化:动态切换、资源继承与热更新 🎉


📩 用户请求
🛠️ UseRequestLocalization
🔍 QueryString / Cookie / Accept-Language
🌍 确定 CultureInfo
📦 加载 JSON 资源 (嵌入/物理)
🔤 注入 IStringLocalizer 渲染文本
🔄 资源回退链: Shared → Validation → UI → Custom
🖥️ Blazor / Razor / JS 前端渲染


📌 前置 using 声明

using System.Globalization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Volo.Abp.Localization;
using Volo.Abp.VirtualFileSystem;

🚀 模块化资源 & 默认文化

📁 目录结构 & 嵌套分组示例

Localization/
└── YourModule/
    ├── en.json
    └── zh-Hans.json

示例(zh-Hans.json)

{
  "User": {
    "Greeting": "你好,{0}",
    "Profile": "个人信息"
  },
  "Validation": {
    "FieldIsRequired": "这是必填字段"
  }
}

📝 注册资源:DefaultResourceCulture vs SupportedCultures

Configure<AbpLocalizationOptions>(options =>
{
    options.Resources
        // "zh-Hans" 是 DefaultResourceCulture(资源回退基准),非 SupportedCultures
        .Add<YourResource>("zh-Hans")
        // 回退链注释:SharedResource → AbpValidationResource → AbpUiResource → YourResource
        .AddBaseTypes(
            typeof(SharedResource),
            typeof(AbpValidationResource),
            typeof(AbpUiResource))
        .AddVirtualJson("/Localization/YourModule");

    // 列出语言供管道使用(SupportedCultures 在 RequestLocalizationOptions 中配置)
    options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文", isDefault: true));
    options.Languages.Add(new LanguageInfo("en",      "en",      "English"));
});

🔧 请求本地化管道 & 中间件顺序

builder.Services.AddLocalization();

var cultures = new[] { new CultureInfo("zh-Hans"), new CultureInfo("en") };

builder.Services.Configure<RequestLocalizationOptions>(options =>
{
    options.DefaultRequestCulture     = new RequestCulture("zh-Hans");
    options.SupportedCultures         = cultures;
    options.SupportedUICultures       = cultures;
    options.FallBackToParentCultures   = true;  // 自动回退至父文化
    options.FallBackToParentUICultures = true;

    // 先查 QueryString → 再 Cookie → 默认 Accept-Language Header
    options.RequestCultureProviders.Insert(0, new QueryStringRequestCultureProvider());
    options.RequestCultureProviders.Insert(1, new CookieRequestCultureProvider());
});

管道中顺序

app.UseRequestLocalization();    // 必须在 Routing/Auth 之前
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
// app.UseAbpRequestLocalization(); // 如使用 ABP 扩展,可替换或调整位置

🌐 前端多端渲染示例

1. Razor / MVC

@inject IStringLocalizer<YourResource> L

<h1>@L["User:Greeting", "张三"]</h1>
[LocalizationResourceName("YourResource")]
public class HomeController : AbpController {}

2. Blazor Server / WASM 完整 CultureSwitcher

@inject IStringLocalizer<YourResource> L
@inject IJSRuntime JSRuntime

<select @onchange="OnCultureChanged">
    <option value="zh-Hans">🇨🇳 简体中文</option>
    <option value="en">🇬🇧 English</option>
</select>

<h3>@L["Welcome"]</h3>

@code {
    private async Task OnCultureChanged(ChangeEventArgs e)
    {
        var culture = e.Value?.ToString()!;
        var cookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
        // 写入 Cookie
        await JSRuntime.InvokeVoidAsync("blazorCulture.set", cookieValue);

        // 更新 .NET 线程文化
        var ci = new CultureInfo(culture);
        CultureInfo.DefaultThreadCurrentCulture    = ci;
        CultureInfo.DefaultThreadCurrentUICulture = ci;

        StateHasChanged(); // 强制刷新 UI
    }
}

注意:在 _Host.cshtmlindex.html 中添加:

<script>
  window.blazorCulture = {
    set: function (cookieValue) {
      document.cookie = ".AspNetCore.Culture=" + cookieValue + ";path=/;";
    }
  };
</script>

🔄 运行时切换 & 安全 Cookie

public IActionResult SetLanguage(string culture)
{
    var cookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
    Response.Cookies.Append(
        CookieRequestCultureProvider.DefaultCookieName,
        cookieValue,
        new CookieOptions {
            Path       = "/",
            Expires    = DateTimeOffset.UtcNow.AddYears(1),
            HttpOnly   = true,
            Secure     = true,
            SameSite   = SameSiteMode.Lax
        });

    return Redirect(Request.Headers["Referer"].ToString());
}

🔥 开发环境 JSON 热更新

Configure<AbpVirtualFileSystemOptions>(options =>
{
    if (env.IsDevelopment())
    {
        // 泛型为模块的 ApplicationModule 类型
        options.FileSets.ReplaceEmbeddedByPhysical<YourModuleApplicationModule>(
            Path.Combine(env.ContentRootPath, "Localization/YourModule"));
    }
});

生产环境:移除此替换,使用嵌入资源提高 I/O 性能。


🔗 资源继承与回退顺序

  1. SharedResource(业务/共用文案)
  2. AbpValidationResource(验证提示)
  3. AbpUiResource(通用 UI 文案)
  4. YourResource(模块专属文案)
options.Resources
    .Add<YourResource>("zh-Hans")
    .AddBaseTypes(
        typeof(SharedResource),
        typeof(AbpValidationResource),
        typeof(AbpUiResource));

🧪 集成测试覆盖示例

[Fact]
public async Task Localizer_Should_Return_Correct_Text_By_Cookie()
{
    var host = await new WebHostBuilder()
        .UseStartup<Startup>()
        .StartAsync();

    var client = host.GetTestClient();
    client.DefaultRequestHeaders.Add("Cookie", ".AspNetCore.Culture=c=zh-Hans|uic=zh-Hans");
    var response = await client.GetAsync("/Home/Index");
    var html = await response.Content.ReadAsStringAsync();
    Assert.Contains("欢迎使用 ABP", html);
}

补充:可再增加 QueryString 和 Accept-Language 的测试场景 🧪


📈 监控 & 日志埋点

builder.Services.Configure<RequestLocalizationOptions>(opts =>
{
    opts.Events = new RequestLocalizationEvents
    {
        OnRequestCultureResolved = ctx =>
        {
            logger.LogInformation(
                "🌐 Culture resolved: {Culture} via {Provider}",
                ctx.RequestCulture.Culture,
                ctx.Provider.GetType().Name);
            return Task.CompletedTask;
        }
    };
});

📅 日期/数字格式化示例

@inject IStringLocalizer<YourResource> L

<p>📆 @DateTime.Now.ToString("D", CultureInfo.CurrentCulture)</p>
<p>💰 @(1234567.89.ToString("N", CultureInfo.CurrentCulture))</p>

💻 Angular/React 前端示例

import { localization } from '@abp/ng.core'; // Angular
const l = await localization.getResource('YourResource');
console.log(l.User.Greeting('王五')); // 你好,王五

import { localization as localeR } from '@abp/react'; // React
const l2 = await localeR.getResource('YourResource');
console.log(l2.User.Profile); // 个人信息

或手动:

async function setCulture(culture) {
  document.cookie = ".AspNetCore.Culture=c=" + culture + "|uic=" + culture + ";path=/;";
  window.location.reload();
}

✅ 总结一览

能力 实现状态 备注
模块化资源 DefaultResourceCulture + AddVirtualJson
请求本地化管道 QueryString/Cookie/Accept-Language + UseRequestLocalization
UI 渲染 Razor/MVC + Blazor/CultureSwitcher + JS 前端支持
运行时切换 安全 Cookie 配置
JSON 热更新 ✅(开发) ReplaceEmbeddedByPhysical
资源继承 & 回退 Shared → Validation → UI → YourResource
集成测试覆盖 TestServer + Cookie/QueryString/Accept-Language 测试
日志 & 监控 RequestLocalizationEvents.OnRequestCultureResolved
自动回退父文化 FallBackToParentCultures/UICultures
多前端框架支持 Angular/React + ABP JS API 示例