Win10 UWP 開發系列:使用多語言工具包讓應用支持多語言


之前我在一篇blog中寫過如何使用多語言工具包,見http://www.cnblogs.com/yanxiaodi/p/3800767.html

在WinEcos社區也發布過一篇詳細的文章介紹多語言工具包的使用,但因社區改版那篇文章已經找不到了。

當時寫的時候還沒有出Win10的SDK,都是基於UAP框架寫的。微軟早已經發布了Win10的SDK,相應的項目結構也發生了變化,以前分為兩個項目通過Share項目共享代碼的方式被拋棄,改為合並為一個項目,真正實現了一套代碼兼容PC和Mobile兩個平台,我已經基於Win10 10586的SDK發布了Currency Exchanger的新版本,下載地址:https://www.microsoft.com/store/apps/9WZDNCRDQ91S  

在開發Currency Exchanger的過程中,我又整理了一下支持多語言的問題,記錄於此。

一、安裝多語言工具包

使用VS2015開發UWP不能再使用老版3.0的多語言工具包,而應該使用新版的V4.0beta,這個還不是正式版,所以兼容性有問題,無法與V3.0共存,安裝之后也無法再用VS2013或VS2015打開WP8.1之前的項目,所以安裝之前請慎重!請慎重!請慎重!重要的事情說三遍。

我們在開發者后台的下載欄目可以找到多語言工具包的下載頁面:https://dev.windows.com/zh-cn/develop/multilingual-app-toolkit

但是!截至到2015年12月31日,這個頁面所下載的中文多語言工具包仍然是V3.0,而不是最新的V4.0beta,就算安裝了,也無法在UWP項目中應用。

最新版的下載地址在此:

https://visualstudiogallery.msdn.microsoft.com/6dab9154-a7e1-46e4-bbfa-18b5e81df520

這也是我一直吐槽MSDN的原因之一,找個東西累死了,官方的東西都不好找。

還有一種方式,直接在VS2015的擴展里搜索Multilingual App Toolkit,主要要有空格,不然搜不到:

要安裝V4.0beta這個。下面那個是舊版的,這兩個無法共存。

二、啟用多語言工具包

還是做個例子吧。新建一個MultilingualDemo項目,VS2015工具菜單-Multilingual App Toolkit -啟用選定內容

會收到提示:1>  未啟用項目"MultilingualDemo"-沒有可本地化的資源被發現。 

 這是因為沒有發現咳本地化的資源,雙擊Package.appxmanifest打開,設置一個默認語言,如果在設計的時候就想支持多語言,最好默認語言設置為英語,輸入en-US:

然后在項目中添加一個Strings文件夾,再在其下添加一個en-US文件夾,這個文件夾名字要和默認語言代碼保持一致,如果默認語言是zh-CN,那就建一個zh-CN的文件夾。

在這個目錄下添加一個Resources.resw資源文件,在這里面編輯所需要的字符串:

添加幾個資源,注意,如果是要顯示在界面上的,可以根據控件的屬性來設置,如TextBlock的文字是Text屬性,那資源的名字就命名為HelloWorld.Text,Button的文字是Content屬性,所以命名為ClickMe.Content,另外我還加了一個AppName,用於在代碼中使用。

 

再次 VS2015工具菜單-Multilingual App Toolkit -啟用選定內容

 

這次可以正常啟用了:

 

1> 項目"MultilingualDemo"已啟用。 該項目的來源,文化是 'en-US' [英語(美國)]。

 

三、在XAML界面上使用語言資源

在Page中放一個 TextBlock,一個Button,一個ComboBox,設置其x:Uid(資源標識符,注意不是x:Name)屬性,這樣控件就可以根據資源找到其對應的內容:

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" DataContext="{StaticResource DesignVM}">
        <TextBlock x:Name="pageTitle" Grid.Column="1" Margin="100" Text="{Binding Title}" />
        <TextBlock x:Name="textBlock" x:Uid="HelloWorld" HorizontalAlignment="Left" Margin="100,156,0,0" TextWrapping="Wrap" VerticalAlignment="Top"/>
        <Button x:Name="button" x:Uid="ClickMe" HorizontalAlignment="Left" Margin="100,181,0,0" VerticalAlignment="Top"/>
        <ComboBox x:Name="comboBox" x:Uid="ChangeLanguage" HorizontalAlignment="Left" Margin="100,249,0,0" VerticalAlignment="Top" Width="120"/>

    </Grid>

 

四、在代碼中使用語言資源

