5,691,626 members and growing! (13,828 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » .NET Framework » Utilities     Intermediate

RESX Synchronizer

By Dario Solera

A tool for synchronizing multilanguage .NET Resource files.
C#, C# 2.0Windows, .NET, .NET 2.0, Win2K, WinXP, Win2003, VistaVS2005, Visual Studio, Dev

Posted: 25 Sep 2006
Updated: 12 Oct 2006
Views: 21,916
Bookmarked: 30 times
Announcements
Loading...



Search    
Advanced Search
Sitemap
8 votes for this Article.
Popularity: 4.19 Rating: 4.65 out of 5
0 votes, 0.0%
1
0 votes, 0.0%
2
0 votes, 0.0%
3
2 votes, 25.0%
4
6 votes, 75.0%
5

Sample Image - ResxSync.jpg

Introduction

An ASP.NET project I'm working on requires localization in different languages. It's nice to use .NET Resource files (.resx), because they are really integrated with the framework and you do not have to worry about explicitly managing the resources. Resource files are, though, really un-maintainable. When you edit the resource files in Visual Studio, ore use the "Generate Local Resources" function, it only updates the default language resources. For example, the file Default.aspx.resx, and not all the translated files, for example, Default.aspx.it-IT.resx. This is obviously a big problem, since you have to update them one by one, manually. RESX Synchronizer allows you to synchronize resource files, adding new keys and removing the deleted ones. It also processes a set of resource files, the "master" file, and all its translated "brothers".

How it Works

This tool uses the two classes System.Resources.ResXResourceReader and System.Resources.ResXResourceWriter, contained in the assembly System.Windows.Forms.dll. These classes allow to read and write .resx files, including comments.

We'll use the name of "source file" to indicate the "master" resource file, for example, Default.aspx.resx, and the name of "destination file" to indicate a "brother" resource file, for example, Default.aspx.it-IT.resx, which must be synchronized. The tool must perform two operations in order to get the files synchronized:

  1. Compare the two files, finding new keys to be added to the "destination" file.
  2. Compare the two files, finding deleted keys to be removed from the "destination" file.

The core of the tool is the class Synchronizer. Its code is reported here, and the concepts behind it are explained below.

public class Synchronizer {

    string sourceFile, destinationFile;

    public Synchronizer(string sFile, string dFile) {
        sourceFile = sFile;
        destinationFile = dFile;
    }

    public void SyncronizeResources(bool backup,
                 bool addOnly, bool verbose,
                 out int added,out int removed) {
        added = 0;
        removed = 0;
        if(backup) {
            string destDir = Path.GetDirectoryName(destinationFile);
            string file = Path.GetFileName(destinationFile);
            File.Copy(destinationFile, destDir + 
                      "\\Backup of " + file, true);
        }

        string tempFile = Path.GetDirectoryName(destinationFile) + 
                                                "\\__TempOutput.resx";

        // Load files in memory

        MemoryStream sourceStream = new MemoryStream(), 
                     destinationStream = new MemoryStream();
        FileStream fs;
        int read;
        byte[] buffer = new byte[1024];

        fs = new FileStream(sourceFile, FileMode.Open, 
                            FileAccess.Read, FileShare.Read);
        read = 0;
        do {
            read = fs.Read(buffer, 0, buffer.Length);
            sourceStream.Write(buffer, 0, read);
        } while(read > 0);
        fs.Close();

        fs = new FileStream(destinationFile, FileMode.Open, 
                            FileAccess.Read, FileShare.Read);
        read = 0;
        do {
            read = fs.Read(buffer, 0, buffer.Length);
            destinationStream.Write(buffer, 0, read);
        } while(read > 0);
        fs.Close();

        sourceStream.Position = 0;
        destinationStream.Position = 0;

        // Create resource readers

        ResXResourceReader source = new ResXResourceReader(sourceStream);
	// Enable support for Data Nodes, important for the comments

	source.UseResXDataNodes = true;
        ResXResourceReader destination = 
                           new ResXResourceReader(destinationStream);
	destination.UseResXDataNodes = true;
        
        // Create resource writer

        if(File.Exists(tempFile)) File.Delete(tempFile);
        ResXResourceWriter writer = new ResXResourceWriter(tempFile);

        // Compare source and destination:

        // for each key in source, check if it is present in destination

        //    if not, add to the output

        // for each key in destination, check if it is present in source

        //    if so, add it to the output


        // Find new keys and add them to the output

        foreach(DictionaryEntry d in source) {
            bool found = false;
            foreach(DictionaryEntry dd in destination) {
                if(d.Key.ToString().Equals(dd.Key.ToString())) {
                    // Found key

                    found = true;
                    break;
                }
            }
            if(!found) {
                // Add the key

		ResXDataNode node = d.Value as ResXDataNode;
                writer.AddResource(node);
                added++;
                if(verbose) {
                    Console.WriteLine("Added new key '" + d.Key.ToString() +
                                      "' with value '" + 
                                      d.Value.ToString() + "'\n");
                }
            }
        }

        if(addOnly) {
            foreach(DictionaryEntry d in destination) {
                ResXDataNode node = d.Value as ResXDataNode;
                writer.AddResource(node);
            }
        }
        else {
            int tot = 0;
            int rem = 0;
            // Find un-deleted keys and add them to the output

            foreach(DictionaryEntry d in destination) {
                bool found = false;
                tot++;
                foreach(DictionaryEntry dd in source) {
                    if(d.Key.ToString().Equals(dd.Key.ToString())) {
                        // Found key

                        found = true;
                    }
                }
                if(found) {
                    // Add the key

                    ResXDataNode node = d.Value as ResXDataNode;
                    writer.AddResource(node);
                    rem++;
                }
                else if(verbose) {
                    Console.WriteLine("Removed deleted key '" + 
                                      d.Key.ToString() + 
                                      "' with value '" + 
                                      d.Value.ToString() + "'\n");
                }
            }
            removed = tot - rem;
        }

        source.Close();
        destination.Close();
        writer.Close();

        // Copy tempFile into destinationFile

        File.Copy(tempFile, destinationFile, true);
        File.Delete(tempFile);
    }

}

The two operations described above are implemented in the two foreach cycles. The first one iterates over the source keys, searching for keys with the same name in the destination. If no key is found, then the current key in the source file is new, and must be added to the output (destination) file. The second cycle iterates over the destination keys, searching for keys with the same name in the source. If a key with the same name is found in the source file, then the key is still needed, and then it must be copied to the output. If no key is found, then that key has been deleted and therefore it is not copied to the output.

The Synchronizer class is also able to:

  • Create a backup copy of the destination file before modifying it
  • Add only new keys, without removing the deleted keys
  • Display in the console each added and removed key
  • Return to the caller the number of added and deleted keys

The Command-Line Tool

The Synchronizer class has been wrapped into a command-line tool, which parses the parameters and performs the synchronization using the class. The code of this utility is really simple, and therefore does not need to be reported.

Usage of the Command-Line Tool

Since the tool is still in a development stage, I don't want to report here the usage instructions which might be obsolete in a few days, so I point you to the official page of the tool, available at this address.

History

  • 2006/10/12 - Version 1.1: support for comments added
  • 2006/09/25 - Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Dario Solera


Dario Solera is currently working as a freelance .NET developer in Italy. He has a (short) past of C/C++, Java and PHP programming. Then he discovered .NET, and currently he develops C# .NET Windows Applications, WebServices and Web Applications.

He started the ScrewTurn Software experiment developing the ScrewTurn Wiki engine.

His interests are mainly politics, F1 and digital photography. He hopes, someday, to learn skydiving.
Occupation: Software Developer
Location: Italy Italy

Other popular .NET Framework articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 10 of 10 (Total in Forum: 10) (Refresh)FirstPrevNext
Generalhexadecimal value 0x00, is an invalid charactermemberxouth7:50 15 Jan '08  
GeneralThanksmemberMSamini4:43 11 Jan '08  
GeneralProblem with ResXDataNodememberJesperGissel9:45 1 May '07  
GeneralRe: Problem with ResXDataNodememberBiju Narayanan P D23:11 31 Jul '07  
AnswerRe: Problem with ResXDataNodememberquotequad15:30 7 Sep '07  
GeneralRe: Problem with ResXDataNodememberRobert Kuma8:31 27 Feb '08  
GeneralThank you..memberkalyankrishna18:13 5 Mar '07  
QuestionHow to update the resx online?membermikedepetris4:23 17 Oct '06  
GeneralComments ARE accesible [modified]memberfdub10:00 11 Oct '06  
GeneralRe: Comments ARE accesiblememberDario Solera10:04 11 Oct '06  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 12 Oct 2006
Editor: Smitha Vijayan
Copyright 2006 by Dario Solera
Everything else Copyright © CodeProject, 1999-2008
Web20 | Advertise on the Code Project