在xaml页面上的xamarin应用程序中,我使用xaml扩展加载本地化字符串(详细信息描述为here)。例如:
<Label Text={i18n:Translate Label_Text}/>
现在,我希望用户能够在运行时更改应用程序的语言(使用选择器)。如果发生这种情况,我想立即更改语言。
我可以以某种方式重新加载所有翻译的文本吗?
我可以删除所有页面并重新创建它们,但我试图避免这种情况。
我还可以将所有本地化文本绑定到页面模型中的字符串。但这对于真正的静态字符串来说是很多不必要的代码。
答案 0 :(得分:7)
不幸的是,您无法强制使用 XAML 中的标记扩展设置控件来使用这些扩展重新评估其属性 - 评估仅在解析 XAML 文件时执行一次。幕后发生的事情是:
ProvideValue
方法,并在目标控件上使用返回的值您可以通过定义终结器(析构函数)并在其中设置断点来确认您的扩展只使用一次。加载页面后很快就会出现(至少在我的情况下 - 你可能需要明确地调用GC.Collect()
)。所以我认为问题很明显 - 您无法在任意时间再次呼叫ProvideValue
您的分机,因为它可能已不复存在。
但是,您的问题有一个解决方案,甚至不需要对 XAML 文件进行任何更改 - 您只需要修改TranslateExtension
类。我们的想法是,它将设置正确的绑定,而不是简单地返回一个值。
首先我们需要一个类作为所有绑定的源(我们将使用单例设计模式):
public class Translator : INotifyPropertyChanged
{
public string this[string text]
{
get
{
//return translation of "text" for current language settings
}
}
public static Translator Instance { get; } = new Translator();
public event PropertyChangedEventHandler PropertyChanged;
public void Invalidate()
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(Binding.IndexerName));
}
}
此处的目标是Translator.Instance["Label_Text"]
应返回当前扩展程序为"Label_Text"
返回的翻译。然后,扩展应在ProvideValue
方法中设置绑定:
public class TranslateExtension : MarkupExtension
{
public TranslateExtension(string text)
{
Text = text;
}
public string Text { get; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
var binding = new Binding
{
Mode = BindingMode.OneWay,
Path = new PropertyPath($"[{Text}]"),
Source = Translator.Instance,
};
return binding.ProvideValue(serviceProvider);
}
}
现在,您需要做的就是每次更改语言时都要调用Translator.Instance.Invalidate()
。
请注意,使用{i18n:Translate Label_Text}
相当于使用{Binding [Label_Text], Source={x:Static i18n:Translator.Instance}}
,但更简洁,可以省去修改 XAML 文件的工作。
答案 1 :(得分:3)
我试图实现@ Grx70的伟大提议解决方案,但是该示例使用的一些类和属性是Xamarin内部的,因此不能以这种方式使用。 尽管不是最初的建议,但是我们可以做到这一点:
获取它们的最后一条评论是获得它的工作的线索。public class TranslateExtension : IMarkupExtension<BindingBase>
{
public TranslateExtension(string text)
{
Text = text;
}
public string Text { get; set; }
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
{
return ProvideValue(serviceProvider);
}
public BindingBase ProvideValue(IServiceProvider serviceProvider)
{
var binding = new Binding
{
Mode = BindingMode.OneWay,
Path = $"[{Text}]",
Source = Translator.Instance,
};
return binding;
}
}
这是最初提出的译者类,但为了清楚起见,这里转载了GetString调用:
public class Translator : INotifyPropertyChanged
{
public string this[string text]
{
get
{
return Strings.ResourceManager.GetString(text, Strings.Culture);
}
}
public static Translator Instance { get; } = new Translator();
public event PropertyChangedEventHandler PropertyChanged;
public void Invalidate()
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
}
}
然后如原始帖子所示,而不是用以下内容绑定文本:
{i18n:Translate Label_Text}
绑定
{Binding [Label_Text], Source={x:Static i18n:Translator.Instance}}
我在项目结束时(添加多种语言)点击了这一点,但是使用Visual Studio社区和使用RegEx进行搜索/替换,可以在项目中替换绑定,替换:
\{resources:Translate (.*?)\}
with:
{Binding [$1], Source={x:Static core:Translator.Instance}}
注意:Regex假设原始Translate宏的'resources'命名空间和Translator类的'core'命名空间,您可能必须适当更新。 我很欣赏这是对@ Grx70的另一个很好的解决方案的一个小调整(我站在这个巨人的肩膀上),但是我在这里发布这个,因为同样的问题让这个工作。