3 # This file defines various procedures which implement a
4 # message catalog facility for Tcl programs. It should be
5 # loaded with the command "package require msgcat".
7 # Copyright (c) 1998-2000 by Ajuba Solutions.
8 # Copyright (c) 1998 by Mark Harrison.
10 # See the file "license.terms" for information on usage and redistribution
11 # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
15 package require
Tcl 8.5
16 # When the version number changes, be sure to update the pkgIndex.tcl file,
17 # and the installation directory in the Makefiles.
18 package provide
msgcat 1.4.2
20 namespace eval msgcat {
21 namespace export mc mcload mclocale mcmax mcmset mcpreferences mcset
\
24 # Records the current locale as passed to mclocale
27 # Records the list of locales to search
30 # Records the mapping between source strings and translated strings. The
31 # dict key is of the form "<locale> <namespace> <src>", where locale and
32 # namespace should be themselves dict values and the value is
33 # the translated string.
34 variable Msgs
[dict create
]
36 # Map of language codes used in Windows registry to those of ISO-639
37 if { $::tcl_platform(platform
) eq
"windows" } {
38 variable WinRegToISO639
[dict create
{*}{
39 01 ar
0401 ar_SA
0801 ar_IQ
0c01 ar_EG
1001 ar_LY
1401 ar_DZ
40 1801 ar_MA
1c01 ar_TN
2001 ar_OM
2401 ar_YE
2801 ar_SY
41 2c01 ar_JO
3001 ar_LB
3401 ar_KW
3801 ar_AE
3c01 ar_BH
45 04 zh
0404 zh_TW
0804 zh_CN
0c04 zh_HK
1004 zh_SG
1404 zh_MO
48 07 de
0407 de_DE
0807 de_CH
0c07 de_AT
1007 de_LU
1407 de_LI
50 09 en
0409 en_US
0809 en_GB
0c09 en_AU
1009 en_CA
1409 en_NZ
51 1809 en_IE
1c09 en_ZA
2009 en_JM
2409 en_GD
2809 en_BZ
52 2c09 en_TT
3009 en_ZW
3409 en_PH
53 0a es
040a es_ES
080a es_MX
0c0a es_ES
@modern
100a es_GT
140a es_CR
54 180a es_PA
1c0a es_DO
200a es_VE
240a es_CO
280a es_PE
55 2c0a es_AR
300a es_EC
340a es_CL
380a es_UY
3c0a es_PY
56 400a es_BO
440a es_SV
480a es_HN
4c0a es_NI
500a es_PR
58 0c fr
040c fr_FR
080c fr_BE
0c0c fr_CA
100c fr_CH
140c fr_LU
63 10 it
0410 it_IT
0810 it_CH
66 13 nl
0413 nl_NL
0813 nl_BE
67 14 no
0414 no_NO
0814 nn_NO
69 16 pt
0416 pt_BR
0816 pt_PT
73 1a hr
041a hr_HR
081a sr_YU
0c1a sr_YU
@cyrillic
76 1d sv
041d sv_SE
081d sv_FI
79 20 ur
0420 ur_PK
0820 ur_IN
91 2c az
042c az_AZ
@latin
082c az_AZ
@cyrillic
106 043c gd_UK
083c ga_IE
108 3e ms
043e ms_MY
083e ms_BN
113 43 uz
0443 uz_UZ
@latin
0843 uz_UZ
@cyrillic
142 60 ks
0460 ks_PK
0860 ks_IN
143 61 ne
0461 ne_NP
0861 ne_IN
169 # Find the translation for the given string based on the current
170 # locale setting. Check the local namespace first, then look in each
171 # parent namespace until the source is found. If additional args are
172 # specified, use the format command to work them into the traslated
176 # src The string to translate.
177 # args Args to pass to the format command
180 # Returns the translated string. Propagates errors thrown by the
183 proc msgcat::mc {src args
} {
184 # Check for the src in each namespace starting from the local and
185 # ending in the global.
191 set ns
[uplevel 1 [list ::namespace current
]]
194 foreach loc
$Loclist {
195 if {[dict exists
$Msgs $loc $ns $src]} {
196 if {[llength $args] == 0} {
197 return [dict get
$Msgs $loc $ns $src]
199 return [format [dict get
$Msgs $loc $ns $src] {*}$args]
203 set ns
[namespace parent
$ns]
205 # we have not found the translation
206 return [uplevel 1 [list [namespace origin mcunknown
] \
207 $Locale $src {*}$args]]
210 # msgcat::mclocale --
212 # Query or set the current locale.
215 # newLocale (Optional) The new locale string. Locale strings
216 # should be composed of one or more sublocale parts
217 # separated by underscores (e.g. en_US).
220 # Returns the current locale.
222 proc msgcat::mclocale {args
} {
225 set len
[llength $args]
228 return -code error "wrong # args: should be\
229 \"[lindex [info level 0] 0] ?newLocale?\""
233 set newLocale
[lindex $args 0]
234 if {$newLocale ne
[file tail
$newLocale]} {
235 return -code error "invalid newLocale value \"$newLocale\":\
236 could be path to unsafe code."
238 set Locale
[string tolower
$newLocale]
241 foreach part
[split $Locale _
] {
242 set word
[string trim
"${word}_${part}" _
]
243 if {$word ne
[lindex $Loclist 0]} {
244 set Loclist
[linsert $Loclist 0 $word]
248 set Locale
[lindex $Loclist 0]
253 # msgcat::mcpreferences --
255 # Fetch the list of locales used to look up strings, ordered from
256 # most preferred to least preferred.
262 # Returns an ordered list of the locales preferred by the user.
264 proc msgcat::mcpreferences {} {
271 # Attempt to load message catalogs for each locale in the
272 # preference list from the specified directory.
275 # langdir The directory to search.
278 # Returns the number of message catalogs that were loaded.
280 proc msgcat::mcload {langdir
} {
282 foreach p
[mcpreferences
] {
286 set langfile
[file join $langdir $p.msg
]
287 if {[file exists
$langfile]} {
289 uplevel 1 [list ::source -encoding utf-8
$langfile]
297 # Set the translation for a given string in a specified locale.
300 # locale The locale to use.
301 # src The source string.
302 # dest (Optional) The translated string. If omitted,
303 # the source string is used.
306 # Returns the new locale.
308 proc msgcat::mcset {locale src
{dest
""}} {
310 if {[llength [info level
0]] == 3} { ;# dest not specified
314 set ns
[uplevel 1 [list ::namespace current
]]
316 set locale
[string tolower
$locale]
318 # create nested dictionaries if they do not exist
319 if {![dict exists
$Msgs $locale]} {
320 dict
set Msgs
$locale [dict create
]
322 if {![dict exists
$Msgs $locale $ns]} {
323 dict
set Msgs
$locale $ns [dict create
]
325 dict
set Msgs
$locale $ns $src $dest
331 # Set the translation for multiple strings in a specified locale.
334 # locale The locale to use.
335 # pairs One or more src/dest pairs (must be even length)
338 # Returns the number of pairs processed
340 proc msgcat::mcmset {locale pairs
} {
343 set length
[llength $pairs]
345 return -code error "bad translation list:\
346 should be \"[lindex [info level 0] 0] locale {src dest ...}\""
349 set locale
[string tolower
$locale]
350 set ns
[uplevel 1 [list ::namespace current
]]
352 # create nested dictionaries if they do not exist
353 if {![dict exists
$Msgs $locale]} {
354 dict
set Msgs
$locale [dict create
]
356 if {![dict exists
$Msgs $locale $ns]} {
357 dict
set Msgs
$locale $ns [dict create
]
359 foreach {src dest
} $pairs {
360 dict
set Msgs
$locale $ns $src $dest
366 # msgcat::mcunknown --
368 # This routine is called by msgcat::mc if a translation cannot
369 # be found for a string. This routine is intended to be replaced
370 # by an application specific routine for error reporting
371 # purposes. The default behavior is to return the source string.
372 # If additional args are specified, the format command will be used
373 # to work them into the traslated string.
376 # locale The current locale.
377 # src The string to be translated.
378 # args Args to pass to the format command
381 # Returns the translated value.
383 proc msgcat::mcunknown {locale src args
} {
384 if {[llength $args]} {
385 return [format $src {*}$args]
393 # Calculates the maximum length of the translated strings of the given
397 # args strings to translate.
400 # Returns the length of the longest translated string.
402 proc msgcat::mcmax {args
} {
404 foreach string $args {
405 set translated
[uplevel 1 [list [namespace origin mc
] $string]]
406 set len
[string length
$translated]
414 # Convert the locale values stored in environment variables to a form
415 # suitable for passing to [mclocale]
416 proc msgcat::ConvertLocale {value
} {
417 # Assume $value is of form: $language[_$territory][.$codeset][@modifier]
418 # Convert to form: $language[_$territory][_$modifier]
420 # Comment out expanded RE version -- bugs alleged
422 # ^ # Match all the way to the beginning
423 # ([^_.@]*) # Match "lanugage"; ends with _, ., or @
424 # (_([^.@]*))? # Match (optional) "territory"; starts with _
425 # ([.]([^@]*))? # Match (optional) "codeset"; starts with .
426 # (@(.*))? # Match (optional) "modifier"; starts with @
427 # $ # Match all the way to the end
428 # } $value -> language _ territory _ codeset _ modifier
429 if {![regexp {^
([^_.
@]+)(_
([^.
@]*))?
([.
]([^
@]*))?
(@(.
*))?
$} $value \
430 -> language _ territory _ codeset _ modifier
]} {
431 return -code error "invalid locale '$value': empty language part"
434 if {[string length
$territory]} {
435 append ret _
$territory
437 if {[string length
$modifier]} {
438 append ret _
$modifier
443 # Initialize the default locale
444 proc msgcat::Init {} {
446 # set default locale, try to get from environment
448 foreach varName
{LC_ALL LC_MESSAGES LANG
} {
449 if {[info exists
::env($varName)] && ("" ne
$::env($varName))} {
451 mclocale
[ConvertLocale
$::env($varName)]
458 # On Darwin, fallback to current CFLocale identifier if available.
460 if {$::tcl_platform(os
) eq
"Darwin" && $::tcl_platform(platform
) eq
"unix"
461 && [info exists
::tcl::mac::locale] && $::tcl::mac::locale ne
""} {
463 mclocale
[ConvertLocale
$::tcl::mac::locale]
469 # The rest of this routine is special processing for Windows;
470 # all other platforms, get out now.
472 if { $::tcl_platform(platform
) ne
"windows" } {
477 # On Windows, try to set locale depending on registry settings,
478 # or fall back on locale of "C".
480 set key
{HKEY_CURRENT_USER
\Control Panel
\International
}
481 if {[catch {package require
registry}] \
482 ||
[catch {registry get
$key "locale"} locale
]} {
487 # Keep trying to match against smaller and smaller suffixes
488 # of the registry value, since the latter hexadigits appear
489 # to determine general language and earlier hexadigits determine
490 # more precise information, such as territory. For example,
491 # 0409 - English - United States
492 # 0809 - English - United Kingdom
493 # Add more translations to the WinRegToISO639 array above.
495 variable WinRegToISO639
496 set locale
[string tolower
$locale]
497 while {[string length
$locale]} {
499 mclocale
[ConvertLocale
[dict get
$WinRegToISO639 $locale]]
503 set locale
[string range
$locale 1 end
]
506 # No translation known. Fall back on "C" locale