msgcat(3tcl)
okay bestie, welcome to msgcat(3tcl) β straight out of Tcl Bundled Packages and ready to make your app speak every language π
β¨ NAME β¨
msgcat β the Tcl message catalog ποΈ
πΈ SYNOPSIS πΈ
package require tcl 9.0
package require msgcat 1.7
::msgcat::mc src-string ?arg arg ...?
::msgcat::mcmax ?src-string src-string ...?
::msgcat::mcexists ?-exactnamespace? ?-exactlocale? src-string
::msgcat::mcpackagenamespaceget
::msgcat::mclocale ?newLocale?
::msgcat::mcpreferences ?locale preference? ...
::msgcat::mcloadedlocales subcommand
::msgcat::mcload dirname
::msgcat::mcset locale src-string ?translate-string?
::msgcat::mcmset locale src-trans-list
::msgcat::mcflset src-string ?translate-string?
::msgcat::mcflmset src-trans-list
::msgcat::mcunknown locale src-string ?arg arg ...?
::msgcat::mcpackagelocale subcommand ?locale?
::msgcat::mcpackageconfig subcommand option ?value?
::msgcat::mcforgetpackage
::msgcat::mcutil subcommand ?locale?
π¦ DESCRIPTION π¦
So here's the tea, bestie β: the msgcat package hands you a whole set of functions for running multi-lingual user interfaces. Your text strings live inside something called a βmessage catalogβ, and the iconique part is that this catalog is completely independent from your application. That means you can edit it or localize it without touching the application source code π«Ά. Wanna drop in a brand new language or locale? Just add a new file to the message catalog. That's it. That's the move. β¨
msgcat tells packages apart by their namespace. Every single package gets its own message catalog and its own configuration settings inside msgcat β no sharing, no chaos, main character boundaries only π
.
Now, a locale is a specification string that describes a user language, like de_ch for Swiss German π¨π. In msgcat there's a global locale, and it gets initialized from the system locale of the current system. Each package can decide for itself: use the global locale, or use a package-specific locale. Your call, your rules. π
The global locale can be changed on demand too β for example when a user initiates a language change, or inside a multi-user application like a web server π. Flexible? Extremely.
And yes, object oriented programming is supported through the use of a package namespace π§ β go off, honestly.
lowkey that's just the setup though β the options are where it gets juicy, keep scrolling bestie π
π οΈ COMMANDS π οΈ
okay bestie, this is the main character section. every command msgcat gives you, no skips, no cuts. let's get into it π
π ::msgcat::mc src-string ?arg arg ...?
Returns a translation of src-string according to the current locale. If you hand it additional arguments past src-string, the format command is used to substitute those additional arguments into the translation of src-string.
Here's how the search goes: ::msgcat::mc looks through the messages defined in the current namespace for a translation of src-string. Nothing there? It goes up to the parent of the current namespace. And so on, and so on, all the way up until it reaches the global namespace. And if there's genuinely no translation string anywhere? ::msgcat::mcunknown gets called, and whatever that returns is what you get back. π«Ά
This is the function you use to localize an application. Instead of hardcoding an English string, you pass that English string through ::msgcat::mc and use the result. And here's the glow-up: if you write your app for a single language this way, adding support for more languages later is easy β you literally just define new message catalog entries. slay. β¨
π ::msgcat::mcn namespace src-string ?arg arg ...?
Exactly like ::msgcat::mc, but the message namespace is handed to it as the first argument.
Use mcn for the cases where the package namespace isn't the namespace of the caller. There's a whole example of this sitting inside the description of ::msgcat::mcpackagenamespaceget below. π
π ::msgcat::mcmax ?src-string src-string ...?
Give it several source strings and ::msgcat::mcmax returns the length of the longest translated string. Super useful when you're designing localized GUIs, where all the buttons, for example, might need to be a fixed width β and that width is gonna be the width of the widest button. π
π ::msgcat::mcexists ?-exactnamespace? ?-exactlocale? ?-namespace namespace? src-string
Returns true if there's a translation for the given src-string. That's it, that's the vibe check.
But you can narrow the search, bestie:
-exactnamespaceβ only check the current namespace, and not any parent namespaces.-exactlocaleβ only check the first preferred locale (like, the first element returned by::msgcat::mcpreferencesif the global locale is used).-namespacenamespace β specify an explicit package namespace. If you don't specify one, the namespace of the caller is used.
π ::msgcat::mcpackagenamespaceget
Returns the package namespace of the caller. This command handles every single case described in the section OBJECT ORIENTED PROGRAMMING. π
The example usage: a tooltip package that saves the caller's package namespace so it can update the translation every time the tooltip is shown:
proc ::tooltip::tooltip {widget message} {
...
set messagenamespace [uplevel 1 {::msgcat::mcpackagenamespaceget}]
...
bind $widget [list ::tooltip::show $widget $messagenamespace $message]
}
proc ::tooltip::show {widget messagenamespace message} {
...
set message [::msgcat::mcn $messagenamespace $message]
...
}
π ::msgcat::mclocale ?newLocale?
Omit newLocale and you get the current locale back. Pass newLocale and the current locale gets set to it.
Setting it to newLocale means the corresponding preferences get calculated and set. For example: if the current locale is en_US_funky, then ::msgcat::mcpreferences returns {en_us_funky en_us en {}}. π¦
You could also get the same result the long way around with:
::msgcat::mcpreferences {*}[::msgcat::mcutil getpreferences newLocale]
The current locale is always the first element of the list returned by mcpreferences.
msgcat stores and compares the locale in a case-insensitive manner, and returns locales in lowercase. The initial locale is determined by the locale specified in the user's environment. Peep LOCALE SPECIFICATION below for the description of the locale string format.
If the locale is set, the preference list of locales is evaluated. Locales in this list are loaded now, if not jet loaded.
ngl "jet" is what the original says, I'm just the messenger π
π ::msgcat::mcpreferences ?locale preference? ...
No arguments? You get an ordered list of the locales preferred by the user, ordered from most specific to least preference.
You can also give it a set of locale preferences to set the list of locale preferences. The current locale is set too β and it's the first element of the locale preferences list.
Locale preferences are loaded now, if not jet loaded.
For example, say the user prefers French or English text. That gets configured like this:
::msgcat::mcpreferences fr en {}
π ::msgcat::mcloadedlocales subcommand
This little group of commands manages the list of loaded locales for packages that don't set a package locale.
loadedβ returns the list of currently loaded locales.clearβ removes all locales and their data, which are not in the current preference list.
π ::msgcat::mcload dirname
Searches the specified directory for files that match the language specifications returned by ::msgcat::mcloadedlocales loaded (or msgcat::mcpackagelocale preferences if a package locale is set) β quick note, these are all lowercase β extended by the file extension .msg.
Each matching file is read in order, assuming a UTF-8 encoding. The file contents are then evaluated as a Tcl script. That means Unicode characters may be present in the message file either directly in their UTF-8 encoded form, or by use of the backslash-u quoting recognized by Tcl evaluation.
It returns the number of message files which matched the specification and were loaded. π₯
On top of that, the given folder is stored in the msgcat package configuration option mcfolder, so that message catalog files required by a locale change can eventually be loaded.
π ::msgcat::mcset locale src-string ?translate-string?
Sets the translation for src-string to translate-string in the specified locale and the current namespace. If translate-string is not specified, src-string is used for both. Returns translate-string. π
π ::msgcat::mcmset locale src-trans-list
Sets the translation for multiple source strings in src-trans-list, in the specified locale and the current namespace.
src-trans-list must have an even number of elements and is in the form:
{src-string translate-string ?src-string translate-string ...?}
::msgcat::mcmset can be significantly faster than multiple invocations of ::msgcat::mcset. The function returns the number of translations set. ποΈ
π ::msgcat::mcflset src-string ?translate-string?
Sets the translation for src-string to translate-string in the current namespace for the locale implied by the name of the message catalog being loaded via ::msgcat::mcload. If translate-string is not specified, src-string is used for both. Returns translate-string. β¨
π ::msgcat::mcflmset src-trans-list
Sets the translation for multiple source strings in src-trans-list in the current namespace for the locale implied by the name of the message catalog being loaded via ::msgcat::mcload.
src-trans-list must have an even number of elements and is in the form:
{src-string translate-string ?src-string translate-string ...?}
::msgcat::mcflmset can be significantly faster than multiple invocations of ::msgcat::mcflset. The function returns the number of translations set. π
π ::msgcat::mcunknown locale src-string ?arg arg ...?
This routine gets called by ::msgcat::mc when a translation for src-string is not defined in the current locale. (the classic "no translation found" moment π)
The default action is to return src-string passed by format if there are any arguments. This procedure can be redefined by the application β for example, to log error messages for each unknown string. The ::msgcat::mcunknown procedure is invoked at the same stack context as the call to ::msgcat::mc. And the return value of ::msgcat::mcunknown is used as the return value for the call to ::msgcat::mc.
heads up bestie: this routine is only called if the concerned package did not set a package locale unknown command name.
π ::msgcat::mcforgetpackage
The calling package clears all its state within the msgcat package β including all settings and translations. clean slate, fresh start, no baggage. πβ¨
π ::msgcat::mcutil getpreferences locale
Returns the preferences list of the given locale, as described in the section LOCALE SPECIFICATION.
Example: the composition of a preference list for the bilingual region "Biel/Bienne" as a concatenation of swiss german and swiss french:
% concat [lrange [msgcat::mcutil getpreferences fr_CH] 0 end-1] [msgcat::mcutil getpreferences de_CH]
fr_ch fr de_ch de {}
π ::msgcat::mcutil getsystemlocale
The system locale is returned, as described by the section LOCALE SPECIFICATION. π
πΊοΈ LOCALE SPECIFICATION π
okay bestie, so here's the whole tea on how msgcat figures out which locale you're in. You hand it a locale string, right? And that goes straight into ::msgcat::mclocale. The locale string is built like a little sandwich: a language code, then optionally a country code, then optionally a system-specific code, and each lil piece is glued together with _. The country and language codes come from the standards ISO-639 and ISO-3166. So like, the locale en is English, and en_US is U.S. English. slay. πΈ
When the msgcat package gets loaded for the very first time, the locale initializes itself based on the user's environment. It peeks at the variables env(LC_ALL), env(LC_MESSAGES), and env(LANG), in that order. The first one that actually has a non-empty value is the winner and determines the initial locale. That value gets parsed following the XPG4 pattern:
language[_country][.codeset][@modifier]
...to pull out all its parts. Then the initial locale is set by calling ::msgcat::mclocale with this argument:
language[_country][_modifier]
On Windows and Cygwin, if literally none of those environment variables is set, msgcat will go dig around in the registry for locale info instead. The RFC4747 locale name "lang-script-country-options" gets transformed into the locale "lang_country_script" β like, for example sr-Latn-CS -> sr_cs_latin. And if all of these attempts to discover an initial locale from the user's environment flop? msgcat just defaults to an initial locale of "C". no cap, that's the fallback. π
When a locale is specified by the user, a "best match" search happens while translating strings. So say a user specifies en_GB_Funky β the locales "en_gb_funky", "en_gb", "en" and "" (the empty string) get searched in that order until a matching translation string turns up. And if no translation string is available at all? Then the unknown handler gets called. periodt. π₯
πͺ NAMESPACES AND MESSAGE CATALOGS π
Strings stored in the message catalog are stored relative to the namespace they were added from. Which is honestly iconic, because it means multiple packages can use the exact same strings without ever fearing collisions with other packages. Bonus: it lets the source string be shorter and way less prone to typographical errors. iykyk. β¨
For example, executing this code:
::msgcat::mcset en hello "hello from ::"
namespace eval foo {
::msgcat::mcset en hello "hello from ::foo"
}
puts [::msgcat::mc hello]
namespace eval foo {puts [::msgcat::mc hello]}
will print:
hello from ::
hello from ::foo
When searching for a translation of a message, the message catalog searches first the current namespace, then the parent of the current namespace, and so on and so on until it reaches the global namespace. This is the whole reason child namespaces can "inherit" messages from their parent namespace. main character energy honestly. π
For example, executing (in the "en" locale) this code:
::msgcat::mcset en m1 ":: message1"
::msgcat::mcset en m2 ":: message2"
::msgcat::mcset en m3 ":: message3"
namespace eval ::foo {
::msgcat::mcset en m2 "::foo message2"
::msgcat::mcset en m3 "::foo message3"
}
namespace eval ::foo::bar {
::msgcat::mcset en m3 "::foo::bar message3"
}
namespace import ::msgcat::mc
puts "[mc m1]; [mc m2]; [mc m3]"
namespace eval ::foo {puts "[mc m1]; [mc m2]; [mc m3]"}
namespace eval ::foo::bar {puts "[mc m1]; [mc m2]; [mc m3]"}
will print:
:: message1; :: message2; :: message3
:: message1; ::foo message2; ::foo message3
:: message1; ::foo message2; ::foo::bar message3
look at those inheritance vibes. literal magic. π¦
π LOCATION AND FORMAT OF MESSAGE FILES π
Message files can live in literally any directory, as long as they follow these conditions:
- All message files for a package are in the same directory.
- The message file name is a msgcat locale specifier (all lowercase) followed by
".msg". For example:
es.msg β spanish
en_gb.msg β United Kingdom English
Exception alert bestie!! The message file for the root locale""is called"ROOT.msg". This exception exists so we don't cause peculiar behavior, like marking the message file as "hidden" on Unix file systems. π«Ά
- The file contains a series of calls to
mcflsetandmcflmset, setting the necessary translation strings for the language β likely wrapped in anamespace evalso that all source strings are tied to the namespace of the package. For example, a shortes.msgmight contain:
namespace eval ::mypackage {
::msgcat::mcflset "Free Beer" "Cerveza Gratis"
}
π¦ RECOMMENDED MESSAGE SETUP FOR PACKAGES β¨
If a package is installed into a subdirectory of the tcl_pkgPath and loaded via package require, here's the recommended procedure. take notes bestie π
- During package installation, create a subdirectory
msgsunder your package directory. - Copy your
*.msgfiles into that directory. - Add the following command to your package initialization script:
# load language files, stored in msgs subdirectory
::msgcat::mcload [file join [file dirname [info script]] msgs]
and boom, you're localized. π
π’ POSITIONAL CODES FOR FORMAT AND SCAN COMMANDS π
So it's totally possible that a message string used as an argument to format might have positionally dependent parameters that need repositioning. Like, sometimes it's syntactically desirable to rearrange the sentence structure while translating. languages are like that, she's flexible. π
format "We produced %d units in location %s" $num $city
format "In location %s we produced %d units" $city $num
This can be handled by using the positional parameters:
format "We produced %1\$d units in location %2\$s" $num $city
format "In location %2\$s we produced %1\$d units" $num $city
Similarly, positional parameters can be used with scan to extract values from internationalized strings. Note that it is not necessary to pass the output of ::msgcat::mc to format directly; by passing the values to substitute in as arguments, the formatting substitution is done directly. obsessed. π
msgcat::mc {Produced %1$d at %2$s} $num $city
# ... where that key is mapped to one of the
# human-oriented versions by msgcat::mcset
π PACKAGE PRIVATE LOCALE π
A package using msgcat may choose to use its own package private locale and its own set of loaded locales β completely independent of the global locale set by ::msgcat::mclocale. π
This lets a package change its locale without causing any locales to load or get removed in other packages, and without invoking the global locale change callback (see below π).
This action is controlled by the following ensemble:
::msgcat::mcpackagelocale set ?locale?β Set or change a package private locale. The package private locale is set to the given locale if the locale is given. If the option locale is not given, the package is set to package private locale mode, but no locale is changed (e.g. if the global locale was valid for the package before, it is copied to the package private locale). β οΈ Heads up: this command may cause the load of locales.::msgcat::mcpackagelocale getβ Return the package private locale, or the global locale if no package private locale is set.::msgcat::mcpackagelocale preferences ?locale preference? ...β With no parameters, return the package private preferences, or the global preferences if no package private locale is set. The package locale state (set or not) is not changed (in contrast to the command::msgcat::mcpackagelocale set). If a set of locale preferences is given, it is set as package locale preference list. The package locale is set to the first element of the preference list. A package locale is activated, if it was not set so far. Locale preferences are loaded now for the package, if not yet loaded. π¦::msgcat::mcpackagelocale loadedβ Return the list of locales loaded for this package.::msgcat::mcpackagelocale issetβ Returns true, if a package private locale is set.::msgcat::mcpackagelocale unsetβ Unset the package private locale and use the global locale. Load and remove locales to adjust the list of loaded locales for the package to the global loaded locales list.::msgcat::mcpackagelocale present localeβ Returns true, if the given locale is loaded for the package.::msgcat::mcpackagelocale clearβ Clear any loaded locales of the package not present in the package preferences.
and bestie, there's still more coming, so don't go anywhere π
π CHANGING PACKAGE OPTIONS π
Every package that uses msgcat gets its own lil set of options living inside msgcat, bestie. Those package options are all laid out in the next section (Package options), and each one can be set or unset individually with this ensemble:
::msgcat::mcpackageconfig getoption β hands you the current value of option. Fr fr, if that option isn't set for the package, this call returns an error, no cap.::msgcat::mcpackageconfig issetoption β returns1if the option is set for the package,0otherwise. Simple as.::msgcat::mcpackageconfig setoption value β sets option to value. Depending on the option this might trigger extra actions too. The return value is0, or the number of loaded packages when option ismcfolder.::msgcat::mcpackageconfig unsetoption β unsets option for the package. If it wasn't set in the first place? Nothing happens, no drama. You get the empty string back.
β¨ Package options β¨
Here's the menu, and every package has access to all of these:
mcfolderβ this is the message folder of the package, periodt. It gets set bymcloadand by the subcommandset. Both do the exact same thing and both return the number of loaded message catalog files.
Setting or changing this value will load all locales contained in the preferences valid for the package. That also means it invokes any loadcmd that's been set (peep below π).
Unsetting this value disables message file loading for the package. Gone, done.
loadcmdβ this callback fires before a set of message catalog files gets loaded for the package that has this property set.
Use it for any prep work before the message file load, or to grab your message data from a whole other source, like a database. In that case, no message files are used at all (mcfolder is unset).
Check the section callback invocation below. The parameter list appended to this callback is the list of locales to load.
If this callback is changed, it's called with the preferences valid for the package.
changecmdβ this callback fires when a default locale change was performed. Its whole purpose is letting a package update anything that depends on the default locale, like showing the GUI in another language π.
See the callback invocation section below. The parameter list appended to this callback is mcpreferences. The registered callbacks are invoked in no particular order β don't go assuming one, iykyk.
unknowncmdβ use a package localemcunknownprocedure instead of the standard version that msgcat supplies (msgcat::mcunknown).
The procedure you call has to return the formatted message that msgcat::mc will finally return.
A generic unknown handler is used if you set this to the empty string. What's that mean? It returns the key if no arguments are given. If arguments are given, the format command is used to process those arguments.
See the callback invocation section below. The appended arguments are identical to msgcat::mcunknown.
πΈ Callback invocation πΈ
A package can decide to register one or multiple callbacks, exactly as described above.
Callbacks get invoked if:
- the callback command is set,
- the command is not the empty string,
- the registering namespace exists.
If a called routine fails with an error, thebgerrorroutine for the interpreter gets invoked after command completion. The one exception is the callbackunknowncmdβ there, an error makes the invokingmc-command fail with that error. π
π OBJECT ORIENTED PROGRAMMING π
msgcat supports packages implemented by object oriented programming, slay. Objects and classes should be defined within a package namespace.
There are 3 supported cases where the package-namespace-sensitive commands of msgcat (mc, mcexists, mcpackagelocale, mcforgetpackage, mcpackagenamespaceget, mcpackageconfig, mcset and mcmset) may be called:
- In a class definition script β the msgcat command is called within a class definition script.
namespace eval ::N2 {
mcload $dir/msgs
oo::class create C1 {puts [mc Hi!]}
}
- Method defined in a class β the msgcat command is called from a method in an object, and that method is defined in a class.
namespace eval ::N3Class {
mcload $dir/msgs
oo::class create C1
oo::define C1 method m1 {
puts [mc Hi!]
}
}
- Method defined in a classless object β the msgcat command is called from a method of a classless object.
namespace eval ::N4 {
mcload $dir/msgs
oo::object create O1
oo::objdefine O1 method m1 {} {
puts [mc Hi!]
}
}
π EXAMPLES π
Packages that display a GUI may want to update their widgets when the global locale changes. To register a callback, you'd do:
namespace eval gui {
msgcat::mcpackageconfig changecmd updateGUI
proc updateGUI args {
puts "New locale is '[lindex $args 0]'."
}
}
% msgcat::mclocale fr
fr
% New locale is 'fr'.
If locales (or extra locales) live in another source, like a database, a package can use the load callback and skip mcload entirely:
namespace eval db {
msgcat::mcpackageconfig loadcmd loadMessages
proc loadMessages args {
foreach locale $args {
if {[LocaleInDB $locale]} {
msgcat::mcmset $locale [GetLocaleList $locale]
}
}
}
}
The clock command implementation uses msgcat with a package locale to make the command line parameter -locale work. Here are some sketches of that implementation π°οΈ:
First, a package locale is initialized and the generic unknown function is deactivated:
msgcat::mcpackagelocale set
msgcat::mcpackageconfig unknowncmd ""
As an example, the user requires the week day in a certain locale like this:
clock format [clock seconds] -format %A -locale fr
clock sets the package locale to fr and looks for the day name like this:
msgcat::mcpackagelocale set $locale
return [lindex [msgcat::mc DAYS_OF_WEEK_FULL] $day]
### Returns "mercredi"
Within clock, some message-catalog items are heavy in computation, so they get dynamically cached using:
proc ::tcl::clock::LocalizeFormat { locale format } {
set key FORMAT_$format
if { [::msgcat::mcexists -exactlocale -exactnamespace $key] } {
return [mc $key]
}
#...expensive computation of format clipped...
mcset $locale $key $format
return $format
}
π«Ά CREDITS π«Ά
The message catalog code was developed by Mark Harrison. Ty for your service, legend. π
β SEE ALSO β
format(3tcl), scan(3tcl), namespace(3tcl), package(3tcl), oo::class(3tcl), oo::object
π KEYWORDS π
internationalization, i18n, localization, l10n, message, text, translation, class, object
And that's a wrap bestie!! π¦ This whole thing is the msgcat page, version 1.5, living in the msgcat(3tcl) corner of the Tcl docs. Go forth and localize everything, you absolute icon. π
β¨
π¦ where this page comes from
- ubuntu questing: tcl9.0-doc 9.0.2+dfsg-1
- ubuntu resolute: tcl9.0-doc 9.0.3+dfsg-1
- ubuntu stonking: tcl9.0-doc 9.0.4+dfsg-1
- Copyright (c) 1998 Mark Harrison
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms