From bed66e0f901ae9f8e21675035f174292120fea4f Mon Sep 17 00:00:00 2001 From: ywang Date: Sat, 27 Mar 2021 14:13:53 +0100 Subject: Rework translation system to use PO files The French translations are provided by Tanavit. Unfortunately I was not able to keep this addition as a separate commit as the translation file was originally added as a .tr file that I then converted to .po file in the meantime. Also note that this commit is created from squashing 20+ commits from the l10n branch that preceded the transition to PO files. In addition to changes to the locale files (which were all included in the single commit for transitioning to PO files), these commits also included code that has now become obsolete for l10n work. In particular, it included a GUI program written in Tcl to edit .tr files; this program can now be found in the following repo: https://codeberg.org/y5nw/mt_tr_editor Co-authored-by: Tanavit --- advtrains/locale/gui | 638 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 638 insertions(+) create mode 100755 advtrains/locale/gui (limited to 'advtrains/locale/gui') diff --git a/advtrains/locale/gui b/advtrains/locale/gui new file mode 100755 index 0000000..7f455e4 --- /dev/null +++ b/advtrains/locale/gui @@ -0,0 +1,638 @@ +#!/usr/bin/tclsh +package require Tk + +# Auxiliary functions +proc maybe_lindex {lst idx fallback} { + set val [lindex $lst $idx] + if {$val eq {}} { + return $fallback + } else { + return $val + } +} + +proc firstupper {str} { + return [string cat [string toupper [string index $str 0]] [string range $str 1 end]] +} + +# CLI arguments +# NOTE: This will likely be changed in the future +set translationDomain [maybe_lindex $argv 0 "advtrains"] +set translationDir [maybe_lindex $argv 1 "."] + +# Translation file I/O, etc + +set translationTemplatePath [file join $translationDir "template.txt"] + +proc translationFilePaths {} { + global translationDir translationDomain + return [glob -path [file join $translationDir $translationDomain] ".*.tr"] +} + +proc readTranslationTemplate {} { + global translationTemplate translationTemplatePath + set translationTemplate [list] + set handle [open $translationTemplatePath "r"] + fconfigure $handle -translation lf + while {[gets $handle line] >= 0} { + if {$line eq ""} { + lappend translationTemplate $line + } elseif {[string match {#*} $line]} { + lappend translationTemplate $line + } elseif {[regexp {^(.+[^@])=.+$} $line x str]} { + lappend translationTemplate $str + } + } + close $handle +} + +proc readTranslationFiles {} { + global translationData translationLangs + array set translationData [list] + set translationLangs [list] + foreach fn [translationFilePaths] { + if {[regexp {\.([^.]+)\.tr$} $fn x lang]} { + set handle [open $fn "r"] + fconfigure $handle -translation lf + while {[gets $handle line] >= 0} { + if {[regexp {^([^#].+[^@])=(.+)$} $line x ori tr]} { + set translationData($lang,$ori) $tr + } + } + lappend translationLangs $lang + close $handle + } + } + set translationLangs [lsort $translationLangs] +} + +proc readTranslations {} { + readTranslationTemplate + readTranslationFiles +} + +proc writeTranslationTemplate {} { + global translationTemplate translationTemplatePath + set handle [open $translationTemplatePath "w"] + fconfigure $handle -translation lf + foreach line $translationTemplate { + if {$line eq ""} { + puts $handle "" + } elseif {[string match {#*} $line]} { + puts $handle $line + } else { + puts $handle "$line=$line" + } + } + close $handle +} + +proc writeTranslationFiles {} { + global translationDir translationDomain translationData translationLangs translationTemplate + foreach lang $translationLangs { + set handle [open [file join $translationDir "$translationDomain.$lang.tr"] "w"] + fconfigure $handle -translation lf + foreach i $translationTemplate { + if {$i eq ""} { + puts $handle "" + } elseif {[string match {#*} $i]} { + puts $handle $i + } else { + puts $handle [format "%s=%s" $i [getTranslationString $lang $i]] + } + } + close $handle + } +} + +proc writeTranslations {} { + writeTranslationTemplate + writeTranslationFiles +} + +proc hasTranslationString {lang str} { + return [expr {[getTranslationString $lang $str] ne $str}] +} + +proc maybeGetTranslationString {lang str} { + set tr [getTranslationString $lang $str] + if {$tr eq $str} { + return {} + } else { + return $tr + } +} + +proc getTranslationString {lang str} { + global translationData + if {[info exists translationData($lang,$str)]} { + return $translationData($lang,$str) + } else { + return $str + } +} + +proc setTranslationString {lang ori tr} { + global translationData + set translationData($lang,$ori) $tr +} + +proc rewordTranslationString {old new} { + global translationData translationLangs + foreach lang $translationLangs { + if {[hasTranslationString $lang $old]} { + setTranslationString $lang $new [getTranslationString $lang $old] + } else { + setTranslationString $lang $new $new + } + } +} + +# GUI helpers +proc gAddTab {basename title elems dummies} { + global gMainNotebook + gAddFrame $basename $elems $dummies + $gMainNotebook add "$gMainNotebook.$basename" -text $title +} + +proc gAddFrame {basename elems dummies} { + global gMainNotebook + set varbase [string cat "g" [firstupper $basename]] + set varname "${varbase}Frame" + set pathname "$gMainNotebook.$basename" + global $varname + set $varname $pathname + ttk::frame $pathname -padding 5 + foreach i $elems { + set varname [string cat $varbase [firstupper $i]] + global $varname + set $varname "$pathname.$i" + } + foreach i $dummies { + set varname [string cat $varbase [firstupper $i]] + global $varname + set $varname {} + } +} + +proc gReloadTranslations {} { + readTranslations + gTrLoadTranslations + gTmLoadTranslations +} + +proc gAddMenu {varname parent menuname entries} { + global $varname + set path "${parent}.${menuname}" + set $varname $path + $parent configure -menu [menu $path -tearoff false] + foreach i $entries { + $path add {*}$i + } + return $parent +} +set gMenuItemNotImplemented [list command -state disabled -label "(Not implemented)"] + +proc gNotImplemented {args} { + tk_messageBox -message "Not implemented" -icon error -type ok +} + +# Main window + +set gMainFrame ".f" +set gMainFrameWidgetCount 0 +foreach i [list readBtn writeBtn notebookTopSeparator notebook] { + set [string cat "gMain" [firstupper $i]] "$gMainFrame.$i" +} + +wm title . "Advtrains Translation File Editor" +grid rowconfigure . 0 -weight 1 +grid columnconfigure . 0 -weight 1 + +ttk::frame $gMainFrame +grid $gMainFrame -column 0 -row 0 -sticky nsew +grid rowconfigure $gMainFrame 2 -weight 1 + +foreach i [list \ + [ttk::button $gMainReadBtn -text "Reload translation files" -command gReloadTranslations] \ + [ttk::button $gMainWriteBtn -text "Write changes" -command writeTranslations] \ +] { + grid $i -column $gMainFrameWidgetCount -row 0 -sticky ns + incr gMainFrameWidgetCount +} + +grid columnconfigure $gMainFrame $gMainFrameWidgetCount -weight 1 + +ttk::separator $gMainNotebookTopSeparator -orient horizontal +grid $gMainNotebookTopSeparator -column 0 -row 1 -columnspan [expr {1+$gMainFrameWidgetCount}] -sticky ew + +ttk::notebook $gMainNotebook +grid $gMainNotebook -column 0 -row 2 -columnspan [expr {1+$gMainFrameWidgetCount}] -sticky nsew + +# Translation Manager +# FIXME?: it seems like Tcl requires -textvariable globals to be set first before they are accessible + +gAddTab tr "Translations" \ + [list langSelect refLangSelect readBtn writeBtn treeviewFrame origTextLabel origTextField translatedTextLabel translatedTextField refTextLabel refTextField] \ + [list langValue refLangValue origTextValue translatedTextValue refTextValue] +set gTrTreeview "$gTrTreeviewFrame.main" +set gTrTreeviewScrollbar "$gTrTreeviewFrame.scrollbar" + +grid rowconfigure $gTrFrame 0 -weight 1 +grid columnconfigure $gTrFrame 2 -weight 1 + +ttk::frame $gTrTreeviewFrame -borderwidth 1 -relief sunken +grid $gTrTreeviewFrame -column 0 -row 0 -columnspan 3 -sticky nsew +grid rowconfigure $gTrTreeviewFrame 0 -weight 1 +grid columnconfigure $gTrTreeviewFrame 0 -weight 1 +ttk::treeview $gTrTreeview -selectmode browse -columns {translation} -yscrollcommand {$gTrTreeviewScrollbar set} +grid $gTrTreeview -column 0 -row 0 -sticky nsew +bind $gTrTreeview <> gTrTreeviewSelectionCallback +ttk::scrollbar $gTrTreeviewScrollbar -orient vertical -command {$gTrTreeview yview} +grid $gTrTreeviewScrollbar -column 1 -row 0 -sticky ns +$gTrTreeview heading #0 -text "Original text" + +lmap i [list \ + [list \ + [ttk::label $gTrOrigTextLabel -text "Original" -anchor w] \ + {} \ + [ttk::entry $gTrOrigTextField -state readonly -textvariable gTrOrigTextValue] \ + ] \ + [list \ + [ttk::label $gTrTranslatedTextLabel -text "Translation" -anchor w] \ + [ttk::combobox $gTrLangSelect -exportselection false -state readonly -textvariable gTrLangValue] \ + [ttk::entry $gTrTranslatedTextField -textvariable gTrTranslatedTextValue] \ + ] \ + [list \ + [ttk::label $gTrRefTextLabel -text "Reference" -anchor w] \ + [ttk::combobox $gTrRefLangSelect -exportselection false -state readonly -textvariable gTrRefLangValue] \ + [ttk::entry $gTrRefTextField -state readonly -textvariable gTrRefTextValue] \ + ] \ +] row [list 1 2 3] { + lmap item $i col [list 0 1 2] { + if {$item eq ""} { + continue + } + grid $item -column $col -row $row -sticky nswe + } + grid rowconfigure $gTrFrame $row -uniform bottom + incr gTrRowCount +} + +bind $gTrLangSelect <> gTrLoadTranslationsToTreeview +trace add variable gTrTranslatedTextValue write gTrApplyTranslationString +bind $gTrRefLangSelect <> gTrTreeviewSelectionCallback + +proc gTrLoadTranslationsToTreeview {} { + global translationTemplate gTrTreeview gTrLangValue + $gTrTreeview heading translation -text "Translation: $gTrLangValue" + set prevFocus [$gTrTreeview item [$gTrTreeview focus] -text] + array set openedSections [list] + foreach i [$gTrTreeview children {}] { + if {[$gTrTreeview item $i -open]} { + set openedSections([$gTrTreeview item $i -text]) 1 + } + } + $gTrTreeview delete [$gTrTreeview children {}] + set parent {} + foreach i [lrange $translationTemplate 1 end] { + if {[regexp {^\#+\s*(.+)} $i x comment]} { + set parent [$gTrTreeview insert {} end -text $comment -tags {notranslate}] + if {[info exists openedSections($comment)]} { + $gTrTreeview item $parent -open true + } + if {$comment eq $prevFocus} { + $gTrTreeview focus $parent + $gTrTreeview selection set $parent + } + } elseif {$i ne ""} { + set last [$gTrTreeview insert $parent end -text $i -values [list [maybeGetTranslationString $gTrLangValue $i]]] + if {$i eq $prevFocus} { + $gTrTreeview focus $last + $gTrTreeview selection set $last + } + } + } + gTrTreeviewSelectionCallback +} + +proc gTrTreeviewSelectionCallback {} { + global gTrOrigTextValue gTrTreeview gTrLangSelect gTrLangValue gTrRefLangSelect gTrRefLangValue gTrTranslatedTextField gTrTranslatedTextValue gTrRefTextField gTrRefTextValue + set focused [$gTrTreeview focus] + set gTrOrigTextValue [$gTrTreeview item $focused -text] + if {$focused eq ""} { + $gTrTranslatedTextField state disabled + $gTrRefTextField state disabled + set gTrTranslatedTextValue "Select an entry to translate" + set gTrRefTextValue "" + } elseif {[$gTrTreeview tag has notranslate $focused]} { + $gTrTranslatedTextField state disabled + $gTrRefTextField state disabled + set gTrTranslatedTextValue "Category names cannot be translated" + set gTrRefTextValue "" + } else { + $gTrTranslatedTextField state !disabled + set gTrTranslatedTextValue [maybeGetTranslationString $gTrLangValue $gTrOrigTextValue] + set gTrRefTextValue [maybeGetTranslationString $gTrRefLangValue $gTrOrigTextValue] + if {$gTrRefLangValue eq ""} { + $gTrRefTextField state disabled + set gTrRefTextValue "" + } elseif {$gTrRefTextValue eq ""} { + $gTrRefTextField state disabled + set gTrRefTextValue "The selected string is not yet translated to the reference language" + } else { + $gTrRefTextField state !disabled + } + } + $gTrLangSelect selection clear + $gTrRefLangSelect selection clear +} + +proc gTrLoadTranslations {} { + global translationLangs gTrLangSelect gTrLangValue gTrRefLangSelect gTrRefLangValue + set prevLang $gTrLangValue + set prevRefLang $gTrRefLangValue + $gTrLangSelect configure -values $translationLangs + $gTrRefLangSelect configure -values [linsert $translationLangs 0 ""] + if {[llength $translationLangs] < 1} { + tk_messageBox -icon error type ok -title "Error" -message "No translation files present." + exit 1 + } + if {$prevLang ni $translationLangs} { + $gTrLangSelect current 0 + } + if {$prevRefLang ni $translationLangs} { + $gTrRefLangSelect current 0 + } + gTrLoadTranslationsToTreeview +} + +proc gTrApplyTranslationString args { + global gTrTreeview gTrLangValue gTrOrigTextValue gTrTranslatedTextValue + set focused [$gTrTreeview focus] + if {![$gTrTreeview tag has notranslate $focused]} { + setTranslationString $gTrLangValue $gTrOrigTextValue $gTrTranslatedTextValue + $gTrTreeview item $focused -values [list $gTrTranslatedTextValue] + } +} + +# Translation Template Editor + +gAddTab tm "Template" \ + [list addStringMenuBtn addHeadingMenuBtn removeMenuBtn moveMenuBtn textField setMenuBtn treeviewFrame] \ + [list textValue] +set gTmTreeview "$gTmTreeviewFrame.main" +set gTmTreeviewScrollbar "$gTmTreeviewFrame.scrollbar" + +set gTmRowCount 0 +foreach i [list \ + [gAddMenu gTmAddStringMenu [ttk::menubutton $gTmAddStringMenuBtn -text "Add string"] "menu" [list \ + [list command -label "Insert before current entry" -command {gTmInsertStringAt true 0}] \ + [list command -label "Insert after current entry" -command {gTmInsertStringAt true 1}] \ + [list command -label "Insert as the first entry" -command {gTmInsertStringAt false 0}] \ + [list command -label "Insert as the last entry" -command {gTmInsertStringAt false end}] \ + ]] \ + [gAddMenu gTmAddHeadingMenu [ttk::menubutton $gTmAddHeadingMenuBtn -text "Add heading"] "menu" [list \ + [list command -label "Insert before current section" -command {gTmInsertHeadingAt true 0}] \ + [list command -label "Insert after current section" -command {gTmInsertHeadingAt true 1}] \ + ]] \ + [gAddMenu gTmRemoveMenu [ttk::menubutton $gTmRemoveMenuBtn -text "Remove"] "menu" [list \ + [list command -label "Remove entry" -command gTmDeleteEntry] \ + [list command -label "Remove section" -command gTmDeleteEntry] \ + [list command -label "Merge with previous section" -command gTmMergeWithPrevious] \ + ]] \ + [gAddMenu gTmMoveMenu [ttk::menubutton $gTmMoveMenuBtn -text "Move"] "menu" [list \ + [list command -label "Up" -command {gTmMoveInSection true -1}] \ + [list command -label "Down" -command {gTmMoveInSection true 1}] \ + [list command -label "To first" -command {gTmMoveInSection false 0}] \ + [list command -label "To last" -command {gTmMoveInSection false end}] \ + ]] \ +] { + grid $i -column 1 -row $gTmRowCount -sticky we + incr gTmRowCount +} + +ttk::frame $gTmTreeviewFrame -borderwidth 1 -relief sunken +grid $gTmTreeviewFrame -column 0 -row 0 -rowspan [expr {1+$gTmRowCount}] -sticky nsew +grid rowconfigure $gTmTreeviewFrame 0 -weight 1 +grid columnconfigure $gTmTreeviewFrame 0 -weight 1 +grid rowconfigure $gTmFrame $gTmRowCount -weight 1 +grid columnconfigure $gTmFrame 0 -weight 1 +ttk::treeview $gTmTreeview -selectmode browse -show tree -yscrollcommand {$gTmTreeviewScrollbar set} +bind $gTmTreeview <> gTmTreeviewSelectionCallback +grid $gTmTreeview -column 0 -row 0 -sticky nsew +ttk::scrollbar $gTmTreeviewScrollbar -orient vertical -command {$gTmTreeview yview} +grid $gTmTreeviewScrollbar -column 1 -row 0 -sticky ns +incr gTmRowCount + +ttk::entry $gTmTextField -textvariable gTmTextValue +grid $gTmTextField -column 0 -row $gTmRowCount -sticky nsew + +gAddMenu gTmSetMenu [ttk::menubutton $gTmSetMenuBtn -text "Set"] "menu" [list \ + [list command -label "Set heading" -command gTmSetEntry] \ + [list command -label "Set and copy translations" -command gTmSetEntryAndCopy] \ + [list command -label "Set without copying translations" -command gTmSetEntry] \ +] +grid $gTmSetMenuBtn -column 1 -row $gTmRowCount -sticky nsew + +proc gTmLoadTranslationsToTreeview {} { + global translationTemplate gTmTreeview + set prevFocus [$gTmTreeview item [$gTmTreeview focus] -text] + array set openedSections [list] + foreach i [$gTmTreeview children {}] { + if {[$gTmTreeview item $i -open]} { + set openedSections([$gTmTreeview item $i -text]) 1 + } + } + $gTmTreeview delete [$gTmTreeview children {}] + set parent {} + foreach i [lrange $translationTemplate 1 end] { + if {[regexp {^\#+\s*(.+)} $i x comment]} { + set parent [$gTmTreeview insert {} end -text $comment -tags {heading}] + if {[info exists openedSections($comment)]} { + $gTmTreeview item $parent -open true + } + if {$comment eq $prevFocus} { + $gTmTreeview focus $parent + $gTmTreeview selection set $parent + } + } elseif {$i ne ""} { + set last [$gTmTreeview insert $parent end -text $i] + if {$i eq $prevFocus} { + $gTmTreeview focus $last + $gTmTreeview selection set $last + } + } + } + gTmTreeviewSelectionCallback +} + +proc gTmTreeviewSelectionCallback {} { + global gTmTreeview gTmTextField gTmTextValue gTmAddStringMenu gTmAddHeadingMenu gTmRemoveMenu gTmMoveMenu gTmSetMenu + set focused [$gTmTreeview focus] + if {$focused eq {}} { + $gTmTextField state disabled + set gTmTextValue "Select an entry to edit" + foreach m [list $gTmAddStringMenu $gTmAddHeadingMenu $gTmRemoveMenu $gTmMoveMenu $gTmSetMenu] { + for {set i 0} {$i <= [$m index end]} {incr i} { $m entryconfigure $i -state disabled} + } + } else { + $gTmTextField state !disabled + set headingSetState disabled + set entrySetState normal + if {[$gTmTreeview tag has heading $focused]} { + set headingSetState normal + set entrySetState disabled + } + foreach ent [list \ + [list $gTmAddStringMenu \ + [list 0 -state $entrySetState] \ + [list 1 -state $entrySetState] \ + [list 2 -state normal] \ + [list 3 -state normal] \ + ] \ + [list $gTmAddHeadingMenu \ + [list 0 -state normal] \ + [list 1 -state normal] \ + ] \ + [list $gTmMoveMenu \ + [list 0 -state normal] \ + [list 1 -state normal] \ + [list 2 -state normal] \ + [list 3 -state normal] \ + ] \ + [list $gTmRemoveMenu \ + [list 0 -state $entrySetState] \ + [list 1 -state $headingSetState] \ + [list 2 -state $headingSetState] \ + ] \ + [list $gTmSetMenu \ + [list 0 -state $headingSetState] \ + [list 1 -state $entrySetState] \ + [list 2 -state $entrySetState] \ + ] \ + ] { + set m [lindex $ent 0] + lmap i [lrange $ent 1 end] { + $m entryconfigure {*}$i + } + } + set gTmTextValue [$gTmTreeview item $focused -text] + } +} + +proc gTmUpdateTemplateAux {parent} { + global translationTemplate gTmTreeview + foreach i [$gTmTreeview children $parent] { + set tval [$gTmTreeview item $i -text] + if {[$gTmTreeview tag has heading $i]} { + lappend translationTemplate {} + set tval "# $tval" + } + lappend translationTemplate $tval + gTmUpdateTemplateAux $i + } +} + +proc gTmUpdateTemplate {} { + global translationTemplate translationDomain + set translationTemplate [list "# textdomain: $translationDomain"] + gTmUpdateTemplateAux {} + gTrLoadTranslations + gTmLoadTranslationsToTreeview +} + +proc gTmSetEntry {} { + global gTmTreeview gTmTextValue + $gTmTreeview item [$gTmTreeview focus] -text $gTmTextValue + gTmUpdateTemplate +} + +proc gTmSetEntryAndCopy {} { + global gTmTreeview gTmTextValue + set focus [$gTmTreeview focus] + rewordTranslationString [$gTmTreeview item $focus -text] $gTmTextValue + gTmSetEntry +} + +proc gTmInsertStringAt {relative idx} { + global gTmTreeview + set focus [$gTmTreeview focus] + set parent [$gTmTreeview parent $focus] + set relidx [$gTmTreeview index $focus] + if {[$gTmTreeview tag has heading $focus]} { + set parent $focus + set relindex 0 + } + set newidx $idx + if {$relative} { + set newidx [expr {$idx+$relidx}] + } + set item [$gTmTreeview insert $parent $newidx -text {}] + $gTmTreeview focus $item + $gTmTreeview selection set $item +} + +proc gTmInsertHeadingAt {relative idx} { + global gTmTreeview + set focus [$gTmTreeview focus] + if {![$gTmTreeview tag has heading $focus]} { + set focus [$gTmTreeview parent $focus] + } + set parent [$gTmTreeview parent $focus] + set relidx [$gTmTreeview index $focus] + if {$focus eq {}} { + set parent {} + set relidx 0 + } + set newidx $idx + if {$relative} { + set newidx [expr {$idx+$relidx}] + } + set item [$gTmTreeview insert $parent $newidx -text {} -tags {heading}] + $gTmTreeview focus $item + $gTmTreeview selection set $item +} + +proc gTmMoveInSection {relative idx} { + global gTmTreeview + set focus [$gTmTreeview focus] + set parent [$gTmTreeview parent $focus] + set relidx [$gTmTreeview index $focus] + set newidx $idx + if {$relative} { + set newidx [expr {$idx+$relidx}] + } + $gTmTreeview move $focus $parent $newidx + gTmUpdateTemplate +} + +proc gTmDeleteEntry {} { + global gTmTreeview + $gTmTreeview delete [$gTmTreeview focus] + gTmUpdateTemplate +} + +proc gTmMergeWithPrevious {} { + global gTmTreeview + set parent [$gTmTreeview focus] + set prev [$gTmTreeview prev $parent] + set newindex end + if {$prev eq {}} { + set prev [$gTmTreeview parent $parent] + set newindex 0 + } + foreach i [$gTmTreeview children $parent] { + $gTmTreeview move $i $prev $newindex + } + $gTmTreeview delete $parent + gTmUpdateTemplate +} + +proc gTmLoadTranslations {} { + gTmLoadTranslationsToTreeview +} + +# Initialization + +gReloadTranslations -- cgit v1.2.3