WPF 本地多語言總結


去年底今年初,由於WPF項目,需要支持日本地區,要求可以切換語言。本篇文章匯總了一下,當時的調研的結果,和項目中所采用的方式。

業務背景

當時,軟件主要針對的是英語國家。后來因業務需要,增加了日本地區,需要一套可切換的日語操作界面。
一方面,由於軟件早期沒有考慮過多語言,改造幅度比較大;另一方面,由於翻譯問題,需要日本方面,可以即時修改翻譯的內容。總結一下需求:

  1. 軟件需全面修改,包括前端界面,后端文字,數據庫配置項等;
  2. 易擴展,易修改;
  3. 調整翻譯內容后,可及時展現在界面上;
  4. 希望使用 Excel 文檔,來作為編輯工具;

我的調研

我在網上查找了不少資料,簡單實現簡單的demo,整體傾向使用,微軟推薦的方法。具體查找的資料如下:

我的實現方法

參考了上面的所有方法,根據項目的需求,實現一個demo。實現起來其實蠻簡單的:

  1. 創建一個多語言通用類庫,這里面只包含多語言的resx文件,resx 可以包含:字符串、圖像、音頻、文件、其他;
    多語言通用類庫

  2. 使用ResXResourceManager,來編寫多語言內容,這個插件的功能很強大,有復制粘貼,排序,檢索,導入導出excel等;還可以選中代碼,快速添加;很多功能有待發現;
    resx資源管理插件
    resx資源管理插件2

  3. 自動生成的cs代碼,具有強類型引用,不易出錯;

/// <summary>
///   硬件自動名稱..
/// </summary>
public static string HW_Auto_Name {
    get {
        return ResourceManager.GetString("HW_Auto_Name", resourceCulture);
    }
}

// 使用方式
string tip = ResourceText.HW_Auto_Name;

  1. 在WPF的 xaml 界面使用方式,示例;
xmlns:resx="clr-namespace:LanguageResources;assembly=LanguageResources"

Content="{x:Static resx:ResourceText.HW_Auto_Name}"
  1. 最后通過VS的編譯,生成一個語言文件夾ja-JP里面包含一個資源dllLanguageResources.resources.dll;
  2. 如果對翻譯內容進行改變,則還額外需要的操作步驟;
    使用Resgen.exe將每個文本XML資源文件編譯為二進制.resources文件。輸出是一組文件,這些文件的文件名與.resx或.txt文件相同,但擴展名為.resources。
Visual Studio 2017,在文件路徑中找到resgen.exe;  
%PROGRAMFILES(X86)%\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.2 Tools\resgen.exe  

//執行resgen命令,生成LanguageResources.ResourceText.ja-JP.resources  
resgen LanguageResources.ResourceText.ja-JP.resx  

//執行al命令,生成LanguageResources.resources.dll  
al -target:lib -embed:LanguageResources.ResourceText.ja-JP.resources -culture:ja-JP -out:LanguageResources.resources.dll  

至此,所有的操作完全完成。雖然,我的方法使用微軟推薦的方法,同時也有相應的輔助工具,提高了開發的效率;但是她有一個致命的缺點,是在改變翻譯內容后,還需要執行額外的操作。對翻譯人員的操作不友好~~。放棄!!!

項目中使用的方法

在我的方法被否決以后,心里還是有不甘心。后來項目組,安排另一位同事,開發了個Excel版本的,時隔三個月再來看,他的方法確實比我的要簡單,實用。下面我就簡單介紹一下:

  1. Excel 文件的讀取,使用 NPOI;
  2. DependencyProperty.RegisterAttached WPF的依賴屬性,通過網上查找的 DependencyProperty 資料,簡單了解到它是WPF的精華所在;
  3. 在具體使用中,使用兩種方式:1、根據Key值來獲取翻譯內容;2、根據完整英文內容,獲取對應的翻譯內容;

具體的代碼,簡化如下:

public class LanguageManager
{
    public bool IsLangOut { get; set; }
    public string Language { get; set; }

    private string langFile;
    private static LanguageManager _Instance;
    private Dictionary<string, LanguageObject> StrMap;

    public static LanguageManager Instance
    {
        get { return _Instance == null ? _Instance = new LanguageManager() : _Instance; }
    }

    private LanguageManager()
    {
        IsLangOut = false;
        Language = "en_US";
        langFile = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs", "LanguageError.log");
    }

