【问题标题】:Xamarin.Forms change UI language at runtime (XAML)Xamarin.Forms 在运行时更改 UI 语言 (XAML)
【发布时间】:2017-11-08 16:11:24
【问题描述】:

我正在使用Strings.resxStrings.de.resx 等来本地化Xamarin.Forms 应用程序。 我需要能够在运行时更改界面语言,并且它(几乎)可以工作。

Xamarin 从资源文件中生成namespace MyProject.Resources 中的static class Strings,我使用这些值在 UI 上显示字符串。 从代码中执行此操作时,它可以完美运行:

await DisplayAlert(Strings.lblConfirmDelete, Strings.lblDeleteMessage, Strings.lblOK, Strings.lblCancel));

问题是 - 当我在运行时更改 UI 文化时,并非所有从 XAML 以这种方式定义的属性都会更新。 按钮、标签、条目属性(占位符等)按应有的方式更改,但 PageTitle、Toolbaritems 和其他一些属性仍保留在以前的语言中。

我认为其中一些是在第一次创建页面时填充的,并且不会随着文化(和 UI 文化)的变化而更新。 所以,基本上,我需要一种将{DynamicResource ...} 与资源值结合起来的方法。 我知道 DynamicResource 可以与 Resource 字典一起使用,但这不是存储语言翻译以进行本地化的好方法。

我试过了

Text="{DynamicResource {x:Static lr:Strings.lblAddNew}}"

也不行。

有没有办法动态刷新页面?

我也试过打电话

global::Xamarin.Forms.Xaml.Extensions.LoadFromXaml(this, typeof(MainListPage));

来自该页面的 Appearing 事件,但这也不起作用。

有什么想法吗?

XAML 文件的一部分

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:local="clr-namespace:MyProject.View"
    xmlns:rs="clr-namespace:MMPI"
    x:Class="MyProject.MainListPage"
    xmlns:lr="clr-namespace:MyProject.Resources"

    Title="{x:Static lr:Strings.appName}"
    >

<ContentPage.ToolbarItems>
    <ToolbarItem 
     Name="New" 
     Order="Primary" 
     Priority="0"
     Text="{x:Static lr:Strings.lblAddNew}" 
     Clicked="New_Clicked"
>

【问题讨论】:

标签: xaml localization xamarin.forms


【解决方案1】:

当我在一个项目中遇到这个挑战时,我通过使用一个简单的类 ResourceLoader 并利用 INotifyPropertyChanged 来解决它。

您可以从任何地方访问Instanceproperty 并改变文化。所有绑定到索引的字符串都会更新。

必须正确设置注入构造函数的ResourceManager 实例。

public class ResourceLoader : INotifyPropertyChanged
{
    private readonly ResourceManager manager;
    private CultureInfo cultureInfo;

    public ResourceLoader(ResourceManager resourceManager)
    {
        this.manager = resourceManager;
        Instance = this;
        this.cultureInfo = CultureInfo.CurrentUICulture;
    }

    public static ResourceLoader Instance { get; private set; }

    public string GetString(string resourceName)
    {
        string stringRes = this.manager.GetString(resourceName, this.cultureInfo);
        return stringRes;
    }

    public string this[string key] => this.GetString(key);

    public void SetCultureInfo(CultureInfo cultureInfo)
    {
        this.cultureInfo = cultureInfo;
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

要在应用程序中显示本地化字符串,您需要通过索引器绑定,如下所示:

<Label Text="{Binding [Test], Source={x:Static ResourceLoader.Instance}}" />

由于它现在已绑定,因此当您调用 ResourceLoader.SetCultureInfo 时它应该会更新,因为 Item[] 'PropertyName' 会导致绑定控件重新获取绑定键的值。


更新

如果我说的是假的,我只是测试了它,并且由于某种原因更改的属性不起作用。我在下面添加了一种不同的方法,这与我在生产中使用的方法很接近我敦促您添加某种弱引用“缓存”,而不是包含所有字符串资源的简单列表 (否则它们将被永久保存)

我保留以上供参考。

public class ResourceLoader
{

    public ResourceLoader(ResourceManager resourceManager)
    {
        this.manager = resourceManager;
        Instance = this;
        this.cultureInfo = CultureInfo.CurrentUICulture;
    }

    private readonly ResourceManager manager;
    private CultureInfo cultureInfo;

    private readonly List<StringResource> resources = new List<StringResource>();

    public static ResourceLoader Instance { get; private set; }

    public StringResource this[string key] {
        get { return this.GetString(key); }
    }

    public StringResource GetString(string resourceName)
    {
        string stringRes = this.manager.GetString(resourceName, this.cultureInfo);
        var stringResource = new StringResource(resourceName, stringRes);
        this.resources.Add(stringResource);
        return stringResource;
    }

    public void SetCultureInfo(CultureInfo cultureInfo)
    {
        this.cultureInfo = cultureInfo;
        foreach (StringResource stringResource in this.resources) {
            stringResource.Value = this.manager.GetString(stringResource.Key, cultureInfo);
        }
    }

}

字符串资源:

public class StringResource : INotifyPropertyChanged
{

    public StringResource(string key, string value)
    {
        this.Key = key;
        this.Value = value;
    }

    private string value;

    public string Key { get; }

    public string Value {
        get { return this.value; }
        set {
            this.value = value;
            this.OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

}

XAML 绑定

<Label  Text="{Binding [ResourceKey].Value, Mode=OneWay, Source={x:Static local:ResourceLoader.Instance}}" 
           />

更新 2

遇到this link,他们的实现方式与我的第一种方法类似。也许你可以试一试。


更新 3

修正了第一种方法。两人现在都在工作。需要的是this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null)); 而不是this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(Item[]));

【讨论】:

  • 感谢您的快速回复。我很抱歉,但我不太明白。如果我使用自动生成的文件 (Strings.Designer.cs) 中的 ResourceManager 创建 ResourceLoader 类的实例,接下来我应该做什么?您能否在几行中给我一个示例用法?没什么太复杂的,只需 1-2-3 步。
  • 我已经检查了我的建议并用不同的正确工作方法更新了我的答案
【解决方案2】:

我解决它的方法与@woelliJ 非常相似。我只是想拥有来自静态类的强类型的键,并且绑定应该在代码后面。

ITranslationService 是来自静态变量的单例。和@woelliJ 非常接近。

[ContentProperty("Text")]
public sealed class TranslateExtension : IMarkupExtension<BindingBase>
{
    private readonly ITranslationService? _translationService;

    public TranslateExtension()
    {
        _translationService = Mobile.App.TranslationService;
    }

    public string? Text { get; set; }

    public BindingBase ProvideValue(IServiceProvider serviceProvider)
    {
        var translationItem = _translationService[Text];

        var binding = new Binding
        {
            Mode = BindingMode.OneWay,
            Path = $"Value",
            Source = translationItem,
        };

        return binding;
    }

    object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
    {
        return (this as IMarkupExtension<BindingBase>).ProvideValue(serviceProvider);
    }
}

[AddINotifyPropertyChangedInterface]
public class TranslationItem
{
    public string? Key { get; set; }
    public string? Value { get; set; }
}

那么标签会是这样的

<Label FontSize="Title" Text="{services:Translate Text={x:Static models:M.AboutTestInfoTitle}}" />

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-02
    • 2012-10-22
    • 1970-01-01
    • 2021-12-05
    • 2011-09-14
    • 1970-01-01
    相关资源
    最近更新 更多