msgcat-1.6.0.tm 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210
  1. # msgcat.tcl --
  2. #
  3. # This file defines various procedures which implement a
  4. # message catalog facility for Tcl programs. It should be
  5. # loaded with the command "package require msgcat".
  6. #
  7. # Copyright (c) 2010-2015 by Harald Oehlmann.
  8. # Copyright (c) 1998-2000 by Ajuba Solutions.
  9. # Copyright (c) 1998 by Mark Harrison.
  10. #
  11. # See the file "license.terms" for information on usage and redistribution
  12. # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  13. package require Tcl 8.5
  14. # When the version number changes, be sure to update the pkgIndex.tcl file,
  15. # and the installation directory in the Makefiles.
  16. package provide msgcat 1.6.0
  17. namespace eval msgcat {
  18. namespace export mc mcexists mcload mclocale mcmax mcmset mcpreferences mcset\
  19. mcunknown mcflset mcflmset mcloadedlocales mcforgetpackage\
  20. mcpackageconfig mcpackagelocale
  21. # Records the list of locales to search
  22. variable Loclist {}
  23. # List of currently loaded locales
  24. variable LoadedLocales {}
  25. # Records the locale of the currently sourced message catalogue file
  26. variable FileLocale
  27. # Configuration values per Package (e.g. client namespace).
  28. # The dict key is of the form "<option> <namespace>" and the value is the
  29. # configuration option. A nonexisting key is an unset option.
  30. variable PackageConfig [dict create mcfolder {} loadcmd {} changecmd {}\
  31. unknowncmd {} loadedlocales {} loclist {}]
  32. # Records the mapping between source strings and translated strings. The
  33. # dict key is of the form "<namespace> <locale> <src>", where locale and
  34. # namespace should be themselves dict values and the value is
  35. # the translated string.
  36. variable Msgs [dict create]
  37. # Map of language codes used in Windows registry to those of ISO-639
  38. if {[info sharedlibextension] eq ".dll"} {
  39. variable WinRegToISO639 [dict create {*}{
  40. 01 ar 0401 ar_SA 0801 ar_IQ 0c01 ar_EG 1001 ar_LY 1401 ar_DZ
  41. 1801 ar_MA 1c01 ar_TN 2001 ar_OM 2401 ar_YE 2801 ar_SY
  42. 2c01 ar_JO 3001 ar_LB 3401 ar_KW 3801 ar_AE 3c01 ar_BH
  43. 4001 ar_QA
  44. 02 bg 0402 bg_BG
  45. 03 ca 0403 ca_ES
  46. 04 zh 0404 zh_TW 0804 zh_CN 0c04 zh_HK 1004 zh_SG 1404 zh_MO
  47. 05 cs 0405 cs_CZ
  48. 06 da 0406 da_DK
  49. 07 de 0407 de_DE 0807 de_CH 0c07 de_AT 1007 de_LU 1407 de_LI
  50. 08 el 0408 el_GR
  51. 09 en 0409 en_US 0809 en_GB 0c09 en_AU 1009 en_CA 1409 en_NZ
  52. 1809 en_IE 1c09 en_ZA 2009 en_JM 2409 en_GD 2809 en_BZ
  53. 2c09 en_TT 3009 en_ZW 3409 en_PH
  54. 0a es 040a es_ES 080a es_MX 0c0a es_ES@modern 100a es_GT 140a es_CR
  55. 180a es_PA 1c0a es_DO 200a es_VE 240a es_CO 280a es_PE
  56. 2c0a es_AR 300a es_EC 340a es_CL 380a es_UY 3c0a es_PY
  57. 400a es_BO 440a es_SV 480a es_HN 4c0a es_NI 500a es_PR
  58. 0b fi 040b fi_FI
  59. 0c fr 040c fr_FR 080c fr_BE 0c0c fr_CA 100c fr_CH 140c fr_LU
  60. 180c fr_MC
  61. 0d he 040d he_IL
  62. 0e hu 040e hu_HU
  63. 0f is 040f is_IS
  64. 10 it 0410 it_IT 0810 it_CH
  65. 11 ja 0411 ja_JP
  66. 12 ko 0412 ko_KR
  67. 13 nl 0413 nl_NL 0813 nl_BE
  68. 14 no 0414 no_NO 0814 nn_NO
  69. 15 pl 0415 pl_PL
  70. 16 pt 0416 pt_BR 0816 pt_PT
  71. 17 rm 0417 rm_CH
  72. 18 ro 0418 ro_RO 0818 ro_MO
  73. 19 ru 0819 ru_MO
  74. 1a hr 041a hr_HR 081a sr_YU 0c1a sr_YU@cyrillic
  75. 1b sk 041b sk_SK
  76. 1c sq 041c sq_AL
  77. 1d sv 041d sv_SE 081d sv_FI
  78. 1e th 041e th_TH
  79. 1f tr 041f tr_TR
  80. 20 ur 0420 ur_PK 0820 ur_IN
  81. 21 id 0421 id_ID
  82. 22 uk 0422 uk_UA
  83. 23 be 0423 be_BY
  84. 24 sl 0424 sl_SI
  85. 25 et 0425 et_EE
  86. 26 lv 0426 lv_LV
  87. 27 lt 0427 lt_LT
  88. 28 tg 0428 tg_TJ
  89. 29 fa 0429 fa_IR
  90. 2a vi 042a vi_VN
  91. 2b hy 042b hy_AM
  92. 2c az 042c az_AZ@latin 082c az_AZ@cyrillic
  93. 2d eu
  94. 2e wen 042e wen_DE
  95. 2f mk 042f mk_MK
  96. 30 bnt 0430 bnt_TZ
  97. 31 ts 0431 ts_ZA
  98. 32 tn
  99. 33 ven 0433 ven_ZA
  100. 34 xh 0434 xh_ZA
  101. 35 zu 0435 zu_ZA
  102. 36 af 0436 af_ZA
  103. 37 ka 0437 ka_GE
  104. 38 fo 0438 fo_FO
  105. 39 hi 0439 hi_IN
  106. 3a mt 043a mt_MT
  107. 3b se 043b se_NO
  108. 043c gd_UK 083c ga_IE
  109. 3d yi 043d yi_IL
  110. 3e ms 043e ms_MY 083e ms_BN
  111. 3f kk 043f kk_KZ
  112. 40 ky 0440 ky_KG
  113. 41 sw 0441 sw_KE
  114. 42 tk 0442 tk_TM
  115. 43 uz 0443 uz_UZ@latin 0843 uz_UZ@cyrillic
  116. 44 tt 0444 tt_RU
  117. 45 bn 0445 bn_IN
  118. 46 pa 0446 pa_IN
  119. 47 gu 0447 gu_IN
  120. 48 or 0448 or_IN
  121. 49 ta
  122. 4a te 044a te_IN
  123. 4b kn 044b kn_IN
  124. 4c ml 044c ml_IN
  125. 4d as 044d as_IN
  126. 4e mr 044e mr_IN
  127. 4f sa 044f sa_IN
  128. 50 mn
  129. 51 bo 0451 bo_CN
  130. 52 cy 0452 cy_GB
  131. 53 km 0453 km_KH
  132. 54 lo 0454 lo_LA
  133. 55 my 0455 my_MM
  134. 56 gl 0456 gl_ES
  135. 57 kok 0457 kok_IN
  136. 58 mni 0458 mni_IN
  137. 59 sd
  138. 5a syr 045a syr_TR
  139. 5b si 045b si_LK
  140. 5c chr 045c chr_US
  141. 5d iu 045d iu_CA
  142. 5e am 045e am_ET
  143. 5f ber 045f ber_MA
  144. 60 ks 0460 ks_PK 0860 ks_IN
  145. 61 ne 0461 ne_NP 0861 ne_IN
  146. 62 fy 0462 fy_NL
  147. 63 ps
  148. 64 tl 0464 tl_PH
  149. 65 div 0465 div_MV
  150. 66 bin 0466 bin_NG
  151. 67 ful 0467 ful_NG
  152. 68 ha 0468 ha_NG
  153. 69 nic 0469 nic_NG
  154. 6a yo 046a yo_NG
  155. 70 ibo 0470 ibo_NG
  156. 71 kau 0471 kau_NG
  157. 72 om 0472 om_ET
  158. 73 ti 0473 ti_ET
  159. 74 gn 0474 gn_PY
  160. 75 cpe 0475 cpe_US
  161. 76 la 0476 la_VA
  162. 77 so 0477 so_SO
  163. 78 sit 0478 sit_CN
  164. 79 pap 0479 pap_AN
  165. }]
  166. }
  167. }
  168. # msgcat::mc --
  169. #
  170. # Find the translation for the given string based on the current
  171. # locale setting. Check the local namespace first, then look in each
  172. # parent namespace until the source is found. If additional args are
  173. # specified, use the format command to work them into the traslated
  174. # string.
  175. # If no catalog item is found, mcunknown is called in the caller frame
  176. # and its result is returned.
  177. #
  178. # Arguments:
  179. # src The string to translate.
  180. # args Args to pass to the format command
  181. #
  182. # Results:
  183. # Returns the translated string. Propagates errors thrown by the
  184. # format command.
  185. proc msgcat::mc {src args} {
  186. # this may be replaced by:
  187. # return [mcget -namespace [uplevel 1 [list ::namespace current]] --\
  188. # $src {*}$args]
  189. # Check for the src in each namespace starting from the local and
  190. # ending in the global.
  191. variable Msgs
  192. variable Loclist
  193. set ns [uplevel 1 [list ::namespace current]]
  194. set loclist [PackagePreferences $ns]
  195. set nscur $ns
  196. while {$nscur != ""} {
  197. foreach loc $loclist {
  198. if {[dict exists $Msgs $nscur $loc $src]} {
  199. return [DefaultUnknown "" [dict get $Msgs $nscur $loc $src]\
  200. {*}$args]
  201. }
  202. }
  203. set nscur [namespace parent $nscur]
  204. }
  205. # call package local or default unknown command
  206. set args [linsert $args 0 [lindex $loclist 0] $src]
  207. switch -exact -- [Invoke unknowncmd $args $ns result 1] {
  208. 0 { return [uplevel 1 [linsert $args 0 [namespace origin mcunknown]]] }
  209. 1 { return [DefaultUnknown {*}$args] }
  210. default { return $result }
  211. }
  212. }
  213. # msgcat::mcexists --
  214. #
  215. # Check if a catalog item is set or if mc would invoke mcunknown.
  216. #
  217. # Arguments:
  218. # -exactnamespace Only check the exact namespace and no
  219. # parent namespaces
  220. # -exactlocale Only check the exact locale and not all members
  221. # of the preferences list
  222. # src Message catalog key
  223. #
  224. # Results:
  225. # true if an adequate catalog key was found
  226. proc msgcat::mcexists {args} {
  227. variable Msgs
  228. variable Loclist
  229. variable PackageConfig
  230. set ns [uplevel 1 [list ::namespace current]]
  231. set loclist [PackagePreferences $ns]
  232. while {[llength $args] != 1} {
  233. set args [lassign $args option]
  234. switch -glob -- $option {
  235. -exactnamespace { set exactnamespace 1 }
  236. -exactlocale { set loclist [lrange $loclist 0 0] }
  237. -* { return -code error "unknown option \"$option\"" }
  238. default {
  239. return -code error "wrong # args: should be\
  240. \"[lindex [info level 0] 0] ?-exactnamespace?\
  241. ?-exactlocale? src\""
  242. }
  243. }
  244. }
  245. set src [lindex $args 0]
  246. while {$ns ne ""} {
  247. foreach loc $loclist {
  248. if {[dict exists $Msgs $ns $loc $src]} {
  249. return 1
  250. }
  251. }
  252. if {[info exists exactnamespace]} {return 0}
  253. set ns [namespace parent $ns]
  254. }
  255. return 0
  256. }
  257. # msgcat::mclocale --
  258. #
  259. # Query or set the current locale.
  260. #
  261. # Arguments:
  262. # newLocale (Optional) The new locale string. Locale strings
  263. # should be composed of one or more sublocale parts
  264. # separated by underscores (e.g. en_US).
  265. #
  266. # Results:
  267. # Returns the normalized set locale.
  268. proc msgcat::mclocale {args} {
  269. variable Loclist
  270. variable LoadedLocales
  271. set len [llength $args]
  272. if {$len > 1} {
  273. return -code error "wrong # args: should be\
  274. \"[lindex [info level 0] 0] ?newLocale?\""
  275. }
  276. if {$len == 1} {
  277. set newLocale [string tolower [lindex $args 0]]
  278. if {$newLocale ne [file tail $newLocale]} {
  279. return -code error "invalid newLocale value \"$newLocale\":\
  280. could be path to unsafe code."
  281. }
  282. if {[lindex $Loclist 0] ne $newLocale} {
  283. set Loclist [GetPreferences $newLocale]
  284. # locale not loaded jet
  285. LoadAll $Loclist
  286. # Invoke callback
  287. Invoke changecmd $Loclist
  288. }
  289. }
  290. return [lindex $Loclist 0]
  291. }
  292. # msgcat::GetPreferences --
  293. #
  294. # Get list of locales from a locale.
  295. # The first element is always the lowercase locale.
  296. # Other elements have one component separated by "_" less.
  297. # Multiple "_" are seen as one separator: de__ch_spec de__ch de {}
  298. #
  299. # Arguments:
  300. # Locale.
  301. #
  302. # Results:
  303. # Locale list
  304. proc msgcat::GetPreferences {locale} {
  305. set locale [string tolower $locale]
  306. set loclist [list $locale]
  307. while {-1 !=[set pos [string last "_" $locale]]} {
  308. set locale [string range $locale 0 $pos-1]
  309. if { "_" ne [string index $locale end] } {
  310. lappend loclist $locale
  311. }
  312. }
  313. if {"" ne [lindex $loclist end]} {
  314. lappend loclist {}
  315. }
  316. return $loclist
  317. }
  318. # msgcat::mcpreferences --
  319. #
  320. # Fetch the list of locales used to look up strings, ordered from
  321. # most preferred to least preferred.
  322. #
  323. # Arguments:
  324. # None.
  325. #
  326. # Results:
  327. # Returns an ordered list of the locales preferred by the user.
  328. proc msgcat::mcpreferences {} {
  329. variable Loclist
  330. return $Loclist
  331. }
  332. # msgcat::mcloadedlocales --
  333. #
  334. # Get or change the list of currently loaded default locales
  335. #
  336. # The following subcommands are available:
  337. # loaded
  338. # Get the current list of loaded locales
  339. # clear
  340. # Remove all loaded locales not present in mcpreferences.
  341. #
  342. # Arguments:
  343. # subcommand One of loaded or clear
  344. #
  345. # Results:
  346. # Empty string, if not stated differently for the subcommand
  347. proc msgcat::mcloadedlocales {subcommand} {
  348. variable Loclist
  349. variable LoadedLocales
  350. variable Msgs
  351. variable PackageConfig
  352. switch -exact -- $subcommand {
  353. clear {
  354. # Remove all locales not contained in Loclist
  355. # skip any packages with package locale
  356. set LoadedLocales $Loclist
  357. foreach ns [dict keys $Msgs] {
  358. if {![dict exists $PackageConfig loclist $ns]} {
  359. foreach locale [dict keys [dict get $Msgs $ns]] {
  360. if {$locale ni $Loclist} {
  361. dict unset Msgs $ns $locale
  362. }
  363. }
  364. }
  365. }
  366. }
  367. loaded { return $LoadedLocales }
  368. default {
  369. return -code error "unknown subcommand \"$subcommand\": must be\
  370. clear, or loaded"
  371. }
  372. }
  373. return
  374. }
  375. # msgcat::mcpackagelocale --
  376. #
  377. # Get or change the package locale of the calling package.
  378. #
  379. # The following subcommands are available:
  380. # set
  381. # Set a package locale.
  382. # This may load message catalog files and may clear message catalog
  383. # items, if the former locale was the default locale.
  384. # Returns the normalized set locale.
  385. # The default locale is taken, if locale is not given.
  386. # get
  387. # Get the locale valid for this package.
  388. # isset
  389. # Returns true, if a package locale is set
  390. # unset
  391. # Unset the package locale and activate the default locale.
  392. # This loads message catalog file which where missing in the package
  393. # locale.
  394. # preferences
  395. # Return locale preference list valid for the package.
  396. # loaded
  397. # Return loaded locale list valid for the current package.
  398. # clear
  399. # If the current package has a package locale, remove all package
  400. # locales not containes in package mcpreferences.
  401. # It is an error to call this without a package locale set.
  402. #
  403. # The subcommands get, preferences and loaded return the corresponding
  404. # default data, if no package locale is set.
  405. #
  406. # Arguments:
  407. # subcommand see list above
  408. # locale package locale (only set subcommand)
  409. #
  410. # Results:
  411. # Empty string, if not stated differently for the subcommand
  412. proc msgcat::mcpackagelocale {subcommand {locale ""}} {
  413. # todo: implement using an ensemble
  414. variable Loclist
  415. variable LoadedLocales
  416. variable Msgs
  417. variable PackageConfig
  418. # Check option
  419. # check if required item is exactly provided
  420. if {[llength [info level 0]] == 2} {
  421. # locale not given
  422. unset locale
  423. } else {
  424. # locale given
  425. if {$subcommand in
  426. {"get" "isset" "unset" "preferences" "loaded" "clear"} } {
  427. return -code error "wrong # args: should be\
  428. \"[lrange [info level 0] 0 1]\""
  429. }
  430. set locale [string tolower $locale]
  431. }
  432. set ns [uplevel 1 {::namespace current}]
  433. switch -exact -- $subcommand {
  434. get { return [lindex [PackagePreferences $ns] 0] }
  435. preferences { return [PackagePreferences $ns] }
  436. loaded { return [PackageLocales $ns] }
  437. present { return [expr {$locale in [PackageLocales $ns]} ]}
  438. isset { return [dict exists $PackageConfig loclist $ns] }
  439. set { # set a package locale or add a package locale
  440. # Copy the default locale if no package locale set so far
  441. if {![dict exists $PackageConfig loclist $ns]} {
  442. dict set PackageConfig loclist $ns $Loclist
  443. dict set PackageConfig loadedlocales $ns $LoadedLocales
  444. }
  445. # Check if changed
  446. set loclist [dict get $PackageConfig loclist $ns]
  447. if {! [info exists locale] || $locale eq [lindex $loclist 0] } {
  448. return [lindex $loclist 0]
  449. }
  450. # Change loclist
  451. set loclist [GetPreferences $locale]
  452. set locale [lindex $loclist 0]
  453. dict set PackageConfig loclist $ns $loclist
  454. # load eventual missing locales
  455. set loadedLocales [dict get $PackageConfig loadedlocales $ns]
  456. if {$locale in $loadedLocales} { return $locale }
  457. set loadLocales [ListComplement $loadedLocales $loclist]
  458. dict set PackageConfig loadedlocales $ns\
  459. [concat $loadedLocales $loadLocales]
  460. Load $ns $loadLocales
  461. return $locale
  462. }
  463. clear { # Remove all locales not contained in Loclist
  464. if {![dict exists $PackageConfig loclist $ns]} {
  465. return -code error "clear only when package locale set"
  466. }
  467. set loclist [dict get $PackageConfig loclist $ns]
  468. dict set PackageConfig loadedlocales $ns $loclist
  469. if {[dict exists $Msgs $ns]} {
  470. foreach locale [dict keys [dict get $Msgs $ns]] {
  471. if {$locale ni $loclist} {
  472. dict unset Msgs $ns $locale
  473. }
  474. }
  475. }
  476. }
  477. unset { # unset package locale and restore default locales
  478. if { ![dict exists $PackageConfig loclist $ns] } { return }
  479. # unset package locale
  480. set loadLocales [ListComplement\
  481. [dict get $PackageConfig loadedlocales $ns] $LoadedLocales]
  482. dict unset PackageConfig loadedlocales $ns
  483. dict unset PackageConfig loclist $ns
  484. # unset keys not in global loaded locales
  485. if {[dict exists $Msgs $ns]} {
  486. foreach locale [dict keys [dict get $Msgs $ns]] {
  487. if {$locale ni $LoadedLocales} {
  488. dict unset Msgs $ns $locale
  489. }
  490. }
  491. }
  492. # Add missing locales
  493. Load $ns $loadLocales
  494. }
  495. default {
  496. return -code error "unknown subcommand \"$subcommand\": must be\
  497. clear, get, isset, loaded, present, set, or unset"
  498. }
  499. }
  500. return
  501. }
  502. # msgcat::mcforgetpackage --
  503. #
  504. # Remove any data of the calling package from msgcat
  505. #
  506. proc msgcat::mcforgetpackage {} {
  507. # todo: this may be implemented using an ensemble
  508. variable PackageConfig
  509. variable Msgs
  510. set ns [uplevel 1 {::namespace current}]
  511. # Remove MC items
  512. dict unset Msgs $ns
  513. # Remove config items
  514. foreach key [dict keys $PackageConfig] {
  515. dict unset PackageConfig $key $ns
  516. }
  517. return
  518. }
  519. # msgcat::mcpackageconfig --
  520. #
  521. # Get or modify the per caller namespace (e.g. packages) config options.
  522. #
  523. # Available subcommands are:
  524. #
  525. # get get the current value or an error if not set.
  526. # isset return true, if the option is set
  527. # set set the value (see also distinct option).
  528. # Returns the number of loaded message files.
  529. # unset Clear option. return "".
  530. #
  531. # Available options are:
  532. #
  533. # mcfolder
  534. # The message catalog folder of the package.
  535. # This is automatically set by mcload.
  536. # If the value is changed using the set subcommand, an evntual
  537. # loadcmd is invoked and all message files of the package locale are
  538. # loaded.
  539. #
  540. # loadcmd
  541. # The command gets executed before a message file would be
  542. # sourced for this module.
  543. # The command is invoked with the expanded locale list to load.
  544. # The command is not invoked if the registering package namespace
  545. # is not present.
  546. # This callback might also be used as an alternative to message
  547. # files.
  548. # If the value is changed using the set subcommand, the callback is
  549. # directly invoked with the current file locale list. No file load is
  550. # executed.
  551. #
  552. # changecmd
  553. # The command is invoked, after an executed locale change.
  554. # Appended argument is expanded mcpreferences.
  555. #
  556. # unknowncmd
  557. # Use a package locale mcunknown procedure instead the global one.
  558. # The appended arguments are identical to mcunknown.
  559. # A default unknown handler is used if set to the empty string.
  560. # This consists in returning the key if no arguments are given.
  561. # With given arguments, format is used to process the arguments.
  562. #
  563. # Arguments:
  564. # subcommand Operation on the package
  565. # option The package option to get or set.
  566. # ?value? Eventual value for the subcommand
  567. #
  568. # Results:
  569. # Depends on the subcommand and option and is described there
  570. proc msgcat::mcpackageconfig {subcommand option {value ""}} {
  571. variable PackageConfig
  572. # get namespace
  573. set ns [uplevel 1 {::namespace current}]
  574. if {$option ni {"mcfolder" "loadcmd" "changecmd" "unknowncmd"}} {
  575. return -code error "bad option \"$option\": must be mcfolder, loadcmd,\
  576. changecmd, or unknowncmd"
  577. }
  578. # check if value argument is exactly provided
  579. if {[llength [info level 0]] == 4 } {
  580. # value provided
  581. if {$subcommand in {"get" "isset" "unset"}} {
  582. return -code error "wrong # args: should be\
  583. \"[lrange [info level 0] 0 2] value\""
  584. }
  585. } elseif {$subcommand eq "set"} {
  586. return -code error\
  587. "wrong # args: should be \"[lrange [info level 0] 0 2]\""
  588. }
  589. # Execute subcommands
  590. switch -exact -- $subcommand {
  591. get { # Operation get return current value
  592. if {![dict exists $PackageConfig $option $ns]} {
  593. return -code error "package option \"$option\" not set"
  594. }
  595. return [dict get $PackageConfig $option $ns]
  596. }
  597. isset { return [dict exists $PackageConfig $option $ns] }
  598. unset { dict unset PackageConfig $option $ns }
  599. set { # Set option
  600. if {$option eq "mcfolder"} {
  601. set value [file normalize $value]
  602. }
  603. # Check if changed
  604. if { [dict exists $PackageConfig $option $ns]
  605. && $value eq [dict get $PackageConfig $option $ns] } {
  606. return 0
  607. }
  608. # set new value
  609. dict set PackageConfig $option $ns $value
  610. # Reload pending message catalogs
  611. switch -exact -- $option {
  612. mcfolder { return [Load $ns [PackageLocales $ns]] }
  613. loadcmd { return [Load $ns [PackageLocales $ns] 1] }
  614. }
  615. return 0
  616. }
  617. default {
  618. return -code error "unknown subcommand \"$subcommand\":\
  619. must be get, isset, set, or unset"
  620. }
  621. }
  622. return
  623. }
  624. # msgcat::PackagePreferences --
  625. #
  626. # Return eventual present package preferences or the default list if not
  627. # present.
  628. #
  629. # Arguments:
  630. # ns Package namespace
  631. #
  632. # Results:
  633. # locale list
  634. proc msgcat::PackagePreferences {ns} {
  635. variable PackageConfig
  636. if {[dict exists $PackageConfig loclist $ns]} {
  637. return [dict get $PackageConfig loclist $ns]
  638. }
  639. variable Loclist
  640. return $Loclist
  641. }
  642. # msgcat::PackageLocales --
  643. #
  644. # Return eventual present package locales or the default list if not
  645. # present.
  646. #
  647. # Arguments:
  648. # ns Package namespace
  649. #
  650. # Results:
  651. # locale list
  652. proc msgcat::PackageLocales {ns} {
  653. variable PackageConfig
  654. if {[dict exists $PackageConfig loadedlocales $ns]} {
  655. return [dict get $PackageConfig loadedlocales $ns]
  656. }
  657. variable LoadedLocales
  658. return $LoadedLocales
  659. }
  660. # msgcat::ListComplement --
  661. #
  662. # Build the complement of two lists.
  663. # Return a list with all elements in list2 but not in list1.
  664. # Optionally return the intersection.
  665. #
  666. # Arguments:
  667. # list1 excluded list
  668. # list2 included list
  669. # inlistname If not "", write in this variable the intersection list
  670. #
  671. # Results:
  672. # list with all elements in list2 but not in list1
  673. proc msgcat::ListComplement {list1 list2 {inlistname ""}} {
  674. if {"" ne $inlistname} {
  675. upvar 1 $inlistname inlist
  676. }
  677. set inlist {}
  678. set outlist {}
  679. foreach item $list2 {
  680. if {$item in $list1} {
  681. lappend inlist $item
  682. } else {
  683. lappend outlist $item
  684. }
  685. }
  686. return $outlist
  687. }
  688. # msgcat::mcload --
  689. #
  690. # Attempt to load message catalogs for each locale in the
  691. # preference list from the specified directory.
  692. #
  693. # Arguments:
  694. # langdir The directory to search.
  695. #
  696. # Results:
  697. # Returns the number of message catalogs that were loaded.
  698. proc msgcat::mcload {langdir} {
  699. return [uplevel 1 [list\
  700. [namespace origin mcpackageconfig] set mcfolder $langdir]]
  701. }
  702. # msgcat::LoadAll --
  703. #
  704. # Load a list of locales for all packages not having a package locale
  705. # list.
  706. #
  707. # Arguments:
  708. # langdir The directory to search.
  709. #
  710. # Results:
  711. # Returns the number of message catalogs that were loaded.
  712. proc msgcat::LoadAll {locales} {
  713. variable PackageConfig
  714. variable LoadedLocales
  715. if {0 == [llength $locales]} { return {} }
  716. # filter jet unloaded locales
  717. set locales [ListComplement $LoadedLocales $locales]
  718. if {0 == [llength $locales]} { return {} }
  719. lappend LoadedLocales {*}$locales
  720. set packages [lsort -unique [concat\
  721. [dict keys [dict get $PackageConfig loadcmd]]\
  722. [dict keys [dict get $PackageConfig mcfolder]]]]
  723. foreach ns $packages {
  724. if {! [dict exists $PackageConfig loclist $ns] } {
  725. Load $ns $locales
  726. }
  727. }
  728. return $locales
  729. }
  730. # msgcat::Load --
  731. #
  732. # Invoke message load callback and load message catalog files.
  733. #
  734. # Arguments:
  735. # ns Namespace (equal package) to load the message catalog.
  736. # locales List of locales to load.
  737. # callbackonly true if only callback should be invoked
  738. #
  739. # Results:
  740. # Returns the number of message catalogs that were loaded.
  741. proc msgcat::Load {ns locales {callbackonly 0}} {
  742. variable FileLocale
  743. variable PackageConfig
  744. variable LoadedLocals
  745. if {0 == [llength $locales]} { return 0 }
  746. # Invoke callback
  747. Invoke loadcmd $locales $ns
  748. if {$callbackonly || ![dict exists $PackageConfig mcfolder $ns]} {
  749. return 0
  750. }
  751. # Invoke file load
  752. set langdir [dict get $PackageConfig mcfolder $ns]
  753. # Save the file locale if we are recursively called
  754. if {[info exists FileLocale]} {
  755. set nestedFileLocale $FileLocale
  756. }
  757. set x 0
  758. foreach p $locales {
  759. if {$p eq {}} {
  760. set p ROOT
  761. }
  762. set langfile [file join $langdir $p.msg]
  763. if {[file exists $langfile]} {
  764. incr x
  765. set FileLocale [string tolower\
  766. [file tail [file rootname $langfile]]]
  767. if {"root" eq $FileLocale} {
  768. set FileLocale ""
  769. }
  770. namespace inscope $ns [list ::source -encoding utf-8 $langfile]
  771. unset FileLocale
  772. }
  773. }
  774. if {[info exists nestedFileLocale]} {
  775. set FileLocale $nestedFileLocale
  776. }
  777. return $x
  778. }
  779. # msgcat::Invoke --
  780. #
  781. # Invoke a set of registered callbacks.
  782. # The callback is only invoked, if its registered namespace exists.
  783. #
  784. # Arguments:
  785. # index Index into PackageConfig to get callback command
  786. # arglist parameters to the callback invocation
  787. # ns (Optional) package to call.
  788. # If not given or empty, check all registered packages.
  789. # resultname Variable to save the callback result of the last called
  790. # callback to. May be set to "" to discard the result.
  791. # failerror (0) Fail on error if true. Otherwise call bgerror.
  792. #
  793. # Results:
  794. # Possible values:
  795. # - 0: no valid command registered
  796. # - 1: registered command was the empty string
  797. # - 2: registered command called, resultname is set
  798. # - 3: registered command failed
  799. # If multiple commands are called, the maximum of all results is returned.
  800. proc msgcat::Invoke {index arglist {ns ""} {resultname ""} {failerror 0}} {
  801. variable PackageConfig
  802. variable Config
  803. if {"" ne $resultname} {
  804. upvar 1 $resultname result
  805. }
  806. if {"" eq $ns} {
  807. set packageList [dict keys [dict get $PackageConfig $index]]
  808. } else {
  809. set packageList [list $ns]
  810. }
  811. set ret 0
  812. foreach ns $packageList {
  813. if {[dict exists $PackageConfig $index $ns] && [namespace exists $ns]} {
  814. set cmd [dict get $PackageConfig $index $ns]
  815. if {"" eq $cmd} {
  816. if {$ret == 0} {set ret 1}
  817. } else {
  818. if {$failerror} {
  819. set result [namespace inscope $ns $cmd {*}$arglist]
  820. set ret 2
  821. } elseif {1 == [catch {
  822. set result [namespace inscope $ns $cmd {*}$arglist]
  823. if {$ret < 2} {set ret 2}
  824. } err derr]} {
  825. after idle [concat [::interp bgerror ""]\
  826. [list $err $derr]]
  827. set ret 3
  828. }
  829. }
  830. }
  831. }
  832. return $ret
  833. }
  834. # msgcat::mcset --
  835. #
  836. # Set the translation for a given string in a specified locale.
  837. #
  838. # Arguments:
  839. # locale The locale to use.
  840. # src The source string.
  841. # dest (Optional) The translated string. If omitted,
  842. # the source string is used.
  843. #
  844. # Results:
  845. # Returns the new locale.
  846. proc msgcat::mcset {locale src {dest ""}} {
  847. variable Msgs
  848. if {[llength [info level 0]] == 3} { ;# dest not specified
  849. set dest $src
  850. }
  851. set ns [uplevel 1 [list ::namespace current]]
  852. set locale [string tolower $locale]
  853. dict set Msgs $ns $locale $src $dest
  854. return $dest
  855. }
  856. # msgcat::mcflset --
  857. #
  858. # Set the translation for a given string in the current file locale.
  859. #
  860. # Arguments:
  861. # src The source string.
  862. # dest (Optional) The translated string. If omitted,
  863. # the source string is used.
  864. #
  865. # Results:
  866. # Returns the new locale.
  867. proc msgcat::mcflset {src {dest ""}} {
  868. variable FileLocale
  869. variable Msgs
  870. if {![info exists FileLocale]} {
  871. return -code error "must only be used inside a message catalog loaded\
  872. with ::msgcat::mcload"
  873. }
  874. return [uplevel 1 [list [namespace origin mcset] $FileLocale $src $dest]]
  875. }
  876. # msgcat::mcmset --
  877. #
  878. # Set the translation for multiple strings in a specified locale.
  879. #
  880. # Arguments:
  881. # locale The locale to use.
  882. # pairs One or more src/dest pairs (must be even length)
  883. #
  884. # Results:
  885. # Returns the number of pairs processed
  886. proc msgcat::mcmset {locale pairs} {
  887. variable Msgs
  888. set length [llength $pairs]
  889. if {$length % 2} {
  890. return -code error "bad translation list:\
  891. should be \"[lindex [info level 0] 0] locale {src dest ...}\""
  892. }
  893. set locale [string tolower $locale]
  894. set ns [uplevel 1 [list ::namespace current]]
  895. foreach {src dest} $pairs {
  896. dict set Msgs $ns $locale $src $dest
  897. }
  898. return [expr {$length / 2}]
  899. }
  900. # msgcat::mcflmset --
  901. #
  902. # Set the translation for multiple strings in the mc file locale.
  903. #
  904. # Arguments:
  905. # pairs One or more src/dest pairs (must be even length)
  906. #
  907. # Results:
  908. # Returns the number of pairs processed
  909. proc msgcat::mcflmset {pairs} {
  910. variable FileLocale
  911. variable Msgs
  912. if {![info exists FileLocale]} {
  913. return -code error "must only be used inside a message catalog loaded\
  914. with ::msgcat::mcload"
  915. }
  916. return [uplevel 1 [list [namespace origin mcmset] $FileLocale $pairs]]
  917. }
  918. # msgcat::mcunknown --
  919. #
  920. # This routine is called by msgcat::mc if a translation cannot
  921. # be found for a string and no unknowncmd is set for the current
  922. # package. This routine is intended to be replaced
  923. # by an application specific routine for error reporting
  924. # purposes. The default behavior is to return the source string.
  925. # If additional args are specified, the format command will be used
  926. # to work them into the traslated string.
  927. #
  928. # Arguments:
  929. # locale The current locale.
  930. # src The string to be translated.
  931. # args Args to pass to the format command
  932. #
  933. # Results:
  934. # Returns the translated value.
  935. proc msgcat::mcunknown {args} {
  936. return [uplevel 1 [list [namespace origin DefaultUnknown] {*}$args]]
  937. }
  938. # msgcat::DefaultUnknown --
  939. #
  940. # This routine is called by msgcat::mc if a translation cannot
  941. # be found for a string in the following circumstances:
  942. # - Default global handler, if mcunknown is not redefined.
  943. # - Per package handler, if the package sets unknowncmd to the empty
  944. # string.
  945. # It returna the source string if the argument list is empty.
  946. # If additional args are specified, the format command will be used
  947. # to work them into the traslated string.
  948. #
  949. # Arguments:
  950. # locale (unused) The current locale.
  951. # src The string to be translated.
  952. # args Args to pass to the format command
  953. #
  954. # Results:
  955. # Returns the translated value.
  956. proc msgcat::DefaultUnknown {locale src args} {
  957. if {[llength $args]} {
  958. return [format $src {*}$args]
  959. } else {
  960. return $src
  961. }
  962. }
  963. # msgcat::mcmax --
  964. #
  965. # Calculates the maximum length of the translated strings of the given
  966. # list.
  967. #
  968. # Arguments:
  969. # args strings to translate.
  970. #
  971. # Results:
  972. # Returns the length of the longest translated string.
  973. proc msgcat::mcmax {args} {
  974. set max 0
  975. foreach string $args {
  976. set translated [uplevel 1 [list [namespace origin mc] $string]]
  977. set len [string length $translated]
  978. if {$len>$max} {
  979. set max $len
  980. }
  981. }
  982. return $max
  983. }
  984. # Convert the locale values stored in environment variables to a form
  985. # suitable for passing to [mclocale]
  986. proc msgcat::ConvertLocale {value} {
  987. # Assume $value is of form: $language[_$territory][.$codeset][@modifier]
  988. # Convert to form: $language[_$territory][_$modifier]
  989. #
  990. # Comment out expanded RE version -- bugs alleged
  991. # regexp -expanded {
  992. # ^ # Match all the way to the beginning
  993. # ([^_.@]*) # Match "lanugage"; ends with _, ., or @
  994. # (_([^.@]*))? # Match (optional) "territory"; starts with _
  995. # ([.]([^@]*))? # Match (optional) "codeset"; starts with .
  996. # (@(.*))? # Match (optional) "modifier"; starts with @
  997. # $ # Match all the way to the end
  998. # } $value -> language _ territory _ codeset _ modifier
  999. if {![regexp {^([^_.@]+)(_([^.@]*))?([.]([^@]*))?(@(.*))?$} $value \
  1000. -> language _ territory _ codeset _ modifier]} {
  1001. return -code error "invalid locale '$value': empty language part"
  1002. }
  1003. set ret $language
  1004. if {[string length $territory]} {
  1005. append ret _$territory
  1006. }
  1007. if {[string length $modifier]} {
  1008. append ret _$modifier
  1009. }
  1010. return $ret
  1011. }
  1012. # Initialize the default locale
  1013. proc msgcat::Init {} {
  1014. global env
  1015. #
  1016. # set default locale, try to get from environment
  1017. #
  1018. foreach varName {LC_ALL LC_MESSAGES LANG} {
  1019. if {[info exists env($varName)] && ("" ne $env($varName))} {
  1020. if {![catch {
  1021. mclocale [ConvertLocale $env($varName)]
  1022. }]} {
  1023. return
  1024. }
  1025. }
  1026. }
  1027. #
  1028. # On Darwin, fallback to current CFLocale identifier if available.
  1029. #
  1030. if {[info exists ::tcl::mac::locale] && $::tcl::mac::locale ne ""} {
  1031. if {![catch {
  1032. mclocale [ConvertLocale $::tcl::mac::locale]
  1033. }]} {
  1034. return
  1035. }
  1036. }
  1037. #
  1038. # The rest of this routine is special processing for Windows or
  1039. # Cygwin. All other platforms, get out now.
  1040. #
  1041. if {([info sharedlibextension] ne ".dll")
  1042. || [catch {package require registry}]} {
  1043. mclocale C
  1044. return
  1045. }
  1046. #
  1047. # On Windows or Cygwin, try to set locale depending on registry
  1048. # settings, or fall back on locale of "C".
  1049. #
  1050. # On Vista and later:
  1051. # HCU/Control Panel/Desktop : PreferredUILanguages is for language packs,
  1052. # HCU/Control Pannel/International : localName is the default locale.
  1053. #
  1054. # They contain the local string as RFC5646, composed of:
  1055. # [a-z]{2,3} : language
  1056. # -[a-z]{4} : script (optional, translated by table Latn->latin)
  1057. # -[a-z]{2}|[0-9]{3} : territory (optional, numerical region codes not used)
  1058. # (-.*)* : variant, extension, private use (optional, not used)
  1059. # Those are translated to local strings.
  1060. # Examples: de-CH -> de_ch, sr-Latn-CS -> sr_cs@latin, es-419 -> es
  1061. #
  1062. foreach key {{HKEY_CURRENT_USER\Control Panel\Desktop} {HKEY_CURRENT_USER\Control Panel\International}}\
  1063. value {PreferredUILanguages localeName} {
  1064. if {![catch {registry get $key $value} localeName]
  1065. && [regexp {^([a-z]{2,3})(?:-([a-z]{4}))?(?:-([a-z]{2}))?(?:-.+)?$}\
  1066. [string tolower $localeName] match locale script territory]} {
  1067. if {"" ne $territory} {
  1068. append locale _ $territory
  1069. }
  1070. set modifierDict [dict create latn latin cyrl cyrillic]
  1071. if {[dict exists $modifierDict $script]} {
  1072. append locale @ [dict get $modifierDict $script]
  1073. }
  1074. if {![catch {mclocale [ConvertLocale $locale]}]} {
  1075. return
  1076. }
  1077. }
  1078. }
  1079. # then check value locale which contains a numerical language ID
  1080. if {[catch {
  1081. set locale [registry get $key "locale"]
  1082. }]} {
  1083. mclocale C
  1084. return
  1085. }
  1086. #
  1087. # Keep trying to match against smaller and smaller suffixes
  1088. # of the registry value, since the latter hexadigits appear
  1089. # to determine general language and earlier hexadigits determine
  1090. # more precise information, such as territory. For example,
  1091. # 0409 - English - United States
  1092. # 0809 - English - United Kingdom
  1093. # Add more translations to the WinRegToISO639 array above.
  1094. #
  1095. variable WinRegToISO639
  1096. set locale [string tolower $locale]
  1097. while {[string length $locale]} {
  1098. if {![catch {
  1099. mclocale [ConvertLocale [dict get $WinRegToISO639 $locale]]
  1100. }]} {
  1101. return
  1102. }
  1103. set locale [string range $locale 1 end]
  1104. }
  1105. #
  1106. # No translation known. Fall back on "C" locale
  1107. #
  1108. mclocale C
  1109. }
  1110. msgcat::Init