Automated Coder

Exploring the Code of CruiseControl.Net

Posts Tagged ‘Internationalisation’

Internationalisation – part 4

Posted by Craig Sutherland on 8 November, 2008

Previously

Up until now I’ve been working on converting hard-coded strings to resource-based strings. This has all been in preparation for allowing people to change their language (or at least have their current language displayed). These changes make use of the resource functionality in .Net, plus the resource-based code generation in Visual Studio.

Now that I have some translated strings (thanks Ruben), I’m ready to test the language changing. In this post I’ll go over what I’ve done and why.

Configuring the Language

When I initially wrote about allowing different languages, the first feedback I got was that people like the idea, but they also want to be able to stick with English for their own use. This means giving people the ability to change which language they use.

By default the current UI culture is set to the UI culture of the machine. This is normally a combination of the language and region (i.e. for me, my UI culture is en-NZ). This allows both different languages and different date/time/numeric settings depending on where in the world a person is (i.e. I live in New Zealand and speak English).

In CruiseControl.Net people want to be able to change this (at least I’m assuming that their culture settings are non-English). Since the CruiseControl.Net server has no user interface, the best place for doing this is in the configuration.

Back when I started contributing to CruiseControl.Net I added the ability to set extensions in CruiseControl.Net. Part of this work involved letting people set, in the configuration file, which extensions to use. To do this I added a class called ServerConfigurationHandler, which implements IConfigurationSectionHandler. This means that configuration will be handled by the .Net configuration framework.

Now it’s time to modify this class and how it works. For my initial implementation I just returned a List<ExtensionConfiguration>, now I need it to return more (but still the list). To handle this I have added a new class called ServerConfiguration, which includes the current list plus adds the ability for people to add other configuration settings. This required modify ServerConfigurationHandler to return the new class and then modifying CruiseServerFactory to use the new class instead of the list.

Now I’m ready to add some new configuration. The only setting I’m going to add is culture name – this will be the language code the application will use. The default setting is the current UI culture name. The handler checks the configuration to see if the value is set, if so it loads it in, otherwise it just uses the default.

So, the new configuration looks like this:

<cruiseServer>
  <culture name="nl"/>
</cruiseServer>

To change the language just change the name of the culture :) But default this is commented out so the machine’s settings are used.

Changing the Language

Changing the language is very simple – just need to set the culture on the current thread. There is one limitation – the CurrentCulture property cannot be a neutral culture. To get around this, I’ve used CurrentUICulture instead. This is the property used by the resource manager to work out which language resources to use, while CurrentCulture is used for additional settings like number and date/time formats.

There are two projects where this needs to be set – console and service. The console project uses ConsoleRunner in the core project, while service does all its own instantiation and initialisation. Therefore the language changing logic needs to be in both locations.

The actual logic is straight forward. First attempt to get the configuration using ConfigurationManager. Next check if the configuration was retrieved (it will return null if there is none) and see if the culture has been set. If these conditions have been met, then instantiate the culture and set the CurrentUICulture property on the current thread.

I also modified the locations where a new thread is started to set the CurrentUICulture based on the culture of the calling thread – this should propagate the culture to all child threads.

Some quick testing showed that the culture is successfully changed and the applicaton now uses the desired language :)

Note: culture codes in .Net have two parts – the language and the area. The language code is always used, while the area defines the additional formatting settings. The resource manager will first attempt to match the language/area combination. If this fails it will attempt the language. Finally if both of these checks fail it will use the default resources – in this case English. As such it is possible to set the culture in the configuration to either (e.g. I could set en-NZ) and it will still work. But unless the language has been added you’ll get English. The other format settings are ignored (for the moment).

The Service

As I stated earlier, the service project also uses the culture settings. Like console, it provides an access point to the functionality in the core project. Unlike console, it has its own hard-coded strings. Therefore my final change for this post was to go through this project and convert all hard-coded UI strings to resource strings.

