[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
  • 移動開發(一):使用.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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...