TIP 490: msgcat for TclOO

Login
FlightAware bounty program for improvements to Tcl and certain Tcl packages.
Author:         Harald Oehlmann <oehhar@sourceforge.com>
State:          Final
Type:           Project
Vote:           Done
Created:        07-Dec-2017
Post-History:
Keywords:       msgcat, oo
Tcl-Version:    8.7

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!')

  1. The package does not use OO

    namespace import msgcat::*
    namespace eval ::N1 {
        mcload $dir/msgs
        proc m1 {} {
            puts [mc Hi!]
        }
    }
    
    % N1::m1
    -> Servus!
    
  2. 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!
    
  3. 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!
    
  4. 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 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:

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.