溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何理解構(gòu)建多語(yǔ)言的WPF應(yīng)用

發(fā)布時(shí)間:2021-10-29 17:56:46 來(lái)源:億速云 閱讀:119 作者:柒染 欄目:編程語(yǔ)言

這篇文章給大家介紹如何理解構(gòu)建多語(yǔ)言的WPF應(yīng)用,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。

下載源代碼 - 84.4 KB

如何理解構(gòu)建多語(yǔ)言的WPF應(yīng)用

導(dǎo)言

在WPF應(yīng)用程序中搭建多語(yǔ)言支持(Multilingual Support)是我最近在做的一件事,對(duì)于不使用英語(yǔ)的人士而言,此舉提高了程序的可用性。實(shí)現(xiàn)起來(lái)要完成以下目標(biāo):

  • 一個(gè)版本容納多種語(yǔ)言. 這就意味著不要?jiǎng)?chuàng)建單獨(dú)的英語(yǔ)版本、法語(yǔ)版本、日語(yǔ)版本等等。 許多電子產(chǎn)品(例如電視和數(shù)碼相機(jī))在同一模塊中支持多語(yǔ)言。你不需要購(gòu)買(mǎi)不同模塊或給軟件打補(bǔ)丁來(lái)得到與默認(rèn)設(shè)置不同的語(yǔ)言

  • 允許在運(yùn)行時(shí)切換接口語(yǔ)言. 這就是說(shuō)不需要關(guān)閉應(yīng)用程序并配置操作系統(tǒng)環(huán)境,一切都交給安裝器。

  • ***運(yùn)行選擇合適語(yǔ)言. W應(yīng)用程序***次運(yùn)行,就把接口語(yǔ)言設(shè)為操作系統(tǒng)的系統(tǒng)語(yǔ)言。這點(diǎn)很有意義--法國(guó)用戶喜歡安裝、運(yùn)行、并馬上使用軟件,而不會(huì)再一個(gè)不熟悉的應(yīng)用程序中找到切換語(yǔ)言的地方。

  • 允許UI可拓展以便翻譯,縮減可能的裁剪文本

此外,具體實(shí)現(xiàn)不應(yīng)該隨著用戶界面的增長(zhǎng)而越來(lái)越來(lái)難實(shí)現(xiàn)。(這是我覺(jué)得最困難的方面。)

所以這篇文章旨在提供一份我開(kāi)發(fā)過(guò)程的詳細(xì)解決方案的大綱,這基于一些我過(guò)去寫(xiě)過(guò)的博客和帖子(這里,這里和這里)。隨著時(shí)間的推移,我將指出例子的相關(guān)部分并告訴你它們是如何適配在一起的。

聲明:例子中的文本是使用自動(dòng)在線翻譯服務(wù)生成的。盡管盡了***的努力來(lái)確保這是盡可能準(zhǔn)確(通過(guò)反向翻譯校對(duì)),有可能翻譯的內(nèi)容有不準(zhǔn)確或錯(cuò)誤。特別是當(dāng)它使用了一個(gè)我不清楚的完全不同的寫(xiě)作系統(tǒng)。

上層概述

這個(gè)為WPF應(yīng)用所設(shè)計(jì)的實(shí)現(xiàn)遵循了一種MVVM(模型-視圖-視圖模型)樣式。語(yǔ)言數(shù)據(jù)存儲(chǔ)在嵌入式XML文件中,這些文件按照需求原則加載到內(nèi)存中,即當(dāng)接口語(yǔ)言發(fā)生改變的時(shí)候。這就是“模型”的部分。.

“視圖模型”具有將當(dāng)前語(yǔ)言的語(yǔ)言數(shù)據(jù)包含到整個(gè)WPF應(yīng)用中的特性。它是XAML文件的集合,XAML文件形成了包含了關(guān)聯(lián)該語(yǔ)言數(shù)據(jù)的“視圖”。為了給一個(gè)特定的文本元素選擇準(zhǔn)確的值,每個(gè)關(guān)聯(lián)都利用具有一個(gè)轉(zhuǎn)換器參數(shù)的用戶定制值轉(zhuǎn)化器來(lái)查找文本鍵值。***,用一個(gè)用戶定制標(biāo)記擴(kuò)展來(lái)抽取這個(gè)關(guān)聯(lián)的細(xì)節(jié),這樣只有鍵值(即轉(zhuǎn)換參數(shù))需要指定。

