[UWP]本地化入門

来源:https://www.cnblogs.com/dino623/archive/2017/12/25/LocalizationUWP.html
-Advertisement-
Play Games

1. 前言 "上一篇文章" 介紹了各種WPF本地化的入門知識,這篇文章介紹UWP本地化的入門知識。 2. 使用resw資源文件實現本地化 在以前的XAML平臺,resx資源文件是一種很方便的本地化方案,但在UWP中微軟又再次推薦x:Uid方案,預設的資源文件也變成resw資源文件。雖然尾碼名只差了一 ...


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本身。

  1. 打開“添加新項”對話框,選中“資源文件(.resw)”,在“名稱”文本框中將文件名稱改為“Labels.resx”,點擊“添加”。
  2. 在“解決方案資源管理器”選中“Labels.resx”,郵件打開“屬性”視圖,“生成操作”選擇“嵌入的資源”。
  3. 將“Labels.resx”複製為“Labels.zh-CN.resx”,打開“Labels.zh-CN.resx”,“訪問修飾符”改為“無代碼生成”。
  4. 在“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;
        else
            Language = 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


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

-Advertisement-
Play Games
更多相關文章
  • 工作中經常需要瞭解plcdb塊的數據!由於工作使用OPC類庫進行通訊,開發,配置,使用都比較麻煩, 特在網上找到一個名為PLCcom.dll的類庫,可以實現PLC讀寫操作,下麵演示C#如何使用PLCcom.dll類庫 首先看一下封裝對PLCcom調用的幫助類: using System;using ...
  • 知識點目錄 >傳送門 首先介紹什麼是抽象類? 抽象類用關鍵字abstract修飾的類就是叫抽象類,抽象類天生的作用就是被繼承的,所以不能實例化,只能被繼承。而且 abstract 關鍵字不能和sealed一起使用,因為sealed是不允許繼承,這樣就是抽象類的意義衝突了。 現在我們知道知道了抽象類長 ...
  • "上一篇文章" 簡單簡單分析了fiddlercore自帶樣例的代碼,本篇文章進入主題,介紹如何使用fiddlercore截獲 HTTPS 流量。 當時學習完樣例代碼後,我覺得結合註釋來抓HTTPS的包應該也很簡單,結果按照註釋的提示修改了下代碼後,還是抓不到,反覆嘗試了很多方法都沒有解決,在goog ...
  • 描述 本篇文章主要概述ASP.NET MVC,具體包括如下內容: 1.MVC模式概述 2.WebForm概述 3.WebForm與MVC區別 4.ASP.NET MVC發展歷程 5.運用程式結構 6.ASP.NET MVC 預設約定 一 MVC模式概述 1. MVC模式運用領域 分析: (1)當前, ...
  • WinMain即(函數運行入口): int WINAPI WinMain (HINSTANCE hinstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int iCmdShow) { MessageBox(NULL,TEXT("Hello,Window 9 ...
  • 最近粗淺的學習了下AutoMapper 這個做對象映射的第三方工具,覺得非常方便使用,所以簡單的總結了一下我能想到的簡單的對象映射的方式。 占時先不考慮源對象成員到目標對象成員的指定映射(即成員名不一致),先準備好兩個類Students-StudentsDto;Teachers-TeachersDt ...
  • “映像劫持”,也被稱為“IFEO”(Image File Execution Options),在WindowsNT架構的系統里,IFEO的本意是為一些在預設系統環境中運行時可能引發錯誤的程式執行體提供特殊的環境設定。當一個可執行程式位於IFEO的控制中時,它的記憶體分配則根據該程式的參數來設定,而W ...
  • Orchard Core Framework:ASP.NET Core 模塊化,多租戶框架 上一篇編寫Orchard Core一分鐘搭建ASP.NET Core CMS ,介紹ASP.NET Core CMS ,Orchard的ASP.NET Core版,同時對應有一個ASP.NET Core框架。 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...