Satellite Assemblies

Alan Dean

Subscribe to feeds:  Atom feed for Alan Dean Atom  | RSS feed for Alan Dean RSS  | RDF feed for Alan Dean RDF

View the W3C Semantic data extraction

 

Preamble

As I have been developing shrinkwrap software products for most of my professional career, I have an abiding respect for internationalization i18n and localization l10n (collectively referred to as globalization in .NET). In fact, it is something of a running joke to warn new developers who join not to kick me off on the subject. Of course, they will hear from me soon enough…

Shrinkwrap products can end up in a bewildering set of environments and countries. Simply saying that your product will only run on {insert OS} with {insert language, usually US English} is not a recipe for success.

One of aspects of .net that I really like, is the sheer pervasiveness of support for globalization.

Satellite assemblies are a powerful way to approach this problem. The basic idea behind satellite assemblies is to isolate localizable resources from your main application, and from each other.

Why isolate? If you have thoroughly isolated your resources then you will not need to recompile your application code in order to support any new culture (essentially a culture represents a language + country/region). This is important when you consider that most of the localization work for an application follows on from the main development period.

Anyway, I have looked around at a number of references but nothing seemed to explicitly set out exactly how to localize a standard class. So, I dug into the localization model of WinForms to understand it (thank you ILDASM) and this is a distillation of what I learnt.

Part 1: The Default Resource

Firstly, let me show you the implementation of a default resource. It is imperative that your application has a default resource, so that there is at least support for one culture for the framework to fallback on gracefully if the appropriate satellite assemblies are missing.

This example contains a base Person class with a single property, Name, and a derived Employee class. The objects can be constructed with a name, but needs to have a default name for the default constructor. The obvious way to do this is to construct the member variable with a value or constant. The better way is to use the ResourceManager class, as show by the following code:

Person and Employee classes using System;
using System.Reflection;
using System.Resources;
using System.Threading;

namespace MyCompany.ResourceManagerDemonstration
{
  public class Person
  {
    private const string ResourceBaseName = "MyCompany.ResourceManagerDemonstration.Person";
    
    private static string DefaultName()
    {
      ResourceManager _manager = new ResourceManager(ResourceBaseName, Assembly.GetExecutingAssembly());
      return _manager.GetString("defaultName", Thread.CurrentThread.CurrentUICulture);
    }
    
    public Person(): this(DefaultName())
    { }
    
    public Person(string name)
    {
      this.Name = name;
    }
    
    public string Name
    {
      get { return _name; }
      set { _name = value; }
    }
    private string _name;
  }
  
  public class Employee: Person
  {
    private const string ResourceBaseName = "MyCompany.ResourceManagerDemonstration.Employee";
    
    private static string DefaultName()
    {
      ResourceManager _manager = new ResourceManager(ResourceBaseName, Assembly.GetExecutingAssembly());
      return _manager.GetString("defaultName", Thread.CurrentThread.CurrentUICulture);
    }
    
    public Employee(): base(DefaultName())
    { }
    
    public Employee(string name): base(name)
    { }
  }
}

Add a new Assembly Resource File to the project called Person.resx and add the following entry into the data table:

Name Value
defaultName New Person

Add a Employee.resx to the project and add:

Name Value
defaultName New Employee

These are now the default resources for the Person and Employee classes.

For each new class requiring localization, add an assembly resource file to the solution with the same name as the class.

If you view the compiled assembly using ILDASM, there will be a section for each resource in the manifest:

ILDASM output .mresource public MyCompany.ResourceManagerDemonstration.Person.resources
{
}
.mresource public MyCompany.ResourceManagerDemonstration.Employee.resources
{
}

From a practical point of view this is enough to carry on with your main development, extending the resource data table as required. Of course data tables can contain other types than strings, I have just demonstrated a simple example.

Part 2 will show to manage and compile the resources for additional cultures. The important point is that you will not need to open up Visual Studio to do this, so you won't need to recompile your code when the sales team tell you that they want a version of the software in a new language.

Part 2: The Satellite Resources

Time has passed, development has continued and now you have to support localization. Please note that you do not have to run Visual Studio at all for this part.

If you look at the contents of your .resx files, you will find that they are just XML. The sections created by Part 1 will look like:

.resx snippet
<data name="defaultName">
  <value>New Person</value>
</data>

Only the default resources currently exist, so in order to localize you will need to make new .resx files.

The naming pattern for .resx files is ClassName.Culture.resx

The first localization is to language. So for our example:

The second localisation is to country/region. So for our example:

These files can then be distributed to localizers for translation.

The files then need to be compiled to the appropriate satellite assemblies.

Compilation is a 2-phase process:

  1. Compile the .resx file to a .resource file with the RESGEN utility.
  2. Compile one or more .resource files to an assembly with the AL utility.

The assemblies have to be emitted to correctly named subdirectories of the main assembly. Each subdirectory is simply named by culture (e.g. …\en\, …\en-GB\, …\fr\, …\fr-FR\, etc.). The satellite assembly has the same name as the main assembly, but with .resources immediately before the file extension.

So, for our example the file structure will look like:

File structure {ApplicationDir}\MyCompany.ResourceManagerDemonstration.dll
{ApplicationDir}\en\MyCompany.ResourceManagerDemonstration.resources.dll
{ApplicationDir}\en-GB\MyCompany.ResourceManagerDemonstration.resources.dll
{ApplicationDir}\en-US\MyCompany.ResourceManagerDemonstration.resources.dll
{ApplicationDir}\fr\MyCompany.ResourceManagerDemonstration.resources.dll
{ApplicationDir}\fr-CA\MyCompany.ResourceManagerDemonstration.resources.dll
{ApplicationDir}\fr-FR\MyCompany.ResourceManagerDemonstration.resources.dll

RESGEN and AL have fairly complex switches available, so here is just an example batch file for compilation of English resources for the Person and Employee classes:

Batch file MD "bin\Release\en"
RESGEN Person.en.resx obj\Release\MyCompany.ResourceManagerDemonstration.Person.en.resources
RESGEN Employee.en.resx obj\Release\MyCompany.ResourceManagerDemonstration.Employee.en.resources
AL /t:lib /culture:en
   /embed:obj\Release\MyCompany.ResourceManagerDemonstration.Person.en.resources,MyCompany.ResourceManagerDemonstration.Person.en.resources
   /embed:obj\Release\MyCompany.ResourceManagerDemonstration.Employee.en.resources,MyCompany.ResourceManagerDemonstration.Employee.en.resources
   /template:bin\Release\MyCompany.ResourceManagerDemonstration.dll
   /out:bin\Release\en\MyCompany.ResourceManagerDemonstration.resources.dll

That's it.

References

Microsoft Global Development and Computing Portal
This is the globalization portal which composes various resources, including Dr. International.
Developing World-Ready Applications
This MSDN reference introduces the globalization development process.
Creating Satellite Assemblies
This is the definitive MSDN reference about resources and satellite assemblies.
Resources in .Resx File Format
This MSDN reference introduces the .resx file format.
Packaging Resources
This MSDN reference walks through the usage of the RESGEN and AL utilities.
Making a Satellite Assembly Out of a Resource File
This MSDN reference walks through the usage of the RESGEN and AL utilities.
Internationalization With Visual Basic
Although this isn't a .NET book, I highly recommend it as it provides a thorough understanding of the subject matter. The author, Michael Kaplan, is probably the premier Microsoft expert on i18n.
Internationalization and Localization Using Microsoft.NET
This is a good high-level reference work
Developing International Software
The standard Microsoft reference on internationalization