例子

為了說(shuō)明這個(gè)實(shí)現(xiàn)在實(shí)際中如何工作,我根據(jù)這個(gè)功能創(chuàng)建一個(gè)小的示例應(yīng)用。這個(gè)叫做'RePaver'的應(yīng)用用于清除路徑標(biāo)記表達(dá)式,并具有基本的翻轉(zhuǎn),反轉(zhuǎn),轉(zhuǎn)換和縮放實(shí)際幾何圖形(即無(wú)需圖層轉(zhuǎn)換)的功能。在后臺(tái),該應(yīng)用用正則表達(dá)式抽取路徑段落,并就地對(duì)每個(gè)段落進(jìn)行轉(zhuǎn)換。

為了給你有個(gè)概念,看如下一個(gè)Path表達(dá)式的例子,這個(gè)表達(dá)式一般從導(dǎo)出為XAML格式的矢量圖形文件中得到(這個(gè)路徑表達(dá)式跟一些我目前經(jīng)手項(xiàng)目的路徑?jīng)]有關(guān)系!):

<Path Data="M 470.567,400.914 L 470.578,               390.903 L466.551,390.863 L 472.6,384.876 L472.598,400.888 Z" ... />

如果你復(fù)制黏貼(引號(hào)中的)數(shù)據(jù)表達(dá)式到輸入框中并點(diǎn)擊'Go',可以看到如下的輸出:

M 4,16 L 4,6 L 0,6 L 6,0 L 6,16 Z

在右邊你還能即時(shí)看到形象化的"轉(zhuǎn)換前"和"轉(zhuǎn)換后"的結(jié)果。

你可以任意設(shè)置一些選項(xiàng) - 可以看到這些操作是按照翻轉(zhuǎn)/反轉(zhuǎn) -> 縮放到[根據(jù)邊框尺寸] -> 偏移。當(dāng)然,你可以用不同的語(yǔ)言試一下。

模塊

XML

如上所述,每個(gè)組成用戶界面的文本都保存在每種語(yǔ)言的XML文件的本地化表格中, 并把XML文件當(dāng)做嵌入式資源來(lái)編譯。每條text的父元素包含一個(gè)鍵屬性用來(lái)檢索本地化文本。下面是英語(yǔ)版本定義文件的例子,LangEN.xml:

<LangSettings>     <IsRtl>0</IsRtl>     <MinFontSize>11</MinFontSize>     <HeadingFontSize>16</HeadingFontSize>     <UIText>         <!-- Menu bar -->         <Entry key="TransformLabel">Transform</Entry>         <Entry key="LanguageLabel">Language</Entry>          <!-- Common Operations -->         <Entry key="ApplyLabel">Apply</Entry>         <Entry key="UndoLabel">Undo</Entry>         <Entry key="CancelLabel">Cancel</Entry>          <!-- Section Headings -->         <Entry key="InputLabel">Input</Entry>         <Entry key="OutputLabel">Output</Entry>         <Entry key="InfoLabel">Info</Entry>         <Entry key="TransformPropertiesLabel">Transform</Entry>          <!-- Item Labels -->         <Entry key="FlipRotateLabel">Flip / Rotate</Entry>         <Entry key="OffsetLabel">Offset</Entry>         <Entry key="ScaleToLabel">Scale To</Entry>         <Entry key="DimensionsLabel">Dimensions</Entry>         <Entry key="WidthLabel">Width</Entry>         <Entry key="HeightLabel">Height</Entry>         <Entry key="GoLabel">Go</Entry>      </UIText> </LangSettings>

