Parce qu'en tant qu'utilisateur, j'apprécie:
Pour ce dernier point, imaginez une interface en chinois dans laquelle vous cherchez désespérément comment basculer en français. C'est tellement plus simple de chercher le mot "Français" que d'essayer de deviner à quoi ressemble ce même mot en chinois!
Parce qu'en tant que programmeur:
Lorsque j'ai découvert Tcl/Tk, j'ai parcouru beaucoup de page web, fait des recherches et lu des forums sur ce sujet. On en revient presque toujours à l'utilisation de msgcat et à utiliser à chaque fois que c'est possible une option -textvariable plutôt que -text. Malheureusement, tous les widgets ne disposent pas de l'option -textvariable (exemples les entrées d'un menu, NoteBook des BWidgets, ...). Cela implique d'avoir des façons de faire différentes en fonction du widget utilisé.
Pour remédier à cela, voici une façon de faire. Je dis bien "une" façon de faire et non "la" façon de faire. A chacun de trouver celle qui lui convient le mieux. Celle-ci peut certainement être améliorée.
Deux types de textes sont à considérer:
Pour tous les widgets créés au lancement de l'application, on procède par étapes:
#!/usr/bin/tclsh #------------------------------------------------------------------------------- # #------------------------------------------------------------------------------- # To be able to use/make starkit/starpack package provide app-chglng 0.1 set APPNAME "ChgLng" set VERSION [ package require app-chglng ] set APPSTRING "$APPNAME $VERSION" set AUTHOR "Myself" # Adjust auto_path to contain script home and current directory set script_home [string trimright [file dirname [info script]] ./] set script_home [file join [pwd] $script_home] lappend auto_path $script_home # Specify required packages set TCLVERSION [ package require Tcl ] set TKVERSION [ package require Tk ] set MCVERSION [ package require msgcat ] namespace import ::msgcat::* package require lngcode namespace import ::lngcode::* namespace eval ::lng:: { variable Obj variable Idx variable Hints variable StatusText set StatusText " " } #******************************************************************************* # Construction de l'interface * #******************************************************************************* #------------------------------------------------------------------------------- # Construit le menu de l'application #------------------------------------------------------------------------------- proc ::lng::BuildMenu {} { set ::lng::Obj(mnu) [ menu .mb ] set index 0 # Fichier set ::lng::Obj(mnuFile) [ menu $::lng::Obj(mnu).file -tearoff 0 ] $::lng::Obj(mnu) add cascade -menu $::lng::Obj(mnuFile) set ::lng::Idx(mnuFile) [ incr index ] # Edition set ::lng::Obj(mnuEdit) [ menu $::lng::Obj(mnu).edit -tearoff 0 ] $::lng::Obj(mnu) add cascade -menu $::lng::Obj(mnuEdit) set ::lng::Idx(mnuEdit) [ incr index ] # Option set ::lng::Obj(mnuTools) [ menu $::lng::Obj(mnu).option -tearoff 0 ] $::lng::Obj(mnu) add cascade -menu $::lng::Obj(mnuTools) set ::lng::Idx(mnuTools) [ incr index ] # Aide set ::lng::Obj(mnuHelp) [ menu $::lng::Obj(mnu).help -tearoff 0 ] $::lng::Obj(mnu) add cascade -menu $::lng::Obj(mnuHelp) set ::lng::Idx(mnuHelp) [ incr index ] # Menu Fichier set index -1 $::lng::Obj(mnuFile) add command -command ::lng::Dummy set ::lng::Idx(mnuFileNew) [ incr index ] $::lng::Obj(mnuFile) add command -command ::lng::Dummy set ::lng::Idx(mnuFileOpen) [ incr index ] $::lng::Obj(mnuFile) add command -command ::lng::Dummy set ::lng::Idx(mnuFileSave) [ incr index ] $::lng::Obj(mnuFile) add command -command ::lng::Dummy set ::lng::Idx(mnuFileSaveAs) [ incr index ] $::lng::Obj(mnuFile) add separator incr index $::lng::Obj(mnuFile) add command -command ::lng::FileQuit set ::lng::Idx(mnuFileQuit) [ incr index ] # Menu Edition set index -1 $::lng::Obj(mnuEdit) add command -command ::lng::Dummy set ::lng::Idx(mnuEditUndo) [ incr index ] $::lng::Obj(mnuEdit) add command -command ::lng::Dummy set ::lng::Idx(mnuEditRedo) [ incr index ] $::lng::Obj(mnuEdit) add separator incr index $::lng::Obj(mnuEdit) add command -command ::lng::Dummy set ::lng::Idx(mnuEditCut) [ incr index ] $::lng::Obj(mnuEdit) add command -command ::lng::Dummy set ::lng::Idx(mnuEditCopy) [ incr index ] $::lng::Obj(mnuEdit) add command -command ::lng::Dummy set ::lng::Idx(mnuEditPaste) [ incr index ] # Menu Option set index -1 set ::lng::Obj(mnuLanguage) [ menu $::lng::Obj(mnuTools).language -tearoff 0 ] $::lng::Obj(mnuTools) add cascade -menu $::lng::Obj(mnuLanguage) set ::lng::Idx(mnuToolsLanguage) [ incr index ] $::lng::Obj(mnuTools) add command -command ::lng::Dummy set ::lng::Idx(mnuToolsOptions) [ incr index ] # Langue set LngCodeList [ ::lngcode::getCodeList "$::script_home/msg" ] foreach code $LngCodeList { $::lng::Obj(mnuLanguage) add command \ -label [ getLngName $code 0 ] \ -command [ list ::lng::SwitchLanguage $code ] } # Menu Aide set index -1 $::lng::Obj(mnuHelp) add command -command ::lng::About set ::lng::Idx(mnuHelpAbout) [ incr index ] $::lng::Obj(mnuHelp) add command -command ::lng::Versions set ::lng::Idx(mnuHelpVersions) [ incr index ] . config -menu $::lng::Obj(mnu) } #------------------------------------------------------------------------------- # Construit la barre d'atat #------------------------------------------------------------------------------- proc ::lng::BuildStatusBar {} { frame .statusBar label .statusBar.label -textvariable ::lng::StatusText -relief sunken -bd 1 -anchor w pack .statusBar.label -side left -padx 2 -expand yes -fill both pack .statusBar -side bottom -fill x -pady 2 } #------------------------------------------------------------------------------- # Construit le contenu de la fenetre #------------------------------------------------------------------------------- proc ::lng::BuildContents {} { # Panneau haut frame .ft # Labels set ::lng::Obj(frmLabel) [ labelframe .ft.f1 ] set ::lng::Obj(lblLabel) [ label .ft.f1.lab -fg red -bg white ] pack $::lng::Obj(lblLabel) -padx 4 set ::lng::Obj(lblMsg) [ label .ft.f1.msg -wraplength 400 -justify left ] pack $::lng::Obj(lblMsg) -padx 4 pack .ft.f1 -side left -fill both -expand yes -padx 4 -pady 4 -ipadx 4 -ipady 4 # Boite liste set select "" set ::lng::Obj(frmList) [ labelframe .ft.f2 ] set ::lng::Obj(lbList) [ listbox .ft.f2.listb ] set ::lng::Obj(lblList) [ label .ft.f2.lab -textvariable select ] pack $::lng::Obj(lbList) -fill y -expand yes pack $::lng::Obj(lblList) bind $::lng::Obj(lbList)⇑{ set select [ selection get ] } pack $::lng::Obj(frmList) -side right -fill y -expand yes -padx 4 -pady 4 -ipadx 4 -ipady 4 pack .ft # Panneau ajustable frame .f2 panedwindow .f2.pane -orient horizontal pack .f2.pane -side top -expand yes -fill both # Boutons radio set ::lng::Obj(frmRadio) [ labelframe .f2.pane.f1 ] set ::lng::Obj(rb1) [ radiobutton .f2.pane.f1.r1 -variable myvar1 -value "1" ] set ::lng::Obj(rb2) [ radiobutton .f2.pane.f1.r2 -variable myvar1 -value "2" ] set ::lng::Obj(rb3) [ radiobutton .f2.pane.f1.r3 -variable myvar1 -value "3" ] $::lng::Obj(rb2) select grid $::lng::Obj(rb1) grid $::lng::Obj(rb2) grid $::lng::Obj(rb3) # Cases a cocher set ::lng::Obj(frmCheck) [ labelframe .f2.pane.f2 ] set ::lng::Obj(chk1) [ checkbutton .f2.pane.f2.chk1 -variable myvar2 ] set ::lng::Obj(chk2) [ checkbutton .f2.pane.f2.chk2 -variable myvar3 ] set ::lng::Obj(chk3) [ checkbutton .f2.pane.f2.chk3 -variable myvar4 ] $::lng::Obj(chk1) select $::lng::Obj(chk3) select grid $::lng::Obj(chk1) grid $::lng::Obj(chk2) grid $::lng::Obj(chk3) .f2.pane add .f2.pane.f1 .f2.pane.f2 pack .f2 -side top -fill both -padx 4 -pady 4 -ipadx 4 -ipady 4 # Boutons set ::lng::Obj(frmButton) [ labelframe .f3 ] set ::lng::Obj(btn1) [ button .f3.b1 -command ::lng::Dummy ] set ::lng::Obj(btn2) [ button .f3.b2 -command ::lng::Dummy ] set ::lng::Obj(btn3) [ button .f3.b3 -command ::lng::Dummy ] grid $::lng::Obj(btn1) $::lng::Obj(btn2) $::lng::Obj(btn3) grid columnconfigure .f3 0 -weight 1 grid columnconfigure .f3 1 -weight 1 grid columnconfigure .f3 2 -weight 1 pack .f3 -side top -fill x -padx 4 -pady 4 -ipadx 4 -ipady 4 } #------------------------------------------------------------------------------- # Construit l'interface graphique #------------------------------------------------------------------------------- proc ::lng::BuildGUI {} { ::lng::BuildMenu ::lng::BuildContents ::lng::BuildStatusBar } #******************************************************************************* # Gestion des langues * #******************************************************************************* #------------------------------------------------------------------------------- # Charge un nouveau fichier de langue #------------------------------------------------------------------------------- proc ::lng::LoadLanguage {code} { if {$code != ""} { mclocale $code } # charge le fichier de langue stocke dans le repertoire msg mcload "$::script_home/msg" } #------------------------------------------------------------------------------- # Change de langue #------------------------------------------------------------------------------- proc ::lng::SwitchLanguage {code} { ::lng::LoadLanguage $code ::lng::UpdateAll } #******************************************************************************* # Adaptation des textes * #******************************************************************************* #------------------------------------------------------------------------------- # Modifie le texte d'un menu #------------------------------------------------------------------------------- proc ::lng::SetMnuTxt {menu index name hint} { set underline [ string first & $name ] if { $underline < 0 } { set label $name } else { if { $underline == 0 } { set label [ string range $name 1 end ] } else { set label [ string range $name 0 [ expr { $underline - 1 } ] ] append label [ string range $name [ expr { $underline + 1 } ] end ] } } $menu entryconfigure $index -label $label if { $underline >= 0 } { $menu entryconfigure $index -underline $underline } # Hint set ::lng::Hints($menu-$index) $hint } #------------------------------------------------------------------------------- # Met a jour la fenetre princupale #------------------------------------------------------------------------------- proc ::lng::UpdateMainWindow {} { wm title . [ mc "msg Change language" ] } #------------------------------------------------------------------------------- # Met a jour le menu #------------------------------------------------------------------------------- proc ::lng::UpdateMenu {} { # Menu Fichier ::lng::SetMnuTxt $::lng::Obj(mnu) $::lng::Idx(mnuFile) \ [ mc "msg &File" ] [ mc "hint File" ] ::lng::SetMnuTxt $::lng::Obj(mnuFile) $::lng::Idx(mnuFileNew) \ [ mc "msg &New" ] [ mc "hint New" ] ::lng::SetMnuTxt $::lng::Obj(mnuFile) $::lng::Idx(mnuFileOpen) \ [ mc "msg &Open ..." ] [ mc "hint Open" ] ::lng::SetMnuTxt $::lng::Obj(mnuFile) $::lng::Idx(mnuFileSave) \ [ mc "msg &Save" ] [ mc "hint Save" ] ::lng::SetMnuTxt $::lng::Obj(mnuFile) $::lng::Idx(mnuFileSaveAs) \ [ mc "msg Save &as ..." ] [ mc "hint SaveAs" ] ::lng::SetMnuTxt $::lng::Obj(mnuFile) $::lng::Idx(mnuFileQuit) \ [ mc "msg &Quit" ] [ mc "hint File" ] # Menu Edition ::lng::SetMnuTxt $::lng::Obj(mnu) $::lng::Idx(mnuEdit) \ [ mc "msg &Edit" ] [ mc "hint Edit" ] ::lng::SetMnuTxt $::lng::Obj(mnuEdit) $::lng::Idx(mnuEditUndo) \ [ mc "msg &Undo" ] [ mc "hint Undo" ] ::lng::SetMnuTxt $::lng::Obj(mnuEdit) $::lng::Idx(mnuEditRedo) \ [ mc "msg &Redo" ] [ mc "hint Redo" ] ::lng::SetMnuTxt $::lng::Obj(mnuEdit) $::lng::Idx(mnuEditCut) \ [ mc "msg &Cut" ] [ mc "hint Cut" ] ::lng::SetMnuTxt $::lng::Obj(mnuEdit) $::lng::Idx(mnuEditCopy) \ [ mc "msg C&opy" ] [ mc "hint Copy" ] ::lng::SetMnuTxt $::lng::Obj(mnuEdit) $::lng::Idx(mnuEditPaste) \ [ mc "msg &Paste" ] [ mc "hint Paste" ] # Menu Option ::lng::SetMnuTxt $::lng::Obj(mnu) $::lng::Idx(mnuTools) \ [ mc "msg &Tools" ] [ mc "hint Tools" ] ::lng::SetMnuTxt $::lng::Obj(mnuTools) $::lng::Idx(mnuToolsLanguage) \ [ mc "msg &Language" ] [ mc "hint Language" ] ::lng::SetMnuTxt $::lng::Obj(mnuTools) $::lng::Idx(mnuToolsOptions) \ [ mc "msg &Options" ] [ mc "hint Options" ] # Menu Aide ::lng::SetMnuTxt $::lng::Obj(mnu) $::lng::Idx(mnuHelp) \ [ mc "msg &Help" ] [ mc "hint Help" ] ::lng::SetMnuTxt $::lng::Obj(mnuHelp) $::lng::Idx(mnuHelpAbout) \ [ mc "msg &About ..." ] [ mc "hint About" ] ::lng::SetMnuTxt $::lng::Obj(mnuHelp) $::lng::Idx(mnuHelpVersions) \ [ mc "msg &Versions ..." ] [ mc "hint Versions" ] } #------------------------------------------------------------------------------- # Met a jour le contenu #------------------------------------------------------------------------------- proc ::lng::UpdateContents {} { $::lng::Obj(frmLabel) configure -text [ mc "msg Labels" ] $::lng::Obj(lblLabel) configure -text [ mc "msg Long label title" ] $::lng::Obj(lblMsg) configure -text [ mc "msg Long label contents" ] $::lng::Obj(frmList) configure -text [ mc "msg List box" ] $::lng::Obj(lbList) delete 0 end foreach item [ mc "msg List box contents" ] { $::lng::Obj(lbList) insert end $item } $::lng::Obj(frmRadio) configure -text [ mc "msg Radio buttons" ] $::lng::Obj(rb1) configure -text [ mc "msg Radio button 1" ] $::lng::Obj(rb2) configure -text [ mc "msg Radio button 2" ] $::lng::Obj(rb3) configure -text [ mc "msg Radio button 3" ] $::lng::Obj(frmCheck) configure -text [ mc "msg Check boxes" ] $::lng::Obj(chk1) configure -text [ mc "msg Check box 1" ] $::lng::Obj(chk2) configure -text [ mc "msg Check box 2" ] $::lng::Obj(chk3) configure -text [ mc "msg Check box 3" ] $::lng::Obj(frmButton) configure -text [ mc "msg Buttons" ] $::lng::Obj(btn1) configure -text [ mc "msg Button 1" ] $::lng::Obj(btn2) configure -text [ mc "msg Button 2" ] $::lng::Obj(btn3) configure -text [ mc "msg Button 3" ] } #------------------------------------------------------------------------------- # Met a jour toute l'application #------------------------------------------------------------------------------- proc ::lng::UpdateAll {} { ::lng::UpdateMainWindow ::lng::UpdateMenu ::lng::UpdateContents } #******************************************************************************* # Boites d'information * #******************************************************************************* #------------------------------------------------------------------------------- # Boite A propos ... #------------------------------------------------------------------------------- proc ::lng::About {} { global APPNAME global VERSION global AUTHOR set aboutstring [ mc "msg Program: \t%s" "$APPNAME" ] append aboutstring "\n" append aboutstring [ mc "msg Version: \t%s" "$VERSION" ] append aboutstring "\n\n" append aboutstring "(c) $AUTHOR 2006" append aboutstring "\n\n" append aboutstring "This software is distributed under the terms of NOL (No Obligation License)" append aboutstring "\n" append aboutstring "http://wfr.tcl.tk/nol\n\n" if { [ winfo exists .aboutWin ] } { focus .aboutWin.close return } toplevel .aboutWin -padx 5 -pady 5 -relief raised -borderwidth 3 wm overrideredirect .aboutWin 1 frame .aboutWin.upper frame .aboutWin.lower label .aboutWin.upper.label -text [ mc "msg About %s" "$APPNAME" ] -relief groove text .aboutWin.lower.about -relief groove -width 40 -height 10 \ -padx 5 -pady 5 -wrap word -background [ .aboutWin.lower cget -background ] .aboutWin.lower.about insert 1.0 $aboutstring .aboutWin.lower.about configure -state disabled button .aboutWin.close -text [ mc "msg Close" ] -command { destroy .aboutWin } bind .aboutWin {destroy .aboutWin} grid .aboutWin.upper -in .aboutWin -row 1 -column 1 -sticky news -padx 5 -pady 5 grid .aboutWin.lower -in .aboutWin -row 2 -column 1 -sticky news -padx 5 -pady 5 grid .aboutWin.close -in .aboutWin -row 3 -column 1 -padx 5 -pady 5 pack .aboutWin.upper.label -in .aboutWin.upper -expand 1 -fill both pack .aboutWin.lower.about -in .aboutWin.lower -expand 1 -fill both set deltax [ expr { ( [ winfo width . ] - [ winfo reqwidth .aboutWin ] ) / 2 } ] set deltay [ expr { ( [ winfo height . ] - [ winfo reqheight .aboutWin ] ) / 2 } ] set xpos [ expr { [ winfo x . ] + $deltax - 22 } ] set ypos [ expr { [ winfo y . ] + $deltay } ] wm geometry .aboutWin "+$xpos+$ypos" grab .aboutWin focus .aboutWin.close } #------------------------------------------------------------------------------- # Boite Versions ... #------------------------------------------------------------------------------- proc ::lng::Versions {} { global APPSTRING global TCLVERSION global TKVERSION global MCVERSION set versionstring "TCL:\t$TCLVERSION" append versionstring "\n" append versionstring "TK:\t$TKVERSION" append versionstring "\n" append versionstring "MSGCAT:\t$MCVERSION" append versionstring "\n" if { [ winfo exists .versionWin ] } { focus .versionWin.close return } toplevel .versionWin -padx 5 -pady 5 -relief raised -borderwidth 3 wm overrideredirect .versionWin 1 frame .versionWin.upper frame .versionWin.lower label .versionWin.upper.label -text "$APPSTRING" -relief groove text .versionWin.lower.version -relief groove -width 30 -height 5 \ -padx 5 -pady 5 -wrap word -background [ .versionWin.lower cget -background ] .versionWin.lower.version insert 1.0 $versionstring .versionWin.lower.version configure -state disabled button .versionWin.close -text [ mc "msg Close" ] -command { destroy .versionWin } bind .versionWin {destroy .versionWin} grid .versionWin.upper -in .versionWin -row 1 -column 1 -sticky news -padx 5 -pady 5 grid .versionWin.lower -in .versionWin -row 2 -column 1 -sticky news -padx 5 -pady 5 grid .versionWin.close -in .versionWin -row 3 -column 1 -padx 5 -pady 5 pack .versionWin.upper.label -in .versionWin.upper -expand 1 -fill both pack .versionWin.lower.version -in .versionWin.lower -expand 1 -fill both set deltax [ expr { ( [ winfo width . ] - [ winfo reqwidth .versionWin ] ) / 2 } ] set deltay [ expr { ( [ winfo height . ] - [ winfo reqheight .versionWin ] ) / 2 } ] set xpos [ expr { [ winfo x . ] + $deltax - 22 } ] set ypos [ expr { [ winfo y . ] + $deltay } ] wm geometry .versionWin "+$xpos+$ypos" grab .versionWin focus .versionWin.close } #******************************************************************************* # Gestion des evenements * #******************************************************************************* #------------------------------------------------------------------------------- # Aucune action #------------------------------------------------------------------------------- proc ::lng::Dummy {} { tk_messageBox \ -icon info \ -type ok \ -message "Aucune action, c'est une démo!" \ -title "Avertissement" } #------------------------------------------------------------------------------- # Quitte l'application #------------------------------------------------------------------------------- proc ::lng::FileQuit {} { exit } #------------------------------------------------------------------------------- # Ajoute les binding necessaires pour les hint des menus #------------------------------------------------------------------------------- proc ::lng::BindHint {} { bind Menu < > { if { [ catch { %W index active } idx ] } { set idx "none" } if { $idx != "none" } { set pos [ expr { [ string last "." %W ] + 1 } ] set id [ string range %W $pos end ]-$idx set id [ string map {"#" "."} $id ] if { [ catch { set label $::lng::Hints($id) } label ] } { set label " " } set ::lng::StatusText $label update idletasks } } } #------------------------------------------------------------------------------- # Process principal #------------------------------------------------------------------------------- proc ::lng::Main {} { wm withdraw . ::lng::BuildGUI ::lng::LoadLanguage {} ::lng::UpdateAll ::lng::BindHint update idletasks wm deiconify . wm protocol . WM_DELETE_WINDOW exit raise . focus -force . wm minsize . [ winfo width . ] [ expr { [ winfo height . ] + 10 } ] } ::lng::Main
Cette documentation ainsi que l'exemple sont mis à disposition sous licence No Obligation License, soit “Aucune obligation pour vous. Aucune obligation pour moi.”
⇑Le fichier source de l'exemple chglng.zip
⇑Date | Version | Auteur | Description des modifications |
---|---|---|---|
22 octobre 2006 | 1.0.0 | Alain JAFFRE | Première version publique. |