I've continued the i18n approach that I posted a week ago to this list, and committed the attached patch and two new files to CVS. As discussed, the idea is that you have a collection of translations which the script gets translated against. The current example is based on leveraging the gettext files that come with packages (for a whitebox testing approach). Other backends could be implemented. Internally it pretranslates the strings in the search predicates. There are a few subtleties in the implementation e.g. there can be multiple translations of a string e.g. "Forward" could be forwarding an email, or forward tp the next page in a wizard, plus I had to be smart about accelerator markings in the gettext files. The debug names for nodes (and hence the logs) contain both the untranslated strings and the list of possible translations. The changes probably (temporarily) break support for non-RPM distros, and possibly for locally-built programs... and probably break examples/recorder.py; all of these should be simple enough to fix. I've tested this and it works; here are a couple of examples, which is probably the best way of showing it: Here's the log of examples/evolution-test-configuring-exchange.py unmodified, running in French: Detecting distribution: Red Hat/Fedora/derived distribution Warning: Dogtail could not import the Python bindings for libwnck. Window-manager manipulation will not be available. Evolution version 2.0.4 evolution-data-server version 1.0.4 gtkhtml3 version 3.3.2 click on {"Settings..." ("Param�es...") menuitem} (evolution:21548): Gtk-CRITICAL **: file gtktreesortable.c: line 137 (gtk_tree_sortable_set_sort_column_id): assertion `GTK_IS_TREE_SORTABLE (sortable)' failed ** (evolution:21548): CRITICAL **: file utils.c: line 100 (html_utils_get_accessible): assertion `o != NULL' failed (evolution:21548): Gtk-CRITICAL **: file gtktogglebutton.c: line 326 (gtk_toggle_button_get_active): assertion `GTK_IS_TOGGLE_BUTTON (toggle_button)' failed click on {child with name="Add" ("Ajouter")} account gui setup checking writable option 'auth' perms=00000040 checking writable option 'use_ssl' perms=00000010 checking writable option 'auth' perms=00000040 checking writable option 'use_ssl' perms=00000010 checking writable option 'auth' perms=00000040 checking writable option 'use_ssl' perms=00000010 {Evolution Account Assistant} is on 'Configuration de la messagerie' page click on {child with name="Forward" ("Suivant", "Faire suivre")} {Evolution Account Assistant} is now on 'Identit�page Setting text of {labelled '"Full Name:" ("Nom complet :")'} to 'John Doe' Setting text of {labelled '"Email Address:" ("Adresse de courrier �ctronique :", "Adresse �ctronique :")'} to 'jdoe example com' Setting text of {labelled '"Reply-To:" ("R�ndre �")'} to '' Setting text of {labelled '"Organization:" ("Organisation :")'} to '' click on {child with name="Forward" ("Suivant", "Faire suivre")} {Evolution Account Assistant} is now on 'R�ption des messages' page Setting combobox {labelled '"Server Type: " ("Type de serveur : ")'} to 'Microsoft Exchange' click on {named "Microsoft Exchange"} checking writable option 'auth' perms=00000040 checking writable option 'use_ssl' perms=00000010 checking writable option 'hostname' perms=00000000 checking writable option 'username' perms=00000000 checking writable option 'ad_server' perms=00000000 checking writable option 'ad_limit' perms=00000000 checking writable option 'mailbox' perms=00000000 checking writable option 'owa_path' perms=00000000 checking writable option 'pf_server' perms=00000000 checking writable option 'filter' perms=00000000 checking writable option 'filter_junk' perms=00000000 checking writable option 'auth' perms=00000040 checking writable option 'use_ssl' perms=00000010 Setting text of {labelled '"Exchange Server:" ("Serveur Exchange :")'} to '192.168.0.1' Setting text of {labelled '"Windows Username:" ("Nom d'utilisateur Windows :")'} to 'EXAMPLE\jdoe'click on {child with name="Forward" ("Suivant", "Faire suivre")} checking writable option 'mailbox' perms=00000000 checking writable option 'pf_server' perms=00000000 {Evolution Account Assistant} is now on 'R�ption des messages' page click on {child with name="Forward" ("Suivant", "Faire suivre")} {Evolution Account Assistant} is now on 'Envoi du message' page Setting combobox {labelled '"Server Type: " ("Type de serveur : ")'} to 'Microsoft Exchange' click on {named "Microsoft Exchange"} Node roleName='panel' name='' description='' click on {child with name="Forward" ("Suivant", "Faire suivre")} {Evolution Account Assistant} is now on 'Gestion des comptes' page Setting text of {labelled '"Name:" ("Nom :")'} to 'test Exchange account' click on {child with name="Forward" ("Suivant", "Faire suivre")} {Evolution Account Assistant} is now on 'Termin�page click on {child with name="Apply" ("Appliquer")} Here's a log of examples/evolution-test-switching-components.py: Detecting distribution: Red Hat/Fedora/derived distribution Warning: Dogtail could not import the Python bindings for libwnck. Window-manager manipulation will not be available. click on {"Mail" ("Courrier") menuitem} click on {"Contacts" menuitem} click on {"Calendars" ("Calendriers") menuitem} click on {"Tasks" ("T�es") menuitem} click on {"Mail" ("Courrier") menuitem} click on {"Contacts" menuitem} click on {"Calendars" ("Calendriers") menuitem} ...etc
Index: dogtail/config.py =================================================================== RCS file: /cvs/gnome/dogtail/dogtail/config.py,v retrieving revision 1.2 diff -u -p -r1.2 config.py --- dogtail/config.py 27 Oct 2005 20:51:31 -0000 1.2 +++ dogtail/config.py 28 Oct 2005 06:23:39 -0000 @@ -49,6 +49,10 @@ class _Config(object): debugSearchPaths (boolean): Whether we should write out debug info when running the SearchPath routines. + + debugTranslation (boolean): + Whether we should write out debug information from the translation/i18n + subsystem. """ __scriptName = staticmethod(_scriptName) __encoding = staticmethod(_encoding) @@ -77,7 +81,8 @@ class _Config(object): 'debugSleep' : False, 'debugSearchPaths' : False, 'absoluteNodePaths' : False, - 'ensureSensitivity' : True + 'ensureSensitivity' : True, + 'debugTranslation' : False } options = {} Index: dogtail/predicate.py =================================================================== RCS file: /cvs/gnome/dogtail/dogtail/predicate.py,v retrieving revision 1.2 diff -u -p -r1.2 predicate.py --- dogtail/predicate.py 17 Oct 2005 23:51:00 -0000 1.2 +++ dogtail/predicate.py 28 Oct 2005 06:23:40 -0000 @@ -5,6 +5,14 @@ __author__ = 'David Malcolm <dmalcolm re import unittest +import dogtail.i18n +from dogtail.i18n import TranslatableString + +def stringMatches(scriptName, reportedName): + assert isinstance(scriptName, TranslatableString) + + return scriptName.matchedBy(reportedName) + def makeScriptRecursiveArgument(isRecursive, defaultValue): if isRecursive==defaultValue: return "" @@ -77,16 +85,17 @@ class Predicate: else: return self.__dict__ == other.__dict__ + class IsAnApplicationNamed(Predicate): """Search subclass that looks for an application by name""" def __init__(self, appName): - self.appName = appName + self.appName = TranslatableString(appName) def satisfiedByNode(self, node): - return node.roleName=='application' and node.name==self.appName + return node.roleName=='application' and stringMatches(self.appName, node.name) def describeSearchResult(self): - return '"%s" application'%self.appName + return '%s application'%self.appName def makeScriptMethodCall(self, isRecursive): # ignores the isRecursive parameter @@ -98,21 +107,27 @@ class IsAnApplicationNamed(Predicate): class GenericPredicate(Predicate): """SubtreePredicate subclass that takes various optional search fields""" - def __init__(self, name = '', roleName = '', description= '', label = '', debugName=None): - self.name = name + def __init__(self, name = None, roleName = None, description= None, label = None, debugName=None): + if name: + self.name = TranslatableString(name) + else: + self.name = None self.roleName = roleName self.description = description - self.label = label + if label: + self.label = TranslatableString(label) + else: + self.label = None if debugName: self.debugName = debugName else: if label: - self.debugName = "labelled '%s'"%label + self.debugName = "labelled '%s'"%self.label else: self.debugName = "child with" if name: - self.debugName += " name='%s'"%name + self.debugName += " name=%s"%self.name if roleName: self.debugName += " roleName='%s'"%roleName if description: @@ -127,12 +142,12 @@ class GenericPredicate(Predicate): # and then checking the label, rather than looking for a label and # then returning whatever LABEL_FOR targets if node.labeller: - return node.labeller.name==self.label + return stringMatches(self.label, node.labeller.name) else: return False else: # Ensure the node matches any criteria that were set: if self.name: - if self.name!=node.name: return False + if not stringMatches(self.name,node.name): return False if self.roleName: if self.roleName!=node.roleName: return False if self.description: @@ -170,13 +185,13 @@ class IsNamed(Predicate): """Predicate subclass that looks simply by name""" def __init__(self, name): - self.name = name + self.name = TranslatableString(name) def satisfiedByNode(self, node): - return node.name==self.name + return stringMatches(self.name, node.name) def describeSearchResult(self): - return "named '%s'"%self.name + return "named %s"%self.name def makeScriptMethodCall(self, isRecursive): return "child(name='%s'%s)"%(self.name, makeScriptRecursiveArgument(isRecursive, True)) @@ -186,13 +201,13 @@ class IsNamed(Predicate): class IsAWindowNamed(Predicate): """Predicate subclass that looks for a top-level window by name""" def __init__(self, windowName): - self.windowName = windowName + self.windowName = TranslatableString(windowName) def satisfiedByNode(self, node): - return node.roleName=='frame' and node.name==self.windowName + return node.roleName=='frame' and stringMatches(self.windowName, node.name) def describeSearchResult(self): - return "'%s' window"%self.windowName + return "%s window"%self.windowName def makeScriptMethodCall(self, isRecursive): return "window('%s'%s)"%(self.windowName, makeScriptRecursiveArgument(isRecursive, False)) @@ -211,13 +226,13 @@ class IsAWindow(Predicate): class IsADialogNamed(Predicate): """Predicate subclass that looks for a top-level dialog by name""" def __init__(self, dialogName): - self.dialogName = dialogName + self.dialogName = TranslatableString(dialogName) def satisfiedByNode(self, node): - return node.roleName=='dialog' and node.name==self.dialogName + return node.roleName=='dialog' and stringMatches(self.dialogName, node.name) def describeSearchResult(self): - return '"%s" dialog'%self.dialogName + return '%s dialog'%self.dialogName def makeScriptMethodCall(self, isRecursive): return "dialog('%s'%s)"%(self.dialogName, makeScriptRecursiveArgument(isRecursive, False)) @@ -232,16 +247,16 @@ class IsLabelledBy(Predicate): class IsLabelledAs(Predicate): """Predicate: is this node labelled with the text string (i.e. by another node with that as a name)""" def __init__(self, labelText): - self.labelText = labelText + self.labelText = TranslatableString(labelText) def satisfiedByNode(self, node): # FIXME if node.labeller: - return node.labeller.name==self.labelText + return stringMatches(self.labelText, node.labeller.name) else: return False def describeSearchResult(self): - return 'labelled "%s"'%self.labelText + return 'labelled %s'%self.labelText def makeScriptMethodCall(self, isRecursive): return "child(label='%s'%s)"%(self.labelText, makeScriptRecursiveArgument(isRecursive, True)) @@ -252,13 +267,13 @@ class IsLabelledAs(Predicate): class IsAMenuNamed(Predicate): """Predicate subclass that looks for a menu by name""" def __init__(self, menuName): - self.menuName = menuName + self.menuName = TranslatableString(menuName) def satisfiedByNode(self, node): - return node.roleName=='menu' and node.name==self.menuName + return node.roleName=='menu' and stringMatches(self.menuName, node.name) def describeSearchResult(self): - return '"%s" menu'%(self.menuName) + return '%s menu'%(self.menuName) def makeScriptMethodCall(self, isRecursive): return "menu('%s'%s)"%(self.menuName, makeScriptRecursiveArgument(isRecursive, True)) @@ -269,14 +284,14 @@ class IsAMenuNamed(Predicate): class IsAMenuItemNamed(Predicate): """Predicate subclass that looks for a menu item by name""" def __init__(self, menuItemName): - self.menuItemName = menuItemName + self.menuItemName = TranslatableString(menuItemName) def satisfiedByNode(self, node): roleName = node.roleName - return (roleName=='menu item' or roleName=='check menu item' or roleName=='radio menu item') and node.name==self.menuItemName + return (roleName=='menu item' or roleName=='check menu item' or roleName=='radio menu item') and stringMatches(self.menuItemName, node.name) def describeSearchResult(self): - return '"%s" menuitem'%(self.menuItemName) + return '%s menuitem'%(self.menuItemName) def makeScriptMethodCall(self, isRecursive): return "menuItem('%s'%s)"%(self.menuItemName, makeScriptRecursiveArgument(isRecursive, True)) @@ -287,13 +302,13 @@ class IsAMenuItemNamed(Predicate): class IsATextEntryNamed(Predicate): """Predicate subclass that looks for a text entry by name""" def __init__(self, textEntryName): - self.textEntryName = textEntryName + self.textEntryName = TranslatableString(textEntryName) def satisfiedByNode(self, node): - return node.roleName=='text' and node.name==self.textEntryName + return node.roleName=='text' and stringMatches(self.textEntryName, node.name) def describeSearchResult(self): - return '"%s" textentry'%(self.textEntryName) + return '%s textentry'%(self.textEntryName) def makeScriptMethodCall(self, isRecursive): return "textentry('%s'%s)"%(self.textEntryName, makeScriptRecursiveArgument(isRecursive, True)) @@ -304,13 +319,13 @@ class IsATextEntryNamed(Predicate): class IsAButtonNamed(Predicate): """Predicate subclass that looks for a button by name""" def __init__(self, buttonName): - self.buttonName = buttonName + self.buttonName = TranslatableString(buttonName) def satisfiedByNode(self, node): - return node.roleName=='push button' and node.name==self.buttonName + return node.roleName=='push button' and stringMatches(self.buttonName, node.name) def describeSearchResult(self): - return '"%s" button'%(self.buttonName) + return '%s button'%(self.buttonName) def makeScriptMethodCall(self, isRecursive): return "button('%s'%s)"%(self.buttonName, makeScriptRecursiveArgument(isRecursive, True)) @@ -321,13 +336,13 @@ class IsAButtonNamed(Predicate): class IsATabNamed(Predicate): """Predicate subclass that looks for a tab by name""" def __init__(self, tabName): - self.tabName = tabName + self.tabName = TranslatableString(tabName) def satisfiedByNode(self, node): - return node.roleName=='page tab' and node.name==self.tabName + return node.roleName=='page tab' and stringMatches(self.tabName, node.name) def describeSearchResult(self): - return '"%s" tab'%(self.tabName) + return '%s tab'%(self.tabName) def makeScriptMethodCall(self, isRecursive): return "tab('%s'%s)"%(self.tabName, makeScriptRecursiveArgument(isRecursive, True)) Index: dogtail/tree.py =================================================================== RCS file: /cvs/gnome/dogtail/dogtail/tree.py,v retrieving revision 1.7 diff -u -p -r1.7 tree.py --- dogtail/tree.py 27 Oct 2005 20:51:31 -0000 1.7 +++ dogtail/tree.py 28 Oct 2005 06:23:41 -0000 @@ -809,6 +809,24 @@ class Node: also logs the search. """ return self.findChild (predicate.IsATabNamed(tabName=tabName), recursive) + + def getUserVisibleStrings(self): + """ + Get all user-visible strings in this node and its descendents. + + (Could be implemented as an attribute) + """ + result=[] + if self.name: + result.append(self.name) + if self.description: + result.append(self.description) + try: + children = self.children + except: return result + for child in children: + result.extend(child.getUserVisibleStrings()) + return result class Root (Node): """ Index: dogtail/apps/wrappers/evolution.py =================================================================== RCS file: /cvs/gnome/dogtail/dogtail/apps/wrappers/evolution.py,v retrieving revision 1.4 diff -u -p -r1.4 evolution.py --- dogtail/apps/wrappers/evolution.py 17 Oct 2005 23:51:00 -0000 1.4 +++ dogtail/apps/wrappers/evolution.py 28 Oct 2005 06:23:41 -0000 @@ -18,8 +18,10 @@ from dogtail.apps.categories import * # The table rows have NODE_CHILD_OF relations (with paths, in at-poke) # - - +# Use the gettext translations sniffed from the package db: +import dogtail.i18n +#dogtail.i18n.loadTranslationsFromPackageMoFiles('evolution') +dogtail.i18n.loadTranslationsFromPackageMoFiles('evolution-connector') # App-specific wrapper classes: Index: examples/evolution-test-switching-components.py =================================================================== RCS file: /cvs/gnome/dogtail/examples/evolution-test-switching-components.py,v retrieving revision 1.1 diff -u -p -r1.1 evolution-test-switching-components.py --- examples/evolution-test-switching-components.py 9 Oct 2005 02:48:21 -0000 1.1 +++ examples/evolution-test-switching-components.py 28 Oct 2005 06:23:41 -0000 @@ -7,6 +7,10 @@ __author__ = 'David Malcolm <dmalcolm re # Assumes evolution is configured and is running # +# Use the gettext translations sniffed from the package db: +import dogtail.i18n +dogtail.i18n.loadTranslationsFromPackageMoFiles('evolution') + import dogtail.tree evo = dogtail.tree.root.application('evolution')
Attachment:
i18n.py
Description: application/python
Attachment:
i18n-test.py
Description: application/python