在上述英文版本示例中,同樣提到了 theIsRtl, MinFontSize, 和HeadingFontSize元素。字體大小用來(lái)決定渲染字體的尺寸,讓字體更易分辨,尤其在顯示日文,韓文和阿拉伯文的時(shí)候。IsRtlel元素決定語(yǔ)言是否從右往左讀(阿拉伯文和希伯來(lái)語(yǔ)就是這樣)。

注意到語(yǔ)言名稱并沒(méi)有出現(xiàn)在上面的XML文件中。這是因?yàn)楸镜鼗Z(yǔ)言名稱放在一個(gè)單獨(dú)的XML文件中定義, LanguageNames.xml:

<LangNames>     <Language code="en">English</Language>     <Language code="ar">???????</Language>     <Language code="de">Deutsch</Language>     <Language code="el">&Epsilon;&lambda;&lambda;&eta;&nu;&iota;&kappa;?</Language>     <Language code="es">Espa&ntilde;ol</Language>     <Language code="fr">Fran&ccedil;ais</Language>     <Language code="he">?????</Language>     <Language code="hi">??????</Language>     <Language code="it">Italiano</Language>     <Language code="jp">日本語(yǔ)</Language>     <Language code="ko">???</Language>     <Language code="ru">Русский</Language>     <Language code="sv">Svenska</Language> </LangNames>

每種語(yǔ)言定義文件的命名遵循這樣一個(gè)慣例, 'LangXX.xml'.其中,XX 與兩個(gè)字母的 ISO語(yǔ)言代碼相對(duì)應(yīng),LanguageNames.xml中的每個(gè)Language元素也該代碼對(duì)應(yīng)。當(dāng)然,這一慣例可以拓展或修改為易于處理本地化(如 en-NZ, en-US),甚至改成三字母的ISO語(yǔ)言代碼。

UILanguageDefn類

