[UWP]本地化入门
1. 前言
上一篇文章介紹了各種WPF本地化的入門知識,這篇文章介紹UWP本地化的入門知識。
2. 使用resw資源文件實現本地化
在以前的XAML平臺,resx資源文件是一種很方便的本地化方案,但在UWP中微軟又再次推薦x:Uid方案,默認的資源文件也變成resw資源文件。雖然后綴名只差了一個字母,但使用方式完全不同。最主要的區別是resw資源文件不會創建對應的Designer.cs類,這就導致本地化的實現方案完全不同。
2.1 在XAML中實現本地化
在XAML中實現本地化的過程很簡單。首先在項目中新建"strings"文件夾,在"strings"文夾下創建"en-US"和"zh-CN"文件夾,并在兩個文件夾中分別添加"Resources.resw"資源文件。最終目錄結構如下:
在zh-CN\Resources.resw和en-US\Resources.resw添加兩個新資源,分別是UsernameTextBox.Width和UsernameTextBox.Header:
在XAML中添加一個TextBox,設置x:Uid為UsernameTextBox,x:Uid將XAML元素和資源文件中的資源進行關聯:
<TextBox x:Uid="UsernameTextBox"/>運行后即可看到UsernameTextBox的Header設置為"用戶名",Width為100。
在“設置\區域和語言”中將"English"設置為默認語言,再次運行應用可看到運行在英語環境下的效果。
這樣基本的本地化功能就實現了。這種本地化方式有如下優點:
- 簡單快速,容易上手
- 語法簡單,不需要Binding等知識
- 可以指定任意屬性進行本地化
- 支持CLR屬性
除此之外,上一篇文章提到的ResXManager也支持Resw資源文件,還可以使用多語言應用工具包對資源文件進行管理,博客園的這篇文章頁對這個工具進行了詳細介紹:
Win10 UWP 開發系列:使用多語言工具包讓應用支持多語言
或者參考這個視頻:
Windows 10 Apps Designing for Global Customers
2.2 關聯到其它資源文件
UI元素默認與Resources.resw進行關聯,如果需要和其它資源文件關聯,可以加上資源文件的路徑。如需要與/OtherResources.resw中的資源關聯,x:Uid的語法如下:
x:Uid="/OtherResources/AddressTextBox"2.3 附加屬性的本地化
對系統提供的附加屬性,資源的名稱語法如下:
UsernameTextBox.Grid.Row對自定義附加屬性,語法稍微復雜一些:
ShowMessageButton.[using:LocalizationDemoUwp]ButtonEx.Content奇怪的是,就這樣直接運行應用會報錯。只有應用這個資源的UI元素已經有這個附加屬性的值才能正常運行,簡單來說就是需要隨便為這個附加屬性設置一個值:
<Button Margin="5" x:Uid="ShowMessageButton" local:ButtonEx.Content="ssssss"/>2.4 其它資源的本地化
除了字符串資源,其它資源的本地化方式不需要設置x:Uid,只需要建立對應語言的目錄結構及命名就可以在XAML中直接引用。如項目中有如下兩張圖片:
在XAML中可以直接通過Images/Flag.png引用。路徑中的"zh-CN"、"en-US"稱為資源限定符,用于支持多種顯示比例、UI 語言、高對比度設置等,具體可參考Load images and assets tailored for scale, theme, high contrast, and others 。
2.5 在代碼里訪問資源
在代碼中訪問資源的代碼如下:
var resourceLoader = ResourceLoader.GetForCurrentView(); var currentLanguage = resourceLoader.GetString("CurrentLanguage"); resourceLoader = ResourceLoader.GetForCurrentView("OtherResources"); var message = resourceLoader.GetString("Message");上面的代碼中,currentLanguage從默認的資源文件Resources.resw中獲取,resourceLoader 無需指定資源文件的名稱;而message 則從OtherResources.resw獲取,resourceLoader 需要指定資源文件的名稱。
如需要使用其它類庫中的資源,代碼如下:
resourceLoader = ResourceLoader.GetForCurrentView("LocalizationDemoUwp.ResourceLibrary/Resources"); currentLanguage = resourceLoader.GetString("CurrentLanguage");雖然語法簡單,但可以看到最大的問題是資源的名稱沒有智能感知和錯誤提示,這樣使用資源很容易出錯。
如上圖所示,對錯誤的資源名稱,ReSharper會有錯誤提示,不過這種構造ResourceLoader的方式已經被標記為Deprecated并提示使用GetForCurrentView獲取ResourceLoader,而使用GetForCurrentView的情況下ReSharper又沒有錯誤提示。不知道ReSharper什么時候才能支持在GetForCurrentView的方式下顯示錯誤提示(我安裝的ReSharper已是最新的2017.2)。
2.6 存在的問題
這個本地化方案雖然簡單,但我覺得很難使用,因為這個方案存在很多問題。
首先是設計時支持,對本地化來說,設計時支持主要包含3部分:
- 在編寫XAML時可以得到資源的智能感知
- 有完整的設計視圖
- 在不同語言之間切換
第一點,沒有,而且寫錯屬性名稱還不會在編譯時報錯,而是用最慘烈的方式呈現:運行時崩潰。
第二點,在Fall Creators Update (16299)以前,沒有,設計視圖一片空白。也可以隨便寫一些內容(如TextBox x:Uid="UsernameTextBox" Header="(here is header)")以輔助設計。但在XAML中寫的任何內容都可能被資源文件覆蓋,無論是文本還是大小、對齊方式或其它所有屬性對XAML的編寫者來說都是不可控的,不到實際運行時根本不清楚UI的最終效果,這就很考驗本地化人員和測試人員。在Fall Creators Update以后終于可以在設計視圖看到本地化的效果,這不得不說是巨大的進步。
第三點,目前來看做不到。
另外,資源管理也是個很麻煩的問題。同一個字符串,如果要對應TextBlock.Text、ContentControl.Content、TextBox.Header,這樣就需要三個資源,造成了冗余,而大量的冗余最終會導致錯誤。
總的來說,這個本地化方案有很多問題,雖然這個方案是微軟推薦的。既然是微軟推薦的,應該是支持最好的,也許是我的用法不對?
接下來在這個方案的基礎上做些改動,希望可以讓本地化更好用。
3. 動態切換語言
不是我太執著動態切換語言,是測試員真的喜歡這個功能,因為不用重啟應用就可以測試到所有語言的UI。
UWP提供了ApplicationLanguages.PrimaryLanguageOverride屬性用于更改語言首選項,即可以改變應用的語言,用法如下:
Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = "zh-CN";這個變更是永久的,但不會對當前UI及一部分系統組件生效,只會影響之后創建的UI元素。更改ApplicationLanguages.PrimaryLanguageOverride,會異步地觸發ResourceContext.QualifierValues的MapChanged事件,可以監聽這個事件并更新UI。這樣就可以實現簡單的動態切換語言功能。
DynamicResources.cs
public class DynamicResources : INotifyPropertyChanged {public DynamicResources(){_defaultContextForCurrentView = ResourceContext.GetForCurrentView();_defaultContextForCurrentView.QualifierValues.MapChanged += async (s, m) =>{await MainPage.Current.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>{OnPropertyChanged("");});};}private ResourceContext _defaultContextForCurrentView;public string Main{get { return ResourceManager.Current.MainResourceMap.GetValue("DynamicResources/Main", _defaultContextForCurrentView).ValueAsString; }}public string Settings{get { return ResourceManager.Current.MainResourceMap.GetValue("DynamicResources/Settings", _defaultContextForCurrentView).ValueAsString; }}public string RestartNote{get { return ResourceManager.Current.MainResourceMap.GetValue("DynamicResources/RestartNote", _defaultContextForCurrentView).ValueAsString; }}public event PropertyChangedEventHandler PropertyChanged;[NotifyPropertyChangedInvocator]protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));} }SettingView.xaml
<Page.Resources><local:DynamicResources x:Key="DynamicResources"/> </Page.Resources> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><StackPanel><ListView x:Name="LanguageListView" Margin="10"><ListViewItem Tag="zh-Hans-CN" Content="中文"/><ListViewItem Tag="en-US" Content="English"/></ListView><TextBlock x:Name="NoteElement" Foreground="#FFF99F00" Margin="20,10" Visibility="Collapsed" Text="{Binding RestartNote,Source={StaticResource DynamicResources}}"/></StackPanel> </Grid>SettingView.xaml.cs
private async void OnLanguageListViewSelectionChanged(object sender, SelectionChangedEventArgs e) {var item = LanguageListView.SelectedItem as ListViewItem;if (item == null)return;ApplicationLanguages.PrimaryLanguageOverride = item.Tag as string;_hasChangedLanguage = true;await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, ShowNoteElement); }private void ShowNoteElement() {NoteElement.Visibility = Visibility.Visible;var appView = Windows.UI.ViewManagement.ApplicationView.GetForCurrentView();appView.Title = (LanguageListView.SelectedItem as ListViewItem)?.Content as string; }只在設置頁面及菜單這些在切換語言時不會重新加載的UI上使用Binding,其它地方不變,這樣簡單的動態切換語言就實現了。運行結果如上,可以看到TextBox右鍵菜單仍未切換語言,需要重新啟動。
UWP默認只安裝電腦對應的語言,這樣可以節省安裝空間,但影響到動態切換語言的功能,要解決這個問題可以參考以下內容(我沒有驗證過):localization - How to always install all localized resources in Windows Store UWP app - Stack Overflow 。
4. 獲得完整的設計視圖
在Fall Creators Update以前為了獲得設計時視圖可以使用索引器。很少有機會在C#中用到索引器,XAML中也很少用到Binding到字符串索引的語法,就是這兩個功能在本地化中幫了大忙。
public class ResourcesStrings {public string this[string key]{get{return ResourceLoader.GetForViewIndependentUse().GetString(key);}} } <Page.Resources><local:ResourcesStrings x:Key="S"/> </Page.Resources> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><TextBlock Text="{Binding Source={StaticResource S},Path=[MainTitle]}" /> </Grid>只需要這樣寫就可以獲得完整的設計時試圖,可是還是沒有解決智能感知和錯誤提示這兩個問題。
在這個方案上也可簡單地實現動態切換語言。
public class ApplicationResources : INotifyPropertyChanged {public ApplicationResources(){DynamicResources = new DynamicResourcesStrings();Resources = new ResourcesStrings();Current = this;}public static ApplicationResources Current { get; private set; }public event PropertyChangedEventHandler PropertyChanged;public DynamicResourcesStrings DynamicResources { get; }public ResourcesStrings Resources { get; }public string Language{get{return ApplicationLanguages.PrimaryLanguageOverride;}set{if (ApplicationLanguages.PrimaryLanguageOverride == value)return;ApplicationLanguages.PrimaryLanguageOverride = value;if (MainPage.Current != null )MainPage.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { OnPropertyChanged(""); });}}[NotifyPropertyChangedInvocator]protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));} } <ListViewItem Content="{Binding Source={StaticResource R},Path=DynamicResources[Main]}"/>不知道為什么,在VisualStudio上有時沒辦法獲得設計時視圖,所有文字都顯示為"Item"。
5. 使用resx資源文件
既然UWP是XAML大家族的一份子,那么應該也可以使用resx資源文件實現本地化,畢竟生成resx對應代碼的是PublicResXFileCodeGenerator,而不是UWP本身。
在“AssemblyInfo.cs”添加如下代碼:
[assembly: NeutralResourcesLanguage("en-US")]這樣就可以在UWP中使用resx資源文件了。實現本地化的代碼和上一篇文章中介紹的WPF本地化方案差不多。
public class ApplicationResources : INotifyPropertyChanged {public static ApplicationResources Current { get; private set; }public ApplicationResources(){Labels = new Labels();if (string.IsNullOrWhiteSpace(ApplicationLanguages.PrimaryLanguageOverride) == false)Language = ApplicationLanguages.PrimaryLanguageOverride;elseLanguage = Windows.System.UserProfile.GlobalizationPreferences.Languages.FirstOrDefault();Current = this;}public Labels Labels { get; set; }public event PropertyChangedEventHandler PropertyChanged;public virtual void OnPropertyChanged([CallerMemberName] string propertyName = null){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}private string _language;/// <summary>/// 獲取或設置 Language 的值/// </summary>public string Language{get { return _language; }set{if (_language == value)return;_language = value;Labels.Culture = new System.Globalization.CultureInfo(_language);ApplicationLanguages.PrimaryLanguageOverride = _language;OnPropertyChanged("");}} }使用體驗和WPF中的resx本地化方案差不多,設計時支持幾乎完美,包括智能感知和錯誤提示,不過還是沒辦法解決系統組件中的本地化問題(如TextBox右鍵菜單)。另外,編譯時會報錯:帶有輸出類型“appcontainerexe”的項目不支持生成操作“EmbeddedResource”。解決方案是不在UWP應用項目中添加resx資源文件,而在類庫中添加resx資源文件,這樣連錯誤都不報了。
不知道Xamarin.Forms是不是也可以這樣實現,畢竟它也是XAML大家族的一員。
6. 結語
研究了這么多resw資源文件的方案,結果還是resx資源文件用得最順手,畢竟這個方案我已經用了很多年(在silverlight中只能用這個方案)。具體使用哪個方案見仁見智。
需要強調的是resx并不能完全替代resw方案,很多時候需要混合使用,例如應用的Display Name可以使用resw輕松實現本地化:
本地化的主題仍有很多內容,這篇文章只打算介紹入門知識,更深入的知識可以參考下面給出的鏈接。
7. 參考
Guidelines for globalization - UWP app developer Microsoft Docs
Localize strings in your UI and app package manifest - UWP app developer Microsoft Docs
Load images and assets tailored for scale, theme, high contrast, and others - UWP app developer Microsoft Docs
快速入門:翻譯 UI 資源 (XAML)
c# - UWP Resource file for languages is not deployed correctly - Stack Overflow
localization - How to always install all localized resources in Windows Store UWP app - Stack Overflow
Win10 UWP 開發系列:使用多語言工具包讓應用支持多語言 - yan_xiaodi - 博客園
Windows 10 Apps Designing for Global Customers
8. 源碼
GitHub - LocalizationDemo
posted on 2018-01-25 23:24 NET未來之路 閱讀(...) 評論(...) 編輯 收藏轉載于:https://www.cnblogs.com/lonelyxmas/p/8353675.html
總結
以上是生活随笔為你收集整理的[UWP]本地化入门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ps -ef | grep 查看进程命令
- 下一篇: SQLServer 事务复制中使用脚本添