    public bool Load(Func<string, Dictionary<string, LanguageObject>> LoadFile)
    {
        StrMap = LoadFile?.Invoke(Language);
        if (StrMap == null || StrMap.Count == 0) return false;
        else return true;
    }

    public string GetTranslateStr(string key)
    {
        if (string.IsNullOrEmpty(key) || StrMap == null) return string.Empty;
        if (StrMap.ContainsKey(key))
        {
            return StrMap[key].Trans;
        }
        else
        {
            Write2File($"no key: [{key}]\n");
            return key;
        }
    }

    public string TranslateStr(string text)
    {
        if (Language == "en-US" || StrMap == null) return text;

        var lang = StrMap.Values.FirstOrDefault(p => p.English.Trim() == text.Trim());
        if (lang == null)
        {
            Write2File($"can't find value: [{text}]\n");
            return text;
        }
        else
            return lang.Trans;
    }

    public void Write2File(string context)
    {
        if (!IsLangOut) return;
        System.IO.File.AppendAllText(langFile, context, System.Text.Encoding.UTF8);
    }
}
public class LanguageBehavior
{
    public static readonly DependencyProperty KeyProperty = 
        DependencyProperty.RegisterAttached("Key", 
            typeof(string), typeof(LanguageBehavior), 
            new FrameworkPropertyMetadata(string.Empty, OnKeyChanged));
    private static void OnKeyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        Update(sender, e.NewValue as string);
    }
    public static void SetKey(DependencyObject dp, string value)
    {
        dp.SetValue(KeyProperty, value);
    }
    public static string GetKey(DependencyObject dp)
    {
        return dp.GetValue(KeyProperty) as string;
    }

    public static readonly DependencyProperty ToolTipProperty = 
        DependencyProperty.RegisterAttached("ToolTip", 
            typeof(string), typeof(LanguageBehavior), 
            new FrameworkPropertyMetadata(string.Empty, OnToolTipChanged));
    private static void OnToolTipChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        UpdateToolTip(sender, e.NewValue as string);
    }
    public static void SetToolTip(DependencyObject dp, string value)
    {
        dp.SetValue(ToolTipProperty, value);
    }
    public static string GetToolTip(DependencyObject dp)
    {
        return dp.GetValue(ToolTipProperty) as string;
    }

    private static void Update(DependencyObject sender, string key)
    {
        if (sender is TabItem)
            (sender as TabItem).Header = LanguageManager.Instance.GetTranslateStr(key);
        else if (sender is Label)
            (sender as Label).Content = LanguageManager.Instance.GetTranslateStr(key);
        else if (sender is ContentControl)
            (sender as ContentControl).Content = LanguageManager.Instance.GetTranslateStr(key);
        else if (sender is TextBlock)
            (sender as TextBlock).Text = LanguageManager.Instance.GetTranslateStr(key);
        else if (sender is Run)
            (sender as Run).Text = LanguageManager.Instance.GetTranslateStr(key);
        else if (sender is GridViewColumn)
            (sender as GridViewColumn).Header = LanguageManager.Instance.GetTranslateStr(key);
    }

    private static void UpdateToolTip(DependencyObject sender, string key)
    {
        if (!(sender is FrameworkElement)) return;
        (sender as FrameworkElement).ToolTip = LanguageManager.Instance.GetTranslateStr(key);
    }
}

在 XAML 的使用示例:  
<Button x:Name="btn1" Height="40" Lang:LanguageBehavior.Key="K00259" />  
<Button x:Name="btn2" Width="32" Height="32" Lang:LanguageBehavior.ToolTip="K00189" />  

在 cs 的使用示例:  
this.textError.Text = LanguageManager.Instance.GetTranslateStr("K00204");  
this.textError.Text = LanguageManager.Instance.TranslateStr("Can not connect server");  

簡單的示例代碼:MultiLanguageDemo
demo示例

總結

在項目運行至今,很明顯第二種方法的好,測試人員,運維人員,在可以完全不接觸代碼的情況下。只操作Excel 來實現文件內容的翻譯,並且“實時”的反應到軟件上。
而我固執己見的“微軟官方推薦方法”,並不適用於項目;好比是紙上談兵的趙括,真的是越來越為當時的“倔強”,感到羞愧。。。


免責聲明!

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



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