添加一個

    public static class AppResources { private static ResourceLoader CurrentResourceLoader { get { return _loader ?? (_loader = ResourceLoader.GetForCurrentView("Resources")); } } private static ResourceLoader _loader; private static readonly Dictionary<string, string> ResourceCache = new Dictionary<string, string>(); public static string GetString(string key) { string s; if (ResourceCache.TryGetValue(key, out s)) { return s; } else { s = CurrentResourceLoader.GetString(key); ResourceCache[key] = s; return s; } } /// <summary>
        /// AppName /// </summary>
        public static string AppName { get { return CurrentResourceLoader.GetString("AppName"); } } }

 打開MainPage_Model.cs,取消OnBindedViewLoad方法的注釋,在里面添加以下代碼:

        /// <summary>
        /// This will be invoked by view when the view fires Load event and this viewmodel instance is already in view's ViewModel property /// </summary>
        /// <param name="view">View that firing Load event</param>
        /// <returns>Task awaiter</returns>
        protected override Task OnBindedViewLoad(MVVMSidekick.Views.IView view) { this.Title = AppResources.AppName; return base.OnBindedViewLoad(view); }

現在運行看看:

現在默認語言是en-US。

五、翻譯成本地化資源

如果沒安裝多語言工具包的話,可以在Strings目錄下手動添加對應語言的文件夾和資源文件,不過有多語言工具包的話這個工作就變得很容易了,在項目上右鍵,添加翻譯語言:

在這里可以選擇要添加什么語言,選擇簡體中文:

這里建議只選擇zh-Hans就可以了,不用選擇zh-CN,因為語言與資源的匹配非常復雜,語言標記存在多種可能影響匹配優先級的可選組件,建議讓系統來選擇,MSDN文檔是這么說的:(https://msdn.microsoft.com/zh-cn/library/windows/apps/xaml/mt607079.aspx

使用某個語言標記的可選組件的示例有:

  • 用於禁止腳本語言的腳本。例如,en-Latn-US 與 en-US 匹配。
  • 區域。例如,en-US 與 en 匹配。
  • 變體。例如,de-DE-1996 與 de-DE 匹配。
  • -x 和其他擴展名。例如,en-US-x-Pirate 與 en-US 匹配。

 

對於不采用 xx 或 xx-yy 形式的語言標記,也存在許多組件,且並非全部匹配。

  • zh-Hant 與 zh-Hans 不匹配。

 

Windows 以一個標准的易於理解的方式排定語言匹配的優先順序。例如,按優先順序,en-US 依次與 en-US、en、en-GB 等等匹配。

  • Windows 執行跨區域匹配。例如,en-US 與 en-US 匹配,然后依次與 en、en-* 匹配。
  • Windows 提供了一些額外數據,它們可用於區域內(如某種語言的主要區域)的相關性匹配。例如,fr-FR 比 fr-CA 更匹配 fr-BE。
  • 如果你使用 Windows API,則可以免費獲取日后 Windows 在語言匹配方面的任何改進。

在與列表中的首個語言匹配之后才會與列表中第二個語言匹配,對於其他區域變體也是如此。例如,如果應用程序語言為 en-US,則會先於 fr-CA 資源選擇用於 en-GB 的資源。僅當沒有用於 en 形式的資源時才選擇用於 fr-CA 的資源。

應用程序語言列表設置為用戶的區域變體,盡管該變體不同於應用提供的區域變體。例如,如果用戶使用 en-GB,但應用支持 en-US,則應用程序語言列表將包含 en-GB。這將確保日期、時間和數字的格式更接近用戶的期望 (en-GB),但仍然使用應用支持的語言 (en-US) 加載 UI 資源(由於語言匹配)。

除非想把本地化做的非常完善,為中國用戶和新加坡用戶都提供不同的語言資源,否則只提供一種中文簡體就夠了。

選擇后,Strings目錄下會自動添加中文簡體資源文件:

但是中文的資源里還是空的,我們需要翻譯一下英文資源。右鍵單擊MultilingualDemo.zh-Hans.xlf,選擇打開方式:

現在可以輸入翻譯了,如果懶的話就點擊菜單翻譯按鈕調用Bing的翻譯接口自動翻譯一下,保存。

重新編譯生成一下項目,多語言工具包會根據默認資源去填充其他語言的資源文件:

現在可以看到中文的資源文件里也已經有了。翻譯的時候要注意,字符串有幾種狀態,新、翻譯、需要評審、最終等,可以根據這幾種狀態靈活切換顯示哪些字符串來處理。

重新運行項目,如果我們的電腦系統默認是中文語言,那app應該已經變成中文界面了。如果用戶電腦或手機默認語言是英語,則會調用en-US,如果是其他語言,則會調用默認語言en-US。

六、更改首選語言

App應具有可更改語言的設置。為了保存用戶的首選語言,需要使用Windows.Storage.ApplicationData.Current.LocalSettings來保存用戶的設置,關於如何使用這個來保存配置網上有很多介紹,這里就不詳細介紹了。

在MainPage.xaml里頭部添加以下命名空間:

xmlns:Interactivity="using:Microsoft.Xaml.Interactivity" xmlns:Core="using:Microsoft.Xaml.Interactions.Core" 

把ComboBox代碼改成這樣:

        <ComboBox x:Name="comboBox" x:Uid="ChangeLanguage" HorizontalAlignment="Left" Margin="100,249,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding LanguageList}" SelectedIndex="{Binding LanguageCodeIndex, Mode=TwoWay}" >
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding DisplayName}"  />
                </DataTemplate>
            </ComboBox.ItemTemplate>
            <Interactivity:Interaction.Behaviors>
                <Core:EventTriggerBehavior EventName="SelectionChanged">
                    <Core:InvokeCommandAction Command="{Binding CommandLanguageChanged}" CommandParameter="{Binding LanguageCodeIndex}"/>
                </Core:EventTriggerBehavior>
            </Interactivity:Interaction.Behaviors>
        </ComboBox>

 

