| #!/usr/bin/env tclsh |
| ############################################################################### |
| # BRLTTY - A background process providing access to the console screen (when in |
| # text mode) for a blind person using a refreshable braille display. |
| # |
| # Copyright (C) 1995-2023 by The BRLTTY Developers. |
| # |
| # BRLTTY comes with ABSOLUTELY NO WARRANTY. |
| # |
| # This is free software, placed under the terms of the |
| # GNU Lesser General Public License, as published by the Free Software |
| # Foundation; either version 2.1 of the License, or (at your option) any |
| # later version. Please see the file LICENSE-LGPL for details. |
| # |
| # Web Page: http://brltty.app/ |
| # |
| # This software is maintained by Dave Mielke <dave@mielke.cc>. |
| ############################################################################### |
| |
| source [file join [file dirname [info script]] "prologue.tcl"] |
| |
| proc splitNames {names oldNamesVariable sameNamesVariable newNamesVariable} { |
| global baseStrings ignorableNames |
| |
| upvar 1 $oldNamesVariable oldNames |
| upvar 1 $sameNamesVariable sameNames |
| upvar 1 $newNamesVariable newNames |
| |
| lassign [intersect3 $names [dict keys $baseStrings]] oldNames sameNames newNames |
| lassign [intersect3 $newNames $ignorableNames] newNames x x |
| } |
| |
| proc writeNames {path names} { |
| global baseStrings |
| file delete $path |
| |
| if {[llength $names] > 0} { |
| if {[catch [list open $path {WRONLY TRUNC CREAT}] channel] == 0} { |
| foreach name $names { |
| set string [dict get $baseStrings $name] |
| puts $channel "$name [dict get $string text]" |
| } |
| |
| close $channel; unset channel |
| } else { |
| semanticError $channel |
| } |
| } |
| } |
| |
| proc loadStringList {} { |
| global androidDirectory |
| |
| set strings [dict create] |
| set path [file join $androidDirectory STRINGS] |
| logMessage detail "loading string list: $path" |
| |
| set order 0 |
| set pattern {^\s*(\S+)\s+(.*?)\s*$} |
| |
| if {[catch [list open $path {RDONLY}] channel] == 0} { |
| while {[gets $channel line] >= 0} { |
| if {[regexp $pattern $line x name text]} { |
| set string [dict create text $text order [incr order]] |
| dict set strings $name $string |
| } |
| } |
| |
| close $channel; unset channel |
| } else { |
| semanticError $channel |
| } |
| |
| return $strings |
| } |
| |
| proc auditStringList {} { |
| global baseStrings listedStrings |
| |
| logNote "auditing string list" |
| splitNames [dict keys $listedStrings] oldNames sameNames newNames |
| writeNames "[getProgramName].txt" $newNames |
| |
| foreach name $oldNames { |
| writeProgramMessage "old listed string: $name" |
| } |
| |
| foreach name $sameNames { |
| set oldText [dict get $listedStrings $name text] |
| set newText [dict get $baseStrings $name text] |
| |
| if {![string equal $newText $oldText]} { |
| writeProgramMessage "base string changed: $name: $oldText -> $newText" |
| } |
| } |
| } |
| |
| proc loadStringResources {directory} { |
| logMessage detail "loading resource directory: $directory" |
| set strings [dict create] |
| |
| set pattern {^\s*<\s*string} |
| append pattern {\s+name\s*=\s*"([^"]*)"} |
| append pattern {(?:\s+translatable\s*=\s*"true")?} |
| append pattern {(?:\s+tools:ignore\s*=\s*"(MissingTranslation)")?} |
| append pattern {\s*>\s*(.*?)\s*<\s*/string\s*>\s*$} |
| |
| foreach path [glob "$directory/*.xml"] { |
| logMessage detail "loading string resources: $path" |
| |
| if {[catch [list open $path {RDONLY}] channel] == 0} { |
| set order 0 |
| |
| while {[gets $channel line] >= 0} { |
| if {[regexp $pattern $line x name ignorable text]} { |
| set ignorable [expr {[string length $ignorable] > 0}] |
| set string [dict create text $text ignorable $ignorable order [incr order]] |
| dict set strings $name $string |
| } |
| } |
| |
| close $channel; unset channel |
| } else { |
| semanticError $channel |
| } |
| } |
| |
| return $strings |
| } |
| |
| proc getIgnorableNames {strings} { |
| set names [list] |
| |
| foreach name [dict keys $strings] { |
| if {[dict get $strings $name ignorable]} { |
| lappend names $name |
| } |
| } |
| |
| return $names |
| } |
| |
| proc auditLanguage {code directory} { |
| logNote "auditing language: $code" |
| set languageStrings [loadStringResources $directory] |
| splitNames [dict keys $languageStrings] oldNames sameNames newNames |
| writeNames "[getProgramName]-$code.txt" $newNames |
| |
| foreach name $oldNames { |
| writeProgramMessage "old language string: $code: $name" |
| } |
| |
| if {([string length $oldNames] + [string length $newNames]) == 0} { |
| global listedStrings |
| |
| foreach name [dict keys $listedStrings] { |
| if {[dict get $listedStrings $name order] != [dict get $languageStrings $name order]} { |
| writeProgramMessage "language string out of order: $code: $name" |
| } |
| } |
| } |
| } |
| |
| proc auditAllLanguages {} { |
| global baseDirectory |
| set languageDelimiter - |
| |
| foreach directory [glob -nocomplain -path "$baseDirectory$languageDelimiter" "?*"] { |
| set code [lindex [split [file tail $directory] $languageDelimiter] 1] |
| auditLanguage $code $directory |
| } |
| } |
| |
| set optionDefinitions { |
| } |
| |
| processProgramArguments optionValues $optionDefinitions |
| |
| set baseDirectory [file join $applicationDirectory res values] |
| set baseStrings [loadStringResources $baseDirectory] |
| set ignorableNames [getIgnorableNames $baseStrings] |
| |
| set listedStrings [loadStringList] |
| auditStringList |
| |
| auditAllLanguages |
| exit 0 |