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
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...