Now both of the back-end interfaces support multi-lingual interfaces.

The next step in this process is to start looking at the user interfaces – CCTray and the dashboard. Over the next few days I’ll take a look at them and then start to plan how I’ll convert them to use resources.

Posted in CruiseControl.Net | Tagged: | Leave a Comment »

Internationalisation – part 3

Posted by Craig Sutherland on 5 November, 2008

Previously

In my last post on this topic I started modified CruiseControl.Net to allow for multiple languages. Initially I only modified Remote, and that only for English.

In this post I’ll start modifying an actual functional area of CruiseControl.Net and start to look at allowing changing languages.

First Area: The Server

While the server doesn’t do much in terms of user interaction, it is the heart of CruiseControl.Net. This consists of three projects: Core, Server and Console. The last two are just facades to Core, they don’t have much to do with the user in them (although Service does have a few strings to be converted).

After spending some time in Core the main user interaction is to log messages. This is stored in a log file, plus for the console version they are displayed to the console. (Log4Net is used to actually handle the message logging – which I don’t know too much about, other than it works as is!)

Some General Changes

As I progressed through changing the strings to resources I noticed I was converting all fixed position substitutions to format substitutions. For example I would convert the following string:

“An unexpected error occurred: ” + ex.Message

To:

string.Format(“An unexpected error has occurred: {0}”, ex.Message)

This is to allow for languages that have a different order for their messages. In English this makes valid sense, but what about a right-to-left (RTL) language? I’m sure they could rewrite the message so it might still make sense, but why make it harder? This way the translators can handle putting the dynamic parts wherever they like.

To simplify this pattern I modified the logging methods in Log (LogUtil.cs) to take in parameters and pass them into Log4Net. The loggers in Log4Net already have this facility, it just was not being used in CruiseControl.Net. This enabled me to simplify the code from something like:

Log.Error(string.Format(“An unexpected error has occurred: {0}”, ex.Message))

To:

Log.Error(“An unexpected error has occurred: {0}”, ex.Message)

Still need to use string.Format for exceptions though :(

The Changes

Unlike remote, there was a large number of strings to convert. There is around 150 strings in the new resource file, so I won’t go through and list them! I’ve divided them into the following categories:

  • Console: For strings to be displayed in the console
  • Error: Strings that are used in exceptions
  • Log: Strings to be written to the log
  • ToString: To be used in the ToString() methods of classes
  • Signal: A signal for a task or source control block starting
  • Debug/Trace: Messages to be used by Debug/Trace

Plus a few categories that are specific to some classes (e.g. Vault or Subversion only strings). These categories are roughly based on where they are used – but there are no hard and fast rules about the naming.

That’s It for Now

I’ve now converted core to use resource-based strings. I’ve convinced a couple of people who speak other languages to do some translating for me – once I have some translations I’ll write some unit tests and then actually give the multi-lingual changes a quick test drive :-)

If anyone would like to volunteer for some translating I can provide the current list of strings and if necessary do all the work for putting them into resource files.

Next, I’ll finish off the server projects (server and console), and then start to look at the other projects (for the dashboard and CCTray). However for the next release I’m just planning on releasing the server version (yes, just baby-steps).

A Postscript

As an English-only user I don’t have much experience with the problems that can occur with internationalisation – for me this is more about expanding the usability of CruiseControl.Net to other people. As such, I’m very interested to hear about any problems or issues people have had with CruiseControl.Net that relate to being on a non-English OS. I’ve seen an e-mail or two in the mailing groups about some issues (one was about non-Latin characters in folder names, I can’t find the other).

So if anyone knows of any issues, let me know (either via comments here or the mailing lists) and I’ll try to take a look at them sometime.

Posted in CruiseControl.Net | Tagged: | Leave a Comment »

Internationalisation – part 2

Posted by Craig Sutherland on 4 November, 2008

Previously

