在软件开发中,只要软件的用户涉及到不同的国家,则需要考虑其所使用的语言,字符集(现在可以使用Unicode通用字符集),货币符号,日期时间格式,数字格式等等,例如,常用的WinRAR就有近20种语言的不同版本,这种技术称作软件的本地化。实际上,Windows系列本身提供了严格的本地化机制,例如各种文化(Culture)都有自己的LCID,文本都有自己的代码页等等,我们在实际开发过程中可以使用Windows提供的这些内容,剩下的只是将软件中的字符串资源进行翻译就可以了,这样可简化本地化工作,极大地提高开发效率。
    为此,我用.NET(C#)编制了一个“Windows文化信息浏览器”,通过该软件可以查阅任何语言和任何国家(语言相同但国家不同的不同文化)的详细文化信息,程序不是很复杂,注释相当详实,有一定经验的开发者都可以作为参考。
这是软件截图:

这是程序的核心代码:

  1. using System;  
  2. using System.Collections;  
  3. using System.Globalization;  
  4. using System.Text;  
  5. using System.Windows.Forms;  
  6.  
  7. namespace Mengliao.CSharp.C21.S01  
  8. {  
  9.     public partial class FormCulture : Form  
  10.     {  
  11.         public FormCulture()  
  12.         {  
  13.             InitializeComponent();  
  14.             AddCulturesToTree();  
  15.         }  
  16.  
  17.         // 添加所有文化(不变的文化、中立文化、特定文化)到TreeView中  
  18.         public void AddCulturesToTree()  
  19.         {  
  20.             // 添加一个顶层根节点  
  21.             treeViewCulture.Nodes.Add("所有区域");  
  22.  
  23.             // 取得中立文化和不变的文化  
  24.             CultureInfo[] cultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures);  
  25.             // 插入所有中立文化和不变的文化  
  26.             for (int i = 0; i < cultures.Length; i++)  
  27.             {  
  28.                 TreeNode tempNode = new TreeNode(); // 建立临时节点  
  29.                 tempNode.Text = cultures[i].DisplayName; // 为节点提供名称(文化的显示名称)  
  30.                 tempNode.Tag = cultures[i]; // 使用属性节点的附加数据属性,存储文化对象,以便将来在用户点击时,显式该文化的信息及使用该文化  
  31.                 treeViewCulture.Nodes[0].Nodes.Add(tempNode); // 在顶层根节点下的节点集合中插入节点  
  32.             }  
  33.  
  34.             // 取得特定文化  
  35.             cultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures);  
  36.             // 插入所有特定文化  
  37.             for (int i = 0; i < cultures.Length; i++)  
  38.             {  
  39.                 // 取得TreeView中的顶层根节点下的第1个子节点(顶层根节点下标为0,即"所有区域"节点,其下的第1个子节点即第2层节点中的第1个)  
  40.                 TreeNode nextRootNode = treeViewCulture.Nodes[0].FirstNode;  
  41.                 while (nextRootNode != null//迭代所有根节点,总可以找到该特定文化的父文化  
  42.                 {  
  43.                     // 特定文化一定有父文化存在,判断该特定文化是否属于该中立文化或不变的文化  
  44.                     if (cultures[i].Parent.LCID == ((CultureInfo)nextRootNode.Tag).LCID)  
  45.                     {  
  46.                         TreeNode tempNode = new TreeNode(); // 建立临时节点  
  47.                         tempNode.Text = cultures[i].DisplayName; // 为节点提供名称(文化的显示名称)  
  48.                         tempNode.Tag = cultures[i]; // 使用属性节点的附加数据属性,存储文化对象,以便将来在用户点击时,显式该文化的信息及使用该文化  
  49.                         nextRootNode.Nodes.Add(tempNode); // 插入节点,作为查找到的节点的子节点  
  50.                         break// 已经为当前特定文化节点找到了父节点,退出循环  
  51.                     }  
  52.                     nextRootNode = nextRootNode.NextNode; //获取下一个同级节点(即下一个根节点,中立文化或不变的文化)  
  53.                 }  
  54.             }  
  55.  
  56.             // 分配IComparer接口对象,实现自定义排序  
  57.             treeViewCulture.TreeViewNodeSorter = new CompareCultureName();  
  58.             // 对TreeView中的内容(即所有文化)进行排序,以方便阅读  
  59.             treeViewCulture.Sort();  
  60.  
  61.             // 利用树节点数量及层次统计非特定区域:即中立文化(语言相关区域)+不变的文化(固定区域);特定区域:特定文化  
  62.             // 也可利用cultures.Length在取得每类文化后分别进行统计  
  63.             treeViewCulture.Nodes[0].Text += String.Format("({0}非特定区域/{1}特定区域)",  
  64.                 treeViewCulture.Nodes[0].GetNodeCount(false), treeViewCulture.Nodes[0].GetNodeCount(true) - treeViewCulture.Nodes[0].GetNodeCount(false));  
  65.  
  66.             //展开"所有区域"下的一个层次的节点  
  67.             treeViewCulture.Nodes[0].Expand();  
  68.         }  
  69.  
  70.         // 用户点选TreeView中的节点后的事件处理方法  
  71.         private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)  
  72.         {  
  73.             // 清空所有显示内容  
  74.             // 因为不变的文化没有区域信息,中立文化没有示例和区域信息  
  75.             // 另外,户点选"所有区域"时同样需要清除所有显示内容  
  76.             ClearAllFields(this);  
  77.  
  78.             // 判断是否点选了"所有区域"节点,其所在层次(level)为0  
  79.             // 当选中"所有区域"时,将文化信息、文本信息置为不可用,否则置为可用,示例、区域信息是否可用在后面处理  
  80.             groupBoxCulture.Enabled = groupBoxText.Enabled = e.Node.Level != 0;  
  81.             if (e.Node.Level == 0)  
  82.             {  
  83.                 // 将示例、区域信息置为不可用  
  84.                 groupBoxSamples.Enabled = groupBoxRegionInfo.Enabled = false;  
  85.                 return;  
  86.             }  
  87.  
  88.             // 从用户点选的节点对象中获取保存在Tag属性中的CultureInfo对象  
  89.             CultureInfo ci = (CultureInfo)e.Node.Tag; // Object类型显式转换  
  90.  
  91.             textBoxName.Text = ci.Name; // 文化名称(文化的字符串表示)  
  92.             checkBoxIsNeutral.Checked = ci.IsNeutralCulture; // 是否中立文化  
  93.             textBoxNativeName.Text = ci.NativeName; // 本地名称  
  94.             textBoxEnglishName.Text = ci.EnglishName; // 英文名称  
  95.             // 默认日历,将日历对象转换为字符串(即日历名称),移除前面的21个字符(命名空间字符串System.Globalization.)  
  96.             // 再移除末尾的字符串Calendar  
  97.             textBoxCalendar.Text = ci.Calendar.ToString().Remove(0, 21).Replace("Calendar""");  
  98.  
  99.             // 处理可选日历  
  100.             comboBoxCalendars.Items.Clear(); // 清空下拉列表  
  101.             foreach (Calendar optCal in ci.OptionalCalendars) // 枚举可选日历集合  
  102.             {  
  103.                 // 使用StringBulider类处理字符串以加快速度  
  104.                 // 等价为:string calName = optCal.ToString().Remove(0, 21).Replace("Calendar", "");  
  105.                 StringBuilder calName = new StringBuilder(50);  
  106.                 calName.Append(optCal.ToString());  
  107.                 calName.Remove(0, 21);  
  108.                 calName.Replace("Calendar""");  
  109.  
  110.                 // 由于格里历(公历)包含了多种语言版本,所以需要为每种不同语言的格里历添加类型信息描述,以便区分  
  111.                 GregorianCalendar gregCal = optCal as GregorianCalendar; // 尝试将可选日历转换为格里历  
  112.                 if (gregCal != null// 转换结果为null,说明可选日历不是格里历  
  113.                 {  
  114.                     // 添加格里历的类型信息(语言本版信息)  
  115.                     // 如不使用StringBuilder,可写为:calName += " " + gregCal.CalendarType.ToString();  
  116.                     calName.AppendFormat(" {0}", gregCal.CalendarType.ToString());  
  117.                 }  
  118.                 // 添加到ComboBox中  
  119.                 // 如不使用StringBuilder可写为:comboBoxCalendars.Items.Add(calName);  
  120.                 comboBoxCalendars.Items.Add(calName.ToString());  
  121.             }  
  122.             comboBoxCalendars.SelectedIndex = 0; // 显示可选日历中的第1项  
  123.             textBoxKeyboard.Text = ci.KeyboardLayoutId.ToString(); // 输入法ID  
  124.             textBoxLCID.Text = ci.LCID.ToString(); // LCID  
  125.             textBoxWinAPILangID.Text = ci.ThreeLetterWindowsLanguageName; // Windows API语言  
  126.             textBoxISO639_1.Text = ci.TwoLetterISOLanguageName; // ISO 639-1语言  
  127.             textBoxISO639_2.Text = ci.ThreeLetterISOLanguageName; // ISO 639-2语言  
  128.  
  129.             // 文本信息  
  130.             textBoxANSI.Text = ci.TextInfo.ANSICodePage.ToString(); // ANSI代码页  
  131.             checkBoxIsRightToLeft.Checked = ci.TextInfo.IsRightToLeft; // 是否从右到左  
  132.             if (ci.TextInfo.IsRightToLeft) //设定本地名称和货币本地名称为从右到左或者从左到右显示  
  133.                 textBoxNativeName.RightToLeft = textBoxSampleNumber.RightToLeft = textBoxSampleDate.RightToLeft =  
  134.                     textBoxSampleTime.RightToLeft = textBoxCurrencyNativeName.RightToLeft = textBoxCurrencySymbol.RightToLeft = RightToLeft.Yes;  
  135.             else 
  136.                 textBoxNativeName.RightToLeft = textBoxSampleNumber.RightToLeft = textBoxSampleDate.RightToLeft =  
  137.                     textBoxSampleTime.RightToLeft = textBoxCurrencyNativeName.RightToLeft = textBoxCurrencySymbol.RightToLeft = RightToLeft.No;  
  138.             textBoxEBCDIC.Text = ci.TextInfo.EBCDICCodePage.ToString(); // EBCDIC代码页  
  139.             textBoxMacintosh.Text = ci.TextInfo.MacCodePage.ToString(); // Macintosh代码页  
  140.             textBoxOEM.Text = ci.TextInfo.OEMCodePage.ToString(); // OEM代码页  
  141.  
  142.             // 显示示例和区域信息  
  143.             if (!ci.IsNeutralCulture) // 不是中立文化  
  144.             {  
  145.                 // 显示示例  
  146.                 groupBoxSamples.Enabled = true;  
  147.                 ShowSamples(ci);  
  148.                 // ISO 639-2定义了三个字母表示的语言名称,以此判断该非中立文化是否是不变的文化  
  149.                 if (ci.LCID == CultureInfo.InvariantCulture.LCID) // 是不变的文化  
  150.                 {  
  151.                     groupBoxRegionInfo.Enabled = false// 不包含区域信息  
  152.                 }  
  153.                 else // 是特定的文化  
  154.                 {  
  155.                     // 包含区域信息  
  156.                     groupBoxRegionInfo.Enabled = true;  
  157.                     ShowRegionInformation(ci.Name);  
  158.                 }  
  159.             }  
  160.             else // 中立文化既没有示例、也没有区域信息  
  161.                 groupBoxSamples.Enabled = groupBoxRegionInfo.Enabled = false;  
  162.         }  
  163.  
  164.         // 清空所有显示内容  
  165.         private void ClearAllFields(Control control)  
  166.         {  
  167.             // 如果该控件包含有子控件,则枚举所有子控件,对每个子控件递归调用该方法,直至控件不包含子控件  
  168.             if (control.HasChildren)  
  169.                 foreach (Control childControl in control.Controls)  
  170.                     ClearAllFields(childControl);  
  171.             else if (control is TextBox) //该控件是TextBox,清除内容  
  172.                 ((TextBox)control).Text = "";  
  173.             else if (control is CheckBox) //该控件是CheckBox,将其置为未选中  
  174.                 ((CheckBox)control).Checked = false;  
  175.             else if (control is ComboBox) //该控件是ComboBox,清空项目  
  176.                 ((ComboBox)control).Items.Clear();  
  177.         }  
  178.  
  179.         // 显式示例中的内容  
  180.         private void ShowSamples(CultureInfo ci)  
  181.         {  
  182.             // 使用指定的文化信息格式化标准数字(Double)  
  183.             textBoxSampleNumber.Text = 9876543.21D.ToString("N5", ci);  
  184.  
  185.             // 使用指定的文化信息格式化长日期和长时间  
  186.             textBoxSampleDate.Text = DateTime.Today.ToString("D", ci);  
  187.             textBoxSampleTime.Text = DateTime.Now.ToString("T", ci);  
  188.         }  
  189.  
  190.         // 显式区域信息中的内容  
  191.         private void ShowRegionInformation(string culture)  
  192.         {  
  193.             // 使用指定的文化信息字符串实例化一个区域信息对象  
  194.             RegionInfo ri = new RegionInfo(culture);  
  195.             textBoxGeoID.Text = ri.GeoId.ToString(); // GeoID  
  196.             checkBoxIsMetric.Checked = ri.IsMetric; // 是否公制单位  
  197.             textBoxCurrencyNativeName.Text = ri.CurrencyNativeName; // 货币本地名称  
  198.             textBoxCurrencyEnglishName.Text = ri.CurrencyEnglishName; // 货币英文名称  
  199.             textBoxCurrencySymbol.Text = ri.CurrencySymbol; // 货币符号  
  200.             textBoxISOCurrencySymbol.Text = ri.ISOCurrencySymbol; // ISO 4217货币符号  
  201.             textBoxISO3166_2.Text = ri.TwoLetterISORegionName; // ISO 3166(2字母)  
  202.             textBoxISO3166_3.Text = ri.ThreeLetterISORegionName; // ISO 3166(3字母)  
  203.             textBoxWinRegionName.Text = ri.ThreeLetterWindowsRegionName; // Windows API代码  
  204.         }  
  205.  
  206.         // 自定义TreeView的排序IComparer接口  
  207.         class CompareCultureName : IComparer  
  208.         {  
  209.             int IComparer.Compare(Object x, Object y) // 实现该接口的唯一方法  
  210.             {  
  211.                 // 需要使用CultureInfo类的Name属性(内部名称)排序(字符串)  
  212.                 // 传入的x、y是两个TreeNode对象,其Tag属性中保存着相应的CultureInfo对象  
  213.                 return ((CultureInfo)(((TreeNode)x).Tag)).Name.CompareTo(((CultureInfo)(((TreeNode)y).Tag)).Name);  
  214.             }  
  215.         }  
  216.  
  217.  
  218.     }  

下面的链接和附件中链接是一样的,都是本软件的完整源码,包含了一个已编译的.exe文件,至少需要.NET 3.5架构支持:
http://mengliao.blog.51cto.com/p_w_upload/201101/876134_1294719610.rar