Asp.Net Core 混合全球化與本地化支持

来源:https://www.cnblogs.com/coredx/archive/2020/02/07/12271537.html
-Advertisement-
Play Games

前言 最近的新型冠狀病毒流行讓很多人主動在家隔離,希望疫情能快點消退。武漢加油,中國必勝! Asp.Net Core 提供了內置的網站國際化(全球化與本地化)支持,微軟還內置了基於 resx 資源字元串的國際化服務組件。可以在入門教程中找到相關內容。 但是內置實現方式有一個明顯缺陷,resx 資源是 ...


前言

       最近的新型冠狀病毒流行讓很多人主動在家隔離,希望疫情能快點消退。武漢加油,中國必勝!

       Asp.Net Core 提供了內置的網站國際化(全球化與本地化)支持,微軟還內置了基於 resx 資源字元串的國際化服務組件。可以在入門教程中找到相關內容。

       但是內置實現方式有一個明顯缺陷,resx 資源是要靜態編譯到程式集中的,無法在網站運行中臨時編輯,靈活性較差。幸好我找到了一個基於資料庫資源存儲的組件,這個組件完美解決了 resx 資源不靈活的缺陷,經過適當的設置,可以在第一次查找資源時順便創建資料庫記錄,而我們要做的就是訪問一次相應的網頁,讓組件創建好記錄,然後我們去編輯相應的翻譯欄位並刷新緩存即可。

       但是!又是但是,經過一段時間的使用,發現基於資料庫的方式依然存在缺陷,開發中難免有需要刪除並重建資料庫,初始化環境。這時,之前辛辛苦苦編輯的翻譯就會一起灰飛煙滅 (╯‵□′)╯︵┻━┻ 。而 resx 資源卻完美避開了這個問題,這時我就在想,能不能讓他們同時工作,兼顧靈活性與穩定性,魚與熊掌兼得。

       經過一番摸索,終於得以成功,在此開貼記錄分享。

正文

設置並啟用國際化服務組件

       安裝 Nuget 包 Localization.SqlLocalizer,這個包依賴 EF Core 進行資料庫操作。然後在 Startup 的 ConfigureServices 方法中加入以下代碼註冊  EF Core 上下文:

1 services.AddDbContext<LocalizationModelContext>(options =>
2     {
3         options.UseSqlServer(connectionString);
4     },
5     ServiceLifetime.Singleton,
6     ServiceLifetime.Singleton);

       註冊自製的混合國際化服務:

services.AddMixedLocalization(opts => { opts.ResourcesPath = "Resources"; }, options => options.UseSettings(true, false, true, true));

       註冊請求本地化配置:

 1 services.Configure<RequestLocalizationOptions>(
 2     options =>
 3     {
 4         var cultures =  Configuration.GetSection("Internationalization").GetSection("Cultures")
 5         .Get<List<string>>()
 6         .Select(x => new CultureInfo(x)).ToList();
 7         var supportedCultures = cultures;
 8 
 9         var defaultRequestCulture = cultures.FirstOrDefault() ?? new CultureInfo("zh-CN");
10         options.DefaultRequestCulture = new RequestCulture(culture: defaultRequestCulture, uiCulture: defaultRequestCulture);
11         options.SupportedCultures = supportedCultures;
12         options.SupportedUICultures = supportedCultures;
13     });

       註冊 MVC 本地化服務:

1 services.AddMvc()
2     //註冊視圖本地化服務
3     .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix, opts => { opts.ResourcesPath = "Resources"; })
4     //註冊數據註解本地化服務
5     .AddDataAnnotationsLocalization();

       在 appsettings.json 的根對象節點添加屬性:

"Internationalization": {
  "Cultures": [
    "zh-CN",
    "en-US"
  ]
}

       在某個控制器加入以下動作:

 1 public IActionResult SetLanguage(string lang)
 2 {
 3     var returnUrl = HttpContext.RequestReferer() ?? "/Home";
 4 
 5     Response.Cookies.Append(
 6         CookieRequestCultureProvider.DefaultCookieName,
 7         CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(lang)),
 8         new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) }
 9     );