In my last post on this topic I started looking at what is required to make CruiseControl.Net more international. My basic approach is to convert all the hard-coded user interface strings to resource-based strings so (hopefully) other people can add translations.

Now it’s time to start on this process!

A Starting Point: Remote

The one project that is referenced in nearly every other project is Remote. This contains a number of interfaces, classes and exceptions that are used in the other projects, including via .Net Remoting. As such it is a very important project to get right! However it has very few strings that are displayed to the end-user, so it makes a nice starting point.

User strings are in the following exceptions:

  • SessionInvalidException
  • SecurityException
  • PermissionDeniedException
  • NoSuchProjectException
  • BadReferenceException

Plus ProjectActivity and IntegrationRequest.

The exceptions are a classic area for multi-lingual messages. Putting language-specific messages can help problem-resolution for non-English users. So this was merely a matter of moving the messages into a resx file and then using them (I used the automatically generated class to simplify coding).

IntegrationRequest uses the string in the ToString() method, so again I moved this into the resx file.

Which just leaves ProjectActivity. This class defines a number of static instances of itself, with the type of activity passed in as a string. It then checks against these static types to work out which state a project is in. For the moment I have changed these to resource strings (since this is a prime area to add comprehension), but this may cause problems when it comes to remoting (e.g. in CCTray and the dashboard, especially if the client is in a different culture from the server). This might be a good area to change to use an enumeration in future, and then return a locale-specific string based on the enumeration, but for the moment I’m going to leave it using strings.

First Project Completed

That finishes the first project to convert – Remote. This was a very simple conversion, but enough to prove that the concept works.

Posted in CruiseControl.Net | Tagged: | Leave a Comment »

Internationalisation – part 1

Posted by Craig Sutherland on 3 November, 2008

Introduction

While I was in China recently I was struck by a lack of English (and Latin characters in general). This then raised an interesting question – why is CruiseControl.Net English only? I know some of the contributors are from non-English countries and every now and then I see e-mails from countries that use a non-Latin character set. This got me to thinking what can be done to make CruiseControl.Net more international?

Breaking Down the Puzzle

To start with, I decided the easiest approach to adding internationalisation (or internationalization depending on whether you use ’s’ or ‘z’) would be to break down CruiseControl.Net into its different components and investigate each part. A quick look at the solution for CruiseControl.Net shows the following projects (with their interactions):

CruiseControl.Net Projects

CruiseControl.Net Projects

Basically this shows on the right the actual applications, and then the projects they depend on, all the way back to Remote (this is used in every project except Objection).

So, rather than try for a big bang approach I’ve decided to break up CruiseControl.Net into the following areas and do one at a time:

  • Server: Console, Service and Code
  • CCTray: CCTray and CCTrayLib
  • Web: WebDashboard and Objection

Since Remote is a core project I will modify this before anything else.

The Approach

Each area will need a slightly different approach, but the basic approach is to remove all hard-coded user-interface strings and replace them with values from a resource file. Then it is a simple matter to add in new language resource files to display localised content.

As such I’m mainly looking at changing the output from CruiseControl.Net to allow multiple languages. While this is a major part of internationalisation I know it is not the only part. There are also issues like date and numeric formats, upper/lower casing and deciding exactly which culture to display (plus what to do if the desired culture is not available).

Additionally I’m going to add a couple of extra limitations:

  • A CruiseControl.Net instance will only be for one culture (e.g. any CruiseControl.Net instance will only have one language – it won’t be able to handle multiple languages for one installation).
  • Configuration will still be in English. I know this is a biggie, but I have no idea how to even start handling multiple languages for the configuration (especially with Exortech.NetReflector in the picture). Perhaps someone else would like to handle this issue :)

This will probably be a slow process of migrating all the hard-coded strings to resource-based strings, plus of course continuing to maintain the language resources in the future. But it will open up CruiseControl.Net to more people in the future.

More to come…

Posted in CruiseControl.Net | Tagged: | Leave a Comment »