在现代软件开发中,应用程序往往需要支持多语言,以满足不同地区用户的需求。本地化是实现这一目标的关键技术之一。本地化不仅涉及到翻译文本,还包括调整日期、货币和数字格式等。本文将介绍一种动态本地化解决方案,该方案允许在应用程序发布后更改字符串资源或添加新的语言支持,而无需重新编译应用程序。此外,本文还将探讨如何在MVVM(Model-View-ViewModel)模式下实现数据绑定。
网络上有许多优秀的文章详细介绍了.NET中的字符串资源和本地化技术。本文的目标是开发一种本地化解决方案,该方案能够在应用程序发布后更改字符串资源或添加新的语言支持。同时,希望尽可能利用.NET内置的资源处理组件,特别是ResourceManager类。通过MSDN和Visual Studio IntelliSense搜索,发现了CreateFileBasedResourceManager方法,该方法允许在运行时导入外部资源文件。MSDN文章还提到可以采用自定义资源读取器来解析资源,这似乎是完美的解决方案,但文章没有详细说明如何实现。
首先,定义了自定义的ResourceSet和ResourceReader对象,它们负责理解和解析外部资源文件。
以下是自定义ResourceSet类的实现。这个类非常简单,它只是返回自定义资源读取器类的实例。
public class StringsResourceSet : ResourceSet
{
public StringsResourceSet(string fileName)
: base(new StringResourceReader(fileName))
{
}
public override Type GetDefaultReader()
{
return typeof(StringResourceReader);
}
}
以下是自定义ResourceReader类的实现,它负责读取字符串资源。
public class StringResourceReader : IResourceReader
{
string _fileName;
public StringResourceReader(string resourceFileName)
{
_fileName = resourceFileName;
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public void Close()
{
throw new NotImplementedException();
}
public IDictionaryEnumerator GetEnumerator()
{
Hashtable htLanguage = new Hashtable();
using (StreamReader sr = new StreamReader(_fileName))
{
while (!sr.EndOfStream)
{
string[] lineParams = sr.ReadLine().Split('=');
htLanguage.Add(lineParams[0], lineParams[1]);
}
}
return htLanguage.GetEnumerator();
}
public void Dispose()
{
throw new NotImplementedException();
}
}
在这种方法中,资源文件只是包含资源键和值的文本文件,键和值之间用等号分隔,例如:RESOURCEKEY=resourcevalue。通过更改GetEnumerator的实现,资源文件可以是任何格式。在之前提到的文章中,作者使用了SQL数据库。
这里是视图模型类,它还负责基于上述资产初始化资源管理器类。
public class ResourceViewModel
{
ResourceManager rm;
public ResourceViewModel()
{
rm = ResourceManager.CreateFileBasedResourceManager(
"Strings",
"Strings",
typeof(StringsResourceSet));
}
public ResourceManager Manager
{
get { return rm; }
}
public string this[string name]
{
get { return rm.GetString(name); }
}
public void SetDefaultCulture(CultureInfo culture)
{
Type type = typeof(CultureInfo);
try
{
type.InvokeMember("s_userDefaultCulture",
BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Static,
null, culture, new object[] { culture });
type.InvokeMember("s_userDefaultUICulture",
BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Static,
null, culture, new object[] { culture });
}
catch { }
try
{
type.InvokeMember("m_userDefaultCulture",
BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Static,
null, culture, new object[] { culture });
type.InvokeMember("m_userDefaultUICulture",
BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Static,
null, culture, new object[] { culture });
}
catch { }
}
}
在XAML中,可以通过索引器属性支持绑定,如下所示:
<TextBlock FontSize="50" Text="{Binding Path=[TESTSTRING]}"/>
这里是支持XAML绑定的提示/技巧。将ResourceManager GetString方法包装在索引器属性中,XAML支持绑定到索引器属性。
ResourceViewModel viewModel = new ResourceViewModel();
public Window1()
{
InitializeComponent();
DataContext = viewModel;
if (!String.IsNullOrEmpty(Properties.Settings.Default.CultureInfo))
{
viewModel.SetDefaultCulture(CultureInfo.CreateSpecificCulture(Properties.Settings.Default.CultureInfo));
}
}