LocaleManager - 用于 Java/Flex 和 .NET 的多区域资源文件管理实用工具






4.82/5 (4投票s)
在 C# 中实现了一个软件工具,用于帮助管理 .NET 的 *.resx 文件,或 Java/AS3 的 *.properties 文件,以支持不同的区域设置。
引言
此应用程序允许用户选择一个基础区域设置以及一个或多个其他要处理的区域设置。然后,它会在表格中显示资源名称和值。用户可以跟踪哪些条目需要翻译,或者直接在此表格中填写所有空白。更改将以 UTF8 编码保存。
它支持两种类型的资源文件
- Java 或 Flex 的 *.properties 文件
- .NET 的 *.resx 文件
背景
最近在 Flex 开发过程中,我遇到一个问题:en_US 基础区域设置文件会随着代码中添加新字符串而时不时发生变化,导致其他区域设置文件的同步变得困难。我在网上找不到类似这样简单且免费的工具,于是我决定自己开发。
如何使用 LocaleManager
假设用户遵循区域文件夹的通用实践:为每个区域设置使用一个单独的文件夹。所以,它们通常看起来像这样:
locale-
|--- en_US - resource files in English
|--- de - resource files in German
|--- fr - resource files in French
使用 LocaleManager 非常简单。它是一个独立的应用程序,可在 Windows 上运行。
使用它,只需按照截图操作即可。
- 启动程序。然后,选择基础区域设置目录,例如,根据截图 1 选择 en_us。
- 点击 OK 后,基础区域设置名称将在对话框中以红色显示。同时,左侧列表框中将显示作为现有区域设置的同级子目录。由于我使用 Subversion,svn 目录也会显示出来,这不应该被选作目标区域设置。请参见截图 2。
- 选择要处理的区域设置后,点击 Load,工作表就会显示出来。我的测试输入文件只有三行,所以看起来并不壮观。此窗口是可调整大小的。此工作表窗口显示资源名称、其在基础区域设置中的值以及在选定区域设置中的值。很容易看出哪些条目尚未翻译。
目标区域设置文件将被重新组织,以使用与基础区域设置相同的资源条目。存在于基础区域设置中但不存在于目标区域设置中的条目将被添加到目标区域设置中,其值为空白。存在于目标区域设置中但不存在于基础区域设置中的条目将被移除,并保存到位于 EXE 文件所在文件夹的 Mismatch_timestamp.log 文件中。
在关闭窗口之前点击 Save Changes,所做的更改将保存到目标文件。基础文件不会被修改。
Using the Code
一、程序结构 - 使用 Model 来存储数据
代码中有两个窗体:MainForm
和 WorkSheetForm
。MainForm
用于通过文件浏览让用户选择基础区域设置目录。然后,它显示基础目录的所有同级子目录。
当用户选择一个或多个目标区域设置并点击 Load 按钮后,WorkSheetForm
就会显示。程序将根据选择的区域设置数量动态地向 DataGrid
添加列。
此程序使用 MVC (Model-View-Controller) 模式在 UI 之间传递数据。应用程序数据存储在 Global.cs(Model)中。因此,MainForm.cs 将 BaseDir、RootDir和 Locales 等数据传递给 Global.cs,而 WorkSheetForm.cs 可以从中获取数据。
二、使用 Directoryinfo 和 Folderbrowserdialog 类
使用 FolderBrowserDialog
而不是 OpenFileDialog
,是因为用户在查找基础区域设置目录时,只能看到文件夹。
.NET 的 DirectoryInfo
类非常方便,您可以检索有关目录或文件的所有不同信息。MainForm.cs 使用此类来获取基础目录和根区域设置目录。fillLocaleList()
函数查找所有同级子目录并将它们添加到列表框中。
private void m_browse_Click(object sender, EventArgs e)
{
m_baseDirBrowserDlg.SelectedPath = Environment.CurrentDirectory;
if (m_baseDirBrowserDlg.ShowDialog(this) != DialogResult.OK)
return;
Global.BaseDir = new DirectoryInfo(m_baseDirBrowserDlg.SelectedPath);
Global.RootDir = Global.BaseDir.Parent;
m_baseLocale.Text = Global.BaseDir.Name;
fillLocaleList();
}
private void fillLocaleList()
{
m_allLocales.Items.Clear();
m_selectedLocale.Items.Clear();
DirectoryInfo[] subs = Global.RootDir.GetDirectories();
foreach (DirectoryInfo sub in subs)
{
String dirName = sub.Name;
if (dirName != Global.BaseDir.Name)
m_allLocales.Items.Add(dirName);
}
if (m_allLocales.Items.Count > 0)
{
m_add.Enabled = true;
m_addAll.Enabled = true;
m_allLocales.SelectedIndex = 0;
}
else
MessageBox.Show("No sibling folders were found for the base locale.");
}
该代码仅在用户完成必要步骤后才启用按钮。这很容易实现,但能极大地帮助用户,使应用程序更友好。
三、动态向 DataGrid 添加列
如果您要显示的 DataGrid 列数始终相同且标题保持不变,则可以通过属性设置配置 datagrid
。但是,有时列数和标题取决于用户的输入,我们需要动态地向 DataGrid 添加列。
以下代码显示了如何在 Worksheet 表单中根据用户选择的目标区域设置来添加列。列索引来自列的枚举。
public enum Columns
{
No = 0, File, Name, Base
};
private void WorkSheetForm_Load(object sender, EventArgs e)
{
FileInfo[] files = Global.BaseDir.GetFiles("*"+ Global.Extension);
...
...
//determine number of columns for the datagrid
m_grid.ColumnCount = Global.Locales.Length + (int)Columns.Base + 1;
_numOfCols = m_grid.ColumnCount;
m_grid.Columns[(int)Columns.No].Name = "no.";
m_grid.Columns[(int)Columns.No].ValueType = typeof(int);
m_grid.Columns[(int)Columns.File].Name = "file";
m_grid.Columns[(int)Columns.Name].Name = "name";
m_grid.Columns[(int)Columns.Base].Name = Global.BaseDir.Name;
int i = (int)Columns.Base + 1;
foreach (string locale in Global.Locales)
m_grid.Columns[i++].Name = locale;
...
}
当添加名为 "no.
" 的列时,我将其 ValueType
设置为 int
,否则它默认为 string
。这在用户单击标题进行排序时很重要。如果值类型是 string
,则排序结果将是 "1, 11, 2, 21, ..." 而不是 "1, 2, 3, ..."。
m_grid.Columns[(int)Columns.No].Name = "no.";
m_grid.Columns[(int)Columns.No].ValueType = typeof(int);
四、动态向 DataGrid 添加行
函数 AddRowToGrid()
显示如何向 DataGrid 添加新行。函数 AddToRow()
显示如何填充单元格或访问现有行中的单元格值。
private void AddRowToGrid(string file, int i, string name, string value)
{
string[] row;
row = new string[_numOfCols];
row[(int)Columns.No] = i.ToString();
row[(int)Columns.File] = file;
row[(int)Columns.Name] = name.Trim();
row[(int)Columns.Base] = value.Trim();
m_grid.Rows.Add(row);
}
private void AddToRow(int i, int localeIndex, string value)
{
if (null!= value && i>=0)
m_grid.Rows[i].Cells[getColumnNo(localeIndex)].Value = value;
}
private int getColumnNo(int localeIndex)
{
return (int)Columns.Base + localeIndex + 1;
}
五、使用 XMLTextReader 解析 *.resx 文件
*.properties 文件由 StreamReader
加载,资源名称/值对通过 String.Split
读取。
tokens = line.Split('=');
*.resx 文件由 XmlTextReader
加载。*.resx 文件条目如下所示:
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema -->
<data name="GoodEvening" xml:space="preserve">
<value>Good Evening</value>
<comment>Evening</comment>
</data>
<data name="GoodMorning" xml:space="preserve">
<value>Good Morning</value>
<comment>Morning</comment>
</data>
<data name="Hello" xml:space="preserve">
<value>Hello</value>
<comment>Greetings</comment>
</data>
</root>
XML 解析需要解析元素属性和文本。解析代码如下:
using (XmlTextReader rd = new XmlTextReader(path))
{
XmlParsingSteps step = XmlParsingSteps.ParsingName;
string name = "";
string value = "";
while (rd.Read())
{
switch (rd.NodeType)
{
case XmlNodeType.Element:
if (step == XmlParsingSteps.ParsingName && rd.Name == "data")
{
while (rd.MoveToNextAttribute())
{
if (rd.Name == "name")
{
name = rd.Value;
step = XmlParsingSteps.ParsingValueTag;
break;
}
}
}
else if (step == XmlParsingSteps.ParsingValueTag &&
rd.Name == "value")
{
step = XmlParsingSteps.ParsingValue;
}
break;
case XmlNodeType.Text:
if (step == XmlParsingSteps.ParsingValue)
{
value = rd.Value;
if (isBase)
{
AddRowToGrid(file, rowIndex, name, value);
rowIndex++;
}
else
{
rowIndex = findRowNo(file, name.Trim());
if (rowIndex < 0)
{
//property not found in base
Global.Log(Global.MismatchLog, name + "-" + value);
}
else
AddToRow(rowIndex, localeIndex, value);
}
//reset name and value strings
step = XmlParsingSteps.ParsingName;
name = "";
value = "";
}
break;
default:
break;
}
}
}
关注点
我喜欢这个应用程序的一点是,WorkSheetForm
的缩放非常流畅。以前,我在 DataGridView
上方有一个组合框和一个 Save 按钮,并且很难设置 Anchor
和 Dock
属性,以使 DataGrid
在用户调整窗口大小时能够扩展。至少,列宽没有改变。
后来,我只是添加了一个菜单,并将 Save 选项添加到了菜单中。ComboBox
只是放在那里,即使它在菜单栏上,仍然工作正常。只需将 DataGridView
的 Dock
属性设置为 "Fill
",它就会填充整个应用程序的空间。
我在这款应用程序中看到的唯一一个问题是,在 DataGrid
的单元格中输入任何内容后,您必须单击其他地方,该单元格的值才能更新,即使屏幕上显示您刚输入的文字。所以,如果您继续编辑,您将没事。只有最后一条输入可能无法正确保存,如果您在点击 Save 之前没有单击其他地方的话。我认为这与 DataGrid
的 EditMode
属性有关。在我的程序中,设置是 EditOnEnter
。我尝试将其设置为其他值,但没有看到它解决此问题。无论如何,我会继续尝试找到答案。
历史
- 2/20/09
- 初始版本支持 *.properties 文件
- 2/26/09
- 添加了对 *.resx 加载和查看的支持
- 尚未为 *.resx 文件实现保存
- 3/8/09
- 文章更新。
- 7/31/09
- 添加了 LocaleManager_1.3_Executables.zip