在語(yǔ)言定義文件中的當(dāng)前界面語(yǔ)言數(shù)據(jù)被加載進(jìn)一個(gè)內(nèi)部類(UILanguageDefn)中是為了被剩下的應(yīng)用消耗掉。主要的組件是一個(gè)<string, string>類型的字典。這個(gè)字典包含了從文本鍵到局部的文本值的映射。其它的屬性顯示:IsRtl(是否右對(duì)齊),MinFontSize(最小字體大?。┖虷eadingFontSize的值。

當(dāng)你使用這個(gè)類的時(shí)候,局部語(yǔ)言文本會(huì)通過(guò)調(diào)用下面的方法重新取回:

/// <summary>  /// Gets the localised text value for the given key.  /// </summary>  /// <param name="key">The key of the localised text to retrieve.</param>  /// <returns>The localised text if found, otherwise an empty string.</returns>  public string GetTextValue(string key)  {      if (_uiText.ContainsKey(key))          return _uiText[key];       return "";  }

除此之外,UILanguageDefn類有一個(gè)靜態(tài)的從語(yǔ)言編碼到局部的語(yǔ)言名稱的映射(這個(gè)映射是從LanguageNames.xml中加載進(jìn)來(lái)的),例如,“en”和“English”、“sv”和“Svenska”。這被用來(lái)填充到'Language'標(biāo)簽的可用語(yǔ)言列表中,而且被應(yīng)用所支持的權(quán)威的語(yǔ)言列表過(guò)濾。因此,任何不再這個(gè)列表的語(yǔ)言不會(huì)被界面所顯示。即使有一個(gè)語(yǔ)言定義文件或在LanguageNames.xml中有所對(duì)應(yīng)的實(shí)體,也不會(huì)顯示這個(gè)語(yǔ)言。這會(huì)在下面的章節(jié)中進(jìn)一步介紹。

加載數(shù)據(jù)

類`UILanguageDefn`形成模型的一部分。模型里面的第二個(gè)主要的實(shí)體就是應(yīng)用全狀態(tài),`MainWindowModel`。它包含了被整個(gè)應(yīng)用程序使用的`UILanguageDefn`的授權(quán)的實(shí)例。這是在全部界面中獲取文本元素的邊界的實(shí)例。(通過(guò)ViewModel)。

當(dāng)`MainWindowModel`被構(gòu)造時(shí),在加載當(dāng)前語(yǔ)言之前,首先會(huì)注冊(cè)語(yǔ)言列表的授權(quán)和從名字為LanguageNames.xml的資源文件中加載本地化語(yǔ)言。下面通過(guò)例子讓我們看看它是如何工作的:

public class MainWindowModel  {      private UILanguageDefn _languageMapping;       public MainWindowModel(int maxWidth, int maxHeight)      {          RegisterLanguages();          LoadLanguageList();                    //Settings are loaded here, where CurrentLanguageCode is decided.           UpdateLanguageData();      }            public string CurrentLanguageCode      {          get           {              // Retrieves the current language code from              // the Settings model (abstracted away)          }      }            /// <summary>     /// Registers the languages by their corresponding ISO code.      /// </summary>     private void RegisterLanguages()      {          // Defined in Constants class          string[] supportedLanguageCodes =          {              "en", "ar", "de", "el",               "es", "fr", "ko", "hi",               "it", "he", "jp", "ru", "sv"            };                    foreach(string languageCode in supportedLanguageCodes)              UILanguageDefn.RegisterSupportedLanguage(languageCode);      }       /// <summary>     /// Loads the list of available languages from the embedded XML resource.      /// </summary>     private void LoadLanguageList()      {          // Defined in Constants class          string resourcePath = "RePaverModel.LanguageData.LanguageNames.xml";                    System.IO.Stream file =                  Assembly.GetExecutingAssembly().GetManifestResourceStream(resourcePath);           XmlDocument languageNames = new XmlDocument();          languageNames.Load(file);           UILanguageDefn.LoadLanguageNames(languageNames.DocumentElement);      }       /// <summary>     /// Updates the UI language data from that      /// defined in the corresponding language file.      /// </summary>     /// <returns>     public bool UpdateLanguageData()      {          string languageCode = CurrentLanguageCode;          if (String.IsNullOrEmpty(languageCode)) return false;           //This follows a convention for language definition files          //to be named &apos;LangXX.xml&apos; (or &apos;LangXX-XX.xml&apos;)          //where XX is the ISO language code.          string resourcePath =             String.Format(Constants.LanguageDefnPathTemplate, languageCode.ToUpper());          System.IO.Stream file =              Assembly.GetExecutingAssembly().GetManifestResourceStream(resourcePath);           XmlDocument languageData = new XmlDocument();          languageData.Load(file);           _languageMapping = new UILanguageDefn();          _languageMapping.LoadLanguageData(languageData.DocumentElement);           return true;      }  }

你可能注意到上面的代碼提到了第三個(gè)主體 - 設(shè)置狀態(tài)。在眾多可在運(yùn)行時(shí)調(diào)整的設(shè)置中,正是這個(gè)狀態(tài)存儲(chǔ)了當(dāng)前正被使用的接口語(yǔ)言。大多數(shù)的設(shè)置項(xiàng)都在應(yīng)用程序關(guān)閉后保存在磁盤(pán)中,當(dāng)程序再次打開(kāi)時(shí)就重新加載出來(lái)。

然而,如果應(yīng)用程序是***次打開(kāi)(沒(méi)有設(shè)置文件存在),那么這些設(shè)置就會(huì)被設(shè)定為默認(rèn)狀態(tài)。對(duì)于語(yǔ)言來(lái)說(shuō),英語(yǔ)是默認(rèn)的,但這并不是用戶友好(user-friendly)的。所以呢,我們就這樣檢索當(dāng)前系統(tǒng)語(yǔ)言:

CultureInfo.CurrentCulture.TwoLetterISOLanguageName;

找到相應(yīng)的語(yǔ)言后,如果應(yīng)用程序不支持該語(yǔ)言,就讓英語(yǔ)作為默認(rèn)語(yǔ)言。這樣,只要你的本地語(yǔ)言受支持,UI就會(huì)在你程序***次運(yùn)行時(shí)顯示該語(yǔ)言。在Setting model hierarchy中,有如下代碼

public LanguageSettings()  {      // Initialise the default language code.      // In most cases this will be overwritten by the      // restored value from the saved settings, or that of the current culture.      _uiLanguageCode = Constants.DefaultLanguageCode;     //"en"       string languageCode = CultureInfo.CurrentCulture.TwoLetterISOLanguageName;       // If the system language is supported, this will      // ensure that the application first loads      // with the UI displayed in that language.      if (UILanguageDefn.AllSupportedLanguageCodes.Contains(languageCode))          _uiLanguageCode = languageCode;  }

這個(gè)類中的另一種方法,姑且叫做后者吧 (有用戶設(shè)置文件存在的時(shí)候使用),它會(huì)提取保存在文件中的設(shè)置項(xiàng)的值,并把它復(fù)寫(xiě)到_uiLanguageCode.

視圖模型

這里出現(xiàn)了一個(gè)MVVM實(shí)現(xiàn)方法,它不同于WPF和Silverlight應(yīng)用程序中的Model-View-Presenter(MVP).在MVP模式中,我們需要一個(gè)Presenter把當(dāng)前語(yǔ)言的定義(或單個(gè)的本地化后的文本)傳給視圖(View),由視圖負(fù)責(zé)UI中文本的顯示與更新??紤]到我們?cè)谑褂肳PF,文本的更新可以很容易地通過(guò)數(shù)據(jù)綁定來(lái)實(shí)現(xiàn);考慮到語(yǔ)言定義要在整個(gè)應(yīng)用程序(組件或窗體)中使用,我們需要一個(gè)共享類來(lái)保存當(dāng)前語(yǔ)言屬性,這樣當(dāng)進(jìn)行數(shù)據(jù)綁定時(shí),就能使該屬性在UI的任何一部分檢索出來(lái)。

在MVVM模式中,這個(gè)共享類同其他視圖模型(例如MainWindowViewModel)一道,將成為組成視圖模型層的一部分。CommonViewModel這個(gè)類是作為單例模式(Singleton)來(lái)實(shí)現(xiàn),這樣靜態(tài)實(shí)例屬性Current就可以作為一個(gè)綁定的源屬性來(lái)賦值了。非靜態(tài)屬性則通過(guò)綁定的Path屬性來(lái)引用。還有一點(diǎn)很重要,ViewModel實(shí)現(xiàn)了INotifyPropertyChanged的接口,以致UI能在源數(shù)值發(fā)生改變時(shí)自動(dòng)更新綁定。

這里是綁定到UI的CommonViewModel屬性,UILanguageDefn類給出了數(shù)據(jù)的定義:

/// <summary>  /// Gets or sets the language definition used by the entire interface.  /// </summary>  /// <value>The language definition.</value>  public UILanguageDefn LanguageDefn  {      get { return _languageDefn; }      set      {          if (_languageDefn != value)          {              _languageDefn = value;              OnPropertyChanged("LanguageDefn");              OnPropertyChanged("HeadingFontSize");              OnPropertyChanged("MinFontSize");              OnPropertyChanged("IsRightToLeft");          }      }  }   public double HeadingFontSize  {      get       {          if (_languageDefn != null)              return (double)_languageDefn.HeadingFontSize;           return (double)UILanguageDefn.DefaultHeadingFontSize;      }  }   public double MinFontSize  {      get      {          if (_languageDefn != null)              return (double)_languageDefn.MinFontSize;           return (double)UILanguageDefn.DefaultMinFontSize;      }  }   public bool IsRightToLeft  {      get      {          if (_languageDefn != null)              return _languageDefn.IsRightToLeft;           return false;      }  }

MainWindowViewModel處在ViewModel架構(gòu)最前端, 負(fù)責(zé)在MainWindowModel值發(fā)生變化時(shí),更新CommonViewModel中的當(dāng)前語(yǔ)言:

/// <summary>  /// Refreshes the UI text to display in the current language.  /// </summary>  public void RefreshUILanguage()  {      _model.UpdateLanguageData();      CommonViewModel.Current.LanguageDefn = _model.CurrentLanguage;       //Notify any other internal logic to prompt a refresh (as necessary)      if (LanguageChanged != null)          LanguageChanged(this, new EventArgs());  }

視圖

正如我所提到的,本地化文本通過(guò)數(shù)據(jù)綁定顯示到視圖中。然而WPF自身并不知道如何處理UILanguageDefn類,更不用說(shuō)提取合適的本地化文本值。這也是***一個(gè)難題。

值轉(zhuǎn)換器

請(qǐng)記住,CommonViewModel.Current.LanguageDefn是一個(gè)UILaunguageDefn,不是TextBlock的Text屬性期待的一個(gè)字符串。因此,此時(shí)需要一個(gè)值轉(zhuǎn)換器來(lái)完成這項(xiàng)轉(zhuǎn)換工作。這個(gè)值轉(zhuǎn)換器使用ConverterParameter來(lái)指定創(chuàng)建查找關(guān)鍵字,用來(lái)恢復(fù)來(lái)自UILanguage實(shí)例中局部符合條件的文本。記住,當(dāng)接口改變了,UILanuageDefn也改變。

這項(xiàng)工作的優(yōu)點(diǎn)在于對(duì)每一段局限在接口當(dāng)中的文本,符合條件的元素需要被添加到language XML文件,確保ConverterParameter和元素名稱匹配。此外不需要定義任何額外的屬性&mdash;&mdash;不管是在視圖層,UILanguageDefn,還是在模型層的其他部分。

這個(gè)converter相對(duì)簡(jiǎn)單. 只需在類級(jí)別上指定 IValueConverter (在System.Windows.Data中)的 ValueConversion 屬性:

[ValueConversion(typeof(UILanguageDefn), typeof(string))]

并且實(shí)現(xiàn)類似如下的函數(shù) Convert :

public object Convert(object value, Type targetType,                         object parameter, CultureInfo culture)  {      string key = parameter as string;      UILanguageDefn defn = value as UILanguageDefn;       if (defn == null || key == null) return "";       return defn.GetTextValue(key);  }

綁定

現(xiàn)在我們獲得了了一個(gè) value converter, 我們可以將它放置在一個(gè) Binding 表達(dá)式中:

<TextBlock Text="{Binding Path=LanguageDefn,      Converter={StaticResource UIText}, ConverterParameter=ApplyLabel,      Source={x:Static vm:CommonViewModel.Current}}" />

如果想要它工作, 這個(gè) XML 的 命名空間必須設(shè)置為 vm(指向 ViewModel的命名空間),并且 UIText 的資源需要被定義 (假設(shè)conv 是這個(gè) value converter 的 XML 的命名空間):

zlt;conv:UITextLookupConverter x:Key="UIText" />

簡(jiǎn)單明了&mdash;&mdash;自定義標(biāo)記擴(kuò)展

如果你當(dāng)前的狀態(tài)(像我一樣)又想要愉快的方式,在大多數(shù)的XAML文件中的長(zhǎng)綁定表達(dá)式里,你發(fā)現(xiàn)它變得乏味,是同一樣?xùn)|西的重復(fù)。甚至不考慮重命名類或者把屬性作為重構(gòu)的一部分!

當(dāng)然,有一種方式能使其更簡(jiǎn)潔,考慮到這些綁定之間的唯一變化就是ConverterParameter。解決方案是使用使用自定義標(biāo)記擴(kuò)展。

為了做到這一點(diǎn),自定義標(biāo)記擴(kuò)展是一個(gè)簡(jiǎn)單的類,它派生自MarkupExtension(在System.Windows.Markup),按照慣例被命名為[name]Extension。在其核心處,關(guān)鍵點(diǎn)是需要重載ProvideValue方法。但是這該怎么做呢?

自定義標(biāo)記拓展的重點(diǎn)就是在XAML中寫(xiě)下類似這樣的代碼:

<TextBlock Text="{ext:LocalisedText Key=ApplyLabel}" />

因此,自定義拓展被稱作LocalisedTextExtension,并添加一個(gè)Key,它的類型是public string.因?yàn)樵诤笈_(tái)中,綁定一直處于使用狀態(tài),所以我創(chuàng)建了一個(gè)private 綁定域,并從構(gòu)造器中實(shí)例化它 :

