ONDotNet.com    
 Published on ONDotNet.com (http://www.ondotnet.com/)
 See this if you're having trouble printing code examples


.NET Localization, Part 2: Creating Satellite Assemblies

by Satya Komatineni
10/14/2002

One time in .NET that you need to know about satellite assemblies is when you are dealing with localization. For localizing text, one doesn't hard code text on a page, but uses a key for that text. The text equivalent for the key is retrieved from a file called a resource file. A resource file is essentially a dictionary of associations between the keys and their textual values. You will have this resource file duplicated once for each language that you support. .NET will retrieve values from these multiple language-resource files based on the chosen language context.

Consider a resource file called MyText.resx. A resource file can be in any of three different formats: plain text, XML, or a binary resources file. There are utilities (resgen.exe) that can convert a resource file from one format to the other. Typically, this resource file, MyTest.resx, is called a default resource because it is this resource file that the .NET libraries will go for, if there are no language-specific resource files. This file gets compiled and becomes part of your executable. If you named your application MyApplication, then you will have MyApplication.exe, containing your resource MyTest.resources (Not quite, but close).

Assume for a second that you are providing a language-dependent resource file called:


MyText.en-gb.resx

(Where en-gb stands for the English language version of Great Britain.)

Typically, this file is planned for, and constructed outside of, the building process that is employed for constructing MyApplication. You will have to refer to the localization resources to find out why. Anyway, .NET provides a mechanism by which you can create an assembly that contains just the compiled MyText.en-gb.resx. This assembly is called a satellite assembly because the presence of this assembly is optional and standalone.

Related Reading

.NET Framework Essentials
By Thuan L. Thai, Hoang Lam

So, in your localization process, you will be supplying these satellite assemblies; one each for each language. And if you go through the MSDN documenation for creating satellite assemblies, you will know that you use a tool called al.exe (Assembly Linker) to do this. When I went through this documentation and also the documentation on the Web, the following questions remained largely unanswered:

  1. How can I use al.exe for multiple resource files?
  2. What naming conventions should I use for the assembled resource files?
  3. Can I use the Visual Studio IDE for this purpose?
  4. How can I use ildasm to help me with this process?
  5. Why do I need to use resgen?
  6. Should I be using /embed or /link in al.exe?

In short, how should I go about, in a practical sense, using satellite assemblies in a non-trivial project (a project containing multiple resource files, one for each module, etc.)?

Why Can't I Use the Visual Studio IDE For My Resources?

You Can Create Resources

For smaller projects, this is quite doable. The process is as follows. Inside of Visual Studio IDE, you can highlight a project or a folder and add a new item of the type "assembly resource." This will create a resource file with the extension .resx. Because you are able to do this at a folder level, you can create resources at the root level of you project or at a sub-folder level. It is also clear that you can have multiple resource files in your project.

Example:


\MyText.resx
\MyModule1\MyMod1Res.resx

You Can Build the Application to Include the Resources

When you build the application after introducing the above resource files, you will see that the Visual Studio IDE has embedded these resource files into your application with the following names:


MyApplication.MyTest.Resources MyApplication.MyModule1.MyMod1.Resources

See the name change? These generated names are important, as you will have to use these generated names to access your resources. Let us follow up with an example of accessing these resources.

You Can Access Your Resources Based on Their Altered Names


ResourceManager rm = new ResourceManager (
     "MyApplication.MyText.Resources",Assembly.ExecutingAssembly());
String MyResourceTextValue = Rm.getString("MyResourceTextID");

Note: Read the included reference for a detailed discussion of how to use the resource managers.

Notice the first argument to the ResourceManager -- this is the altered name of the resource file. And it is also noteworthy that you will have to individually access your resource files. There is no single global way of accessing the resources from all of the resource files.

You Can Use Visual Studio IDE to Define Resources in Other Languages as Well

Taking your MyText.resx file and defining a language-dependent version is as easy as adding a new item under the same directory called:


MyText.en-gb.resx

Inside of this language-dependent file, you will specify your resources in Great Britain English. And when you tell Visual Studio IDE to build MyApplication, you will see that an extra directory is constructed under the bin directory, and a .dll file inside of it.


Bin\en-gb\MyApplication.resources.dll

This DLL is essentially the satellite assembly of your resources. As you add more and more languages, you will see these satellite assemblies created automatically.

What is Not Entirely Right About This Picture

This will work fine in a smaller environment where the developers are working as language translators as well. Once we introduce external language translators, it is better to remove these files (just the language-dependent ones and not the default ones) from the Visual Studio IDE environment and send them off to the language translators. Once the translations are available, we should be able to generate the satellite assemblies independently of the mainline Visual Studio IDE.

This separation will increase productivity due to its separation and fewer dependencies. The whole goal of this article is to show you how to go about explaining this extenral satellite assembly generation for multiple resource files.

Making a Satellite Assembly Out of a Resource File

So the first question is, what kind of tools are available for creating satellite assemblies? You need the following tools:

  1. resgen.exe
  2. al.exe
  3. ildasm.exe

Uitimately, al.exe is the program that embeds your resources into a satellite assemby. But al.exe will only accept resources in .resources binary format. But our inputs are usually either plain text resource files or XML-based resource files in .resx format. resgen.exe is used to convert these alternate forms of resources to the .resources binary format palatable to al.exe.

Where does ildasm.exe fits into this picture? If you remember what Visual Studio IDE is doing, you will see that there is a name translation between your resource file directory structure and how that resource file is known inside of the assembly. Becaue we are using the Visual Studio IDE to generate the default resources and the extenal process to generate the satellite assemblies, both mechanisms must produce assemblies with the same kind of naming hierarchy for the resource files.

So we use ildasm to examine the DLLs that Visual Studio IDE generates to find out what the structure is, and use the same mechanism to generate the satellite assemblies. You can also examine the satellite assemblies using ildasm to make sure that you get the names right. This will be useful for debugging errors from the resource manager telling you that it can not locate a resource.

Now that the tools are outlined, how do we convert an external resource file into a satellite assembly? As noted below, this is a three (really, two) step process.

Step 0: Set your paths for resgen and al.exe:


@set path=%path%;
    "C:\Program Files\Microsoft Visual Studio .NET\FrameworkSDK\Bin";
	 c:\winnt\microsoft.NET\framework\v1.0.3705

Step 1: Use resgen to create a .resources file from a .resx file.


Resgen MyText.resx

The above command will create a file called:


MyText.resources

Step 2: Use al.exe to create the satellite assembly:


Al.exe
     /t:lib
     /embed:MyText.en-gb.Resources,MyApplication.MyText.en-gb.Resources
     /culture:hi-gb
     /out:MyApplication.resources.dll

There are a couple of things worth noting here:

/t:lib: Says you are interested in a .dll.

/embed:MyText.en-gb.Resources,MyApplication.MyText.en-gb.Resources : Embeds and renames the resource to a target name to match the Visual Studio IDE naming structure.

/culture:hi-gb : Identifies the culture in which you are interested.

/out:MyApplication.resources.dll : Name of the DLL in which you are interested.

The generated .dll has to have that naming convention for .NET to find it. Also notice that you have to specify the culture setting, even though the culture is available in the name of the resource files. So it has to be mentioned in both places.

Place the Satellite Assembly in the Appropriate Directory

Once the satellite assembly is created, physically copy the .dll to the following directory:


\MyApplication\bin\en-gb\MyApplication.Resources.DLL

This would have been identical if Visual Studio IDE had generated this file. Repeat this process for each languagein which you are interested.

Aren't We Done Yet? A Case for Multiple Resoruce Files

Having a single resource file can give raise to some issues in a large project. There will be contention for this resource file from multiple developers. This file can grow quite large, making it difficult to locate the resource keys in which you are interested. A good way to break this dependency is to encourage multiple resource files; one for each module. If there are common language keys, you can group them into a common module.

Following this recommendation, you will have multiple resource files, as follows:


\MyApplication\resources\files\CommonResources.resx
\MyApplication\resources\files\Module1Resources.resx
\MyApplication\resources\files\Module2Resources.resx

And you can define keys for these resources in a separate hierarchy, as follows:


\MyApplication\resources\keys\CommonKeys.cs
\MyApplication\resources\keysModule1Keys.cs
\MyApplication\resources\keys\Module2Keys.cs

The idea is that you can zip and send the following directory to a language specialist.


\MyApplication\resources\files\*.*

This language specialist will return to us a new directory, as follows:

Great Britain English version


\MyApplication\resources\files\en-gb\CommonResources.resx
\MyApplication\resources\files\en-gb\Module1Resources.resx
\MyApplication\resources\files\en-gb\Module2Resources.resx

Russian version


\MyApplication\resources\files\ru-RU\CommonResources.resx
\MyApplication\resources\files\ru-RU\Module1Resources.resx
\MyApplication\resources\files\ru-RU\Module2Resources.resx

Our goal is: given a directory worth of language dependent resource files, how can I create a satellite module for that language?

Designing a Batch Program For Converting Multiple Resource Files Into a Satellite Assembly

Much of the effort here is understanding al.exe and its options to create multi-file processing for creating the satellite assembly. Let us reconsider what we have done for a single file.


Al.exe /t:lib /embed:file1 /culture:en-gb /out:out-assembly.dll

Although it doesn't seem to be well documented, by experimentation you will find that the following command will work for multiple files:


Al.exe /t:lib /embed:file1 /embed:file2 /embed:file3 /culture:en-gb /out:out-assembly.dll

You will also see that it will get tiring very quickly, as the command line grows larger and larger.

Again, based on some meager documentation and a little experimentation, you will see that the following works as well:


Al.exe @responsefile.txt

where the response file has multiple lines, as follows:


/t:lib
/embed:file1
/embed:file2
/embed:file3
/culture:en-gb
/out:out-assembly.dll

So the answer lies in creating a batch file that can create this response file, so that you invoke al.exe with this response file. One more final point before presenting you with an annotated batch file: al.exe has an alternate option for embed, called /link. An embed will embed the resource file directly into the assembly, whereas link will merely place a reference. It seems to me that this is similar to making an executable and making a .dll. When you link things, you expect the resource files to be available for getting the resources out, whereas embedding will pull in the resources. Either way for satellite assemblies, /embed appears to be a good enough choice.

Annotated Batch Program For Creating a Satellite Assembly From a Directory's Worth of Resource Files


@rem****************************************************************************
@rem* Batch file for generating a satelite assembly 
@rem* from a directory full of .resx files
@rem*
@rem*     inputs
@rem*       %1: language: hi-IN
@rem*       %2: Your application name: SKLocalizationSample
@rem*
@rem*     output:
@rem*       will create SKLocalizationSample.resources.dll 
@rem*       in the same directory
@rem*
@rem****************************************************************************


@rem**************************************************
@rem* Set path for resgen.exe and al.exe
@rem**************************************************

@set path=%path%;
    "C:\Program Files\Microsoft Visual Studio .NET\FrameworkSDK\Bin";
    c:\winnt\microsoft.NET\framework\v1.0.3705


@rem**************************************************
@rem* Convert all .resx files to .resources files
@rem**************************************************
@del *.resources
@for %%f in (*.resx) do @resgen %%f

@rem**************************************************
@rem* Create text based command file for each resourc
@rem**************************************************

@rem del a.txt
@echo /t:lib > a.txt
@for %%f in (*.resources) do @echo /embed:%%f,%2.resources.files.%%f >> a.txt
@echo /culture:%1 >> a.txt @echo /out:%2.resources.dll >> a.txt

@rem**************************************************
@rem*
@rem* The generated input command file a.txt will look like this
@rem*
@rem* /t:lib
@rem* /embed:CommonResources.hi-in.resources,
@rem* SKLocalizationSample.resources.files.CommonResources.hi-in.resources


@rem* /embed:Common1Resources.hi-in.resources,
@rem* SKLocalizationSample.resources.files.Common1Resources.hi-in.resources


@rem* /embed:Common2Resources.hi-in.resources,
@rem* SKLocalizationSample.resources.files.Common2Resources.hi-in.resources


@rem* /embed:Common3Resources.hi-in.resources,
@rem* SKLocalizationSample.resources.files.Common3Resources.hi-in.resources


@rem* /culture:hi-in
@rem* /out:SKLocalizationSample.resources.dll
@rem*
@rem* Feed this file to al.exe
@rem**************************************************


@rem****************************
@rem* Create the satelite assembly
@rem****************************
del %2.resources.dll
al @a.txt

Satya Komatineni is the CTO at Indent, Inc. and the author of Aspire, an open source web development RAD tool for J2EE/XML.


Return to .NET DevCenter

Copyright © 2009 O'Reilly Media, Inc.