summaryrefslogtreecommitdiffstats
path: root/library/msgcat/msgcat.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'library/msgcat/msgcat.tcl')
-rw-r--r--library/msgcat/msgcat.tcl1137
1 files changed, 940 insertions, 197 deletions
diff --git a/library/msgcat/msgcat.tcl b/library/msgcat/msgcat.tcl
index 044dcbb..928474d 100644
--- a/library/msgcat/msgcat.tcl
+++ b/library/msgcat/msgcat.tcl
@@ -4,160 +4,173 @@
# message catalog facility for Tcl programs. It should be
# loaded with the command "package require msgcat".
#
+# Copyright (c) 2010-2015 by Harald Oehlmann.
# Copyright (c) 1998-2000 by Ajuba Solutions.
# Copyright (c) 1998 by Mark Harrison.
#
# 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.17.2.3 2004/03/31 18:51:01 dgp Exp $
-package require Tcl 8.2
+package require Tcl 8.5-
# When the version number changes, be sure to update the pkgIndex.tcl file,
# and the installation directory in the Makefiles.
-package provide msgcat 1.3.2
+package provide msgcat 1.6.0
namespace eval msgcat {
- namespace export mc mcload mclocale mcmax mcmset mcpreferences mcset \
- mcunknown
-
- # Records the current locale as passed to mclocale
- variable Locale ""
+ namespace export mc mcexists mcload mclocale mcmax mcmset mcpreferences mcset\
+ mcunknown mcflset mcflmset mcloadedlocales mcforgetpackage\
+ mcpackageconfig mcpackagelocale
# Records the list of locales to search
variable Loclist {}
+ # List of currently loaded locales
+ variable LoadedLocales {}
+
+ # Records the locale of the currently sourced message catalogue file
+ variable FileLocale
+
+ # Configuration values per Package (e.g. client namespace).
+ # The dict key is of the form "<option> <namespace>" and the value is the
+ # configuration option. A nonexisting key is an unset option.
+ variable PackageConfig [dict create mcfolder {} loadcmd {} changecmd {}\
+ unknowncmd {} loadedlocales {} loclist {}]
+
# Records the mapping between source strings and translated strings. The
- # array key is of the form "<locale>,<namespace>,<src>" and the value is
+ # dict key is of the form "<namespace> <locale> <src>", where locale and
+ # namespace should be themselves dict values and the value is
# the translated string.
- array set Msgs {}
+ variable Msgs [dict create]
# Map of language codes used in Windows registry to those of ISO-639
- array set WinRegToISO639 {
- 01 ar 0401 ar_SA 0801 ar_IQ 0c01 ar_EG 1001 ar_LY 1401 ar_DZ
- 1801 ar_MA 1c01 ar_TN 2001 ar_OM 2401 ar_YE 2801 ar_SY
- 2c01 ar_JO 3001 ar_LB 3401 ar_KW 3801 ar_AE 3c01 ar_BH
- 4001 ar_QA
- 02 bg 0402 bg_BG
- 03 ca 0403 ca_ES
- 04 zh 0404 zh_TW 0804 zh_CN 0c04 zh_HK 1004 zh_SG 1404 zh_MO
- 05 cs 0405 cs_CZ
- 06 da 0406 da_DK
- 07 de 0407 de_DE 0807 de_CH 0c07 de_AT 1007 de_LU 1407 de_LI
- 08 el 0408 el_GR
- 09 en 0409 en_US 0809 en_GB 0c09 en_AU 1009 en_CA 1409 en_NZ
- 1809 en_IE 1c09 en_ZA 2009 en_JM 2409 en_GD 2809 en_BZ
- 2c09 en_TT 3009 en_ZW 3409 en_PH
- 0a es 040a es_ES 080a es_MX 0c0a es_ES@modern 100a es_GT 140a es_CR
- 180a es_PA 1c0a es_DO 200a es_VE 240a es_CO 280a es_PE
- 2c0a es_AR 300a es_EC 340a es_CL 380a es_UY 3c0a es_PY
- 400a es_BO 440a es_SV 480a es_HN 4c0a es_NI 500a es_PR
- 0b fi 040b fi_FI
- 0c fr 040c fr_FR 080c fr_BE 0c0c fr_CA 100c fr_CH 140c fr_LU
- 180c fr_MC
- 0d he 040d he_IL
- 0e hu 040e hu_HU
- 0f is 040f is_IS
- 10 it 0410 it_IT 0810 it_CH
- 11 ja 0411 ja_JP
- 12 ko 0412 ko_KR
- 13 nl 0413 nl_NL 0813 nl_BE
- 14 no 0414 no_NO 0814 nn_NO
- 15 pl 0415 pl_PL
- 16 pt 0416 pt_BR 0816 pt_PT
- 17 rm 0417 rm_CH
- 18 ro 0418 ro_RO
- 19 ru
- 1a hr 041a hr_HR 081a sr_YU 0c1a sr_YU@cyrillic
- 1b sk 041b sk_SK
- 1c sq 041c sq_AL
- 1d sv 041d sv_SE 081d sv_FI
- 1e th 041e th_TH
- 1f tr 041f tr_TR
- 20 ur 0420 ur_PK 0820 ur_IN
- 21 id 0421 id_ID
- 22 uk 0422 uk_UA
- 23 be 0423 be_BY
- 24 sl 0424 sl_SI
- 25 et 0425 et_EE
- 26 lv 0426 lv_LV
- 27 lt 0427 lt_LT
- 28 tg 0428 tg_TJ
- 29 fa 0429 fa_IR
- 2a vi 042a vi_VN
- 2b hy 042b hy_AM
- 2c az 042c az_AZ@latin 082c az_AZ@cyrillic
- 2d eu
- 2e wen 042e wen_DE
- 2f mk 042f mk_MK
- 30 bnt 0430 bnt_TZ
- 31 ts 0431 ts_ZA
- 33 ven 0433 ven_ZA
- 34 xh 0434 xh_ZA
- 35 zu 0435 zu_ZA
- 36 af 0436 af_ZA
- 37 ka 0437 ka_GE
- 38 fo 0438 fo_FO
- 39 hi 0439 hi_IN
- 3a mt 043a mt_MT
- 3b se 043b se_NO
- 043c gd_UK 083c ga_IE
- 3d yi 043d yi_IL
- 3e ms 043e ms_MY 083e ms_BN
- 3f kk 043f kk_KZ
- 40 ky 0440 ky_KG
- 41 sw 0441 sw_KE
- 42 tk 0442 tk_TM
- 43 uz 0443 uz_UZ@latin 0843 uz_UZ@cyrillic
- 44 tt 0444 tt_RU
- 45 bn 0445 bn_IN
- 46 pa 0446 pa_IN
- 47 gu 0447 gu_IN
- 48 or 0448 or_IN
- 49 ta
- 4a te 044a te_IN
- 4b kn 044b kn_IN
- 4c ml 044c ml_IN
- 4d as 044d as_IN
- 4e mr 044e mr_IN
- 4f sa 044f sa_IN
- 50 mn
- 51 bo 0451 bo_CN
- 52 cy 0452 cy_GB
- 53 km 0453 km_KH
- 54 lo 0454 lo_LA
- 55 my 0455 my_MM
- 56 gl 0456 gl_ES
- 57 kok 0457 kok_IN
- 58 mni 0458 mni_IN
- 59 sd
- 5a syr 045a syr_TR
- 5b si 045b si_LK
- 5c chr 045c chr_US
- 5d iu 045d iu_CA
- 5e am 045e am_ET
- 5f ber 045f ber_MA
- 60 ks 0460 ks_PK 0860 ks_IN
- 61 ne 0461 ne_NP 0861 ne_IN
- 62 fy 0462 fy_NL
- 63 ps
- 64 tl 0464 tl_PH
- 65 div 0465 div_MV
- 66 bin 0466 bin_NG
- 67 ful 0467 ful_NG
- 68 ha 0468 ha_NG
- 69 nic 0469 nic_NG
- 6a yo 046a yo_NG
- 70 ibo 0470 ibo_NG
- 71 kau 0471 kau_NG
- 72 om 0472 om_ET
- 73 ti 0473 ti_ET
- 74 gn 0474 gn_PY
- 75 cpe 0475 cpe_US
- 76 la 0476 la_VA
- 77 so 0477 so_SO
- 78 sit 0478 sit_CN
- 79 pap 0479 pap_AN
+ if {[info sharedlibextension] eq ".dll"} {
+ variable WinRegToISO639 [dict create {*}{
+ 01 ar 0401 ar_SA 0801 ar_IQ 0c01 ar_EG 1001 ar_LY 1401 ar_DZ
+ 1801 ar_MA 1c01 ar_TN 2001 ar_OM 2401 ar_YE 2801 ar_SY
+ 2c01 ar_JO 3001 ar_LB 3401 ar_KW 3801 ar_AE 3c01 ar_BH
+ 4001 ar_QA
+ 02 bg 0402 bg_BG
+ 03 ca 0403 ca_ES
+ 04 zh 0404 zh_TW 0804 zh_CN 0c04 zh_HK 1004 zh_SG 1404 zh_MO
+ 05 cs 0405 cs_CZ
+ 06 da 0406 da_DK
+ 07 de 0407 de_DE 0807 de_CH 0c07 de_AT 1007 de_LU 1407 de_LI
+ 08 el 0408 el_GR
+ 09 en 0409 en_US 0809 en_GB 0c09 en_AU 1009 en_CA 1409 en_NZ
+ 1809 en_IE 1c09 en_ZA 2009 en_JM 2409 en_GD 2809 en_BZ
+ 2c09 en_TT 3009 en_ZW 3409 en_PH
+ 0a es 040a es_ES 080a es_MX 0c0a es_ES@modern 100a es_GT 140a es_CR
+ 180a es_PA 1c0a es_DO 200a es_VE 240a es_CO 280a es_PE
+ 2c0a es_AR 300a es_EC 340a es_CL 380a es_UY 3c0a es_PY
+ 400a es_BO 440a es_SV 480a es_HN 4c0a es_NI 500a es_PR
+ 0b fi 040b fi_FI
+ 0c fr 040c fr_FR 080c fr_BE 0c0c fr_CA 100c fr_CH 140c fr_LU
+ 180c fr_MC
+ 0d he 040d he_IL
+ 0e hu 040e hu_HU
+ 0f is 040f is_IS
+ 10 it 0410 it_IT 0810 it_CH
+ 11 ja 0411 ja_JP
+ 12 ko 0412 ko_KR
+ 13 nl 0413 nl_NL 0813 nl_BE
+ 14 no 0414 no_NO 0814 nn_NO
+ 15 pl 0415 pl_PL
+ 16 pt 0416 pt_BR 0816 pt_PT
+ 17 rm 0417 rm_CH
+ 18 ro 0418 ro_RO 0818 ro_MO
+ 19 ru 0819 ru_MO
+ 1a hr 041a hr_HR 081a sr_YU 0c1a sr_YU@cyrillic
+ 1b sk 041b sk_SK
+ 1c sq 041c sq_AL
+ 1d sv 041d sv_SE 081d sv_FI
+ 1e th 041e th_TH
+ 1f tr 041f tr_TR
+ 20 ur 0420 ur_PK 0820 ur_IN
+ 21 id 0421 id_ID
+ 22 uk 0422 uk_UA
+ 23 be 0423 be_BY
+ 24 sl 0424 sl_SI
+ 25 et 0425 et_EE
+ 26 lv 0426 lv_LV
+ 27 lt 0427 lt_LT
+ 28 tg 0428 tg_TJ
+ 29 fa 0429 fa_IR
+ 2a vi 042a vi_VN
+ 2b hy 042b hy_AM
+ 2c az 042c az_AZ@latin 082c az_AZ@cyrillic
+ 2d eu
+ 2e wen 042e wen_DE
+ 2f mk 042f mk_MK
+ 30 bnt 0430 bnt_TZ
+ 31 ts 0431 ts_ZA
+ 32 tn
+ 33 ven 0433 ven_ZA
+ 34 xh 0434 xh_ZA
+ 35 zu 0435 zu_ZA
+ 36 af 0436 af_ZA
+ 37 ka 0437 ka_GE
+ 38 fo 0438 fo_FO
+ 39 hi 0439 hi_IN
+ 3a mt 043a mt_MT
+ 3b se 043b se_NO
+ 043c gd_UK 083c ga_IE
+ 3d yi 043d yi_IL
+ 3e ms 043e ms_MY 083e ms_BN
+ 3f kk 043f kk_KZ
+ 40 ky 0440 ky_KG
+ 41 sw 0441 sw_KE
+ 42 tk 0442 tk_TM
+ 43 uz 0443 uz_UZ@latin 0843 uz_UZ@cyrillic
+ 44 tt 0444 tt_RU
+ 45 bn 0445 bn_IN
+ 46 pa 0446 pa_IN
+ 47 gu 0447 gu_IN
+ 48 or 0448 or_IN
+ 49 ta
+ 4a te 044a te_IN
+ 4b kn 044b kn_IN
+ 4c ml 044c ml_IN
+ 4d as 044d as_IN
+ 4e mr 044e mr_IN
+ 4f sa 044f sa_IN
+ 50 mn
+ 51 bo 0451 bo_CN
+ 52 cy 0452 cy_GB
+ 53 km 0453 km_KH
+ 54 lo 0454 lo_LA
+ 55 my 0455 my_MM
+ 56 gl 0456 gl_ES
+ 57 kok 0457 kok_IN
+ 58 mni 0458 mni_IN
+ 59 sd
+ 5a syr 045a syr_TR
+ 5b si 045b si_LK
+ 5c chr 045c chr_US
+ 5d iu 045d iu_CA
+ 5e am 045e am_ET
+ 5f ber 045f ber_MA
+ 60 ks 0460 ks_PK 0860 ks_IN
+ 61 ne 0461 ne_NP 0861 ne_IN
+ 62 fy 0462 fy_NL
+ 63 ps
+ 64 tl 0464 tl_PH
+ 65 div 0465 div_MV
+ 66 bin 0466 bin_NG
+ 67 ful 0467 ful_NG
+ 68 ha 0468 ha_NG
+ 69 nic 0469 nic_NG
+ 6a yo 046a yo_NG
+ 70 ibo 0470 ibo_NG
+ 71 kau 0471 kau_NG
+ 72 om 0472 om_ET
+ 73 ti 0473 ti_ET
+ 74 gn 0474 gn_PY
+ 75 cpe 0475 cpe_US
+ 76 la 0476 la_VA
+ 77 so 0477 so_SO
+ 78 sit 0478 sit_CN
+ 79 pap 0479 pap_AN
+ }]
}
}
@@ -168,41 +181,98 @@ namespace eval msgcat {
# parent namespace until the source is found. If additional args are
# specified, use the format command to work them into the traslated
# string.
+# If no catalog item is found, mcunknown is called in the caller frame
+# and its result is returned.
#
# Arguments:
# src The string to translate.
# args Args to pass to the format command
#
# Results:
-# Returns the translatd string. Propagates errors thrown by the
+# Returns the translated string. Propagates errors thrown by the
# format command.
proc msgcat::mc {src args} {
+ # this may be replaced by:
+ # return [mcget -namespace [uplevel 1 [list ::namespace current]] --\
+ # $src {*}$args]
+
# Check for the src in each namespace starting from the local and
# ending in the global.
variable Msgs
variable Loclist
- variable Locale
set ns [uplevel 1 [list ::namespace current]]
-
- while {$ns != ""} {
- foreach loc $Loclist {
- if {[info exists Msgs($loc,$ns,$src)]} {
- if {[llength $args] == 0} {
- return $Msgs($loc,$ns,$src)
- } else {
- return [uplevel 1 \
- [linsert $args 0 ::format $Msgs($loc,$ns,$src)]]
- }
+ set loclist [PackagePreferences $ns]
+
+ set nscur $ns
+ while {$nscur != ""} {
+ foreach loc $loclist {
+ if {[dict exists $Msgs $nscur $loc $src]} {
+ return [DefaultUnknown "" [dict get $Msgs $nscur $loc $src]\
+ {*}$args]
}
}
+ set nscur [namespace parent $nscur]
+ }
+ # call package local or default unknown command
+ set args [linsert $args 0 [lindex $loclist 0] $src]
+ switch -exact -- [Invoke unknowncmd $args $ns result 1] {
+ 0 { return [uplevel 1 [linsert $args 0 [namespace origin mcunknown]]] }
+ 1 { return [DefaultUnknown {*}$args] }
+ default { return $result }
+ }
+}
+
+# msgcat::mcexists --
+#
+# Check if a catalog item is set or if mc would invoke mcunknown.
+#
+# Arguments:
+# -exactnamespace Only check the exact namespace and no
+# parent namespaces
+# -exactlocale Only check the exact locale and not all members
+# of the preferences list
+# src Message catalog key
+#
+# Results:
+# true if an adequate catalog key was found
+
+proc msgcat::mcexists {args} {
+
+ variable Msgs
+ variable Loclist
+ variable PackageConfig
+
+ set ns [uplevel 1 [list ::namespace current]]
+ set loclist [PackagePreferences $ns]
+
+ while {[llength $args] != 1} {
+ set args [lassign $args option]
+ switch -glob -- $option {
+ -exactnamespace { set exactnamespace 1 }
+ -exactlocale { set loclist [lrange $loclist 0 0] }
+ -* { return -code error "unknown option \"$option\"" }
+ default {
+ return -code error "wrong # args: should be\
+ \"[lindex [info level 0] 0] ?-exactnamespace?\
+ ?-exactlocale? src\""
+ }
+ }
+ }
+ set src [lindex $args 0]
+
+ while {$ns ne ""} {
+ foreach loc $loclist {
+ if {[dict exists $Msgs $ns $loc $src]} {
+ return 1
+ }
+ }
+ if {[info exists exactnamespace]} {return 0}
set ns [namespace parent $ns]
}
- # we have not found the translation
- return [uplevel 1 \
- [linsert $args 0 [::namespace origin mcunknown] $Locale $src]]
+ return 0
}
# msgcat::mclocale --
@@ -215,27 +285,62 @@ proc msgcat::mc {src args} {
# separated by underscores (e.g. en_US).
#
# Results:
-# Returns the current locale.
+# Returns the normalized set locale.
proc msgcat::mclocale {args} {
variable Loclist
- variable Locale
+ variable LoadedLocales
set len [llength $args]
if {$len > 1} {
- error {wrong # args: should be "mclocale ?newLocale?"}
+ return -code error "wrong # args: should be\
+ \"[lindex [info level 0] 0] ?newLocale?\""
}
if {$len == 1} {
- set Locale [string tolower [lindex $args 0]]
- set Loclist {}
- set word ""
- foreach part [split $Locale _] {
- set word [string trimleft "${word}_${part}" _]
- set Loclist [linsert $Loclist 0 $word]
+ set newLocale [string tolower [lindex $args 0]]
+ if {$newLocale ne [file tail $newLocale]} {
+ return -code error "invalid newLocale value \"$newLocale\":\
+ could be path to unsafe code."
+ }
+ if {[lindex $Loclist 0] ne $newLocale} {
+ set Loclist [GetPreferences $newLocale]
+
+ # locale not loaded jet
+ LoadAll $Loclist
+ # Invoke callback
+ Invoke changecmd $Loclist
+ }
+ }
+ return [lindex $Loclist 0]
+}
+
+# msgcat::GetPreferences --
+#
+# Get list of locales from a locale.
+# The first element is always the lowercase locale.
+# Other elements have one component separated by "_" less.
+# Multiple "_" are seen as one separator: de__ch_spec de__ch de {}
+#
+# Arguments:
+# Locale.
+#
+# Results:
+# Locale list
+
+proc msgcat::GetPreferences {locale} {
+ set locale [string tolower $locale]
+ set loclist [list $locale]
+ while {-1 !=[set pos [string last "_" $locale]]} {
+ set locale [string range $locale 0 $pos-1]
+ if { "_" ne [string index $locale end] } {
+ lappend loclist $locale
}
}
- return $Locale
+ if {"" ne [lindex $loclist end]} {
+ lappend loclist {}
+ }
+ return $loclist
}
# msgcat::mcpreferences --
@@ -254,6 +359,391 @@ proc msgcat::mcpreferences {} {
return $Loclist
}
+# msgcat::mcloadedlocales --
+#
+# Get or change the list of currently loaded default locales
+#
+# The following subcommands are available:
+# loaded
+# Get the current list of loaded locales
+# clear
+# Remove all loaded locales not present in mcpreferences.
+#
+# Arguments:
+# subcommand One of loaded or clear
+#
+# Results:
+# Empty string, if not stated differently for the subcommand
+
+proc msgcat::mcloadedlocales {subcommand} {
+ variable Loclist
+ variable LoadedLocales
+ variable Msgs
+ variable PackageConfig
+ switch -exact -- $subcommand {
+ clear {
+ # Remove all locales not contained in Loclist
+ # skip any packages with package locale
+ set LoadedLocales $Loclist
+ foreach ns [dict keys $Msgs] {
+ if {![dict exists $PackageConfig loclist $ns]} {
+ foreach locale [dict keys [dict get $Msgs $ns]] {
+ if {$locale ni $Loclist} {
+ dict unset Msgs $ns $locale
+ }
+ }
+ }
+ }
+ }
+ loaded { return $LoadedLocales }
+ default {
+ return -code error "unknown subcommand \"$subcommand\": must be\
+ clear, or loaded"
+ }
+ }
+ return
+}
+
+# msgcat::mcpackagelocale --
+#
+# Get or change the package locale of the calling package.
+#
+# The following subcommands are available:
+# set
+# Set a package locale.
+# This may load message catalog files and may clear message catalog
+# items, if the former locale was the default locale.
+# Returns the normalized set locale.
+# The default locale is taken, if locale is not given.
+# get
+# Get the locale valid for this package.
+# isset
+# Returns true, if a package locale is set
+# unset
+# Unset the package locale and activate the default locale.
+# This loads message catalog file which where missing in the package
+# locale.
+# preferences
+# Return locale preference list valid for the package.
+# loaded
+# Return loaded locale list valid for the current package.
+# clear
+# If the current package has a package locale, remove all package
+# locales not containes in package mcpreferences.
+# It is an error to call this without a package locale set.
+#
+# The subcommands get, preferences and loaded return the corresponding
+# default data, if no package locale is set.
+#
+# Arguments:
+# subcommand see list above
+# locale package locale (only set subcommand)
+#
+# Results:
+# Empty string, if not stated differently for the subcommand
+
+proc msgcat::mcpackagelocale {subcommand {locale ""}} {
+ # todo: implement using an ensemble
+ variable Loclist
+ variable LoadedLocales
+ variable Msgs
+ variable PackageConfig
+ # Check option
+ # check if required item is exactly provided
+ if {[llength [info level 0]] == 2} {
+ # locale not given
+ unset locale
+ } else {
+ # locale given
+ if {$subcommand in
+ {"get" "isset" "unset" "preferences" "loaded" "clear"} } {
+ return -code error "wrong # args: should be\
+ \"[lrange [info level 0] 0 1]\""
+ }
+ set locale [string tolower $locale]
+ }
+ set ns [uplevel 1 {::namespace current}]
+
+ switch -exact -- $subcommand {
+ get { return [lindex [PackagePreferences $ns] 0] }
+ preferences { return [PackagePreferences $ns] }
+ loaded { return [PackageLocales $ns] }
+ present { return [expr {$locale in [PackageLocales $ns]} ]}
+ isset { return [dict exists $PackageConfig loclist $ns] }
+ set { # set a package locale or add a package locale
+
+ # Copy the default locale if no package locale set so far
+ if {![dict exists $PackageConfig loclist $ns]} {
+ dict set PackageConfig loclist $ns $Loclist
+ dict set PackageConfig loadedlocales $ns $LoadedLocales
+ }
+
+ # Check if changed
+ set loclist [dict get $PackageConfig loclist $ns]
+ if {! [info exists locale] || $locale eq [lindex $loclist 0] } {
+ return [lindex $loclist 0]
+ }
+
+ # Change loclist
+ set loclist [GetPreferences $locale]
+ set locale [lindex $loclist 0]
+ dict set PackageConfig loclist $ns $loclist
+
+ # load eventual missing locales
+ set loadedLocales [dict get $PackageConfig loadedlocales $ns]
+ if {$locale in $loadedLocales} { return $locale }
+ set loadLocales [ListComplement $loadedLocales $loclist]
+ dict set PackageConfig loadedlocales $ns\
+ [concat $loadedLocales $loadLocales]
+ Load $ns $loadLocales
+ return $locale
+ }
+ clear { # Remove all locales not contained in Loclist
+ if {![dict exists $PackageConfig loclist $ns]} {
+ return -code error "clear only when package locale set"
+ }
+ set loclist [dict get $PackageConfig loclist $ns]
+ dict set PackageConfig loadedlocales $ns $loclist
+ if {[dict exists $Msgs $ns]} {
+ foreach locale [dict keys [dict get $Msgs $ns]] {
+ if {$locale ni $loclist} {
+ dict unset Msgs $ns $locale
+ }
+ }
+ }
+ }
+ unset { # unset package locale and restore default locales
+
+ if { ![dict exists $PackageConfig loclist $ns] } { return }
+
+ # unset package locale
+ set loadLocales [ListComplement\
+ [dict get $PackageConfig loadedlocales $ns] $LoadedLocales]
+ dict unset PackageConfig loadedlocales $ns
+ dict unset PackageConfig loclist $ns
+
+ # unset keys not in global loaded locales
+ if {[dict exists $Msgs $ns]} {
+ foreach locale [dict keys [dict get $Msgs $ns]] {
+ if {$locale ni $LoadedLocales} {
+ dict unset Msgs $ns $locale
+ }
+ }
+ }
+
+ # Add missing locales
+ Load $ns $loadLocales
+ }
+ default {
+ return -code error "unknown subcommand \"$subcommand\": must be\
+ clear, get, isset, loaded, present, set, or unset"
+ }
+ }
+ return
+}
+
+# msgcat::mcforgetpackage --
+#
+# Remove any data of the calling package from msgcat
+#
+
+proc msgcat::mcforgetpackage {} {
+ # todo: this may be implemented using an ensemble
+ variable PackageConfig
+ variable Msgs
+ set ns [uplevel 1 {::namespace current}]
+ # Remove MC items
+ dict unset Msgs $ns
+ # Remove config items
+ foreach key [dict keys $PackageConfig] {
+ dict unset PackageConfig $key $ns
+ }
+ return
+}
+
+# msgcat::mcpackageconfig --
+#
+# Get or modify the per caller namespace (e.g. packages) config options.
+#
+# Available subcommands are:
+#
+# get get the current value or an error if not set.
+# isset return true, if the option is set
+# set set the value (see also distinct option).
+# Returns the number of loaded message files.
+# unset Clear option. return "".
+#
+# Available options are:
+#
+# mcfolder
+# The message catalog folder of the package.
+# This is automatically set by mcload.
+# If the value is changed using the set subcommand, an evntual
+# loadcmd is invoked and all message files of the package locale are
+# loaded.
+#
+# loadcmd
+# The command gets executed before a message file would be
+# sourced for this module.
+# The command is invoked with the expanded locale list to load.
+# The command is not invoked if the registering package namespace
+# is not present.
+# This callback might also be used as an alternative to message
+# files.
+# If the value is changed using the set subcommand, the callback is
+# directly invoked with the current file locale list. No file load is
+# executed.
+#
+# changecmd
+# The command is invoked, after an executed locale change.
+# Appended argument is expanded mcpreferences.
+#
+# unknowncmd
+# Use a package locale mcunknown procedure instead the global one.
+# The appended arguments are identical to mcunknown.
+# A default unknown handler is used if set to the empty string.
+# This consists in returning the key if no arguments are given.
+# With given arguments, format is used to process the arguments.
+#
+# Arguments:
+# subcommand Operation on the package
+# option The package option to get or set.
+# ?value? Eventual value for the subcommand
+#
+# Results:
+# Depends on the subcommand and option and is described there
+
+proc msgcat::mcpackageconfig {subcommand option {value ""}} {
+ variable PackageConfig
+ # get namespace
+ set ns [uplevel 1 {::namespace current}]
+
+ if {$option ni {"mcfolder" "loadcmd" "changecmd" "unknowncmd"}} {
+ return -code error "bad option \"$option\": must be mcfolder, loadcmd,\
+ changecmd, or unknowncmd"
+ }
+
+ # check if value argument is exactly provided
+ if {[llength [info level 0]] == 4 } {
+ # value provided
+ if {$subcommand in {"get" "isset" "unset"}} {
+ return -code error "wrong # args: should be\
+ \"[lrange [info level 0] 0 2] value\""
+ }
+ } elseif {$subcommand eq "set"} {
+ return -code error\
+ "wrong # args: should be \"[lrange [info level 0] 0 2]\""
+ }
+
+ # Execute subcommands
+ switch -exact -- $subcommand {
+ get { # Operation get return current value
+ if {![dict exists $PackageConfig $option $ns]} {
+ return -code error "package option \"$option\" not set"
+ }
+ return [dict get $PackageConfig $option $ns]
+ }
+ isset { return [dict exists $PackageConfig $option $ns] }
+ unset { dict unset PackageConfig $option $ns }
+ set { # Set option
+
+ if {$option eq "mcfolder"} {
+ set value [file normalize $value]
+ }
+ # Check if changed
+ if { [dict exists $PackageConfig $option $ns]
+ && $value eq [dict get $PackageConfig $option $ns] } {
+ return 0
+ }
+
+ # set new value
+ dict set PackageConfig $option $ns $value
+
+ # Reload pending message catalogs
+ switch -exact -- $option {
+ mcfolder { return [Load $ns [PackageLocales $ns]] }
+ loadcmd { return [Load $ns [PackageLocales $ns] 1] }
+ }
+ return 0
+ }
+ default {
+ return -code error "unknown subcommand \"$subcommand\":\
+ must be get, isset, set, or unset"
+ }
+ }
+ return
+}
+
+# msgcat::PackagePreferences --
+#
+# Return eventual present package preferences or the default list if not
+# present.
+#
+# Arguments:
+# ns Package namespace
+#
+# Results:
+# locale list
+
+proc msgcat::PackagePreferences {ns} {
+ variable PackageConfig
+ if {[dict exists $PackageConfig loclist $ns]} {
+ return [dict get $PackageConfig loclist $ns]
+ }
+ variable Loclist
+ return $Loclist
+}
+
+# msgcat::PackageLocales --
+#
+# Return eventual present package locales or the default list if not
+# present.
+#
+# Arguments:
+# ns Package namespace
+#
+# Results:
+# locale list
+
+proc msgcat::PackageLocales {ns} {
+ variable PackageConfig
+ if {[dict exists $PackageConfig loadedlocales $ns]} {
+ return [dict get $PackageConfig loadedlocales $ns]
+ }
+ variable LoadedLocales
+ return $LoadedLocales
+}
+
+# msgcat::ListComplement --
+#
+# Build the complement of two lists.
+# Return a list with all elements in list2 but not in list1.
+# Optionally return the intersection.
+#
+# Arguments:
+# list1 excluded list
+# list2 included list
+# inlistname If not "", write in this variable the intersection list
+#
+# Results:
+# list with all elements in list2 but not in list1
+
+proc msgcat::ListComplement {list1 list2 {inlistname ""}} {
+ if {"" ne $inlistname} {
+ upvar 1 $inlistname inlist
+ }
+ set inlist {}
+ set outlist {}
+ foreach item $list2 {
+ if {$item in $list1} {
+ lappend inlist $item
+ } else {
+ lappend outlist $item
+ }
+ }
+ return $outlist
+}
+
# msgcat::mcload --
#
# Attempt to load message catalogs for each locale in the
@@ -266,20 +756,154 @@ proc msgcat::mcpreferences {} {
# Returns the number of message catalogs that were loaded.
proc msgcat::mcload {langdir} {
+ return [uplevel 1 [list\
+ [namespace origin mcpackageconfig] set mcfolder $langdir]]
+}
+
+# msgcat::LoadAll --
+#
+# Load a list of locales for all packages not having a package locale
+# list.
+#
+# Arguments:
+# langdir The directory to search.
+#
+# Results:
+# Returns the number of message catalogs that were loaded.
+
+proc msgcat::LoadAll {locales} {
+ variable PackageConfig
+ variable LoadedLocales
+ if {0 == [llength $locales]} { return {} }
+ # filter jet unloaded locales
+ set locales [ListComplement $LoadedLocales $locales]
+ if {0 == [llength $locales]} { return {} }
+ lappend LoadedLocales {*}$locales
+
+ set packages [lsort -unique [concat\
+ [dict keys [dict get $PackageConfig loadcmd]]\
+ [dict keys [dict get $PackageConfig mcfolder]]]]
+ foreach ns $packages {
+ if {! [dict exists $PackageConfig loclist $ns] } {
+ Load $ns $locales
+ }
+ }
+ return $locales
+}
+
+# msgcat::Load --
+#
+# Invoke message load callback and load message catalog files.
+#
+# Arguments:
+# ns Namespace (equal package) to load the message catalog.
+# locales List of locales to load.
+# callbackonly true if only callback should be invoked
+#
+# Results:
+# Returns the number of message catalogs that were loaded.
+
+proc msgcat::Load {ns locales {callbackonly 0}} {
+ variable FileLocale
+ variable PackageConfig
+ variable LoadedLocals
+
+ if {0 == [llength $locales]} { return 0 }
+
+ # Invoke callback
+ Invoke loadcmd $locales $ns
+
+ if {$callbackonly || ![dict exists $PackageConfig mcfolder $ns]} {
+ return 0
+ }
+
+ # Invoke file load
+ set langdir [dict get $PackageConfig mcfolder $ns]
+
+ # Save the file locale if we are recursively called
+ if {[info exists FileLocale]} {
+ set nestedFileLocale $FileLocale
+ }
set x 0
- foreach p [mcpreferences] {
+ foreach p $locales {
+ if {$p eq {}} {
+ set p ROOT
+ }
set langfile [file join $langdir $p.msg]
if {[file exists $langfile]} {
incr x
- set fid [open $langfile "r"]
- fconfigure $fid -encoding utf-8
- uplevel 1 [read $fid]
- close $fid
+ set FileLocale [string tolower\
+ [file tail [file rootname $langfile]]]
+ if {"root" eq $FileLocale} {
+ set FileLocale ""
+ }
+ namespace inscope $ns [list ::source -encoding utf-8 $langfile]
+ unset FileLocale
}
}
+ if {[info exists nestedFileLocale]} {
+ set FileLocale $nestedFileLocale
+ }
return $x
}
+# msgcat::Invoke --
+#
+# Invoke a set of registered callbacks.
+# The callback is only invoked, if its registered namespace exists.
+#
+# Arguments:
+# index Index into PackageConfig to get callback command
+# arglist parameters to the callback invocation
+# ns (Optional) package to call.
+# If not given or empty, check all registered packages.
+# resultname Variable to save the callback result of the last called
+# callback to. May be set to "" to discard the result.
+# failerror (0) Fail on error if true. Otherwise call bgerror.
+#
+# Results:
+# Possible values:
+# - 0: no valid command registered
+# - 1: registered command was the empty string
+# - 2: registered command called, resultname is set
+# - 3: registered command failed
+# If multiple commands are called, the maximum of all results is returned.
+
+proc msgcat::Invoke {index arglist {ns ""} {resultname ""} {failerror 0}} {
+ variable PackageConfig
+ variable Config
+ if {"" ne $resultname} {
+ upvar 1 $resultname result
+ }
+ if {"" eq $ns} {
+ set packageList [dict keys [dict get $PackageConfig $index]]
+ } else {
+ set packageList [list $ns]
+ }
+ set ret 0
+ foreach ns $packageList {
+ if {[dict exists $PackageConfig $index $ns] && [namespace exists $ns]} {
+ set cmd [dict get $PackageConfig $index $ns]
+ if {"" eq $cmd} {
+ if {$ret == 0} {set ret 1}
+ } else {
+ if {$failerror} {
+ set result [namespace inscope $ns $cmd {*}$arglist]
+ set ret 2
+ } elseif {1 == [catch {
+ set result [namespace inscope $ns $cmd {*}$arglist]
+ if {$ret < 2} {set ret 2}
+ } err derr]} {
+ after idle [concat [::interp bgerror ""]\
+ [list $err $derr]]
+ set ret 3
+ }
+ }
+ }
+ }
+ return $ret
+}
+
# msgcat::mcset --
#
# Set the translation for a given string in a specified locale.
@@ -296,15 +920,40 @@ proc msgcat::mcload {langdir} {
proc msgcat::mcset {locale src {dest ""}} {
variable Msgs
if {[llength [info level 0]] == 3} { ;# dest not specified
- set dest $src
+ set dest $src
}
set ns [uplevel 1 [list ::namespace current]]
- set Msgs([string tolower $locale],$ns,$src) $dest
+ set locale [string tolower $locale]
+
+ dict set Msgs $ns $locale $src $dest
return $dest
}
+# msgcat::mcflset --
+#
+# Set the translation for a given string in the current file locale.
+#
+# Arguments:
+# src The source string.
+# dest (Optional) The translated string. If omitted,
+# the source string is used.
+#
+# Results:
+# Returns the new locale.
+
+proc msgcat::mcflset {src {dest ""}} {
+ variable FileLocale
+ variable Msgs
+
+ if {![info exists FileLocale]} {
+ return -code error "must only be used inside a message catalog loaded\
+ with ::msgcat::mcload"
+ }
+ return [uplevel 1 [list [namespace origin mcset] $FileLocale $src $dest]]
+}
+
# msgcat::mcmset --
#
# Set the translation for multiple strings in a specified locale.
@@ -316,30 +965,53 @@ proc msgcat::mcset {locale src {dest ""}} {
# Results:
# Returns the number of pairs processed
-proc msgcat::mcmset {locale pairs } {
+proc msgcat::mcmset {locale pairs} {
variable Msgs
set length [llength $pairs]
if {$length % 2} {
- error {bad translation list: should be "mcmset locale {src dest ...}"}
+ return -code error "bad translation list:\
+ should be \"[lindex [info level 0] 0] locale {src dest ...}\""
}
-
+
set locale [string tolower $locale]
set ns [uplevel 1 [list ::namespace current]]
-
+
foreach {src dest} $pairs {
- set Msgs($locale,$ns,$src) $dest
+ dict set Msgs $ns $locale $src $dest
}
-
- return $length
+
+ return [expr {$length / 2}]
+}
+
+# msgcat::mcflmset --
+#
+# Set the translation for multiple strings in the mc file locale.
+#
+# Arguments:
+# pairs One or more src/dest pairs (must be even length)
+#
+# Results:
+# Returns the number of pairs processed
+
+proc msgcat::mcflmset {pairs} {
+ variable FileLocale
+ variable Msgs
+
+ if {![info exists FileLocale]} {
+ return -code error "must only be used inside a message catalog loaded\
+ with ::msgcat::mcload"
+ }
+ return [uplevel 1 [list [namespace origin mcmset] $FileLocale $pairs]]
}
# msgcat::mcunknown --
#
# This routine is called by msgcat::mc if a translation cannot
-# be found for a string. This routine is intended to be replaced
+# be found for a string and no unknowncmd is set for the current
+# package. This routine is intended to be replaced
# by an application specific routine for error reporting
-# purposes. The default behavior is to return the source string.
+# purposes. The default behavior is to return the source string.
# If additional args are specified, the format command will be used
# to work them into the traslated string.
#
@@ -351,9 +1023,32 @@ proc msgcat::mcmset {locale pairs } {
# Results:
# Returns the translated value.
-proc msgcat::mcunknown {locale src args} {
+proc msgcat::mcunknown {args} {
+ return [uplevel 1 [list [namespace origin DefaultUnknown] {*}$args]]
+}
+
+# msgcat::DefaultUnknown --
+#
+# This routine is called by msgcat::mc if a translation cannot
+# be found for a string in the following circumstances:
+# - Default global handler, if mcunknown is not redefined.
+# - Per package handler, if the package sets unknowncmd to the empty
+# string.
+# It returna the source string if the argument list is empty.
+# If additional args are specified, the format command will be used
+# to work them into the traslated string.
+#
+# Arguments:
+# locale (unused) The current locale.
+# src The string to be translated.
+# args Args to pass to the format command
+#
+# Results:
+# Returns the translated value.
+
+proc msgcat::DefaultUnknown {locale src args} {
if {[llength $args]} {
- return [uplevel 1 [linsert $args 0 ::format $src]]
+ return [format $src {*}$args]
} else {
return $src
}
@@ -361,7 +1056,7 @@ proc msgcat::mcunknown {locale src args} {
# msgcat::mcmax --
#
-# Calculates the maximun length of the translated strings of the given
+# Calculates the maximum length of the translated strings of the given
# list.
#
# Arguments:
@@ -374,10 +1069,10 @@ proc msgcat::mcmax {args} {
set max 0
foreach string $args {
set translated [uplevel 1 [list [namespace origin mc] $string]]
- set len [string length $translated]
- if {$len>$max} {
- set max $len
- }
+ set len [string length $translated]
+ if {$len>$max} {
+ set max $len
+ }
}
return $max
}
@@ -413,33 +1108,79 @@ proc msgcat::ConvertLocale {value} {
# Initialize the default locale
proc msgcat::Init {} {
+ global env
+
#
# set default locale, try to get from environment
#
foreach varName {LC_ALL LC_MESSAGES LANG} {
- if {[info exists ::env($varName)]
- && ![string equal "" $::env($varName)]} {
- if {![catch {mclocale [ConvertLocale $::env($varName)]}]} {
+ if {[info exists env($varName)] && ("" ne $env($varName))} {
+ if {![catch {
+ mclocale [ConvertLocale $env($varName)]
+ }]} {
return
}
}
}
#
- # The rest of this routine is special processing for Windows;
- # all other platforms, get out now.
+ # On Darwin, fallback to current CFLocale identifier if available.
+ #
+ if {[info exists ::tcl::mac::locale] && $::tcl::mac::locale ne ""} {
+ if {![catch {
+ mclocale [ConvertLocale $::tcl::mac::locale]
+ }]} {
+ return
+ }
+ }
#
- if { ![string equal $::tcl_platform(platform) windows] } {
+ # The rest of this routine is special processing for Windows or
+ # Cygwin. All other platforms, get out now.
+ #
+ if {([info sharedlibextension] ne ".dll")
+ || [catch {package require registry}]} {
mclocale C
return
}
#
- # On Windows, try to set locale depending on registry settings,
- # or fall back on locale of "C".
+ # On Windows or Cygwin, try to set locale depending on registry
+ # settings, or fall back on locale of "C".
+ #
+
+ # On Vista and later:
+ # HCU/Control Panel/Desktop : PreferredUILanguages is for language packs,
+ # HCU/Control Pannel/International : localName is the default locale.
+ #
+ # They contain the local string as RFC5646, composed of:
+ # [a-z]{2,3} : language
+ # -[a-z]{4} : script (optional, translated by table Latn->latin)
+ # -[a-z]{2}|[0-9]{3} : territory (optional, numerical region codes not used)
+ # (-.*)* : variant, extension, private use (optional, not used)
+ # Those are translated to local strings.
+ # Examples: de-CH -> de_ch, sr-Latn-CS -> sr_cs@latin, es-419 -> es
#
- set key {HKEY_CURRENT_USER\Control Panel\International}
- if {[catch {package require registry}] \
- || [catch {registry get $key "locale"} locale]} {
- mclocale C
+ foreach key {{HKEY_CURRENT_USER\Control Panel\Desktop} {HKEY_CURRENT_USER\Control Panel\International}}\
+ value {PreferredUILanguages localeName} {
+ if {![catch {registry get $key $value} localeName]
+ && [regexp {^([a-z]{2,3})(?:-([a-z]{4}))?(?:-([a-z]{2}))?(?:-.+)?$}\
+ [string tolower $localeName] match locale script territory]} {
+ if {"" ne $territory} {
+ append locale _ $territory
+ }
+ set modifierDict [dict create latn latin cyrl cyrillic]
+ if {[dict exists $modifierDict $script]} {
+ append locale @ [dict get $modifierDict $script]
+ }
+ if {![catch {mclocale [ConvertLocale $locale]}]} {
+ return
+ }
+ }
+ }
+
+ # then check value locale which contains a numerical language ID
+ if {[catch {
+ set locale [registry get $key "locale"]
+ }]} {
+ mclocale C
return
}
#
@@ -454,7 +1195,9 @@ proc msgcat::Init {} {
variable WinRegToISO639
set locale [string tolower $locale]
while {[string length $locale]} {
- if {![catch {mclocale [ConvertLocale $WinRegToISO639($locale)]}]} {
+ if {![catch {
+ mclocale [ConvertLocale [dict get $WinRegToISO639 $locale]]
+ }]} {
return
}
set locale [string range $locale 1 end]