這里我們主要使用了Windows.Globalization里面的類,Windows.Globalization 還具有作為幫助程序對象提供的 Language 對象。它幫助應用檢查有關語言的詳細信息,例如,語言的腳本、顯示名稱和本地名稱。主要語言替代PrimaryLanguageOverride是一個簡單的替代設置,它用於讓用戶獨立選擇語言的應用,或者有充分理由替代默認語言選擇的應用。

相應的vm里添加以下代碼:

public ObservableCollection<Language> LanguageList { get { return _LanguageListLocator(this).Value; } set { _LanguageListLocator(this).SetValueAndTryNotify(value); } } #region Property ObservableCollection<Language> LanguageList Setup        
        protected Property<ObservableCollection<Language>> _LanguageList = new Property<ObservableCollection<Language>> { LocatorFunc = _LanguageListLocator }; static Func<BindableBase, ValueContainer<ObservableCollection<Language>>> _LanguageListLocator = RegisterContainerLocator<ObservableCollection<Language>>("LanguageList", model => model.Initialize("LanguageList", ref model._LanguageList, ref _LanguageListLocator, _LanguageListDefaultValueFactory)); static Func<ObservableCollection<Language>> _LanguageListDefaultValueFactory = () => { return new ObservableCollection<Language>(); }; #endregion


        /// <summary>
        /// 語言設置 /// </summary>
        public int LanguageCodeIndex { get { return _LanguageCodeIndexLocator(this).Value; } set { _LanguageCodeIndexLocator(this).SetValueAndTryNotify(value); } } #region Property int LanguageCodeIndex Setup        
        protected Property<int> _LanguageCodeIndex = new Property<int> { LocatorFunc = _LanguageCodeIndexLocator }; static Func<BindableBase, ValueContainer<int>> _LanguageCodeIndexLocator = RegisterContainerLocator<int>("LanguageCodeIndex", model => model.Initialize("LanguageCodeIndex", ref model._LanguageCodeIndex, ref _LanguageCodeIndexLocator, _LanguageCodeIndexDefaultValueFactory)); static Func<int> _LanguageCodeIndexDefaultValueFactory = () => { return -1; }; #endregion


        public CommandModel<ReactiveCommand, String> CommandLanguageChanged { get { return _CommandLanguageChangedLocator(this).Value; } set { _CommandLanguageChangedLocator(this).SetValueAndTryNotify(value); } } #region Property CommandModel<ReactiveCommand, String> CommandLanguageChanged Setup        

        protected Property<CommandModel<ReactiveCommand, String>> _CommandLanguageChanged = new Property<CommandModel<ReactiveCommand, String>> { LocatorFunc = _CommandLanguageChangedLocator }; static Func<BindableBase, ValueContainer<CommandModel<ReactiveCommand, String>>> _CommandLanguageChangedLocator = RegisterContainerLocator<CommandModel<ReactiveCommand, String>>("CommandLanguageChanged", model => model.Initialize("CommandLanguageChanged", ref model._CommandLanguageChanged, ref _CommandLanguageChangedLocator, _CommandLanguageChangedDefaultValueFactory)); static Func<BindableBase, CommandModel<ReactiveCommand, String>> _CommandLanguageChangedDefaultValueFactory = model => { var resource = "CommandLanguageChanged";           // Command resource 
                var commandId = "CommandLanguageChanged"; var vm = CastToCurrentType(model); var cmd = new ReactiveCommand(canExecute: true) { ViewModel = model }; //New Command Core
 cmd.DoExecuteUIBusyTask( vm, async e => { //Todo: Add LanguageChanged logic here, or
                            await MVVMSidekick.Utilities.TaskExHelper.Yield(); string oldLan = AppSettings.Instance.LanguageCode; if (vm.LanguageCodeIndex >= 0) { var lan = vm.LanguageList[vm.LanguageCodeIndex]; AppSettings.Instance.LanguageCode = lan.LanguageTag; ApplicationLanguages.PrimaryLanguageOverride = lan.LanguageTag; } }) .DoNotifyDefaultEventRouter(vm, commandId) .Subscribe() .DisposeWith(vm); var cmdmdl = cmd.CreateCommandModel(resource); cmdmdl.ListenToIsUIBusy( model: vm, canExecuteWhenBusy: false); return cmdmdl; }; #endregion

