Author: Harald Oehlmann <[email protected]>
State: Final
Type: Project
Vote: Done
Created: 07-Dec-2017
Post-History:
Keywords: msgcat, oo
Tcl-Version: 8.7
Tcl-Branch: tip490-msgcat-oo-2
Abstract
Package msgcat implements message catalogues for packages organized in nested namespaces. This TIP proposes the extension to TclOO.
Rationale
Since TclOO was included in the core, packages may also be defined as TclOO classes or classless objects. The msgcat package should feature this.
A package should have its methods within a package namespace:
namespace eval ::foo { oo::class create Foo } package provide foo 1.0
The message catlog belongs to the package, not to an individual class.
namespace eval ::foo { msgcat::mcload $dir/msgs oo::class create Foo { ... } } package provide foo 1.0
Key Use Cases
There are 4 use-cases to consider (which may be intermixed in the same package):
(with 'Servus!' as translation for 'Hi!')
The package does not use OO
namespace import msgcat::* namespace eval ::N1 { mcload $dir/msgs proc m1 {} { puts [mc Hi!] } } % N1::m1 -> Servus!
msgcat is called within a class definition script
% namespace import msgcat::* % namespace eval ::N2 { mcload $dir/msgs oo::class create C1 {puts [mc Hi!]} } -> Servus!
msgcat is called from a method in an object and the method is defined in a class
namespace import msgcat::* namespace eval ::N3Class { mcload $dir/msgs oo::class create C1 oo::define C1 method m1 { puts [mc Hi!] } } # The class object may be used in another namespace namespace eval ::N3Obj { set O1 [::N3Class::C1 new] } % $N3Obj::O1 m1 -> Servus!
msgcat is called from a method of a classless object
namespace import msgcat::* namespace eval ::N4 { mcload $dir/msgs oo::object create O1 oo::objdefine O1 method m1 {} { puts [mc Hi!] } } % N4::O1 m1 -> Servus!
Note that use-case 1 may emulate Use-cases 2 to 4 using namespace eval
. Before this extension, a programmer for use-case 2 to 4 must have used namespace eval
to explicitly specify the package namespace:
namespace import msgcat::* namespace eval ::N4 { mcload $dir/msgs oo::object create O1 oo::objdefine O1 method m1 {} { puts [namespace eval ::N4 {mc Hi!}] } } % N4::O1 m1 -> Servus!
This should still work with the new extension for compatibility reasons.
Proposal
The following 4 extensions are proposed and covered by the TIP.
Extension 1: Extend all msgcat commands to support all 4 use-cases.
So any msgcat command will detect the scenario on its own and extract the package namespace automatically.
The commands which are packet-namespace related are: mc, mcexists, mcpackagelocale, mcforgetpackage, mcpackagenamespaceget (new command, see below), mcpackageconfig, mcset and mcmset.
This has the following advantages (compared to the alternatives):
- no new commands, no learning
- if another foreign procedure is called and the procedure wants to use the callers message catalog, it may just use
uplevel 1 {msgcat tag}
and does not need to know if it is a class or not.
Here is an example for the second advantage:
The tklib package "tooltip" may invoke msgcat::mc msg
for all text to get eventual translations (it does something like that but I don't understand it, IMHO broken).
The package namespace of the caller should be used (not the one of the tooltip package).
So:
proc ::tooltip::tooltip {widget message} { ... set message [uplevel 1 {::msgcat::mc $message}] }
This will work in all use-cases, e.g. if tooltip::tooltip
is called by a method following use-case 1 to 4.
Extension 2: new command to get package namespace
The "magic" to extract the package namespace is exposed by the command:
mcpackagenamespaceget
This may be used:
- for the same case like the upper tooltip example, but for late binding
- for introspection and debugging
The upper tooltip example, where the translation is extracted when the tooltip is actually shown (to show an updated message if the current locale changed)
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 [namespace eval $messagenamespace [list ::msgcat::mc $message]] ... }
Extension 3: new command to get a translation with a package namespace as argument
A new command is proposed to get a translation with an explicit namespace:
mcn ns src args...
with the arguments:
- ns: package namespace to do the translation for
- src: the translation source string (like mc command)
- args: eventual arguments to contained format patterns (like mc command)
This command is identical to the mc
command, with the difference, that the package namespace is not found by an implicit call to mcpackagenamespaceget
, but may be explicitly specified as first argument
Then, the mc
command may be expressed like:
mcn [mcpackagenamespaceget] src args...
There are the following purposes for this command:
- foreign packages. The package namespace is known (for example by a call to
mcpackagenamespaceget
). The translation may be retrieved by a call tomcn
without anynamespace eval $ns
around it. - Authors of C packages required to specify the namespace explicitly.
- Optimizations in an eventual time critical path. The speed of the old msgcat is beaten by
mcn [namespace current]..
An example for the case of a foreign package is the tooltip package described above.
The contained call:
proc ::tooltip::show {widget messagenamespace message} { ... set message [namespace eval $messagenamespace [list ::msgcat::mc $message]] }
may be expressed like:
proc ::tooltip::show {widget messagenamespace message} { ... set message [::msgcat::mcn $messagenamespace $message] }
Extension 4: Command "mcexists" should get a parameter -namespace to explicitly specify the namespace
The command mcexists
has currently the syntax:
mcexists ?-exactnamespace? ?-exactlocale? src
A switch, -namespace ns, is added to specify the namespace explicitly:
mcexists ?-exactnamespace? ?-exactlocale? ?-namespace ns? src
This may be useful in similar situations as the mcn
command.
Implementation
Within msgcat, the package namespace is currently extracted by:
proc msgcat::mc {src args} { ... set ns [uplevel 1 {namespace current}]
This is replaced by:
proc msgcat::mc {src args} { ... set ns [PackageNamespaceGet] ... } proc ::msgcat::PackageNamespaceGet {} { uplevel 2 { # Check for no object switch -exact -- [namespace which self] { {::oo::define::self} { # We are within a class definition return [namespace qualifiers [self]] } {::oo::Helpers::self} { # We are within an object set Class [info object class [self]] # Check for classless defined object if {$Class eq {::oo::object}} { return [namespace qualifiers [self]] } # Class defined object return [namespace qualifiers $Class] } default { # Not in object environment return [namespace current] } } } }
The implementation is in tcl fossil in branch tip490-msgcat-oo-2.
There are tests but no man page changes yet. Please use this text as man-page.
Discussion
See this page for further discussion.
Credits
- René Zaumseil: initiative and partial implementation
- Eric Boudallier: alternate implementation
- Donal Fellows: implementation and examples
- Ashok P. Nadkarni: teach me oo by his excellent book
Copyright
This document has been placed in the public domain.