[PATCH] Add translation support to check_accelerators

David Shea dshea at redhat.com
Mon Sep 23 19:01:24 UTC 2013


Added a --translate option to check_accelerators.py. When set, it will
check all of the translated keyboard accelerators in addition to the
English ones. Changed the exception check so that language information
can be included as <!-- check_accelerators(<lang>): comment -->. Added
support for the new parameter to the run_check_accelerators.sh wrapper
script.

`make check' will not check translations.

Requires python-polib to parse the .po files.
---
 tests/accelerators/check_accelerators.py     | 127 +++++++++++++++++++++++----
 tests/accelerators/run_check_accelerators.sh |  18 +++-
 2 files changed, 126 insertions(+), 19 deletions(-)

diff --git a/tests/accelerators/check_accelerators.py b/tests/accelerators/check_accelerators.py
index 892ff2b..461220a 100755
--- a/tests/accelerators/check_accelerators.py
+++ b/tests/accelerators/check_accelerators.py
@@ -22,6 +22,8 @@ import argparse
 import re
 import os.path
 import copy
+import collections
+import locale
 
 try:
     from lxml import etree
@@ -30,33 +32,80 @@ except ImportError:
     sys.exit(1)
 
 accel_re = re.compile(r'_(?P<accel>.)')
-
 success = True
-def add_check_accel(glade_filename, accels, label):
+
+# Only used when --translate is requested.
+class PODict(collections.Mapping):
+    def __init__(self, filename):
+        try:
+            import polib
+        except ImportError:
+            print("You need to install the python-polib package to check translations")
+            sys.exit(1)
+
+        self._dict = {}
+
+        pofile = polib.pofile(filename)
+        self.metadata = pofile.metadata
+        for entry in pofile.translated_entries():
+            self._dict[entry.msgid] = entry.msgstr
+
+    def __getitem__(self, key):
+        return self._dict[key]
+
+    def __iter__(self):
+        return self._dict.__iter__()
+
+    def __len__(self):
+        return len(self._dict)
+
+def is_exception(node, language=None):
+    if language:
+        comment_str = "check_accelerators(%s)" % language
+    else:
+        comment_str = "check_accelerators"
+
+    return bool(node.xpath("./parent::*/comment()[contains(., '%s')]" % comment_str))
+
+def add_check_accel(glade_filename, accels, label, po_map):
     """Check whether an accelerator conflicts with existing accelerators.
        and add it to the current accelerator context.
     """
     global success
 
+    language = None
+    if po_map:
+        if label.text not in po_map:
+            return
+        label.text = po_map[label.text]
+        language = po_map.metadata['Language']
+
     match = accel_re.search(label.text)
     if match:
         accel = match.group('accel').lower()
         if accel in accels:
             # Check for an exception comment
-            prev = label.getprevious()
-            if (prev is not None) and (prev.tag == etree.Comment) and \
-                    prev.text.strip().startswith('check_accelerators:'):
+            if is_exception(label, language):
                 return
 
-            print("Accelerator collision for key %s in %s\n    line %d: %s\n    line %d: %s" %\
-                    (accel, os.path.normpath(glade_filename),
-                        accels[accel].sourceline, accels[accel].text,
-                        label.sourceline, label.text))
+            if language:
+                lang_str = " for language %s" % language
+            else:
+                lang_str = ""
+
+            try:
+                print(("Accelerator collision for key %s in %s%s\n    line %d: %s\n    line %d: %s" %\
+                        (accel, os.path.normpath(glade_filename), lang_str,
+                            accels[accel].sourceline, accels[accel].text,
+                            label.sourceline, label.text)).encode("utf-8"))
+            except UnicodeEncodeError:
+                import pdb; pdb.set_trace()
+                raise
             success = False
         else:
             accels[accel] = label
 
-def combine_accels(glade_filename, list_a, list_b):
+def combine_accels(glade_filename, list_a, list_b, po_map):
     if not list_a:
         return list_b
     if not list_b:
@@ -67,7 +116,7 @@ def combine_accels(glade_filename, list_a, list_b):
         for accels_b in list_b:
             new_accels = copy.copy(accels_a)
             for accel in accels_b.keys():
-                add_check_accel(glade_filename, new_accels, accels_b[accel])
+                add_check_accel(glade_filename, new_accels, accels_b[accel], po_map)
             newlist.append(new_accels)
     return newlist
 
@@ -80,7 +129,7 @@ def combine_accels(glade_filename, list_a, list_b):
 # these is compared against the list of accelerators returned for the object's
 # other GtkNotebook children.
 
-def process_object(glade_filename, interface_object):
+def process_object(glade_filename, interface_object, po_map):
     """Process keyboard shortcuts for a given glade object.
 
        The return value from this function is a list of accelerator
@@ -94,12 +143,12 @@ def process_object(glade_filename, interface_object):
 
     # Add everything that isn't a child of a GtkNotebook
     for label in interface_object.xpath(".//property[@name='label' and ../property[@name='use_underline']/text() = 'True' and not(ancestor::object[@class='GtkNotebook'])]"):
-        add_check_accel(glade_filename, accels[0], label)
+        add_check_accel(glade_filename, accels[0], label, po_map)
 
     # For each GtkNotebook tab that is not a child of another notebook,
     # add the tab to the top-level context
     for notebook_label in interface_object.xpath(".//object[@class='GtkNotebook' and not(ancestor::object[@class='GtkNotebook'])]/child[@type='tab']//property[@name='label' and ../property[@name='use_underline']/text() = 'True']"):
-        add_check_accel(glade_filename, accels[0], notebook_label)
+        add_check_accel(glade_filename, accels[0], notebook_label, po_map)
 
     # Now process each non-tab object in each Gtknotebook that is not a child
     # of another notebook. For each Gtk notebook, each non-tab child represents
@@ -129,34 +178,76 @@ def process_object(glade_filename, interface_object):
         # Create the list of dictionaries for the notebook
         notebook_list = []
         for child in notebook.xpath("./child[not(@type='tab')]"):
-            notebook_list.extend(process_object(glade_filename, copy.deepcopy(child)))
+            notebook_list.extend(process_object(glade_filename, copy.deepcopy(child), po_map))
 
         # Now combine this with our list of accelerators
-        accels = combine_accels(glade_filename, accels, notebook_list)
+        accels = combine_accels(glade_filename, accels, notebook_list, po_map)
 
     return accels
 
-def check_glade(glade_filename):
+def check_glade(glade_filename, po_map=None):
     with open(glade_filename) as glade_file:
         # Parse the XML
         glade_tree = etree.parse(glade_file)
 
         # Treat each top-level object as a separate context
         for interface_object in glade_tree.xpath("/interface/object"):
-            process_object(glade_filename, interface_object)
+            process_object(glade_filename, interface_object, po_map)
 
 def main(argv=None):
     if argv is None:
         argv = sys.argv
 
     parser = argparse.ArgumentParser("Check for duplicated accelerators")
+    parser.add_argument("-t", "--translate", action='store_true',
+            help="Check translated strings")
+    parser.add_argument("-p", "--podir", action='store', type=str,
+            metavar='PODIR', help='Directory containing po files', default='./po')
     parser.add_argument("glade_files", nargs="+", metavar="GLADE-FILE",
             help='The glade file to check')
     args = parser.parse_args(args=argv)
 
+    # First check the untranslated strings in each file
     for glade_file in args.glade_files:
         check_glade(glade_file)
 
+    # Now loop over all of the translations
+    if args.translate:
+        import langtable
+
+        with open(os.path.join(args.podir, 'LINGUAS')) as linguas:
+            for line in linguas.readlines():
+                if re.match(r'^#', line):
+                    continue
+
+                for lang in line.strip().split(" "):
+                    # Reset the locale to C before parsing the po file because
+                    # polib has erroneous uses of lower().
+                    # See https://bitbucket.org/izi/polib/issue/54/pofile-parsing-crashes-in-turkish-locale
+                    locale.setlocale(locale.LC_ALL, 'C')
+                    po_map = PODict(os.path.join(args.podir, lang + ".po"))
+
+                    # Set the locale so that we can use lower() on accelerator keys.
+                    # If the language is of the form xx_XX, use that as the
+                    # locale name. Otherwise use the first locale that
+                    # langtable returns for the language. If that doesn't work,
+                    # just use C and hope for the best.
+                    if '_' in lang:
+                        locale.setlocale(locale.LC_ALL, lang)
+                    else:
+                        locale_list = langtable.list_locales(languageId=lang)
+                        if locale_list:
+                            try:
+                                locale.setlocale(locale.LC_ALL, locale_list[0])
+                            except locale.Error:
+                                print("No such locale %s, using C" % locale_list[0])
+                                locale.setlocale(locale.LC_ALL, 'C')
+                        else:
+                            locale.setlocale(locale.LC_ALL, 'C')
+
+                    for glade_file in args.glade_files:
+                        check_glade(glade_file, po_map)
+
 if __name__ == "__main__":
     main(sys.argv[1:])
 
diff --git a/tests/accelerators/run_check_accelerators.sh b/tests/accelerators/run_check_accelerators.sh
index a49cda1..401f081 100755
--- a/tests/accelerators/run_check_accelerators.sh
+++ b/tests/accelerators/run_check_accelerators.sh
@@ -3,4 +3,20 @@
 : "${top_srcdir:=$(dirname "$0")/../..}"
 srcdir="${top_srcdir}/tests/accelerators"
 
-find "${top_srcdir}" -name '*.glade' -exec "${srcdir}/check_accelerators.py" {} +
+# If --translate was specified but not --podir, add --podir
+i=0
+translate_set=0
+podir_set=0
+for arg in "$@" ; do
+    if [ "$arg" = "--translate" -o "$arg" = "-t" ]; then
+        translate_set=1
+    elif [ "$arg" = "--podir" -o "$arg" = "-p" ]; then
+        podir_set=1
+    fi
+done
+
+if [ "$translate_set" -eq 1 -a "$podir_set" -eq 0 ]; then
+    set -- "$@" --podir "${top_srcdir}/po"
+fi
+
+find "${top_srcdir}" -name '*.glade' -exec "${srcdir}/check_accelerators.py" "$@" {} +
-- 
1.8.3.1



More information about the anaconda-patches mailing list