public LocalisedTextExtension()  {      _lookupBinding = UITextLookupConverter.CreateBinding("");  }

而靜態(tài)的CreateBinding方法定義在值轉(zhuǎn)換器(value converter)中:

public static Binding CreateBinding(string key)  {      Binding languageBinding = new Binding("LanguageDefn")      {          Source = CommonViewModel.Current,          Converter = _sharedConverter,          ConverterParameter = key,      };      return languageBinding;  }

所以定義好了Binding后,可以通過(guò)ConverterParameter參數(shù)來(lái)獲取和設(shè)置Key屬性的值。這也使得ProvideValue方法可以大展身手:

public override object ProvideValue(IServiceProvider serviceProvider)  {      return _lookupBinding.ProvideValue(serviceProvider);  }

而一個(gè)Binding是一個(gè)MarkupExtension,所以它有自己的可以調(diào)用的ProvideValue方法。

Rinse and Repeat - 字體大小與流方向

某些語(yǔ)言的字符集包含十分復(fù)雜的圖形元素,以致在拉丁文可以辨認(rèn)的字符大小,用來(lái)顯示這些語(yǔ)言的時(shí)候,變得模糊不清了。你注意到CommonViewModel提供了HeadingFontSize和MinFontSize屬性。這就為本地化標(biāo)題和剩余的本地化文本相應(yīng)地提供了字體大小。例如日文的字體大小就大于英文。

