TIP 490: msgcat for TclOO

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

  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):

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:

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:

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:

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

Copyright

This document has been placed in the public domain.