# TIP 490: msgcat for TclOO
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:
<pre>
namespace eval ::foo {
oo::class create Foo
}
package provide foo 1.0
</pre>
The message catlog belongs to the package, not to an individual class.
<pre>
namespace eval ::foo {
msgcat::mcload $dir/msgs
oo::class create Foo {
...
}
}
package provide foo 1.0
</pre>
# Key Use Cases
There are 4 use-cases to consider (which may be intermixed in the same package):
(with _'Servus!'_ as translation for _'Hi!'_)
1. The package does not use OO
<pre>
namespace import msgcat::*
namespace eval ::N1 {
mcload $dir/msgs
proc m1 {} {
puts [mc Hi!]
}
}
% N1::m1
-> Servus!
</pre>
2. msgcat is called within a class definition script
<pre>
% namespace import msgcat::*
% namespace eval ::N2 {
mcload $dir/msgs
oo::class create C1 {puts [mc Hi!]}
}
-> Servus!
</pre>
3. msgcat is called from a method in an object and the method is defined in a class
<pre>
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!
</pre>
4. msgcat is called from a method of a classless object
<pre>
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!
</pre>
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:
<pre>
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!
</pre>
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:
<pre>
proc ::tooltip::tooltip {widget message} {
...
set message [uplevel 1 {::msgcat::mc $message}]
}
</pre>
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:
<pre>
mcpackagenamespaceget
</pre>
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)
<pre>
proc ::tooltip::tooltip {widget message} {
...
set messagenamespace [uplevel 1 {::msgcat::mcpackagenamespaceget}]
...
bind $widget <Enter> [list ::tooltip::show $widget $messagenamespace $message]
}
proc ::tooltip::show {widget messagenamespace message} {
...
set message [namespace eval $messagenamespace [list ::msgcat::mc $message]]
...
}
</pre>
## 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:
<pre>
mcn ns src args...
</pre>
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:
<pre>
mcn [mcpackagenamespaceget] src args...
</pre>
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 to `mcn` without any `namespace 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:
<pre>
proc ::tooltip::show {widget messagenamespace message} {
...
set message [namespace eval $messagenamespace [list ::msgcat::mc $message]]
}
</pre>
may be expressed like:
<pre>
proc ::tooltip::show {widget messagenamespace message} {
...
set message [::msgcat::mcn $messagenamespace $message]
}
</pre>
## Extension 4: Command "mcexists" should get a parameter -namespace to explicitly specify the namespace
The command `mcexists` has currently the syntax:
<pre>
mcexists ?-exactnamespace? ?-exactlocale? src
</pre>
A switch, **-namespace** _ns_, is added to specify the namespace explicitly:
<pre>
mcexists ?-exactnamespace? ?-exactlocale? ?-namespace ns? src
</pre>
This may be useful in similar situations as the `mcn` command.
# Implementation
Within **msgcat**, the package namespace is currently extracted by:
<pre>
proc msgcat::mc {src args} {
...
set ns [uplevel 1 {namespace current}]
</pre>
This is replaced by:
<pre>
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]
}
}
}
}
</pre>
The implementation is in tcl fossil in branch
[tip490-msgcat-oo-2](https://core.tcl-lang.org/tcl/timeline?r=tip490-msgcat-oo-2).
There are tests but no man page changes yet.
Please use this text as man-page.
# Discussion
See [this page](490-discussion.md) 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.