幸運(yùn)的是,使用類似下面的這個(gè)模式就可以把上述的文字尺寸綁定到共享的樣式中,而不需要值轉(zhuǎn)換器:

<Style TargetType="{x:Type TextBlock}">      <Setter Property="FontSize" Value="{Binding Path=MinFontSize,              Source={x:Static vm:CommonViewModel.Current}}" />      <!-- Remaining setters ... -->  </Style>

下圖顯示的是兩個(gè)同樣界面不同語(yǔ)言下的差異:

如何理解構(gòu)建多語(yǔ)言的WPF應(yīng)用

也有一些語(yǔ)言是從右向左讀的,例如阿拉伯語(yǔ)和希伯來(lái)語(yǔ)。為了讓UI正確的定位到這些語(yǔ)言,反轉(zhuǎn)接口是有意義的,否則會(huì)帶來(lái)一些混淆,如果在使用程序的時(shí)候讀取的順序和邏輯的順序不一致。

如何理解構(gòu)建多語(yǔ)言的WPF應(yīng)用

幸運(yùn)的是,WPF有一個(gè)方便的屬性可以完成反轉(zhuǎn)整個(gè)UI的艱苦工作:

FrameworkElement.FlowDirection

是什么讓這個(gè)功能相當(dāng)強(qiáng)大,我只需要綁定一個(gè)包含在主窗口內(nèi)的根級(jí)別控件,因?yàn)檫@個(gè)值是由它下面的每個(gè)FrameworkElement的在視覺(jué)層次繼承。綁定僅僅需要查看CommonViewModel的IsRightToLeft屬性,轉(zhuǎn)換到(通過(guò)其他的值轉(zhuǎn)換器)FlowDirection的枚舉值。自定義的標(biāo)記擴(kuò)展被創(chuàng)建,遵循以前類似的模板,簡(jiǎn)化為XAML:

