dogtail-devel I've committed the i18n support to CVS



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



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]