【问题标题】:Localize WPF with dynamic content使用动态内容本地化 WPF
【发布时间】:2017-10-31 10:14:06
【问题描述】:

我是 WPF 的新手,但想用 XAML 中定义的 UI 构建一个 Windows 应用程序,从一开始就支持本地化。需要本地化的不仅是 UI 元素,还有很多驻留在数据库中的内容

我已经建立了一个数据库结构来保存本地化信息,但我不确定如何根据所选的语言环境来显示它,例如“en-US”或“de-DE”。

如何以 WPF/XAML 的自然方式向 XAML UI 元素提供本地化文本?我确实想依赖任何第三方代码如WPFLocalizationExtension

我已经看到资源文件如何实现这一点,但它支持已知 UI 元素的本地化,而不是动态生成的内容。

是否有我应该实施的特定提供者,或者我错过了一些完全不同的东西?

【问题讨论】:

  • 您不能只在动态生成的元素上分配“文本”或“内容”吗?我的意思是,当您添加一个按钮时,您可以对您的数据库进行查询并从后面的代码中设置内容属性。
  • 是的,当然可以。但是,对于 WPF XAML,我想以“正确的方式”执行此操作,我不确定这是否是在 WPF 中处理此问题的预期方式。
  • 嗯,你没有特别的选择。就像我在第一条评论中所说的那样,或者您定义了一个实现 INotifyPropertyChanged 的​​视图模型并定义了足够的属性来涵盖所有可能的动态控制。每个属性表示一个文本或一个填充有查询并绑定到单个控件的内容。但是在我看来,您不应该使用视图模型,因为我们谈论的是纯视觉的东西(即您的数据没有受到影响,它只是某些文本的不同本地化),这就是为什么我建议通过后面的代码来做到这一点
  • 我认为您在@DanieleSartori 的第一条评论中是对的。在进一步研究之后,我得出了相同的结论,即本地化标签(即“en-US”)应该是数据查询的一部分。您可以将其视为商店中以不同语言有不同名称的商品。

标签: c# wpf xaml localization


【解决方案1】:

我最终解决这个问题的方法如下。请记住,我需要从数据库/远程 api 中提取本地化文本。

有了这个解决方案,我可以像这样进行数据绑定,当我更改语言时,所有绑定的文本都会自动更改:

<Label Content="{Binding Path=Strings.ErrorLevelColumnHeaderLabel}"/>

当然,Strings 对象必须可以从所有数据上下文中访问。

字符串存储在如下所示的数据库表中,每个支持的语言都有一个 ID 列和一个列:

ID  en        da        de
24  'Level'   'Niveau'  'Stufe'

我创建了一个实现INotifyPropertyChanged 接口的UIStringsVM 类。在我的示例中,我在一个名为Observable 的基类中实现了这一点,我相信许多其他人也有。 See this answer for details.

public class UIStringsVM : Observable
{
    public static UIStringsVM CurrentStringsInstance;

    private bool stringsAreLoading = false;
    private Dictionary<int, string> stringDictionary;
}

UIStringsVM 类对我需要本地化的每个字符串都有一个属性。由于该类通过基类支持INotifyPropertyChanged 接口,因此我可以依赖更改在语言更改时反映在 UI 中。

UIStringsVM 类中,当前语言的字符串存储在Dictionary&lt;int, string&gt; 中。这样做的原因是我可以使用数据库中的字符串 ID 来访问正确的字符串。

现在我可以使用属性 Get 方法中的 ID 来返回为该值存储的任何字符串。所以属性看起来像这样:

public string ErrorLevelColumnHeaderLabel
{
    get =>
        this.stringDictionary[24].Replace("\\n", Environment.NewLine);
    private set =>
        this.stringDictionary[24] = value;
}

属性永远不会单独设置,因此可以省略设置器。

构造函数:

public UIStringsVM()
{
    this.stringDictionary = new Dictionary<int, string>();

    // Initialize with default values. The ! at the end makes it easier to identify missing values in the database. 
    this.LoginButtonText = "Login!";
    this.LogoutButtonText = "Logout!";
    this.UserLabelFormatString = "{0} logged in!";
    this.ErrorLevelColumnHeaderLabel = "Level!";

    UIStringsVM.CurrentStringsInstance = this;
}

为了加载字符串,我使用以下方法:

public async Task LoadStringsAsync(string languageCode, CancellationToken ct)
{
    if (languageCode.Length != 2)
        throw new ArgumentException("languageCode must be exactly 2 characters.", nameof(languageCode));

    this.StringsAreLoading = true;

    var client = new UIStringsClient(AppVM.BaseURL);
    try
    {
        var apiStrings = await client.GetByLanguageAsync(languageCode, ct);
        foreach (var r in apiStrings)
        {
            /* Note: this will make it impossible to overwrite a string with an empty string from the database, 
             * thus always keeping the string defined in this class' constructor. However strings will always 
             * have a value as defined in the constructor even if one does not exist in the database.
             * */
            if (string.IsNullOrWhiteSpace(r.Value))
                continue;

            this.stringDictionary[r.Key] = r.Value;
        }

        this.OnPropertyChanged((string)null); // Raise a change event for the entire object, not just a named property
    }
    finally
    {
        this.StringsAreLoading = false;
    }
}

我希望这对任何可能碰巧遇到这个迟到的答案的人有所帮助。我已经运行这个解决方案 15 个月左右,使用它真的很棒。

【讨论】:

    猜你喜欢
    • 2010-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-06
    相关资源
    最近更新 更多