10 
11     return Redirect(returnUrl);
12 }

       準備一個頁面調用這個動作切換語言。然後,大功告成!

       這個自製服務遵循以下規則:優先查找基於 resx 資源的翻譯數據,如果找到則直接使用,如果沒有找到,再去基於資料庫的資源中查找,如果找到則正常使用,如果沒有找到則按照對服務的配置決定是否在資料庫中生成記錄並使用。

自製混合國際化服務組件的實現

       本體:

  1 public interface IMiscibleStringLocalizerFactory : IStringLocalizerFactory
  2 {
  3 }
  4 
  5 public class MiscibleResourceManagerStringLocalizerFactory : ResourceManagerStringLocalizerFactory, IMiscibleStringLocalizerFactory
  6 {
  7     public MiscibleResourceManagerStringLocalizerFactory(IOptions<LocalizationOptions> localizationOptions, ILoggerFactory loggerFactory) : base(localizationOptions, loggerFactory)
  8     {
  9     }
 10 }
 11 
 12 public class MiscibleSqlStringLocalizerFactory : SqlStringLocalizerFactory, IStringExtendedLocalizerFactory, IMiscibleStringLocalizerFactory
 13 {
 14     public MiscibleSqlStringLocalizerFactory(LocalizationModelContext context, DevelopmentSetup developmentSetup, IOptions<SqlLocalizationOptions> localizationOptions) : base(context, developmentSetup, localizationOptions)
 15     {
 16     }
 17 }
 18 
 19 public class MixedStringLocalizerFactory : IStringLocalizerFactory
 20 {
 21     private readonly IEnumerable<IMiscibleStringLocalizerFactory> _localizerFactories;
 22     private readonly ILogger<MixedStringLocalizerFactory> _logger;
 23 
 24     public MixedStringLocalizerFactory(IEnumerable<IMiscibleStringLocalizerFactory> localizerFactories, ILogger<MixedStringLocalizerFactory> logger)
 25     {
 26         _localizerFactories = localizerFactories;
 27         _logger = logger;
 28     }
 29 
 30     public IStringLocalizer Create(string baseName, string location)
 31     {
 32         return new MixedStringLocalizer(_localizerFactories.Select(x =>
 33         {
 34             try
 35             {
 36                 return x.Create(baseName, location);
 37             }
 38             catch (Exception ex)
 39             {
 40                 _logger.LogError(ex, ex.Message);
 41                 return null;
 42             }
 43         }));
 44     }
 45 
 46     public IStringLocalizer Create(Type resourceSource)
 47     {
 48         return new MixedStringLocalizer(_localizerFactories.Select(x =>
 49         {
 50             try
 51             {
 52                 return x.Create(resourceSource);
 53             }
 54             catch (Exception ex)
 55             {
 56                 _logger.LogError(ex, ex.Message);
 57                 return null;
 58             }
 59         }));
 60     }
 61 }
 62 
 63 public class MixedStringLocalizer : IStringLocalizer
 64 {
 65     private readonly IEnumerable<IStringLocalizer> _stringLocalizers;
 66 
 67     public MixedStringLocalizer(IEnumerable<IStringLocalizer> stringLocalizers)
 68     {
 69         _stringLocalizers = stringLocalizers;
 70     }
 71 
 72     public virtual LocalizedString this[string name]
 73     {
 74         get
 75         {
 76             var localizer = _stringLocalizers.SingleOrDefault(x => x is ResourceManagerStringLocalizer);
 77             var result = localizer?[name];
 78             if (!(result?.ResourceNotFound ?? true)) return result;
 79 
 80             localizer = _stringLocalizers.SingleOrDefault(x => x is SqlStringLocalizer) ?? throw new InvalidOperationException($"沒有找到可用的 {nameof(IStringLocalizer)}");
 81             result = localizer[name];
 82             return result;
 83         }
 84     }
 85 
 86     public virtual LocalizedString this[string name, params object[] arguments]
 87     {
 88         get
 89         {
 90             var localizer = _stringLocalizers.SingleOrDefault(x => x is ResourceManagerStringLocalizer);
 91             var result = localizer?[name, arguments];
 92             if (!(result?.ResourceNotFound ?? true)) return result;
 93 
 94             localizer = _stringLocalizers.SingleOrDefault(x => x is SqlStringLocalizer) ?? throw new InvalidOperationException($"沒有找到可用的 {nameof(IStringLocalizer)}");
 95             result = localizer[name, arguments];
 96             return result;
 97         }
 98     }
 99 
100     public virtual IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)
101     {
102         var localizer = _stringLocalizers.SingleOrDefault(x => x is ResourceManagerStringLocalizer);
103         var result = localizer?.GetAllStrings(includeParentCultures);
104         if (!(result?.Any(x => x.ResourceNotFound) ?? true)) return result;
105 
106         localizer = _stringLocalizers.SingleOrDefault(x => x is SqlStringLocalizer) ?? throw new InvalidOperationException($"沒有找到可用的 {nameof(IStringLocalizer)}");
107         result = localizer?.GetAllStrings(includeParentCultures);
108         return result;
109     }
110 
111     [Obsolete]
112     public virtual IStringLocalizer WithCulture(CultureInfo culture)
113     {
114         throw new NotImplementedException();
115     }
116 }
117 
118 public class MixedStringLocalizer<T> : MixedStringLocalizer, IStringLocalizer<T>
119 {
120     public MixedStringLocalizer(IEnumerable<IStringLocalizer> stringLocalizers) : base(stringLocalizers)
121     {
122     }
123 
124     public override LocalizedString this[string name] => base[name];
125 
126     public override LocalizedString this[string name, params object[] arguments] => base[name, arguments];
127 
128     public override IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)
129     {
130         return base.GetAllStrings(includeParentCultures);
131     }
132 
133     [Obsolete]
134     public override IStringLocalizer WithCulture(CultureInfo culture)
135     {
136         throw new NotImplementedException();
137     }
138 }
View Code

       註冊輔助擴展:

 1 public static class MixedLocalizationServiceCollectionExtensions
 2 {
 3     public static IServiceCollection AddMixedLocalization(
 4         this IServiceCollection services,
 5         Action<LocalizationOptions> setupBuiltInAction = null,
 6         Action<SqlLocalizationOptions> setupSqlAction = null)
 7     {
 8         if (services == null) throw new ArgumentNullException(nameof(services));
 9 
10         services.AddSingleton<IMiscibleStringLocalizerFactory, MiscibleResourceManagerStringLocalizerFactory>();
11 
12         services.AddSingleton<IMiscibleStringLocalizerFactory, MiscibleSqlStringLocalizerFactory>();
13         services.TryAddSingleton<IStringExtendedLocalizerFactory, MiscibleSqlStringLocalizerFactory>();
14         services.TryAddSingleton<DevelopmentSetup>();
15 
16         services.TryAddTransient(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));
17 
18         services.AddSingleton<IStringLocalizerFactory, MixedStringLocalizerFactory>();
19 
20         if (setupBuiltInAction != null) services.Configure(setupBuiltInAction);
21         if (setupSqlAction != null) services.Configure(setupSqlAction);
22 
23         return services;
24     }
25 }
View Code

 

      原理簡介

       服務組件利用了 DI 中可以為同一個服務類型註冊多個實現類型的特性,併在構造方法中註入服務集合,便可以將註冊的所有實現註入組件同時使用。要註意主控服務和工作服務不能註冊為同一個服務類型,不然會導致迴圈依賴。 內置的國際化框架已經指明瞭依賴 IStringLocalizerFatory ,必須將主控服務註冊為 IStringLocalizerFatory,工作服只能註冊為其他類型,不過依然要實現 IStringLocalizerFatory,所以最方便的辦法就是定義一個新服務類型作為工作服務類型並繼承 IStringLocalizerFatory。

       想直接體驗效果的可以到文章底部訪問我的 Github 下載項目並運行。

