Synchronizing #Resx files within a .NET project
If you are developing a multi-lingual website or application in .NET, then you undoubtedly use resx files. Over time, it’s likely that these files go out of sync, when you add text to the english version, and forget to send the french version for translation.
Here’s some code in C# that can compare a master resx file (english) with a candidate file (french), and determine what keys are missing, and what keys are present, but not translated.
To start off with, lets create an XSD model for the resx xml;
<?xml version=”1.0″ encoding=”UTF-8″?>
<xs:schema xmlns:xs=”http://www.w3.org/2001/XMLSchema” elementFormDefault=”qualified”>
<xs:import namespace=”http://www.w3.org/XML/1998/namespace” schemaLocation=”xml.xsd”/>
<xs:element name=”root”>
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs=”unbounded” ref=”resheader”/>
<xs:element maxOccurs=”unbounded” ref=”data”/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name=”resheader”>
<xs:complexType>
<xs:complexContent>
<xs:extension base=”value”>
<xs:attribute name=”name” use=”required” type=”xs:NCName”/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name=”data”>
<xs:complexType>
<xs:complexContent>
<xs:extension base=”value”>
<xs:sequence>
<xs:element minOccurs=”0″ ref=”comment”/>
</xs:sequence>
<xs:attribute name=”name” use=”required”/>
<xs:attribute ref=”xml:space”/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name=”comment” type=”xs:string”/>
<xs:complexType name=”value”>
<xs:sequence>
<xs:element ref=”value”/>
</xs:sequence>
</xs:complexType>
<xs:element name=”value” type=”xs:string”/>
</xs:schema>
From this, I create resx.cs by running xsd.exe, and include it in my project
Two helper functions I’m going to need is some code to serialise and deserialise the XML into objects as follows;
public static T ParseXmlData<T>(string xml)
{
var xsT = new XmlSerializer(typeof(T));
var srT = new StringReader(xml);
return (T)xsT.Deserialize(srT);
}
public static string Serialize<T>(T o)
{
var swOutput = new StringWriter();
var xs = new XmlSerializer(typeof(T));
xs.Serialize(swOutput, o);
return swOutput.ToString();
}
Now, lets create the main function
private static void CompareResx(string masterFile, string testFile, string outputFile)
{
var strMasterXml = File.ReadAllText(masterFile);
var strTestXml = File.ReadAllText(testFile);
var rootMaster = ParseXmlData<root>(strMasterXml);
var rootTest = ParseXmlData<root>(strTestXml);
if (rootTest.data == null) return;
var lMissingNodes = new List<data>();
foreach (var entry in rootMaster.data)
{
var testEntry = rootTest.data.FirstOrDefault(e => e.name == entry.name);
if (testEntry != null)
{
if (testEntry.value1 == entry.value1)
{
Console.WriteLine(“English?” + entry.name);
lMissingNodes.Add(entry);
}
else if (string.IsNullOrEmpty(testEntry.value1))
{
Console.WriteLine(“Blank entry:” + entry.name);
lMissingNodes.Add(entry);
}
}
else
{
Console.WriteLine(“Missing entry:” + entry.name);
lMissingNodes.Add(entry);
}
}
var output = new root();
output.data = lMissingNodes.ToArray();
var strOutputXml = Serialize(output);
File.WriteAllText(outputFile, strOutputXml);
}
Pass in the location of the master resx, the candidate resx, and an output file, and then run this code.