<Window x:Class="RePaver.UI.MainWindow" ... >      <DockPanel FlowDirection="{ext:LocalisedFlowDirection}">          <!-- Contents -->      </DockPanel>  </Window>

鑒于到上述功能的強(qiáng)大,這里仍然要考慮一些陷阱和要點(diǎn):

  • 自定義面板自動(dòng)反轉(zhuǎn)布局,所以你不需要?jiǎng)?chuàng)建一個(gè)IsRevered屬性(或者類似的)或者按照你的估算調(diào)整ArrangeOverride。

  • 位圖和形狀(如線路)是反轉(zhuǎn)的。如果您想要保留這些,呈現(xiàn)獨(dú)立的流向(如公司的logo或者商標(biāo)),那么你需要重寫(xiě)FlowDirection,設(shè)置它為L(zhǎng)eftToRight。

  • 如果接口有RightToLeft的FlowDirection,而元素(如Image)具有LeftToRight的FlowDirection,那么元素的Margin會(huì)以RightToLeft的方式展示。由于Padding展示在元素內(nèi)部可視層次,所以一個(gè)padding將會(huì)以LeftToRight的方式展示。

  • TextBoxes包含語(yǔ)言恒定的數(shù)據(jù),應(yīng)當(dāng)將FlowDirection設(shè)置為L(zhǎng)eftToRight。理想情況下,此屬性應(yīng)設(shè)置為盡量減少重復(fù)并保證一致性的風(fēng)格。

