1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210 |
- # 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 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.
- 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.0
- 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
|