結語

       這個組件是在計劃集成 IdentityServer4 管理面板時發現那個組件使用了 resx 的翻譯,而我的現存項目已經使用了資料庫翻譯存儲,兩者又不相互相容的情況下產生的想法。

       當時 Localization.SqlLocalizer 舊版本(2.0.4)還存在無法在視圖本地化時正常創建資料庫記錄的問題,也是我調試修複了 bug 並向原作者提交了拉取請求,原作者也在合併了我的修複後發佈了新版本。

       這次在集成 IdentityServer4 管理面板時又發現了 bug,正準備聯繫原作者看怎麼處理。

 

       轉載請完整保留以下內容併在顯眼位置標註,未經授權刪除以下內容進行轉載盜用的,保留追究法律責任的權利!

  本文地址:https://www.cnblogs.com/coredx/p/12271537.html

  完整源代碼:Github

  裡面有各種小東西,這隻是其中之一,不嫌棄的話可以Star一下。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在MainModule里 Design 模式 1]RecallLastTheme 設為True 2]Theme選一個皮膚 總共有 classicgraycrispneptunetritontriton.modifiedariagraphite 8個預設皮膚 uses uniStrUtils, pro ...
  • SpringBoot官方不推薦使用jsp,因為jsp不好發揮SpringBoot的特性。官方推薦使用模板引擎代替jsp,現在很多公司都使用FreeMarker來作為SpringBoot的視圖。 SpringBoot對動態頁面的支持很好,為多種模板引擎提供了預設配置,常用的有: Thymeleaf F ...
  • Lamda表達式學習筆記一 一、Lamda語法詮釋 三傻大鬧寶萊塢的主人公蘭徹說的一句話讓我映像深刻:用簡單的語言來表達同樣的意 我並不是說書上的定義怎麼怎麼不對,而是應該理解書本上的定義,並用簡單的話語描述! 那麼正題來了,lamda表達式是什麼? 定義:lamda表達式是一個可傳遞的代碼塊,可以 ...
  • 1.單調隊列簡介: 單調隊列是一種數據結構,類似如單調棧,但裡面的元素必須在一個區間內,如果“過時”就要出隊。所以,單調隊列可以在兩端進行出隊,但只能再隊尾入隊。按此性質,傳統的隊列已無法滿足需求,需要使用雙端隊列,再C++的STL里,雙端隊列定義在deque里: #include <deque> ...
  • 效果圖: 左邊的樹 的樹結點 ,通過 結點名 與 右 側TabSheet名 一致時,顯示 相關頁面。 這是相關 源代碼 procedure TMainForm.UniFormCreate(Sender: TObject); var I: Integer; begin for I := UniPage ...
  • 壁紙的選擇其實很大程度上能看出電腦主人的內心世界,有的人喜歡風景,有的人喜歡星空,有的人喜歡美女,有的人喜歡動物。 ...
  • 前言 之前終於在Linux上部署好了.NetCore站點,但是這個站點非常“脆弱”。當我的ssh連接關閉或者我想在當前連接執行其他命令時候就必須關閉dotnet站點的執行程式。這顯然不是我想要達到的效果,還好知道有所謂的守護進程這個東西,大多數人都是推薦採取Supervisor來進行Linux上的應 ...
  • 首先我們先來說說這個ONNX ONNX是一種針對機器學習所設計的開放式的文件格式,用於存儲訓練好的模型。它使得不同的人工智慧框架(如Pytorch, MXNet)可以採用相同格式存儲模型數據並交互。 ONNX的規範及代碼主要由微軟,亞馬遜 ,Facebook 和 IBM 等公司共同開發,以開放源代碼 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...