Spaces:
Running
Running
| # msgcat.tcl -- | |
| # | |
| # This file defines various procedures which implement a | |
| # message catalog facility for Tcl programs. It should be | |
| # loaded with the command "package require msgcat". | |
| # | |
| # Copyright (c) 2010-2015 Harald Oehlmann. | |
| # Copyright (c) 1998-2000 Ajuba Solutions. | |
| # Copyright (c) 1998 Mark Harrison. | |
| # | |
| # See the file "license.terms" for information on usage and redistribution | |
| # of this file, and for a DISCLAIMER OF ALL WARRANTIES. | |
| 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.6.1 | |
| namespace eval msgcat { | |
| 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 | |
| # 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. | |
| variable Msgs [dict create] | |
| # Map of language codes used in Windows registry to those of ISO-639 | |
| 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 | |
| }] | |
| } | |
| } | |
| # msgcat::mc -- | |
| # | |
| # Find the translation for the given string based on the current | |
| # locale setting. Check the local namespace first, then look in each | |
| # 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 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 | |
| set ns [uplevel 1 [list ::namespace current]] | |
| 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] | |
| } | |
| return 0 | |
| } | |
| # msgcat::mclocale -- | |
| # | |
| # Query or set the current locale. | |
| # | |
| # Arguments: | |
| # newLocale (Optional) The new locale string. Locale strings | |
| # should be composed of one or more sublocale parts | |
| # separated by underscores (e.g. en_US). | |
| # | |
| # Results: | |
| # Returns the normalized set locale. | |
| proc msgcat::mclocale {args} { | |
| variable Loclist | |
| variable LoadedLocales | |
| set len [llength $args] | |
| if {$len > 1} { | |
| return -code error "wrong # args: should be\ | |
| \"[lindex [info level 0] 0] ?newLocale?\"" | |
| } | |
| if {$len == 1} { | |
| 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 | |
| } | |
| } | |
| if {"" ne [lindex $loclist end]} { | |
| lappend loclist {} | |
| } | |
| return $loclist | |
| } | |
| # msgcat::mcpreferences -- | |
| # | |
| # Fetch the list of locales used to look up strings, ordered from | |
| # most preferred to least preferred. | |
| # | |
| # Arguments: | |
| # None. | |
| # | |
| # Results: | |
| # Returns an ordered list of the locales preferred by the user. | |
| proc msgcat::mcpreferences {} { | |
| variable Loclist | |
| 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 | |
| # preference list from the specified directory. | |
| # | |
| # Arguments: | |
| # langdir The directory to search. | |
| # | |
| # Results: | |
| # 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 $locales { | |
| if {$p eq {}} { | |
| set p ROOT | |
| } | |
| set langfile [file join $langdir $p.msg] | |
| if {[file exists $langfile]} { | |
| incr x | |
| 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. | |
| # | |
| # Arguments: | |
| # locale The locale to use. | |
| # src The source string. | |
| # dest (Optional) The translated string. If omitted, | |
| # the source string is used. | |
| # | |
| # Results: | |
| # Returns the new locale. | |
| proc msgcat::mcset {locale src {dest ""}} { | |
| variable Msgs | |
| if {[llength [info level 0]] == 3} { ;# dest not specified | |
| set dest $src | |
| } | |
| set ns [uplevel 1 [list ::namespace current]] | |
| 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. | |
| # | |
| # Arguments: | |
| # locale The locale to use. | |
| # pairs One or more src/dest pairs (must be even length) | |
| # | |
| # Results: | |
| # Returns the number of pairs processed | |
| proc msgcat::mcmset {locale pairs} { | |
| variable Msgs | |
| set length [llength $pairs] | |
| if {$length % 2} { | |
| 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 { | |
| dict set Msgs $ns $locale $src $dest | |
| } | |
| 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 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. | |
| # If additional args are specified, the format command will be used | |
| # to work them into the traslated string. | |
| # | |
| # Arguments: | |
| # locale The current locale. | |
| # src The string to be translated. | |
| # args Args to pass to the format command | |
| # | |
| # Results: | |
| # Returns the translated value. | |
| 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 [format $src {*}$args] | |
| } else { | |
| return $src | |
| } | |
| } | |
| # msgcat::mcmax -- | |
| # | |
| # Calculates the maximum length of the translated strings of the given | |
| # list. | |
| # | |
| # Arguments: | |
| # args strings to translate. | |
| # | |
| # Results: | |
| # Returns the length of the longest translated string. | |
| 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 | |
| } | |
| } | |
| return $max | |
| } | |
| # Convert the locale values stored in environment variables to a form | |
| # suitable for passing to [mclocale] | |
| proc msgcat::ConvertLocale {value} { | |
| # Assume $value is of form: $language[_$territory][.$codeset][@modifier] | |
| # Convert to form: $language[_$territory][_$modifier] | |
| # | |
| # Comment out expanded RE version -- bugs alleged | |
| # regexp -expanded { | |
| # ^ # Match all the way to the beginning | |
| # ([^_.@]*) # Match "lanugage"; ends with _, ., or @ | |
| # (_([^.@]*))? # Match (optional) "territory"; starts with _ | |
| # ([.]([^@]*))? # Match (optional) "codeset"; starts with . | |
| # (@(.*))? # Match (optional) "modifier"; starts with @ | |
| # $ # Match all the way to the end | |
| # } $value -> language _ territory _ codeset _ modifier | |
| if {![regexp {^([^_.@]+)(_([^.@]*))?([.]([^@]*))?(@(.*))?$} $value \ | |
| -> language _ territory _ codeset _ modifier]} { | |
| return -code error "invalid locale '$value': empty language part" | |
| } | |
| set ret $language | |
| if {[string length $territory]} { | |
| append ret _$territory | |
| } | |
| if {[string length $modifier]} { | |
| append ret _$modifier | |
| } | |
| return $ret | |
| } | |
| # 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)] && ("" ne $env($varName))} { | |
| if {![catch { | |
| mclocale [ConvertLocale $env($varName)] | |
| }]} { | |
| return | |
| } | |
| } | |
| } | |
| # | |
| # 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 | |
| } | |
| } | |
| # | |
| # 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 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 | |
| # | |
| 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 | |
| } | |
| # | |
| # Keep trying to match against smaller and smaller suffixes | |
| # of the registry value, since the latter hexadigits appear | |
| # to determine general language and earlier hexadigits determine | |
| # more precise information, such as territory. For example, | |
| # 0409 - English - United States | |
| # 0809 - English - United Kingdom | |
| # Add more translations to the WinRegToISO639 array above. | |
| # | |
| variable WinRegToISO639 | |
| set locale [string tolower $locale] | |
| while {[string length $locale]} { | |
| if {![catch { | |
| mclocale [ConvertLocale [dict get $WinRegToISO639 $locale]] | |
| }]} { | |
| return | |
| } | |
| set locale [string range $locale 1 end] | |
| } | |
| # | |
| # No translation known. Fall back on "C" locale | |
| # | |
| mclocale C | |
| } | |
| msgcat::Init | |