Click here to Skip to main content
Click here to Skip to main content

ResxWriter: Generating .resx files from an Excel spreadsheet

By , 17 Nov 2006
 

ResxWriter Screen Shot

Introduction

This is my first article so bear with me :) I want to share this with you because I think ResxWriter has some very nice functionality and some cool features. A break down:

  • Generates .resx files using ADO.NET and an Excel spreadsheet.
  • Fully customizable.
  • Settings are per machine user.
  • Clean uninstallation, with launcher.

ResxWriter is a .NET tool that can read Excel spreadsheets with data used for translation, and convert this data into separate .resx files.

Recently, I was working on an ASP.NET application that needed to have full translation for all static text. We used .NET resource files (.resx) and things were moving along just fine. Updating the text, however, was another story. Using Visual Studio to manually update the .resx files was cumbersome to say the least. Formatting was a pain if you used the IDE, although formatting the XML was an option…

Instead I decided to write an app that would take data from an Excel spreadsheet and generate the .resx files. This allowed our translation staff to use a program they are familiar with, as well as share the files for others to work on.

Reading the Excel File

Suppose you had the following data in a spreadsheet:

ControlName English French
BtnHello Hello World! Bonjour Monde!
LblHello Hello World Again! Bonjour Monde encore!

ResxWriter can extract the data from each column and create .resx files. Any number of columns can be used, and the application can be fully customized.

public bool GetDataFromExcelFile(string filePath)
{
    try
    {
        string strConnectionString = 
            @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" 
            + filePath + @";Extended Properties=""Excel 8.0;HDR=Yes;IMEX=1""";
        OleDbConnection  dbConn = new OleDbConnection(strConnectionString);
        dbConn.Open();
        string fetch = BuildCommand();
        OleDbCommand  cmdSelect = new OleDbCommand(fetch, dbConn);
        OleDbDataAdapter dbAdapter = new OleDbDataAdapter();
        dbAdapter.SelectCommand  = cmdSelect;
        dbAdapter.Fill(this.m_table);
        dbConn.Close();
        dbConn = null;
        return true;
    }
    catch(System.Data.OleDb.OleDbException ex)
    {
        FrmMessage m = new FrmMessage();
        m.SetMessageText("ResxWriter - Error Message", 
            "Cannot Load Excel File!\n\n\nException:\n\n" 
            + ex.ToString());
        m.ShowDialog();
        return false;
    }
}

Writing the Resource Files

I created a class ClsLanguage to encapsulate the name-value functionality.

[Serializable]
public class ClsLanguage 
{
    private string langColumnName; 
    private string langCulture;

    public ClsLanguage() {} 
    
    public ClsLanguage(string columnName, string culture) 
    { 
        this.langColumnName = columnName; 
        this.langCulture = culture; 
    } 

    [XmlElement(ElementName = "LanguageColumnName")]
    public string LanguageColumnName 
    {
        get {return this.langColumnName;} 
        set {this.langColumnName = value;} 
    } 

    [XmlElement(ElementName = "LanguageCulture")] 
    public string LanguageCulture 
    { 
        get {return this.langCulture;} 
        set {this.langCulture = value;} 
    }
}

A strongly-typed collection of ClsLanguage objects is written out as Resource Files.

public void WriteResources(string filePath)
{
    if (filePath.Length > 0)
    {
        // get columns //
        foreach (ClsLanguage l in ClsMain.settings.LanguageCollection)
        {
            ResXResourceWriter rw = 
              new ResXResourceWriter(filePath + "." + 
              l.LanguageCulture + ".resx");

            for (int i=0; i<this.m_table.Rows.Count; i++)
            {
                ClsMain.frmMain.WriteProgressBar.Visible = true;
                ClsMain.frmMain.WriteProgressBar.Maximum = m_table.Rows.Count;

                try
                {
                  string name = 
                   m_table.Rows[i][ClsMain.settings.ControlColumnName].ToString();
                  string valu = m_table.Rows[i][l.LanguageColumnName].ToString();
                  rw.AddResource(name, valu);

                  ClsMain.frmMain.WriteProgressBar.Increment(i);
                }
                catch (System.ArgumentException)
                {
                }
            }

            rw.Generate();
            rw.Close();
        }

        ClsMain.frmMain.WriteProgressBar.Visible = false;

        FrmMessage m = new FrmMessage();
        m.SetMessageText("ResxWriter - Output Results",
            "Files were written to: " + filePath);
        m.ShowDialog();
    }
}

Points of Interest

Application settings (size, location and column names) were serialized through a class, ClsSettings.

[Serializable]        
public class ClsSettings
{        
    private readonly string m_settingsPath = 
          Application.UserAppDataPath + @"\ResxWriter.xml";
    private ClsLanguageCollection m_langCollection 
          = new ClsLanguageCollection();
    private Size    m_size = new Size(250, 175);
    private Point    m_location = new Point(200, 200);
    private string    m_controlColumnName;
    private string    m_sheetName;