所以,下面就是趕時(shí)髦的“處理后”的截圖:

如何理解構(gòu)建多語(yǔ)言的WPF應(yīng)用

注意路徑,旋轉(zhuǎn)選擇控件,輸入輸出文本框是以從左至右的方式展示,這與語(yǔ)言無(wú)關(guān)。這是因?yàn)檫@些元素是特定的問(wèn)題區(qū)域,如果它們以從右至左的方式展示,就沒(méi)有道理了(可能會(huì)引起誤解)。

總結(jié)

現(xiàn)在明白了&mdash;&mdash;一個(gè)局部的WPF應(yīng)用程序可以在運(yùn)行時(shí)動(dòng)態(tài)地改變UI。***次運(yùn)行它是在法語(yǔ)的本地計(jì)算機(jī)環(huán)境中,瞧, il est affich&eacute; en Fran&ccedil;ais. 它們都來(lái)自同一種語(yǔ)言版本。

***一個(gè)要點(diǎn)需要注意,這里不做詳細(xì)介紹,整個(gè)UI布局以流體方式布局,這樣的布局會(huì)自動(dòng)調(diào)整以適應(yīng)內(nèi)容。 而不是顯式地設(shè)置寬度和高度, 網(wǎng)格的行/列定義,等等。這些都是“自動(dòng)”為左的,同時(shí)還可以定義最小和***值。這是很普通的實(shí)例中***的一個(gè)(而不是特定的本地化), 但當(dāng)切換語(yǔ)言的時(shí)候,不允許這樣的實(shí)例真的顯示出來(lái)。

后記

軟件開(kāi)發(fā)中本地化是一個(gè)熱門(mén)的話題,理所當(dāng)然,我也不是唯一一個(gè)寫(xiě)這方面的人。事實(shí)上,我也發(fā)現(xiàn)了一些人在做同樣的事:

  • Sebastian Przybylski (article) 也把UI文本存儲(chǔ)在XML文件作為嵌入資源,而把XAML直接綁定到XML資源上而不是通過(guò)ViewModel.

  • David Sleeckx (article) 使用自定義標(biāo)記拓展來(lái)檢索本地緩存的翻譯文本,或者調(diào)用Google語(yǔ)言API來(lái)實(shí)現(xiàn)實(shí)時(shí)翻譯。

  • 'SeriousM' 在CodePlex上更新了 WPF本地化拓展 . 它是通過(guò)提取資源文件/資源程序集中的本地化文本(或其他值)來(lái)實(shí)現(xiàn)的。

顯然,實(shí)現(xiàn)WPF程序的本地化有很多種選擇,它們并不互斥。根據(jù)你的權(quán)衡,我所提到的實(shí)現(xiàn)方法僅適用于你程序的部分,另一部分則會(huì)出現(xiàn)在其他的地方。所以你要根據(jù)你的需求,隨意調(diào)整實(shí)現(xiàn)方法。

關(guān)于如何理解構(gòu)建多語(yǔ)言的WPF應(yīng)用就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

wpf
AI