Previous section   Next section

Practical Programming in Tcl & Tk, Third Edition
By Brent B. Welch

Table of Contents
Chapter 15.  Internationalization

Message Catalogs

A message catalog is a list of messages that your application will display. The main idea is that you can maintain several catalogs, one for each language you support. Unfortunately, you have to be explicit about using message catalogs. Everywhere you generate output or display strings in Tk widgets, you need to change your code to go through a message catalog. Fortunately, Tcl uses a nice trick to make this fairly easy and to keep your code readable. Instead of using keys like "message42" to get messages out of the catalog, Tcl just uses the strings you would use by default. For example, instead of this code:

puts "Hello, World!"

A version that uses message catalogs looks like this:

puts [msgcat::mc "Hello, World!"]

If you have not already loaded your message catalog, or if your catalog doesn't contain a mapping for "Hello, World!", then msgcat::mc just returns its argument. Actually, you can define just what happens in the case of unknown inputs by defining your own msgcat::mcunknown procedure, but the default behavior is quite good.

The message catalog is implemented in Tcl in the msgcat package. You need to use package require to make it available to your scripts:

package require msgcat

In addition, all the procedures in the package begin with "mc," so you can use namespace import to shorten their names further. I am not a big fan of namespace import, but if you use message catalogs, you will be calling the msgcat::mc function a lot, so it may be worthwhile to import it:

namespace import msgcat::mc
puts [mc "Hello, World!"]

Specifying a Locale

A locale identifies a language or language dialect to use in your output. A three-level scheme is used in the locale identifier:


The language codes are defined by the ISO-3166 standard. For example, "en" is English and "es" is Spanish. The country codes are defined by the ISO-639 standard. For example, US is for the United States and UK is for the United Kingdom. The dialect is up to you. The country and dialect parts are optional. Finally, the locale specifier is case insensitive. The following examples are all valid locale specifiers:


Users can set their initial locale with the LANG and LOCALE environment variables. If there is no locale information in the environment, then the "c" locale is used (i.e., the C programming language.) You can also set and query the locale with the msgcat::mclocale procedure:

=> c
msgcat::mclocale en_US

The msgcat::mcpreferences procedure returns a list of the user's locale preferences from most specific (i.e., including the dialect) to most general (i.e., only the language). For example:

msgcat::mclocale en_UK_Scottish
=> en_UK_Scottish en_UK en

Managing Message Catalog Files

A message catalog is simply a Tcl source file that contains a series of msgcat::mcset commands that define entries in the catalog. The syntax of the msgcat::mcset procedure is:

msgcat::mcset locale src-string ?dest-string?

The locale is a locale description like es or en_US_Scottish. The src-string is the string used as the key when calling msgcat::mc. The dest-string is the result of msgcat::mc when the locale is in force.

The msgcat::mcload procedure should be used to load your message catalog files. It expects the files to be named according to their locale (e.g., en_US_Scottish.msg), and it binds the message catalog to the current namespace.

The msgcat::mcload procedure loads files that match the msgcat::mcpreferences and have the .msg suffix. For example, with a locale of en_UK_Scottish, msgcat::mcload would look for these files:

en_UK_Scottish.msg en_UK.msg en.msg

The standard place for message catalog files is in the msgs directory below the directory containing a package. With this arrangement you can call msgcat::mcload as shown below. The use of info script to find related files is explained on page 181.

msgcat::mcload [file join [file dirname [info script]] msgs]

The message catalog file is sourced, so it can contain any Tcl commands. You might find it convenient to import the msgcat::mcset procedure. Be sure to use -force with namespace import because that command might already have been imported as a result of loading other message catalog files. Example 15-3 shows three trivial message catalog files:

Example 15-3 Three sample message catalog files.
## en.msg
namespace import -force msgcat::mcset

mcset en Hello Hello_en
mcset en Goodbye Goodbye_en
mcset en String String_en
# end of en.msg

## en_US.msg
namespace import -force msgcat::mcset

mcset en_US Hello Hello_en_US
mcset en_US Goodbye Goodbye_en_US
# end of en_US.msg

## en_US_Texan.msg
namespace import -force msgcat::mcset

mcset en_US_Texan Hello Howdy!
# end of en_US_Texan.msg

Assuming the files from Example 15-3 are all in the msgs directory below your script, you can load all these files with these commands:

msgcat::mclocale en_US_Texan
msgcat::mcload [file join [file dirname [info script]] msgs]

The dialect has the highest priority:

msgcat::mc Hello
=> Howdy!

If the dialect does not specify a mapping, then the country mapping is checked:

msgcat::mc Goodbye
=> Goodbye_en_US

Finally, the lowest priority is the language mapping:

msgcat::mc String
=> String_en

Message Catalogs and Namespaces

What happens if two different library packages have conflicting message catalogs? Suppose the foo package contains this call:

msgcat::set fr Hello Bonjour

But the bar package contains this conflicting definition:

msgcat::mcset fr Hello Ello

What happens is that msgcat::mcset and msgcat::mc are sensitive to the current Tcl namespace. Namespaces are described in detail in Chapter 14. If the foo package loads its message catalog while inside the foo namespace, then any calls to msgcat::mc from inside the foo namespace will see those definitions. In fact, if you call msgcat::mc from inside any namespace, it will find only message catalog definitions defined from within that namespace.

If you want to share message catalogs between namespaces, you will need to implement your own version of msgcat::mcunknown that looks in the shared location. Example 15-4 shows a version that looks in the global namespace before returning the default string.

Example 15-4 Using msgcat::mcunknown to share message catalogs.
proc msgcat::mcunknown {local src} {
   variable insideUnknown
   if {![info exist insideUnknown]} {

      # Try the global namespace, being careful to note
      # that we are already inside this procedure.

      set insideUnknown true
      set result [namespace eval :: [list \
         msgcat::mc $src \
      unset insideUnknown
      return $result
   } else {

      # Being called because the message isn't found
      # in the global namespace

      return $src

The msgcat package

Table 15-2 summarizes the msgcat package.

Table 15-2. The msgcat package
msgcat::mc srcReturns the translation of src according to the current locale and namespace.
msgcat::mclocale ?locale?Queries or set the current locale.
msgcat::mcpreferencesReturns a list of locale preferences ordered from the most specific to the most general.
msgcat::mcload directoryLoads message files for the current locale from directory.
msgcat::mcset locale src translationDefines a mapping for the src string in locale to the translation string.
msgcat::mcunknown locale srcThis procedure is called to resolve unknown translations. Applications can provide their own implementations.

      Previous section   Next section