    public ClsSettings()
    {
    }

    public string SettingsPath
    {
        get {return this.m_settingsPath;}
    }

    public ClsLanguageCollection LanguageCollection
    {
        get {return this.m_langCollection;}
    }

    public Size FormSize
    {
        get {return this.m_size;}
        set {this.m_size = value;}
    }

    public Point FormLocation
    {
        get {return this.m_location;}
        set {this.m_location = value;}
    }

    public string ControlColumnName
    {
        get {return this.m_controlColumnName;}
        set {this.m_controlColumnName = value;}
    }

    public string SheetName
    {
        get {return this.m_sheetName;}
        set {this.m_sheetName = value;}
    }
}

This allowed me to save (and load) application settings with a few lines of code:

XmlSerializer xs = new XmlSerializer(typeof(ClsSettings));
using (StreamWriter sw = new StreamWriter(xml, false))
{
    ClsMain.settings.FormSize = ClsMain.frmMain.Size;
    ClsMain.settings.FormLocation = ClsMain.frmMain.Location;
    xs.Serialize(sw, ClsMain.settings);
}

All settings in this app are specific to the current user, saved at /Documents and Settings/{User name}/Application Data/... This conforms closely to Microsoft's recommendations, and lets each user have their own experience and preferences.

I hate it when an application uninstalls but doesn't remove all files it was using. So, I made an app that cleans up for a true uninstallation. It essentially deletes all directories and files in its current location. Also, the only method (through Visual Studio) to create an uninstallation icon was to create a Uninstallation Launcher. In the source code, the launcher finds the product key used by the MSI file. This triggers the repair or remove screen, and allows uninstallation.

(These are both included as separate projects in the Solution file.)

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

Patrick Bounaix
Web Developer
United States United States
Member
- philosophy
- french
- computer science

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralAn excellent ideamemberDave Midgley21 Nov '06 - 22:37 
I am currently developing an application for the market that will need globalising, and I shall certainly give your app a go. What we really need to start with, however, is a tool to do the opposite - to take our English resx files and convert them to a spreadsheet that we can then present to the translators. Anyone have any suggestions?
 
Dave

GeneralUseful toolmemberivankrsul16 Nov '06 - 13:09 
Very useful, and just what I was looking for. I'm surprised that this is not a part of Visual Studio.
 
Thanks!
 
Ivan
GeneralRe: Useful toolmemberPatrick Bounaix18 Nov '06 - 10:57 
Thanks for your comments! I believe in VS2005 there is a resx utility built in, but i dont think it can open Excel spreadsheets, so hopefully this might be a useful tool for a while... Smile | :)
 
//--------------------------//
The following sentence is true.
The previous sentence is false.
 
http://l0g1c4l.com

GeneralJust what I needed :-)memberNikola Knezevic1 Sep '06 - 22:26 
I'm developing an application in 20 languages and I can't tell you how helpfull this is for me.
Thanks !!!
 
--Nikola--

GeneralRe: Just what I needed :-)memberPatrick Bounaix18 Nov '06 - 10:55 
I'm glad you found it useful! I have recently posted an update, with a better UI for selecting and managing the columns and languages. (It's still .NET 1.1)
 
//--------------------------//
The following sentence is true.
The previous sentence is false.
 
http://l0g1c4l.com

GeneralGenial!!memberNew-Bee18 Jul '06 - 4:23 
great stuff, this is going to save me plenty of time Smile | :)
If you're still thinking of adding functionality, I would have allowed the user to control the number of output files (I'm working with 14 languages...D'Oh! | :doh: )
GeneralRe: Genial!!memberPatrick Bounaix18 Jul '06 - 10:02 
thanks for your comment!
 
you should be able to output any number of files, you just need to add the appropriate settings. for each column (culture) you have, a seperate .resx file will be generated.
 
i do plan on updating this sometime tho Smile | :)
 
//--------------------------//
The following sentence is true.
The previous sentence is false.
 
http://l0g1c4l.com
GeneralRe: Genial!!memberNew-Bee19 Jul '06 - 0:00 
This is just perfect!
 
Thanks again!!
JokeYou saved my time!memberPaolo Gios7 Jun '06 - 4:37 
I was starting developing the same thing but... you saved my time and got my 5!
 
Well Done.
Paolo.
GeneralRe: You saved my time!memberPatrick Bounaix19 Jun '06 - 16:35 
I'm glad you like it! Big Grin | :-D I plan on releasing an update at some point with some tweaks and code aimed at the 2.0 framework.
 
Thanks for your input!
 
//--------------------------//
The following sentence is true.
The previous sentence is false.
 
http://l0g1c4l.com

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 17 Nov 2006
Article Copyright 2005 by Patrick Bounaix
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid