'''`[http://www.tcl.tk/man/tcl/TclCmd/msgcat.htm%|%msgcat]`''' is a [Tcl Commands%|%built-in] manages Tcl message catalogs for localising text ** Documentation ** [http://www.tcl.tk/man/tcl/TclCmd/msgcat.htm%|%official reference]: ** Contributors ** [HaO]: ** Description ** msgcat command are used to localise text in a program. Commands are provided to manipulate a database of translations for strings of text. The commands can be stored in separate files having the suffix, `.msg`. Instead of ====== label .x -text Files ====== if you write ====== label .x -text [mc Files] ====== ** Message Catalog ** Use `mcload` to load translations into the message catalog from files ending in the suffix `.msg`. The naming convention for `.msg` files is that the root name shall be the target locale of its contents, e.g. "en_us_funky". `msgcat` searches the contents first of `en_us_funky.msg`, then `en_us.msg`, then `en.msg`. `mcset` can be used to add translations to the message catalog. See, for example, [Vogel spiral]. ** Setting the Locale ** `$env(LANG)`, `$env(LC_ALL)`, or `$env(LC_MESSAGES)`. is used to determine the initial locale. The locale can be explicitly set, e.g.: ====== ::msgcat::mclocale fr ====== ** Formatting ** `mc` applies `[format]` to the translated string, so ====== mc "Directory contains %d files" $nfiles ====== allows a translator to work with the string, "Directory contains %d files". [escargo] 2003-12-08: What if you want to do some form of parameter substitution in the strings that you might use with msgcat? In some systems I have developed, messages were somewhat macro-like; you might pass a number or a name (of a file, for example) that should be part of the message. Is there anything in the '''msgcat''' that does the substitution internally, or do I have to add my own layer on top of it? [RS]: Easy, for instance with `[format]`: ====== msgcat::mcset fr "Directory contains %d files" "Il y a %d fichiers" ... puts [format [mc "Directory contains %d files"] $nfiles] ====== [DGP]: Even easier: There's a `[format]` already built-in to `mc`: ====== puts [mc "Directory contains %d files" $nfiles] ====== [EKB]: I moved this example up to the top of the page and combined it with the intro text so it's easier to find. [AM] Also very useful: the `%1$s` type format codes - with this you can interchange variables if needed. ====== % mc "this is a %s test for %s" first anything this is a first test for anything % mc {this is a %2$s test for %1$s} first anything this is a anything test for first ====== Note the `{}` around the text to prevent interpretation of `$` variable substitution ** Usage ** ====== package require msgcat ;# and done namespace import msgcat::* ;# (or msgcat::mc). ====== Provided you have `.msg` files containing ====== ::msgcat::mcset de Files Dateien ::msgcat::mcset fr Files Fichiers ====== The following will produce the translation, depending on the current locale: ====== mc Files ====== ** Example: Basic ** [RS] 2007-01-08: Here's a simple self-contained example for testing: ====== #!/usr/bin/env tclsh package require msgcat msgcat::mcset de yes Ja msgcat::mcset de no Nein msgcat::mcset fr yes Oui msgcat::mcset fr no Non proc localbool value { if {$value} {msgcat::mc yes} else {msgcat::mc no} } puts [localbool 0]/[localbool 1] hat's all. Save that to a file, and run it with different LANG settings: $ /Tcl/msgcat.tcl Nein/Ja $ LANG=fr /Tcl/msgcat.tcl Non/Oui $ LANG=en /Tcl/msgcat.tcl no/yes ====== ** Example: Switching Languages ** compliments of [dgp]: ====== package require Tk bgerror 123 msgcat::mclocale "fr" msgcat::mcload [file join $::tk_library msgs] bgerror 123 msgcat::mclocale "en_gb" msgcat::mcload [file join $::tk_library msgs] bgerror 123 ====== ** Using Tags Instead of Text ** [HaO] 2012-07-17: Given the code with a localised error message: ====== if {[catch {open $File r} Err]} { puts stderr [mc "File error accessing '%s': %s" $File $Err] } ====== and the German translation (the contents of the `de.msg` file): ====== msgcat::mcset de "File error accessing '%s': %s" "Zugriffsfehler Datei '%s': %s" ====== I use tags instead of a default translation. The default (English) text is contained in the root translation: Contents of the `de.msg` file: ====== msgcat::mcset de errFile "Zugriffsfehler Datei '%s': %s" ====== Contents of the `ROOT.msg` file: ====== msgcat::mcset {} errFile "File error accessing '%s': %s" ====== Example: ====== if {[catch {open $File r} Err]} { puts stderr [mc errFile $File $Err] } ====== Why that ? * If I change the default text, I do not have to change the translation keys * It feels more systematic to me to separate text and code * If a text is the same in english but different in german, I may not make any difference * If I forget a translation, I get only a stupid tag. Nothing for lazy guys... In this case, it is helpful to redefine 'mcunknown' to also return the given parameters: ======tcl proc ::msgcat::mcunknown {locale args} { return $args } ====== Default mcunknown: ====== % msgcat::mc tag1 p1 p2 tag1 ====== Custom mcunknown: ====== % msgcat::mc tag1 p1 p2 tag1 p1 p2 ====== ** Other Examples ** [simple text editor]: [HiLo-international]: [multilingual menu]: [Tk internationalization]: [A little stopwatch]: [Vogel Spiral]: Uses `mcset` to populate the message catalog. Illustrates use of source strings that serve as tags rather than as the default text, using "%" as an ad-hoc prefix that denotes tags. ** Locale Detection ** [HaO] 2013-05-10: It is relatively hard to debug language guessing, as the developpers depend on computers in any language which is seldomly the case. Thus I ask your help, if you think, the language guessing is not correct. From Windows Vista on, the language system of windows passed from a numerical identifier to IETF language tags. Start wish and type: ======tcl % msgcat::mclocale de_de ====== On Windows Vista and later, you should mostly get a language and a country. If you only get the language, please check the following: Verify, if one of the following environment variables are set: 'LC_ALL', 'LC_MESSAGES' or 'LANG'. This might be the fact due to an installed Cygwin: ======tcl % msgcat::mclocale de % parray env ... LANG=de ====== In this case start wish without those environment variables set, for example by the following batch file: ======none set LANG= c:\tcl\bin\wish86.exe ====== Now, the language settings are taken from the registry. If you still get bad results, please execute the following commands in a wish and post them with the correct language choice: ======tcl package require msgcat msgcat::mclocale package require registry registry get {HKEY_CURRENT_USER\Control Panel\Desktop} PreferredUILanguages registry get {HKEY_CURRENT_USER\Control Panel\International} LocaleName registry get {HKEY_CURRENT_USER\Control Panel\International} locale ====== In the upcoming msgcat 1.5.2, those keys are checked in this order. The first is the IETF language tag, and exists only if language packs are installed. The second is the IETF language tag for default file language. The third is the numeric language ID as used up to Windows XP Here is a result table: %|mclocale|PreferredUILanguages|LocaleName|locale|System description and comments|% &|de_de|value does not exist|de-DE|00000407|German Windows Vista 32 bit SP3|& &|el_gr|el-GR|el-GR|00000408|English Windows 7 Ultimate 64 bit, SP 1, with Greek UI language enabled|& ** Implicit locale for mc files ** Per TIP 404. [HaO] 2012-10-15: This feature is now included in TCL8.6b3 [http://docs.activestate.com/activetcl/8.6/tcl/TclCmd/msgcat.htm%|%(docs)%|%] and is backported to TCL8.5 to be included in TCL8.5.13. It is now recommended to use: ====== msgcat::mcflset original translation ====== instead of ====== msgcat::mcset locale original translation ====== within message files which are loaded by `msgcat::mcload`. ** Open Issue: Using msgcat with TclOO ** [Twylite] 2013-03-01: msgcat searches for messages in the current namespace, then parent namespaces up to the global namespace. To look up a message in a different (non-parent) namespace you must use [namespace eval] or an equivalent. TclOO class names are distinct from the unique namespace dynamically-allocated to the class on construction. The class name may or may not correspond to the name of a namespace. To support file-based message catalogs (*.msg) we would need to (i) have the messages in a namespace; and (ii) have a static pre-determined name for the namespace. [Twylite] 2013-03-12: Working towards a TIP, as requested (see below). Following feedback from [DKF] I think that it is conceptually reasonable to search for messages in an object's inheritance hierarchy, and to avoid using namespace search at all when dealing with objects. To keep the interface clean there should be new `msgcat::oo::*` procs to indicate that we are working with a message in the inheritance hierarchy, rather than turning `msgcat::mc` into a DWIM. The solution below is a minimal embodiment of the interface, and does not yet support message inheritance. Use `oo::define $cls mcset ...` to define a message on a class, or `msgcat::oo::mcset $cls ...` to do the same in a .msg file. Use `mymc ...` in a method declared on a class to retrieve a message defined on that class. `mymc` should support inheritance and polymorphism in future, but for now you can use `msgcat::oo::mc $otherclass ...` to explicitly refer to a message in a superclass. For the original solution see the page History. Description of the original solution: ''The solution I propose is to put messages for a class `::$ns::$cls` into the namespace `::msgs::$ns::$cls`, and to provide helpers `oo::define $cls mcset` and `::msgcat::classmc` to respectively set and retrieve catalog messages. As an alternative to `::msgcat::classmc` a class method `mc` is also provided.'' ====== # See http://wiki.tcl.tk/21595 for oo::DefWhat proc ::oo::DefWhat {} { uplevel 3 [list namespace which [lindex [info level -2] 1]] } namespace eval ::msgcat::oo {} proc ::msgcat::oo::mcset {cls locale src {dest {}}} { namespace eval $cls [list ::msgcat::mcset $locale $src $dest] } proc ::msgcat::oo::mc {cls src args} { namespace eval $cls [list ::msgcat::mc $src {*}$args] } proc ::oo::define::mcset {args} { tailcall ::msgcat::oo::mcset [::oo::DefWhat] {*}$args } proc oo::Helpers::mymc {args} { tailcall ::msgcat::oo::mc [uplevel 1 { self class }] {*}$args } ====== Using it: ====== package require compat oo::class create ::Alpha oo::define Alpha mcset en FooMsg "this is the foo msg" namespace eval ::Alpha { ::msgcat::mcset fr FooMsg "this is the french msg" } oo::define ::Alpha method testMsg1 {} { mymc FooMsg } oo::define ::Alpha method testMsg2 {} { msgcat::oo::mc [self class] FooMsg } set a [Alpha new] oo::class create ::Beta { superclass ::Alpha mcset en BarMsg "this is the bar msg" method testMsg3 {} { mymc BarMsg } } set b [Beta new] oo::class create ::Gamma { method testMsg4 {} { mymc FooMsg ;# no inheritance yet } method testMsg5 {} { msgcat::oo::mc ::Alpha FooMsg } } set g [Gamma new] ::msgcat::mclocale en $a testMsg1 $a testMsg2 $b testMsg1 $b testMsg2 $b testMsg3 $g testMsg4 $g testMsg5 ::msgcat::mclocale fr $a testMsg1 $a testMsg2 $b testMsg1 $b testMsg2 $b testMsg3 $g testMsg4 $g testMsg5 ====== [HaO] Looks reasonable to me. Could you prepare a TIP? [EB]: Here is another attempt to use [msgcat and TclOO]. ** Using an Alternate msgcat Package ** [HaO] 2012-07-24 [PYK] 2013-08-22: Both Tk and `[clock]`, which may be loaded before any script is able to extend the `auto_path`, load msgcat. Therefore, to use an alternate `msgcat`, place it in the default module path. Name the replacement `msgcat.tcl` to `msgcat-1.x.x.tm`, where 1.x.x is greater than the version that is being replaced. Typically, these files should be placed in `/lib/lib8/8.5`: If the program is wrapped into an executable starkit (not .kit), the msgcat file should be placed in: `.vfs/lib/lib8/8.5`: If an application is wrapped using ActiveState TclApp, I did not find any way to wrap anything to the upper path. But I made a custom tap file containing a `pckIndex.tcl` and a `msgcat.tcl` and this worked (at least for a console application). As an alternative, an already loaded `msgcat` package (here below 1.6.0) may be replaced by: ======tcl if {1 != [catch {package present msgcat} ver] && ![package vsatisfies $ver 1.6-] } { package forget msgcat namespace delete ::msgcat } package require msgcat 1.6- # if tk is used eventually reload the tk msgcat setup msgcat::mcload [file join $::tk_library msgs] ====== ** TIP399: Change Locale at Run-time ** There was previously an alternate implementation here, which has been superseded by the following information. [HaO] 2012-07-17: [http://www.tcl.tk/cgi-bin/tct/tip/399.html%|%TIP 399%|%] contains a patch to use msgcat in an environment which dynamically changes languages. [https://sourceforge.net/tracker/download.php?group_id=10894&atid=360894&file_id=449122&aid=3511941%|%msgcat-1.5.0.tm%|%] is available in the linked feature request [https://sourceforge.net/tracker/?func=detail&aid=3511941&group_id=10894&atid=360894%|%#3511941%|%]. This file may be put to the tcl lib to be tested. In my experience, it is not sufficient to have the file within an individual project. It must be copied to the Tcl installation beside the present file `msgcat-1.4.4.tm`. * This feature is also included in the download files of [https://sourceforge.net/tracker/?func=detail&aid=3544988&group_id=10894&atid=360894%|%#3544988%|%]. There is also a tclapp `tap`-file available as download. '''[HaO] 2012-08-27:''' Two things happened: * [AK] has asked how other packages may ask for information about a locale change (see below '''Dynamic namespace change callback'''). * TIP 399 was voted yes. [DGP] voted present with the following comment: What I would prefer is to vote YES for the "turn the whole package over to Harald Oehlmann to do as he pleases". The proposal seems fine, to address the problem posed. What gives me pause is that the TIP seems to demonstrate that the original design of msgcat just isn't any good. At some point we'd be better off not slapping in more bells and whistles and alternatives and just creating a new (version of a?) package that learns the lessons and gets it right this time. *** Complete solution *** I came to the conclusion, that dynamic local change is wanted but the proposed solution only solves parts of the issue. Here is an extract of my message on the core-list: ---- Unfortunately, the solution within the tip is half-baken and I ask for some time to design a better solution and rewrite a tip. I am sorry for that but I am convinced, this is the best way to go. I might be wrong, but at least I will try. A good solution for TIP 399 must automatically '''reload''' locale files on a locale change. *** Sketch of my current view of a solution *** There is a new command to set per caller namespace config data (namespace equal package, as each package should have one namespace and one set of locale files): ====== msgcat::mcpackageconfig ?-namespace namespace? setting ?value? ====== Settings per caller namespace/package: * message file folder (also auto-set by msgcat::mcload) (to be able to load missing locale files on a locale change) * callback function before reload of the message file due to a locale change (change to a jet unloaded locale) * callback function after a locale change * flag, if locale files should be reloaded if needed All of them are get and setable. The function returns with an error, if the namespace does not exist. The advantages of this: * the concept of the caller/package namespace already exists within msgcat. This is continued. * the caller namespace may be autodetected or specified * the settings might automatically be cleared if the namespace is removed and the corresponding function may be automatically disabled * IMHO very transparent There are the following additional configuration settings: * currently loaded locales (sum of all mcpreferences, readonly) * Flag, if unneeded locale data is cleared on locale change ** TIP 399: Detecting a Locale Change ** [HaO] 2012-08-22: [AK] asked by personal email: Do we have a '''<>''' virtual event like we have '''<>''', which is used to inform all widgets when ''''mclocale'''' was switched at runtime ? [HaO] answered: No, we dont have this currently. As it is a tcl-only package, we do not have the bind command. Thus we could register a command which is called when the locale changes: ====== msgcat::mcconfig -command ?lcmd? ====== * '''lcmd''' : list of cmd ?namespace? * '''cmd''': Command to call when locale changes. Specify empty string to unregister. * '''namespace''': the callers module namespace which is only used as a register id to store the command. If not specified, the callers current namespace is used. There is one registered command possible per namespace value. When the command fails, `[bgerror]` is called. An eventual locale change command (ex: msgcat::mclocale en_us), which executes the commands does not return with an error if a locale change callback fails. Example: ====== package provide mymodule 1.0 namespace eval mymodule { # Register a locale change callback msgcat::mcconfig -command [list myLocaleChanged] ... # Unregister the locale change callback msgcat::mcconfig -command "" } ====== [AK] 2012-08-22 by email: > As it is a tcl-only package, we do not have the bind command. True. Nor the 'event generate'. I had thought about auto-detecting a Tk environment to restrict when it broadcasts a change, however your idea with a configurable callback is better, from an architectural point of view. Hm. ... Hadn't thought about multiple callbacks. Guess because I was thinking in terms of events, where I can bind multiple things as I see fit. This restriction to one-per-namespace, and providing it through a list ... I do not like that too much. It is workable, true. Still don't like it. This shoehorning multiple callbacks through a single config option feels wrong. For multiple callbacks I would use dedicated (un)register commands, and tokens. I.e. ====== msgcat::register ====== returns a token, which is then the argument to ====== msgcat::unregister ====== to identify the callbacks and remove them individually. See [http://docs.activestate.com/activetcl/8.5/tcllib/uev/uevent.html%|%uevent] for an example of how I did that for a tcl event package. [WHD] 2012-08-22 by email: See also the hook module in [Tcllib]. [HaO] 2012-08-27: Discussion continues in the upper point '''Dynamic mode - change locale on run-time'''. There I write, why I prefer namespace instead of tolkens. ---- [MG] 2012-08-22: I use something similar to this in his app: ====== proc setLocale {new_locale} { variable locales; variable current_locale; set split [split $new_locale "_"] set preflist [list] for {set i 0} {$i < [llength $split]} {incr i} { lappend preflist [join [lrange $split 0 end-$i] "_"] } set current_locale "" foreach x $preflist { if { [info exists locales($x)] } { set current_locale $x break; } } if { ![info exists current_locale] || $current_locale eq "" } { set current_locale "en_gb" } # Set our best available ::msgcat::mclocale $current_locale # Update display to make sure we're using it if { $skin ne "" } { ::skin::${skin}::locale } setUpMenu setAppTitle return; };# setLocale ====== The `$locales` array records exactly which locales I've loaded some form of translation for, since msgcat doesn't seem to expose that information. The code makes sure that there actually is a translation for the requested locale, and picks a less specific one if necessary in much the same way msgcat does. It falls back on en_gb as a default. Like msgcat, all the locale names are stored entirely in lowercase (en_gb not en_GB). The code after the last comment calls various procs which alter existing GUI elements to use the new locale, but could easily be replaced with something like ====== proc rchildren {widget} { event generate $widget <> foreach x [winfo children $widget] { rchildren $x } } rchildren . ====== so you could bind to <> for the assorted widgets instead. [HaO] 2012-08-27: Thank you, Mike, for the contribution. The language change is user level and should still work as you do it, but this is slightly off-topic. Andreas is asking, how non-user packages which store translated messages somehow (like the tcllib tooltip package) may by their action be informed about a locale change. ** TIP 412 Dynamic Locale Changing for msgcat with On-Demand File Load ** [HaO] 2012-10-15: The new TIP and draft implementation is the current step in the dynamic locale msgcat feature. It is ready for public discussion: * [http://tip.tcl.tk/412%|%TIP # 412%|%] * [https://core.tcl.tk/tcl/timeline?r=msgcat_dyn_locale%|%implementation draft in tcl fossil branch msgcat_dyn_locale%|%] Anybody is invited to contribute! <> New TIP text contents, as save does not work with the often seen error: "Could not acquire a lock on a working copy of 412.tip. Please wait a moment and try your submission again." ~ Rationale ~~ Dynamic Locale Switching Within a multi-language application like a web-server, one may change the locale quite frequently, for example if users with different locales are requesting pages. Unfortunately, this does not fit well with the model adopted by the '''msgcat''' package, which assumes that all code follows this sequence: 1. Set locale list: '''mclocale''' ''locale'' 2. Load language files with other package load: '''mcload''' ''msg-folder'' 3. Translate strings: '''mc''' ''key args...'' Note that if the locale should be changed after other packages are loaded, one must restart at step 2. This requires reloading all packages which is mostly not practical. The aim of this TIP is to extend the package by dynamic locale change capabilities. msgcat will reload any missing message catalog files of all currently loaded packages on a locale change. In addition, any package may register to get informed to a locale change. Other packages may do changes to reflect the locale change like rebuilding the GUI. This TIP compares to [399] that the package is able to load message catalog files on demand, e.g. specially on a locale change. ~~ package locale If the clock command gets called with the argument "-locale", the locale is changed using '''msgcat::mclocale'''. After processing, the initial value is restored. The package keeps track, which locales where already used and calls '''msgcat::mcload''' for any new locale. The locale is restored after processing. This is an implementation of dynamic locales but conflicts with the new features described above. Other packages may be informed to change the locale and may trigger expensive operations like a rebuild of the GUI. In consequence, each package may define a package locale which is independent of the default locale. ~ Overview of the proposed solution Proposed changes in brief: ~~ Dynamically load message catalog files if the locale is changed by '''mclocale''' ''locale'', the message file load process is executed for every present package. ~~ Package locale A package may install a package local ''locale'' which is independent to the global ''locale''. ~~ Locale change callback A callback may be registered to get informed about the change of ''locale''. A use case is to refresh a GUI if the locale changed. ~~ Non message file operation A program may use message files to issue '''mcset''' commands or may issue them by other means, if the message catalogs are, for example, stored in a data base. Each package may register a callback to get informed that a certain ''locale'' should be loaded and may issue the corresponding '''mcset''' commands. ~~ Package mcunknown A package may have a certain way to provide translations for message keys not included in the message catalog. Thus, it may register an own package message unknown callback to provide a translation. ~ Specification ~~ Package Equals Client Namespace A '''client package''' is a package which uses msgcat. A unique namespace is required for each client package. Within msgcat, namespace and package is always connected. Up to now, the msgcat package used this namespace as an identifier to store the catalog data of a certain package. This is now extended to additional properties which are stored for a package. ~~ Package locale A package locale may be used by a package instead the default ''locale'' set by '''msgcat::mclocale'''. A package may choose to use a package locale or the default locale. ~~ Default and Package State Some state values (like the ''locale'') are available as default (global) values. In addition, each package may choose to use a package locale state. The used naming is: * default state: valid for all packages which do not set a package state. * package state: only valid for one package if it has set a package state. The following state values are present as default state and may be set individually per package: * The ''locale'' like "de_ch". * The ''preferences'' property is a list of locales in their preference order and is automatically computed from ''locale''. Example ''locale'' = "de_ch" -> ''preferences'' = "de_ch de {}". * The ''loadedlocales'' state value is the list of currently loaded locales. ~~ Default State The following standard methods exist to get or set the default state: ~~~ msgcat::mclocale The default ''locale''. It may be read using '''msgcat::mclocale'''. It may be set using '''msgcat::mclocale''' ''locale''. This command is extended, that the message catalogs of all missing locales for all packages not having set a package state are loaded. ~~~ msgcat::mcpreferences Get the default ''preferences'' (derived from the default locale). ~~~ msgcat::mcloadedlocales The following new command may be used to deal with the ''default state'': > '''msgcat::mcloadedlocales''' ''subcommand'' ''?locale?'' The parameter ''locale'' is mandatory for the subcommand '''present'''. The following ''subcommands'' are available: ~~~ Subcommand "get" Get the list of current loaded locales ~~~ Subcommand "present" Returns true, if the given locale is loaded ~~~ Subcommand "clear" The list of currently loaded locales is set to '''mcpreferences''' and all message catalog keys of packages without a package locale set and with locales not in '''mcpreferences''' are unset. ~~ Package Configuration The package configuration of the calling package may be changed using the following new command: > '''msgcat::mcpackagelocale''' ''subcommand'' ?''locale''? The parameter ''locale'' is mandatory for the subcommands '''set''' and '''present'''. Available subcommands are: ~~~ Subcommand "set" Set or change the package locale. The global state values are copied, if there were no package locale set before. The package locale is changed to the optional given new package locale. ~~~ Subcommand "get" Return the package locale or the default locale, if no package locale set. ~~~ Subcommand "preferences" Return the package preferences or the default preferences, if no package locale set. ~~~ Subcommand "loaded" The list of locales loaded for this package is returned. ~~~ Subcommand "isset" Returns true, if a package locale is set. ~~~ Subcommand "unset" Unset the package locale and use the default state for the package. Load all message catalog files of the package for locales, which were not present in the package '''loadedlocales''' list and are present in the default list. ~~~ Subcommand "present" Returns true, if the given locale is loaded ~~~ Subcommand "clear" Set the current loaded locales list of the package to '''preferences''' and unset all message catalog keys of the package with locales not included in the package '''preferences'''. ~~ Package Configuration Options Each package may have a set of configuration options set to invoke certain actions. They may be retrieved or changed with the following new command: > '''msgcat::mcpackageconfig''' ''subcommand option'' ?''value''? Available subcommands are: get: Get the current value of the option or an error if not set. isset: Returns true if option is set. set: Set the given value to the option. May have additional consequences and return values as described in the option section. unset: Unset the option. Available options are: ~~~ Package Option "mcfolder" This is the message folder of the package. This option is set by '''mcload''' and by the subcommand '''set'''. Both are identical 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. This implies also to invoke any set '''loadcmd''' (see below). Unsetting this value will disable message file load for the package. If the locale valid for this package changes, this value is used to eventually load message catalog files. Message catalog files are always sourced in the namespace of the package registering the value. ~~~ Package Option "loadcmd" This callback is invoked before a set of message catalog files are loaded for the package which has this property set. This callback may be used to do any preparation work for message file load or to get the message data from another source like a data base. In this case, no message files are used ('''mcfolder''' is unset). See chapter '''callback invocation''' below. The parameter list appended to this callback is the list of locales to load. If this callback is changed, it is called with the preferences valid for the package. ~~~ Package Option "changecmd" This callback is invoked when a default local change was performed. Its purpose is to allow a package to update any dependency on the default locale like showing the GUI in another language. Tk may be extended to register to this callback and to invoke a virtual event. See the '''callback invocation''' section below. The parameter list appended to this callback is '''mcpreferences'''. All registered packages are invoked in no particular order. ~~~ Package Option "unknowncmd" Use a package locale '''mcunknown''' procedure instead of the standard version supplied by the msgcat package ('''msgcat::mcunknown'''). The called procedure must return the formatted message which will finally be returned by '''msgcat::mc'''. A generic unknown handler is used if set to the empty string. This consists in returning the key if no arguments are given. With given arguments, format is used to process the arguments. See chapter '''callback invocation''' below. The appended arguments are identical to mcunknown. ~~~ Callback Invocation Callbacks are invoked under the following conditions: * the callback command is set, * the command is not the empty string, * the registration namespace exists. Any error within the callback stops the operation which invoked the callback. This might be surprising, as the error might be in another package. ~~ Test if Message Key is Set Message catalog keys may be expensive to calculate and thus may be set on demand. The following new procedure returns false, if '''mc''' would call '''mcunknown''' for a key: > '''msgcat::mcexists''' ''src'' There are two options, to limit the key search to just the current namespace (don't search in parent namespaces) and just the current locale (don't search the preferences but the first item): > '''msgcat::mcexists''' ?'''-exactnamespace'''? ?''-exactlocale''? ''src'' ~~ forget package A package may clear all its keys and state using the new command: > '''msgcat::mcforgetpackage''' ~~ Locale and Preferences Format Locales set by '''mcset''' may eventually not correspond to the current preferences, as the preferences are treated as follows: * put to lower case, * remove any multiple "_" and any "_" at the beginning or at the end of the locale. It is proposed, that: * the locale and the first preferences element is always identical to the lowercase passed locale, * any multiple "_" are seen as one separator. Example: preferences of locale "sy__cyrl_win" * current preferences: "sy_cyrl_win sy_cyrl sy" * proposed preferences: "sy__cyrl_win sy__cyrl sy". Alternatively, all locales may normalized using the upper algorithm, which felt heavy in computation with little gain. ~ Example Usage ~~ Example from TIP #399 Imagine an application which supports the current user language and French, German and English. An external package '''tp''' is used. The package uses '''msgcat''' and installs itself during the '''package require tp''' call: |package require msgcat |msgcat::mcload [file join [file dirname [info script]] msgs] An implementation of the application with the current msgcat 1.5.0 would require the following initialization sequence: |package require msgcat |package require np and the following code to change the locale to French: |package forget np |msgcat::mclocale fr |package require np Using the extension of this TIP, one may load as usual: |package require msgcat |package require np and to change to french locale: |msgcat::mclocale fr The first time, a locale is required, all corresponding message files of all packages which use msgcat get loaded. This might be a heavy operation. If a locale is reactivated (and the message catalog data was not cleared), it is a quick operation. Without this TIP, it is computational expensive (if possible, as many packages are not reloadable or a reload may disturb current processing, e.g., by forcing the closing of sockets, etc.). ~~ Change with No Need to Come Back If it is certain that a locale is changed and the then obsolete data is of no use, one may clear unused message catalog items: |msgcat::mclocale fr |msgcat::mcloadedlocale clear ~~ Use a Callback to be Notified About a Locale Change Packages which display a GUI may update their widgets when the locale changes. To register to a callback, use: |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'. ~~ To Use Another Locale Source than Message Catalog Files If locales (or additional locales) are contained in another source like a data base, a package may use the load callback and not ''mcload'': |namespace eval db { | msgcat::mcpackageconfig loadcmd loadMessages | msgcat::mcconfig loadedpackages\ | [concat [msgcat::mcconfig loadedpackages] [namespace current]] | | proc loadMessages args { | foreach locale $args { | if {[LocaleInDB $locale]} { | msgcat::mcmset $locale [GetLocaleList $locale] | } | } | } |} ~~ Use a package locale The reference implementation also contains a changed clock command which uses a package locale. Here are some sketches from the implementation. First, a package locale is initialized and the generic unknown function is activated: |msgcat::mcpackagelocale set |msgcat::mcpackageconfig unknowncmd "" If the user requires the week day in a certain locale, it is changed: |clock format [clock seconds] -format %A -locale fr and the code: |msgcat::mcpackagelocale set $locale |return [lindex [msgcat::mc DAYS_OF_WEEK_FULL] $day] |### Returns "mercredi" Some message-catalog items are heavy in computation and thus are 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 |} ~ Reference Implementation See Tcl fossil tag '''msgcat_dyn_locale''' [https://core.tcl.tk/tcl/timeline?r=msgcat_dyn_locale]. ~ Compatibility Imagined incompatibilities: * If packages call '''mcload''' multiple times with different folders, the data was currently appended. This is still the case, but only the last folder is used for any reload. The property '''mcfolder''' may be transformed to a list to cover this case. * The return value of '''mcload''' (file count) may be much higher as there may be loaded much more files. I suppose, this value is only used by the test suite to verify functionality and is not for big general use. * Message files may not be aware, that they may be loaded at any moment and not only after their own '''mcload'''. I suppose, this is the biggest issue but I think, there is no alternative. * Message files do not get reloaded any more, if a second '''mcload''' is issued with the same path argument. * Package which temporary change the default locale trigger any callback and may lead to user visible side effects. ~ Issues Known issues: * Packages might not be aware of a locale change and may buffer translations outside of '''msgcat'''. Packages should not buffer msgcat messages if they are used in a dynamic locale application (like tklib tooltip does for example). * The clock command currently has a small dynamic patch for msgcat implemented. This must be removed in favor to new msgcat features due to the temporarily change of the default locale. ~ Extensions * Expose the function to calculate the preference list from a given locale. * Load a message catalog file for a given locale without changing the default/package locale. * Methods '''isloaded''' to check if a locale is currently loaded. * Access message catalog with specified namespace, locale and search behavior. ~ Alternatives The alternative is the former [399], but that is problematic because the list of locales must be known before any package load. The additional complexity of this TIP is a justifiable trade-off against the greatly improved flexibility in the loading and locale selection order. ~ Copyright This document has been placed in the public domain. <> *** Discussion on future msgcat on ETCL 2014 conference *** [HaO] 2014-02-25: I proposed a discussion on the ETCL 2014 conference about the future of msgcat. Please feel free to contribute (also below this text). Here are the slides: [http://www.eurotcl.tcl3d.org/eurotcl-2014/presentations/EuroTcl2014-Oehlmann-msgcat.pdf] ** History ** msgcat became a built-in package with Tcl version 8.1 msgcat 1.3, distributed with Tcl 8.4 also initializes the locale from `$env(LC_ALL)` or `$env(LC_MESSAGES)`. ---- The 8.3.4 release of the [PPC] binary is broken in that it lacks msgcat and [bgerror] implementations. [Melissa Schrumpf] explains the situation with her customary clarity ... bgerror and msgcat are contained in tcl/library/msgcat/msgcat.tcl. Do this: Copy "Simply Tk (PPC)" to "Simply Tk (PPC).edit" Run ResEdit (or Resourcer, or whathaveyou). Open a "Simply Tk (PPC).edit" in ResEdit. Open the "TEXT" resources. Open resource ID "package require msgcat" This will be right at the top. Next, open tcl/library/msgcat/msgcat.tcl in a text editor. Copy from the beginning of the line: package provide msgcat 1.1.1 through to the end of the file. Return to ResEdit. Delete the line "package require msgcat" and, in its place, paste the code you copied from msgcat.tcl. Close all resources and save. You now have a fixed stand-alone Tk shell. ** See Also ** [msgcat magic]: [msgcat and shortcut keys]: important for GUI application writers [ampersand magic]: further information about shortcut keys [msgcat-Ready Script Utility]: [msgcat-x]: [locale]: [localization]: [message catalog]: [Tk and msgcat]: [frink]: uses msgcat when formatting source code, replacing source strings with their translations directly in the source code [http://web.archive.org/web/20050407093901/http://kovalenko.webzone.ru/unicodize.tcl%|%unicodize.tcl]: A small but useful script by [Anton Kovalenko] which converts non-ascii characters from current system encoding to \uXXXX sequences. Possibly useful to anyone who works often with tcl message catalogs. [https://www.gnu.org/software/gettext/%|%GNU gettext]: [http://groups.yahoo.com/group/tcl_announce/message/1750%|%as of] version 0.13, has support for Tcl msgcat and conversion tools from and to PO format. <> Tcl syntax help | Arts and crafts of Tcl-Tk programming | Command | Local | String Processing | Package