TIP: 399BIS Title: Dynamic Locale Changing for msgcat with pn-demand File Load Version: $Revision: 1.0 $ Author: Harald Oehlmann State: Draft Type: Project Vote: Done Created: 27-Mar-2012 Post-History: Keywords: Tcl,localization,msgcat Tcl-Version: 8.6 ~ Abstract This TIP adds dynamic locale switching capabilities to the '''msgcat''' package. ~ Rationale 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. This tip compares to tip399 that the package is able to load message catalog files on demand, e.g. specially on a locale change. ~ 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. ~~ msgcat state msgcat stores message catalog data in dependency of the locale, the package and the key (see '''mcset''' command). The msgcat package maintains two new state lists: * loadedlocales: list of currently loaded locals * loadedpackages: list of currently loaded packages. The message catalog data always corresponds to these lists. The listed locals and packages are currently present. Those lists may be get or set by the new command '''mcconfig'''. They may also be changed my '''mcload''' or '''mclocale'''. A new option '''removeobsoletelocale''' controls, if any currently unneded locale data is discarded. * If set, '''loadedlocales''' is always set erqual to '''mcpreferences'''. This may add or remove items on the '''loadedlocales''' list. * If not set, '''loadedlocales''' may only be extended by a locale change ('''mclocale locale'''). Those options are exposed by the following new command: > '''msgcat::mcconfig option''' ?''value''? where option is one of the above options '''loadedlocales''', '''loadedpackages''' or '''removeobsoletelocale'''. The initial default values are emty lists and false. An option may be read by not specifying a value. If a value is given, the option is set. Setting those options may lead to deletion or loading of message catalog data as described in the two chapers below '''loadedlocales change action''' and '''loadedpackages change action'''. ~~ package state msgcat maintains 3 options per client package. Those options may be get or set using: > '''msgcat::mcpackageconfig ?-package namespace? option ?value? The optional argument '''-package''' specifies the client package namespace of the option. If it is not given, the caller namespace is used. This package must not be included in the list '''loadedpackages'''. The namespace must not exist. Available options are: * mcfolder: folder of the message files of the package * loadcmd: a callback before any package message files are loaded * changecmd: callback if the locale is changed The options are cleared if set to the empty string. ~~~ package option mcfolder (and mcload) This is the message folder of the package. On locale change, message files may be reloaded on the following conditions: * the package is listed in '''loadedpackages''' * the package namespace exists * this option is set to a folder Setting this value is identical to an '''mcload''' in the given namespace and performs the following actions: * if already in '''loadedpackages''' and same '''mcfolder''' specified: return with 0 * if already in '''loadedpackages''', '''mcfolder''' changed and '''removeobsoletelocale''' active, clear the current package data * if not in '''loadedpackages''', append it * set property '''mcfolder''' * do the '''MC package list changed action''' below * If called as mcload, return number of loaded message files ~~~ 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. See chapter '''callback invokation''' below. The paramer list appended to this callback is the list of locales to load. ~~~ package option changecmd This callback is invoked when a local change was performed. Its purpose is to allow a package to update a gui. Tk may be extended to register to this callback and to invoke a virtual event. See chapter '''callback invokation''' below. The paramer list appended to this callback is '''mcpreferences'''. All registered packages are invoked in no particular order. ~~ callback invokation There are two callbacks as described above, packages may register to. Callbacks are invoked under the following conditions: * the registration namespace exists The callback is invoked in the registered namespace with a set of locales as parameters as follows: > namespace inscope $ns $callback {*}$loclist Any error within the callback stops the operation which invoked the callback. This might be surprising, as the error might be in another package. ~~ loadedpackages change action The list of loaded packages may change in the following circumstances: * '''msgcat::mcload''' invokation adds the invocating package, if it is not already on the list. * Setting '''mcfolder''' using '''msgcat::mcconfig''' has the same effect as '''msgcat::mcload''' * '''msgcat::mcpackageconfig''' may manipulate the list directly The following action is done on a list change: * check if the namespaces of all members on the new '''loadedpackages''' list exist. Remove any unexisting members. * remove all message catalog entries which belong to packages not any more on the list. * for each new package, append it to the list and start the catalog load action (see below) with '''mcpreferences''' as set of locales to load. ~~ loadedlocales change action The list of loaded locales may change in the following circumstances: * '''msgcat::mclocale''' sets the list to '''mcpreferences''' if '''removeobsoletelocale''' true. Otherwise, it appends any item in '''mcpreferences''' not currently on the list. * '''msgcat::mcpackageconfig''' may manipulate the list directly The following action is done on a list change: * Remove any member not present any more in the new list. * remove all message catalog entries which belong to removed locales. * build a list of locales which are new on the list and append it to the list. * For all packages on '''loadedpackages''', invoke the catalog load action (see below) with the list of new locales. ~~ catalog load action The following steps are performed when message catalog files should be loaded: * Invoke the callback '''loadcmd''' if registered for the package. * If the package property '''mcfolder''' is not set, return. * for each locale to load, build the message catalog path and source the message file if it exists. * on any file error, the invoking command returns with a script error. This might be surprising, as it may not be in the invoking package. * the message file may itself load packages which may use msgcat as another package namespace. ~ Example Usage ~~ Example from tip399 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 performes within 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.4.4 would require the following initialisation 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 shure, that a locale is changed and the then obsolete data is of no use, one may activate its removal: |msgcat::mclocale fr |msgcat::mcconfig removeobsoletelocale 1 or |msgcat::mcconfig removeobsoletelocale 1 |msgcat::mclocale fr ~~ 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] | } | } | } |} ~ Reference Implementation See Tcl Feature Request x. [http://sourceforge.net/support/tracker.php/?aid=x] ~ 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. ~ Issues Imaginated 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 may be removed in favour to new msgcat features. But there is quite an impact, as a '''clock -locale''' issues a '''msgcat::mclocale''' with all its new consequences. We could have a '''locale list per package''' and a '''locale per package''' to prevent this issue but this seams to aim bejond the aim. Another possibility would be, that the clock package would use an own mechanism (and not msgcat) which would be my favorite if there are issues. ~ Extensions * The proposed interface to manipulate the list of packages is quite basic. Additional commands like '''mcaddpackage''' and specially '''mcclearpackage''' may be handy and easy to implement. * The proposed interfaces to '''mcconfig''' and '''mcpackageconfig''' (compared to tip399) allow only to set one option at a time. This might be changed but is IMHO only for little use. The dashes in front of the options were also removed. ~ Alternatives The alternative is the former TIP399. This will IMHO not work, as the list of locales must be known before any package load. If the easy of this patch is once experienced, nobody would accept the burden. ~ Copyright This document has been placed in the public domain.