[EKB] This is a small command-line (tclsh) utility for taking an existing script and making it "msgcat-ready". It does this by: * Looking for each unique string (delimited by "") and asking whether it should be put in the message catalog * Creating a new copy of the script with any msgcat-strings wrapped in [[mc...]] * Creating a .msg file (which should be put in a "msgs" subfolder) The new files also have some commands for loading the msgcat package, importing some msgcat commands and loading the message files. Here's the script: # Transform a script into a msgcat-ready alternative # Wrap strings in [mc ] and export a properly-formatted # list. set deflang "en_US" switch $argc { 1 { set infile [lindex $argv 0] } 3 { # Allow either "-l lang file" or "file -l lang" for {set i 0} {$i < 3} {incr i} {set cmd$i [lindex $argv $i]} if {$cmd0 == "-l"} { set $deflang $cmd1 set infile $cmd2 } else { # In this case, cmd1 ought to be -l set deflang $cmd2 set infile $cmd0 } } default { set me [file tail $argv0] puts "Usage:" puts "\n\ttclsh $me \[-l language\] file" puts "\n$me helps build a message catalog from an existing script." exit } } set newfile "[file rootname $infile]_$deflang.tcl" set mcatfile "$deflang.msg" set infp [open $infile r] set newfp [open $newfile w] puts $newfp "package require msgcat" puts $newfp "namespace import msgcat::mc" puts $newfp "msgcat::mclocale $deflang" # NOTE: Put the .msg files into a "msgs" folder under the script puts $newfp {msgcat::mcload [file join [file dirname [info script]] msgs]} puts $newfp "" while {[gets $infp currline] != -1} { # Is this a comment? Print it and just keep going if {[regexp -- "^\s*#" $currline] == 1} { puts $newfp $currline continue } set splitline [split $currline "\""] set outline "" # Assume (!!) first part of the line is not a quote. set isstring false foreach fragment $splitline { if {$isstring} { if [info exists ignore($fragment)] { set isstring false set outline "$outline\"$fragment\"" continue } puts -nonewline stdout "\"$fragment\"? " flush stdout if {[regexp -nocase -- "^\s*y" [gets stdin]] == 1} { set outline "$outline\[mc \"$fragment\"\]" # Store strings in an array -- won't duplicate set strings($fragment) 1 } else { set ignore($fragment) 1 set outline "$outline\"$fragment\"" } set isstring false } else { set outline "$outline$fragment" set isstring true } } puts $newfp $outline } close $infp close $newfp set mcfp [open $mcatfile w] puts $mcfp "namespace import -force msgcat::mcset" puts $mcfp "" foreach mcstring [lsort [array names strings]] { puts $mcfp "mcset $deflang \"$mcstring\" \"$mcstring\"" } close $mcfp Here's an example. I took [RS]'s [A little sluice simulation] and passed it through. Choosing only the strings that needed translating, this gave a default (English-language) .msg file (which I saved under a "msgs" folder): namespace import -force msgcat::mcset mcset en_US "Can't open gate - water not level" "Can't open gate - water not level" mcset en_US "Can't open valve when gate still open" "Can't open valve when gate still open" mcset en_US "Welcome to the sluice simulation! (Hint: open the right valve)" "Welcome to the sluice simulation! (Hint: open the right valve)" mcset en_US "sluice simulator" "sluice simulator" and a new script: package require msgcat namespace import msgcat::mc msgcat::mclocale en_US msgcat::mcload [file join [file dirname [info script]] msgs] wm title . [mc "sluice simulator"] pack [label .info -textvar info -anchor w] -side bottom -fill x set info [mc "Welcome to the sluice simulation! (Hint: open the right valve)"] pack [canvas .c -width 600 -height 280 -bg lightblue] .c create polygon 0 300 0 90 450 90 600 120 600 300 -fill green3 .c create polygon 140 300 140 80 460 80 460 300 -fill grey .c create polygon 150 250 150 80 450 80 450 250 -fill grey60 .c create rect 150 80 153 150 -fill brown -tag gate1 set isOpen(gate1) 0 .c bind gate1 <1> {toggleGate .c gate1} .c create rect 450 80 453 250 -fill brown -tag gate2 set isOpen(gate2) 0 .c bind gate2 <1> {toggleGate .c gate2} .c create polygon 0 152 0 100 150 100 150 152 -fill blue1\ -tag {water upriver} -stipple gray50 .c create polygon 452 250 452 200 600 200 600 250 -fill blue1 \ -stipple gray50 -tag {water downriver} .c create polygon 150 100 150 250 452 250 452 100 -fill blue1 \ -tag {water sluicewater sluiced} -stipple gray50 .c create line 90 150 90 160 100 170 150 170 -width 5 -fill blue1 \ -smooth 1 -tag water .c create polygon 140 290 140 250 460 250 460 290 -fill grey -tag water .c create oval 110 160 130 180 -fill white -tag {valve1 water} .c create rect 118 160 122 180 -fill grey -tag {valve1 valve1r water} set isOpen(valve1r) 0 .c bind valve1 <1> {toggleValve .c valve1r} .c create line 420 250 420 260 430 270 480 270 490 265 490 250 \ -width 5 -fill blue1 -smooth 1 -tag fg .c create oval 450 260 470 280 -fill white -tag valve2 .c create rect 458 260 462 280 -fill grey -tag {valve2 valve2r} set isOpen(valve2r) 0 .c bind valve2 <1> {toggleValve .c valve2r} proc boat {w} { $w create poly 10 90 10 77 50 77 50 90 -fill red -tag boat $w create rect 8 78 52 75 -fill grey -tag boat $w create rect 13 86 23 79 -fill white -tag boat $w create rect 28 86 38 79 -fill white -tag boat $w create poly 0 90 0 95 203 95 205 90 -fill white -tag boat $w create poly 0 95 0 125 5 130 200 130 203 95 \ -fill black -tag boat $w create poly 50 90 90 80 130 90 160 80 200 90\ -fill bisque -outline black -tag boat $w move boat 160 0 $w lower boat water set ::moveBoat 0 set ::boatDirection 1 } boat .c proc toggleGate {w tag} { global info isOpen moveBoat boatDirection if { $tag=="gate1" && [maxy $w sluicewater]>[maxy $w upriver] \ ||$tag=="gate2" && [maxy $w sluicewater]<[maxy $w downriver]} { set info [mc "Can't open gate - water not level"] return } foreach {x0 y0 x1 y1} [$w coords $tag] break set x0 [expr {$x0 + ($isOpen($tag)? 50 : -50)}] $w coords $tag $x0 $y0 $x1 $y1 set isOpen($tag) [expr {1-$isOpen($tag)}] set info "$tag [expr {$isOpen($tag)? {opened} : {closed}}]" foreach {bx0 by0 bx1 by1} [$w bbox boat] break if {$bx1<100*$boatDirection || $bx0<460*$boatDirection} { set moveBoat 0 } if {$isOpen($tag)} {set moveBoat [expr $boatDirection*2]} } proc toggleValve {w tag} { global isOpen if {!$isOpen($tag) && ($isOpen(gate1) || $isOpen(gate2))} { set ::info [mc "Can't open valve when gate still open"] return } foreach {x0 y0 x1 y1} [$w coords $tag] break set dx2 [expr {($x1-$x0)/2.}] set mx [expr {($x0+$x1)/2}] set dy2 [expr {($y1-$y0)/2.}] set my [expr {($y0+$y1)/2}] set isOpen($tag) [expr {$dx2<$dy2}] $w itemconfig $tag -fill [expr {$isOpen($tag)? "blue1": "grey"}] $w coords $tag [expr {$mx-$dy2}] [expr {$my-$dx2}] \ [expr {$mx+$dy2}] [expr {$my+$dx2}] set ::info "$tag [expr {$::isOpen($tag)? {opened} : {closed}}]" } proc every {ms body} {eval $body; after $ms [info level 0]} proc maxy {w tag} {lindex [$w bbox $tag] 1} proc animate {w} { global moveBoat isOpen foreach {bx0 by0 bx1 by1} [$w bbox boat] break foreach {sx0 top sx1 sy1} [$w bbox sluicewater] break if {$bx0 > $sx0 && $bx1 < $sx1} { $w addtag sluiced withtag boat if {$bx1>390 && $bx0<460 && $moveBoat>0 && !$isOpen(gate2)} { set moveBoat 0 } if {$bx0<160 && $bx1>90 && $moveBoat<0 && !$isOpen(gate1)} { set moveBoat 0 } } else { $w dtag boat sluiced if {$bx0<470 && $bx0>150 && $moveBoat<0 && !$isOpen(gate2) \ || $bx1>100 && $bx1<450 && $moveBoat>0 && !$isOpen(gate1)} { set moveBoat 0 } } if {$top<[maxy $w downriver] && $isOpen(valve2r)} { $w move sluiced 0 1 set moveBoat 0 } if {$top>[maxy $w upriver] && $isOpen(valve1r)} { $w move sluiced 0 -1 set moveBoat 0 } $w move boat $moveBoat 0 if {$bx0>700} { if {rand()>0.5} { $w scale boat [expr {($bx0+$bx1)/2}] $by0 -1 1 set moveBoat -2; set ::boatDirection -1 } else {$w move boat -1000 -100} } if {$bx0<-300} { if {rand()>0.5} { $w scale boat [expr {($bx0+$bx1)/2}] $by0 -1 1 set moveBoat 2; set ::boatDirection 1 } else {$w move boat 1000 100} } } every 100 {animate .c} wm resizable . 0 0 Next I made a translation in my not-so-good French (I really really don't know if I got the right translation for "sluice" in this case) and saved it as "fr.msg" in a "msgs" folder: namespace import -force msgcat::mcset mcset fr "Can't open gate - water not level" "Impossible d'ouvrir la porte - le niveau de l'eau n'est pas le même" mcset fr "Can't open valve when gate still open" "Impossible d'ouvre la valve lorsque la porte est encore ouverte" mcset fr "Welcome to the sluice simulation! (Hint: open the right valve)" "Bienvenu à la simulation de l'écluse! (Suggestion: ouvrez la valve à droite)" mcset fr "sluice simulator" "simulation de l'écluse" Then in the main script I set "msgcat::locale" to "fr" and ran the program. Here's the result: [http://www.kb-creative.net/screenshots/sluicesim_fr.gif] ---- [MG] Without having actually tried this, I think it's a really nice idea. Personally, I never code for msgcat-translations when I write a script (apart from in [menu]s, for some reason) and have to go through and find it all afterwards to translate, if it's needed. One (very minor) criticism/suggestion, having looked at it briefly, though - you should be able to pass the language on the command line, as well as the file name to fix, IMHO (with it still defaulting to US English), to save having to constantly edit this script. [EKB] Thanks! I agree that it needs to accept the language. I've modified the script above so that it accepts input like (assuming the script is saved as mkmsgcat.tcl): % tclsh mkmsgcat.tcl -l de myfile.tcl or % tclsh mkmsgcat.tcl myfile.tcl -l de