屬性可以用propvm代碼段,命令用propcmd代碼段來快速生成。

在MainPage的vm的load事件中初始化語言列表:

if (!LanguageList.Any()) { var lanList = ApplicationLanguages.ManifestLanguages; foreach (var lan in lanList) { LanguageList.Add(new Language(lan)); } } if (!string.IsNullOrEmpty(AppSettings.Instance.LanguageCode)) { Language userLan = LanguageList.FirstOrDefault(x => x.LanguageTag == AppSettings.Instance.LanguageCode); LanguageCodeIndex = LanguageList.IndexOf(userLan); }

如果用戶更改語言后,在程序載入時應該按照用戶選擇的語言來調用,打開App.xaml.cs,在App構造 函數中添加以下代碼:

public App() { //TODO 這里可以根據用戶需要更改語言
            if (!string.IsNullOrEmpty(AppSettings.Instance.LanguageCode)) { ApplicationLanguages.PrimaryLanguageOverride = AppSettings.Instance.LanguageCode; } //ApplicationLanguages.PrimaryLanguageOverride = "cs"; //ResourceContext.GetForCurrentView().Reset();
            this.InitializeComponent(); this.Suspending += OnSuspending; }

現在一個具有基本多語言支持、可更改語言的app就完成了。用戶選擇不同的語言后,重新打開就會重新設置語言。

七、其他

這里是微軟官方的一個例子,不過是win8平台的,可以參考:https://code.msdn.microsoft.com/windowsapps/Application-resources-and-cd0c6eaa/

還有一些特殊的情況需要考慮,如果非要做的那么完美的話:

  • 有可能有的語言字符數比較多,導致控件寬度不夠,這就需要為每種語言設置控件的寬度,比如創建App_Name.Width的資源;
  • 有可能語言的排列方向不一致,比如阿拉伯語是右對齊,可以設置對齊屬性;
  • 有可能圖像也需要本地化,那就需要按照資源限定符的格式來定義圖片路徑:

    標准命名約定為foldername/qualifiername-value_qualifiername-value/filename.qualifiername-value_qualifiername-value.ext,當資源路徑為Images/en-US/homeregion-USA/logo.scale-100_contrast-white.png時,應以Images/logo.png的方式來加載。

關於如何使用資源限定符,可參考:https://msdn.microsoft.com/zh-cn/library/windows/apps/xaml/hh965324.aspx

這個頁面是一些本地化的最佳實踐:https://msdn.microsoft.com/zh-cn/library/windows/apps/xaml/hh967762.aspx

CurrencyExchanger的本地化我也沒有做的那么完善,基本就是只翻譯了語言,沒有考慮寬度啊排列方式這些,工作量太大了。

 

至於如何翻譯這些文字,建議在app里留一個郵件,讓志願者來幫助翻譯,可以將xlf文件上右鍵導出翻譯,選擇xlf格式或者csv格式,發送給翻譯人員翻譯,然后再導入進來即可。但這里又會遇到一個問題,導出的csv用excel打開的話,再另存,會丟失里面的雙引號。

從xlf文件導出的csv格式是這樣的:

除了表頭,每個字段兩邊都有雙引號。

這個文件用excel打開再另存后,再用文本編輯軟件打開,會變成這樣:

兩邊的引號沒有了,導入的時候會提示錯誤,無法導入。

我想了個笨辦法,在excel里編輯csv的時候,修改單元格屬性,改為   !"@!" 然后再保存,這樣在文本編輯軟件里打開會發現一個雙引號變成了兩個,然后再使用批量替換功能,把"""替換為",同時別忘了把第一行表頭的雙引號去掉,才能正確導入。

其實如果直接把資源文件里的內容復制到excel里發送給翻譯人員翻譯,翻譯好了再粘貼回來也行,但要是以后再增加修改默認語言的資源時,其他語言也得手動挨個改,不如用多語言工具包自動填充完整。各有利弊。

 

最后附上demo下載地址:

鏈接:http://pan.baidu.com/s/1i4jBn8L 密碼:idpz


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM