From 082e3f3ed08ece41e41c7d8344e768918e7ae155 Mon Sep 17 00:00:00 2001 From: stanton Date: Mon, 7 Dec 1998 20:56:53 +0000 Subject: cleaned up docs * tests/msgcat.test: Added message catalog test suite. * library/msgcat1.0/msgcat.tcl: minor bug fixes, integrated latest changes from Mark Harrison. --- doc/msgcat.n | 72 ++++++---- library/msgcat/msgcat.tcl | 29 ++-- library/msgcat1.0/msgcat.tcl | 29 ++-- tests/msgcat.test | 333 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 414 insertions(+), 49 deletions(-) create mode 100644 tests/msgcat.test diff --git a/doc/msgcat.n b/doc/msgcat.n index ea9005a..ce72a74 100644 --- a/doc/msgcat.n +++ b/doc/msgcat.n @@ -11,7 +11,7 @@ .BS '\" Note: do not modify the .SH NAME line immediately below! .SH NAME -msgcat \- Tcl Message Catalog +msgcat \- Tcl message catalog .SH SYNOPSIS \fB::msgcat::mc src-string\fR .sp @@ -40,19 +40,28 @@ the message catalog. Use of the message catalog is optional by any application or package, but is encouraged if the application or package wishes to be enabled for multi-lingual applications. + .SH COMMANDS .TP \fB::msgcat::mc src-string\fR Returns a translation of \fIsrc-string\fR according to the user's current locale. If no translation string -exists, \fB::msgcat::mcunknown\fR is called. The string +exists, \fB::msgcat::mcunknown\fR is called and the string returned from \fB::msgcat::mcunknown\fR is returned. +.PP +\fB::msgcat::mc\fR is the main function used to localize an +application. Instead of using an English string directly, an +applicaton can pass the English string through \fB::msgcat::mc\fR and +use the result. If an application is written for a single language in +this fashion, then it is easy to add support for additional languages +later simply by defining new message catalog entries. .TP -\fB::msgcat::mclocale \fR?\fInewLocale\fR? -The locale defaults to the locale specified in the -user's environment. This function sets the locale -to \fInewLocale\fR. If \fInewLocale\fR is omitted, the -current locale is returned. +\fB::msgcat::mclocale \fR?\fInewLocale\fR? +This function sets the locale to \fInewLocale\fR. If \fInewLocale\fR +is omitted, the current locale is returned, otherwise the new locale +is returned. The initial locale defaults to the locale specified in +the user's environment. See \fBLOCALE AND SUBLOCALE SPECIFICATION\fR +below for a description of the locale string format. .TP \fB::msgcat::mcpreferences\fR Returns an ordered list of the locales preferred by @@ -62,21 +71,26 @@ preference. If the user has specified LANG=en_US_funky, this procedure would return {en_US_funky en_US en}. .TP \fB::msgcat::mcload \fIdirname\fR -Searches the specified directory for files which match -the language specifications returned by mcpreferences. +Searches the specified directory for files that match +the language specifications returned by \fB::msgcat::mcpreferences\fR. Each file located is sourced. The file extension is ``.msg''. +The number of message files which matched the specification +and were loaded is returned. .TP \fB::msgcat::mcset \fIlocale src-string \fR?\fItranslate-string\fR? -Sets the translation for \fIsrc-string\fR to \fItranlate-string\fR +Sets the translation for \fIsrc-string\fR to \fItranslate-string\fR in the specified \fIlocale\fR. If \fItranslate-string\fR is not -specified, \fIsrc-string\fR is used for both. +specified, \fIsrc-string\fR is used for both. The function +return \fItranslate-string\fR. .TP \fB::msgcat::mcunknown \fIlocale src-string\fR This routine is called by \fB::msgcat::mc\fR in the case when a translation for \fIsrc-string\fR is not defined in the current locale. The default action is to return -\fIsrc-string\fR. This command can be redefined by the -application. +\fIsrc-string\fR. This procedure can be redefined by the +application, for example to log error messages for each unknown +string. + .SH "LOCALE AND SUBLOCALE SPECIFICATION" .PP The locale is specified by a locale string. @@ -87,14 +101,17 @@ codes are specified in standards ISO-639 and ISO-3166. For example, the locale ``en'' specifies English and ``en_US'' specifes U.S. English. .PP -The locale defaults to the value in env(LANG) at the time the -\fBmsgcat\fR package is loaded. If env(LANG) is not defined, then the +The locale defaults to the value in \fBenv(LANG)\fR at the time the +\fBmsgcat\fR package is loaded. If \fBenv(LANG)\fR is not defined, then the locale defaults to ``C''. .PP -When a locale is specified by the user, a ``best match'' -search is performed. If a user specifies en_UK_Funky, the -locales ``en_UK_Funky'', ``en_UK'', and ``en'' are searched -in order. +When a locale is specified by the user, a ``best match'' search is +performed during string translation. For example, if a user specifies +en_UK_Funky, the locales ``en_UK_Funky'', ``en_UK'', and ``en'' are +searched in order until a matching translation string is found. If no +translation string is available, then \fB::msgcat::unknown\fR is +called. + .SH "NAMESPACES AND MESSAGE CATALOGS" .PP Strings stored in the message catalog are stored relative @@ -106,8 +123,8 @@ error. .PP For example, executing the code .CS -mcset EN hello "hello from ::" -namespace eval foo {mcset EN hello "hello from ::foo"} +mcset en hello "hello from ::" +namespace eval foo {mcset en hello "hello from ::foo"} puts [mc hello] namespace eval foo {puts [mc hello]} .CE @@ -116,6 +133,7 @@ will print hello from :: hello from ::foo .CE + .SH "LOCATION AND FORMAT OF MESSAGE FILES" .PP Message files can be located in any directory, subject @@ -135,10 +153,11 @@ necessary translation strings for the language. For example: .CS ::msgcat::mcset es "Free Beer!" "Cerveza Gracias!" .CE + .SH "RECOMMENDED MESSAGE SETUP FOR PACKAGES" .PP If a package is installed into a subdirectory of the -tcl_pkgPath and loaded via ``package require'', the +\fBtcl_pkgPath\fR and loaded via \fBpackage require\fR, the following procedure is recommended. .IP [1] During package installation, create a subdirectory @@ -150,12 +169,13 @@ Copy your *.msg files into that directory. initialization script: .CS # load language files, stored in lang subdirectory -::msgcat::mcloadmsgs [file join [file dirname [info script]] lang] +::msgcat::mcload [file join [file dirname [info script]] lang] .CE + .SH "POSTITIONAL CODES FOR FORMAT AND SCAN COMMANDS" .PP It is possible that a message string used as an argument -to \fBformat\fR might have positionally dependent parameters which +to \fBformat\fR might have positionally dependent parameters that might need to be repositioned. For example, it might be syntactically desirable to rearrange the sentence structure while translating. @@ -173,10 +193,12 @@ format "In location %2\\\\$s we produced %1\\\\$d units" $num $city .PP Similarly, positional parameters can be used with \fBscan\fR to extract values from internationalized strings. + .SH "SEE ALSO" format(n), scan(n), namespace(n), package(n) + .SH CREDITS .PP The message catalog code was developed by Mark Harrison. .SH KEYWORDS -internationalization i18n message text translation +internationalization, i18n, localization, l10n, message, text, translation diff --git a/library/msgcat/msgcat.tcl b/library/msgcat/msgcat.tcl index 330002d..61b8327 100644 --- a/library/msgcat/msgcat.tcl +++ b/library/msgcat/msgcat.tcl @@ -10,7 +10,7 @@ # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # -# RCS: @(#) $Id: msgcat.tcl,v 1.1.2.2 1998/12/04 21:58:08 stanton Exp $ +# RCS: @(#) $Id: msgcat.tcl,v 1.1.2.3 1998/12/07 20:56:53 stanton Exp $ package provide msgcat 1.0 @@ -27,13 +27,6 @@ namespace eval msgcat { # array key is of the form ",," and the value is # the translated string. array set msgs {} - - # set default locale, try to get from environment - if {[info exists ::env(LANG)]} { - mclocale $::env(LANG) - } else { - mclocale "C" - } } # msgcat::mc -- @@ -114,16 +107,18 @@ proc msgcat::mcpreferences {} { # langdir The directory to search. # # Results: -# None. +# Returns the number of message catalogs that were loaded. proc msgcat::mcload {langdir} { + set x 0 foreach p [::msgcat::mcpreferences] { set langfile [file join $langdir $p.msg] if {[file exists $langfile]} { + incr x uplevel [list source $langfile] } } - return + return $x } # msgcat::mcset -- @@ -137,7 +132,7 @@ proc msgcat::mcload {langdir} { # the source string is used. # # Results: -# None. +# Returns the new locale. proc msgcat::mcset {locale src {dest ""}} { if {$dest == ""} { @@ -147,7 +142,7 @@ proc msgcat::mcset {locale src {dest ""}} { set ns [uplevel {namespace current}] set ::msgcat::msgs($locale,$ns,$src) $dest - return + return $dest } # msgcat::mcunknown -- @@ -168,3 +163,13 @@ proc msgcat::mcunknown {locale src} { return $src } +# Initialize the default locale + +namespace eval msgcat { + # set default locale, try to get from environment + if {[info exists ::env(LANG)]} { + mclocale $::env(LANG) + } else { + mclocale "C" + } +} diff --git a/library/msgcat1.0/msgcat.tcl b/library/msgcat1.0/msgcat.tcl index 330002d..61b8327 100644 --- a/library/msgcat1.0/msgcat.tcl +++ b/library/msgcat1.0/msgcat.tcl @@ -10,7 +10,7 @@ # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # -# RCS: @(#) $Id: msgcat.tcl,v 1.1.2.2 1998/12/04 21:58:08 stanton Exp $ +# RCS: @(#) $Id: msgcat.tcl,v 1.1.2.3 1998/12/07 20:56:53 stanton Exp $ package provide msgcat 1.0 @@ -27,13 +27,6 @@ namespace eval msgcat { # array key is of the form ",," and the value is # the translated string. array set msgs {} - - # set default locale, try to get from environment - if {[info exists ::env(LANG)]} { - mclocale $::env(LANG) - } else { - mclocale "C" - } } # msgcat::mc -- @@ -114,16 +107,18 @@ proc msgcat::mcpreferences {} { # langdir The directory to search. # # Results: -# None. +# Returns the number of message catalogs that were loaded. proc msgcat::mcload {langdir} { + set x 0 foreach p [::msgcat::mcpreferences] { set langfile [file join $langdir $p.msg] if {[file exists $langfile]} { + incr x uplevel [list source $langfile] } } - return + return $x } # msgcat::mcset -- @@ -137,7 +132,7 @@ proc msgcat::mcload {langdir} { # the source string is used. # # Results: -# None. +# Returns the new locale. proc msgcat::mcset {locale src {dest ""}} { if {$dest == ""} { @@ -147,7 +142,7 @@ proc msgcat::mcset {locale src {dest ""}} { set ns [uplevel {namespace current}] set ::msgcat::msgs($locale,$ns,$src) $dest - return + return $dest } # msgcat::mcunknown -- @@ -168,3 +163,13 @@ proc msgcat::mcunknown {locale src} { return $src } +# Initialize the default locale + +namespace eval msgcat { + # set default locale, try to get from environment + if {[info exists ::env(LANG)]} { + mclocale $::env(LANG) + } else { + mclocale "C" + } +} diff --git a/tests/msgcat.test b/tests/msgcat.test new file mode 100644 index 0000000..92e5c4b --- /dev/null +++ b/tests/msgcat.test @@ -0,0 +1,333 @@ +# Commands covered: ::msgcat::mc ::msgcat::mclocale +# ::msgcat::mcpreferences ::msgcat::mcload +# ::msgcat::mcset ::msgcat::mcunknown +# +# This file contains a collection of tests for the msgcat script library. +# Sourcing this file into Tcl runs the tests and +# generates output for errors. No output means no errors were found. +# +# Copyright (c) 1998 Mark Harrison. +# Copyright (c) 1998 by Scriptics Corporation. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# RCS: @(#) $Id: msgcat.test,v 1.1.2.1 1998/12/07 20:56:54 stanton Exp $ + +if {[string compare test [info procs test]] == 1} then {source defs} + +if {[catch {package require msgcat 1.0}]} { + if {[info exist msgcat1]} { + catch {puts stderr "Cannot load msgcat 1.0 package"} + return + } else { + catch {puts stderr "Running msgcat 1.0 tests in slave interp"} + set interp [interp create msgcat1] + $interp eval [list set msgcat1 "running"] + $interp eval [list source [info script]] + interp delete $interp + return + } +} + +set oldlocale [::msgcat::mclocale] + +# +# Test the various permutations of mclocale +# and mcpreferences. +# + +test msgcat-1.1 {::msgcat::mclocale default} { + ::msgcat::mclocale +} {C} + +test msgcat-1.2 {::msgcat::mcpreferences, single element} { + ::msgcat::mcpreferences +} {C} + +test msgcat-1.3 {::msgcat::mclocale, single element} { + ::msgcat::mclocale en +} {en} + +test msgcat-1.4 {::msgcat::mclocale, single element} { + ::msgcat::mclocale +} {en} + +test msgcat-1.5 {::msgcat::mcpreferences, single element} { + ::msgcat::mcpreferences +} {en} + +test msgcat-1.6 {::msgcat::mclocale, two elements} { + ::msgcat::mclocale en_US +} {en_US} + +test msgcat-1.7 {::msgcat::mclocale, two elements} { + ::msgcat::mclocale +} {en_US} + +test msgcat-1.8 {::msgcat::mcpreferences, two elements} { + ::msgcat::mcpreferences +} {en_US en} + +test msgcat-1.9 {::msgcat::mclocale, three elements} { + ::msgcat::mclocale en_US_funky +} {en_US_funky} + +test msgcat-1.10 {::msgcat::mclocale, three elements} { + ::msgcat::mclocale +} {en_US_funky} + +test msgcat-1.11 {::msgcat::mcpreferences, three elements} { + ::msgcat::mcpreferences +} {en_US_funky en_US en} + +# +# Test mcset and mcc, ensuring that namespace partitioning +# is working. +# + +test msgcat-2.1 {::msgcat::mcset, global scope} { + ::msgcat::mcset foo_BAR text1 text2 +} {text2} + +test msgcat-2.2 {::msgcat::mcset, global scope, default} { + ::msgcat::mcset foo_BAR text3 +} {text3} + +test msgcat-2.2 {::msgcat::mcset, namespace overlap} { + namespace eval bar {::msgcat::mcset foo_BAR con1 con1bar} + namespace eval baz {::msgcat::mcset foo_BAR con1 con1baz} +} {con1baz} + +test msgcat-2.3 {::msgcat::mcset, namespace overlap} { + ::msgcat::mclocale foo_BAR + namespace eval bar {::msgcat::mc con1} +} {con1bar} + +test msgcat-2.4 {::msgcat::mcset, namespace overlap} { + ::msgcat::mclocale foo_BAR + namespace eval baz {::msgcat::mc con1} +} {con1baz} + +# +# Test mcset and mc, ensuring that more specific locales +# (e.g. "en_UK") will search less specific locales +# (e.g. "en") for translation strings. +# +# Do this for the 12 permutations of +# locales: {foo foo_BAR foo_BAR_baz} +# strings: {ov1 ov2 ov3 ov4} +# locale foo defines ov1, ov2, ov3 +# locale foo_BAR defines ov2, ov3 +# locale foo_BAR_BAZ defines ov3 +# (ov4 is defined in none) +# So, +# ov3 should be resolved in foo, foo_BAR, foo_BAR_baz +# ov2 should be resolved in foo, foo_BAR +# ov2 should resolve to foo_BAR in foo_BAR_baz +# ov1 should be resolved in foo +# ov1 should resolve to foo in foo_BAR, foo_BAR_baz +# ov4 should be resolved in none, and call mcunknown +# + +test msgcat-3.1 {::msgcat::mcset, overlap} { + ::msgcat::mcset foo ov1 ov1_foo + ::msgcat::mcset foo ov2 ov2_foo + ::msgcat::mcset foo ov3 ov3_foo + ::msgcat::mcset foo_BAR ov2 ov2_foo_BAR + ::msgcat::mcset foo_BAR ov3 ov3_foo_BAR + ::msgcat::mcset foo_BAR_baz ov3 ov3_foo_BAR_baz +} {ov3_foo_BAR_baz} + +# top level, locale foo + +test msgcat-3.2 {::msgcat::mcset, overlap} { + ::msgcat::mclocale foo + ::msgcat::mc ov1 +} {ov1_foo} + +test msgcat-3.3 {::msgcat::mcset, overlap} { + ::msgcat::mclocale foo + ::msgcat::mc ov2 +} {ov2_foo} + +test msgcat-3.4 {::msgcat::mcset, overlap} { + ::msgcat::mclocale foo + ::msgcat::mc ov3 +} {ov3_foo} + +test msgcat-3.5 {::msgcat::mcset, overlap} { + ::msgcat::mclocale foo + ::msgcat::mc ov4 +} {ov4} + +# second level, locale foo_BAR + +test msgcat-3.6 {::msgcat::mcset, overlap} { + ::msgcat::mclocale foo_BAR + ::msgcat::mc ov1 +} {ov1_foo} + +test msgcat-3.7 {::msgcat::mcset, overlap} { + ::msgcat::mclocale foo_BAR + ::msgcat::mc ov2 +} {ov2_foo_BAR} + +test msgcat-3.8 {::msgcat::mcset, overlap} { + ::msgcat::mclocale foo_BAR + ::msgcat::mc ov3 +} {ov3_foo_BAR} + +test msgcat-3.9 {::msgcat::mcset, overlap} { + ::msgcat::mclocale foo_BAR + ::msgcat::mc ov4 +} {ov4} + +# third level, locale foo_BAR_baz + +test msgcat-3.10 {::msgcat::mcset, overlap} { + ::msgcat::mclocale foo_BAR_baz + ::msgcat::mc ov1 +} {ov1_foo} + +test msgcat-3.11 {::msgcat::mcset, overlap} { + ::msgcat::mclocale foo_BAR_baz + ::msgcat::mc ov2 +} {ov2_foo_BAR} + +test msgcat-3.12 {::msgcat::mcset, overlap} { + ::msgcat::mclocale foo_BAR_baz + ::msgcat::mc ov3 +} {ov3_foo_BAR_baz} + +test msgcat-3.13 {::msgcat::mcset, overlap} { + ::msgcat::mclocale foo_BAR_baz + ::msgcat::mc ov4 +} {ov4} + +# +# Test mcunknown, first the default operation +# and then with an overridden definition. +# + +test msgcat-4.1 {::msgcat::mcunknown, default} { + ::msgcat::mcset foo unk1 "unknown 1" +} {unknown 1} + +test msgcat-4.2 {::msgcat::mcunknown, default} { + ::msgcat::mclocale foo + ::msgcat::mc unk1 +} {unknown 1} + +test msgcat-4.3 {::msgcat::mcunknown, default} { + ::msgcat::mclocale foo + ::msgcat::mc unk2 +} {unk2} + +test msgcat-4.4 {::msgcat::mcunknown, overridden} { + rename ::msgcat::mcunknown oldproc + proc ::msgcat::mcunknown {dom s} { + return "unknown:$dom:$s" + } + ::msgcat::mclocale foo + set result [::msgcat::mc unk1] + rename ::msgcat::mcunknown {} + rename oldproc ::msgcat::mcunknown + set result +} {unknown 1} + +test msgcat-4.5 {::msgcat::mcunknown, overridden} { + rename ::msgcat::mcunknown oldproc + proc ::msgcat::mcunknown {dom s} { + return "unknown:$dom:$s" + } + ::msgcat::mclocale foo + set result [::msgcat::mc unk2] + rename ::msgcat::mcunknown {} + rename oldproc ::msgcat::mcunknown + set result +} {unknown:foo:unk2} + +# +# Test mcload. Need to set up an environment for +# these tests by creating a temporary directory and +# message files. +# + +set locales {en en_US en_US_funky} + +catch {file mkdir msgdir} +foreach l $locales { + set fd [open [file join msgdir $l.msg] w] + puts $fd "::msgcat::mcset $l abc abc-$l" + close $fd +} + +test msgcat-5.1 {::msgcat::mcload} { + ::msgcat::mclocale en + ::msgcat::mcload msgdir +} {1} + +test msgcat-5.2 {::msgcat::mcload} { + ::msgcat::mclocale en_US + ::msgcat::mcload msgdir +} {2} + +test msgcat-5.3 {::msgcat::mcload} { + ::msgcat::mclocale en_US_funky + ::msgcat::mcload msgdir +} {3} + +# Even though en_US_notexist does not exist, +# en_US and en should be loaded. + +test msgcat-5.4 {::msgcat::mcload} { + ::msgcat::mclocale en_US_notexist + ::msgcat::mcload msgdir +} {2} + +test msgcat-5.5 {::msgcat::mcload} { + ::msgcat::mclocale no_FI_notexist + ::msgcat::mcload msgdir +} {0} + +test msgcat-5.6 {::msgcat::mcload} { + ::msgcat::mclocale en + ::msgcat::mc abc +} {abc-en} + +test msgcat-5.7 {::msgcat::mcload} { + ::msgcat::mclocale en_US + ::msgcat::mc abc +} {abc-en_US} + +test msgcat-5.8 {::msgcat::mcload} { + ::msgcat::mclocale en_US_funky + ::msgcat::mc abc +} {abc-en_US_funky} + +test msgcat-5.9 {::msgcat::mcload} { + rename ::msgcat::mcunknown oldproc + proc ::msgcat::mcunknown {dom s} { + return "unknown:$dom:$s" + } + ::msgcat::mclocale no_FI_notexist + set result [::msgcat::mc abc] + rename ::msgcat::mcunknown {} + rename oldproc ::msgcat::mcunknown + set result +} {unknown:no_FI_notexist:abc} + +# +# Clean up the test files +# + +foreach l $locales { + file delete [file join msgdir $l.msg] +} + +# Clean out the msg catalogs + + +::msgcat::mclocale $oldlocale +file delete msgdir -- cgit v0.12