¥£$ ДЕУΙГ

漫谈Silverlight(2)更加友好的国际化

    Silverlight的国际化在4.0之后才逐渐让我们感到满意,例如提供了FlowDirection属性,更多的语言支持等等,本篇想与大家分享的是在多语言的Silverlight程序中动态切换语言的一些问题和经验。

    在Silverlight中建立一个多语言的程序并不困难,与传统的多语言程序区别不大,通过建立不同语言的Resource文件切换语言,区别在于如何在Xaml中实现多语言,按照文档中的说明,我们要建立一个类像这样:

  1: public class LocalizedStrings
  2: {
  3:    public LocalizedStrings()
  4:    {
  5:    }
  6:    private static SilverlightApp.Resource1 resource1 = new SilverlightApp.Resource1();
  7:    public SilverlightApp.Resource1 Resource1 { get { return resource1; } }
  8: }

    为了能在Xaml中访问资源文件,我们要在Xaml中添加资源,例如在App.Resources中:

  1: <Application.Resources>
  2:    <local:LocalizedStrings xmlns:local ="clr-namespace:appNamespace"
  3:                            x:Key="LocalizedStrings" />
  4: </Application.Resources>

    然后在Xaml中这样使用:

  1: <Button Content="{Binding Path=Resource1.PressText, Source={StaticResource LocalizedStrings }}"/>

    在这个过程中需要注意两点:一是对于Resource1.Designer.cs中生成的Resource1类的修饰符要改成public,默认为internal,而且当你添加一个新的项时修饰符会自动变回internal,这里我不得不说…(省略若干字);二是若要使上述代码生效还要对工程文件csproj进行修改,打开csproj文件找到SupportedCultures节(若没有就在PropertyGroup下添加),指定程序支持的语言,否则VS不会为我们生成相应的resource.dll

  1: <?xml version="1.0" encoding="utf-8"?>
  2: <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  3:   <PropertyGroup>
  4:     <!-- ... -->
  5:     <SupportedCultures>zh-CN;en-US</SupportedCultures>
  6:   </PropertyGroup>
  7:   <PropertyGroup Condition="'$(MSBuildToolsVersion)' == '3.5'">
  8:     <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
  9:   </PropertyGroup>
 10:   <!-- ... -->
 11: </Project>

    只有这样修改后VS才会在编译生成的xap包中添加相应的语言目录,里面存放了各自语言的resource.dll。

    这里已经简单说明了Silverlight文档中的建议做法,您觉得这种方式方便吗?

    首先是在Xaml中的引用如此的累赘,暴长的引用方式,使我们的Xaml文件又增加了很多字节,其次是死板,假如我现在想在每个标题后面加上一个冒号“:”,我该怎么做呢,最差的做法是直接改动Resource文件,那么如果程序的其它地方也需要这个字符串而不需要加冒号怎么办?有人抬杠说在Resource中加两项,一个有冒号一个没冒号,呵呵,这显然不是一种好做法;还有人说可以在后面再加一个TextBlock只有一个冒号,这是一个可行的做法,可是还不够友好,如果我要加括号是不是在前后各加一个TextBlock呢?

    其次是当我动态切换语言选项的时候,已经显示的UI并不会自动切换到新的语言,很显然,上面的例子只是将Button的Content单向绑定到了Resource1.PressText,有人会说那就改成这样:

  1: <Button Content="{Binding Path=Resource1.PressText, Source={StaticResource LocalizedStrings, Mode=TwoWay}}"/>

    但是这样并没有用,UI依然不会自动切换,因为Resource1并没有实现INotifyPropertyChanged接口,UI是无法得到通知的。

    我们能不能统一处理这些问题呢?现在的问题在于在从Resource中读取相应的语言字符串并绑定到UI的过程中我们并没有机会干预,那我们能不能在中间加一层干预一下呢?分享一下我的做法:

  1:     public abstract class StringResourceBase : INotifyPropertyChanged, IDisposable
  2:     {
  3:         private static List<StringResourceBase> srList;
  4:         private static List<StringResourceBase> SrList
  5:         {
  6:             get
  7:             {
  8:                 if (srList == null)
  9:                     srList = new List<StringResourceBase>();
 10:                 return srList;
 11:             }
 12:         }
 13:         public static void Refresh()
 14:         {
 15:             foreach (var sr in SrList)
 16:                 sr.OnPropertyChanged(null);
 17:         }
 18:         private ResourceManager manager;
 19:         protected StringResourceBase(ResourceManager manager)
 20:         {
 21:             if (!SrList.Contains(this))
 22:                 SrList.Add(this);
 23:             this.manager = manager;
 24:         }
 25:         public ResourceManager Manager
 26:         {
 27:             get
 28:             {
 29:                 return this.manager;
 30:             }
 31:         }
 32:         public virtual string this[string name]
 33:         {
 34:             get
 35:             {
 36:                 if (name.EndsWith(":"))
 37:                     return (GetString(name.TrimEnd(':'),
 38:                         Thread.CurrentThread.CurrentUICulture) ?? String.Empty) + ":";
 39:                 return GetString(name, Thread.CurrentThread.CurrentUICulture);
 40:             }
 41:         }
 42:         public virtual string GetString(string name, CultureInfo culture)
 43:         {
 44:             return manager.GetString(name,
 45:                 culture ?? Thread.CurrentThread.CurrentUICulture);
 46:         }
 47:         public event PropertyChangedEventHandler PropertyChanged;
 48:         protected virtual void OnPropertyChanged(string name)
 49:         {
 50:             if (this.PropertyChanged != null)
 51:                 this.PropertyChanged(this, new PropertyChangedEventArgs(name));
 52:         }
 53:         public void Dispose()
 54:         {
 55:             if (SrList.Contains(this))
 56:                 SrList.Remove(this);
 57:         }
 58:     }

    这是一个抽象基类,在构造函数中需要将生成的ResourceManager类传入,这个类做的事情很简单,把传入的ResourceManager缓存起来,并提供Refresh的静态方法来触发所有缓存的ResourceManager的PropertyChanged事件,提供GetString方法以及[string name]的字符串索引,在索引程序中我们特殊处理了冒号,那我们如何使用它呢,很简单,只需要在自己的程序集中指定一个类继承它,把该程序集的ResourceManager传入即可,这样做是考虑到多个类库的情况,每个类库会有自己的resource文件:

  1:     public class SR : StringResourceBase
  2:     {
  3:         public SR()
  4:             : base(StringResource.ResourceManager)
  5:         {
  6:         }
  7:     }

    然后我们在Xaml中这样来使用,在App中指定SR:

  1:     <Application.Resources>
  2:         <local:SR x:Key="SR" xmlns:local="clr-namespace:LocalizationDemo"></local:SR>
  3:     </Application.Resources>

    然后可以使用了:

  1:     <StackPanel x:Name="LayoutRoot" Background="White">
  2:         <Button Content="英文" Name="en" Click="en_Click"></Button>
  3:         <Button Content="中文" Name="cn" Click="cn_Click"></Button>
  4:         <TextBlock Text="{Binding Path=[UserName],Source={StaticResource SR}}"></TextBlock>
  5:         <TextBlock Text="{Binding Path=[Password:],Source={StaticResource SR}}"></TextBlock>
  6:     </StackPanel>

    注意这里的“Password:”带了冒号在UI上就会显示带冒号,并且这里我们利用的是Silverlight4的新特性,在Binding中可以使用字符串索引,用方括号包含的就是索引的Name,这个真的是很实用的功能,早就该提供啦,呵呵:)

    后台的代码如下:

  1:         private void en_Click(object sender, RoutedEventArgs e)
  2:         {
  3:             System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
  4:             System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");
  5:             StringResourceBase.Refresh();
  6:         }
  7:         private void cn_Click(object sender, RoutedEventArgs e)
  8:         {
  9:             System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("zh-CN");
 10:             System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("zh-CN");
 11:             StringResourceBase.Refresh();
 12:         }

    当我们切换语言的时候,UI会自动的切换,并且对于格式化等特殊处理,比如冒号、括号还是其他需求,你可以在Base类中的索引代码的位置自由发挥。希望本文对您有帮助。

    完整代码下载

posted @ 2010-06-13 14:18  devil0153  阅读(1784)  评论(7编辑  收藏  举报