← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad/translations-pagetests-future-imports into lp:launchpad

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/translations-pagetests-future-imports into lp:launchpad.

Commit message:
Convert pagetests under lp.translations to Launchpad's preferred __future__ imports.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/translations-pagetests-future-imports/+merge/347330
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/translations-pagetests-future-imports into lp:launchpad.
=== modified file 'lib/lp/translations/browser/tests/distroseries-views.txt'
--- lib/lp/translations/browser/tests/distroseries-views.txt	2012-06-29 08:40:05 +0000
+++ lib/lp/translations/browser/tests/distroseries-views.txt	2018-06-03 00:41:33 +0000
@@ -54,16 +54,16 @@
     ...     distroseries.hide_all_translations = False
     ...     objection = check_translations_access(distroseries)
     ...     if objection is None:
-    ...         print "User can access revealed translations."
+    ...         print("User can access revealed translations.")
     ...     else:
-    ...         print "No access to revealed translations!", objection
+    ...         print("No access to revealed translations!", objection)
     ...
     ...     distroseries.hide_all_translations = True
     ...     objection = check_translations_access(distroseries)
     ...     if objection is None:
-    ...         print "User can access hidden translations."
+    ...         print("User can access hidden translations.")
     ...     else:
-    ...         print "User can not access hidden translations:", objection
+    ...         print("User can not access hidden translations:", objection)
     ...
     ...     distroseries.hide_all_translations = original_hide_flag
 
@@ -158,4 +158,3 @@
     User can access revealed translations.
     User can not access hidden translations:
     Translations for this release series are not available yet.
-

=== modified file 'lib/lp/translations/browser/tests/language-views.txt'
--- lib/lp/translations/browser/tests/language-views.txt	2014-02-19 04:01:46 +0000
+++ lib/lp/translations/browser/tests/language-views.txt	2018-06-03 00:41:33 +0000
@@ -14,20 +14,20 @@
 
 The language +admin view provides a label and a page_title for the page.
 
-    >>> print language_admin_view.label
+    >>> print(language_admin_view.label)
     Edit Portuguese (Brazil) in Launchpad
 
-    >>> print language_admin_view.page_title
+    >>> print(language_admin_view.page_title)
     Change details
 
 The view provides also a cancel_url and a next_url, but they're the same
 because the previous and next steps of editing a language are returning
 to its index.
 
-    >>> print language_admin_view.cancel_url
+    >>> print(language_admin_view.cancel_url)
     http://translations.launchpad.dev/+languages/pt_BR
 
-    >>> print language_admin_view.next_url
+    >>> print(language_admin_view.next_url)
     http://translations.launchpad.dev/+languages/pt_BR
 
 
@@ -41,7 +41,7 @@
     ...     'pluralforms': 2,
     ...     'pluralexpression': '1/n',
     ...     })
-    >>> print language_admin_view.getFieldError('pluralexpression')
+    >>> print(language_admin_view.getFieldError('pluralexpression'))
     Division by zero in plural expression for n = 0.
 
 
@@ -50,18 +50,18 @@
 
     >>> language_add_view = create_view(language_set, '+add')
 
-    >>> print language_add_view.label
+    >>> print(language_add_view.label)
     Register a language in Launchpad
 
-    >>> print language_add_view.page_title
+    >>> print(language_add_view.page_title)
     Register a language
 
 The view provides also a cancel_url and a next_url:
 
-    >>> print language_add_view.cancel_url
+    >>> print(language_add_view.cancel_url)
     http://translations.launchpad.dev/+languages
 
-    >>> print language_add_view.field_names
+    >>> print(language_add_view.field_names)
     ['code', 'englishname', 'nativename', 'pluralforms',
     'pluralexpression', 'visible', 'direction']
 
@@ -82,7 +82,7 @@
     >>> language_add_view.errors
     []
 
-    >>> print language_add_view.next_url
+    >>> print(language_add_view.next_url)
     http://translations.launchpad.dev/+languages/lp_US
 
 
@@ -110,7 +110,7 @@
     >>> friendly_plural_forms = language_view.friendly_plural_forms
 
     >>> for form_dict in friendly_plural_forms:
-    ...     print form_dict['form'], ':', form_dict['examples']
+    ...     print(form_dict['form'], ':', form_dict['examples'])
     0 : 1, 21, 31, 41, 51, 61...
     1 : 2, 3, 4, 22, 23, 24...
     2 : 0, 5, 6, 7, 8, 9...
@@ -170,7 +170,7 @@
     >>> translator_merged in top_contributors
     False
     >>> for translator in top_contributors:
-    ...     print translator.name
+    ...     print(translator.name)
     translator-main
     translator-0
     translator-1
@@ -190,7 +190,7 @@
 The user_languages property contains a list of the current user's preferred
 languages formated as links.
 
-    >>> print languageset_view.user_languages
+    >>> print(languageset_view.user_languages)
     <a href=".../ca" ...>Catalan</a>,
     <a href=".../en" ...>English</a>,
     <a href=".../es" ...>Spanish</a>
@@ -198,10 +198,9 @@
 For a user without any preferred languages, English will be returned.
 
     >>> person = factory.makePerson()
-    >>> print person.languages
+    >>> print(person.languages)
     []
     >>> ignored = login_person(person)
     >>> languageset_view = create_initialized_view(language_set, '+index')
-    >>> print languageset_view.user_languages
+    >>> print(languageset_view.user_languages)
     <a href=".../en" ...>English</a>
-

=== modified file 'lib/lp/translations/browser/tests/pofile-views.txt'
--- lib/lp/translations/browser/tests/pofile-views.txt	2016-01-26 15:47:37 +0000
+++ lib/lp/translations/browser/tests/pofile-views.txt	2018-06-03 00:41:33 +0000
@@ -51,8 +51,8 @@
 
 We know that we want all messages.
 
-    >>> pofile_view.show
-    'all'
+    >>> print(pofile_view.show)
+    all
 
 This time, we are going to see what happens if we get an IPOFile without
 the plural form information.
@@ -81,8 +81,8 @@
 
 Yeah, it detects it correctly and stores the attribute as it should be.
 
-    >>> pofile_view.show
-    'translated'
+    >>> print(pofile_view.show)
+    translated
 
 Let's move to the navigation URLS testing.
 
@@ -149,9 +149,9 @@
     >>> for translationmessage_view in (
     ...     pofile_view.translationmessage_views):
     ...     translationmessage_view.initialize()
-    >>> print pofile_view.autofocus_html_id
+    >>> print(pofile_view.autofocus_html_id)
     msgset_130_es_translation_0_new
-    >>> print pofile_view.translations_order
+    >>> print(pofile_view.translations_order)
     msgset_130_es_translation_0_new msgset_131_es_translation_0_new
     msgset_132_es_translation_0_new msgset_133_es_translation_0_new
     msgset_134_es_translation_0_new msgset_135_es_translation_0_new
@@ -207,7 +207,7 @@
 their POTMsgSets' sequence numbers.
 
     >>> for potmsgset in pofile_view._getSelectedPOTMsgSets():
-    ...     print potmsgset.getSequence(pofile_es.potemplate)
+    ...     print(potmsgset.getSequence(pofile_es.potemplate))
     1
     2
     3
@@ -293,11 +293,11 @@
 #    >>> pomsgset.potmsgset.id
 #    144
 #    >>> for text in pomsgset.active_texts:
-#    ...     print text
+#    ...     print(text)
 #    %d contacto
 #    %d contactos
 #    >>> for text in pomsgset.published_texts:
-#    ...     print text
+#    ...     print(text)
 #    %d contacto
 #    %d contactos
 #    >>> list(pomsgset.getNewSubmissions(0))
@@ -305,10 +305,10 @@
 #    >>> list(pomsgset.getNewSubmissions(1))
 #    []
 #    >>> for submission in pomsgset.getCurrentSubmissions(0):
-#    ...     print submission.datecreated.isoformat()
+#    ...     print(submission.datecreated.isoformat())
 #    2005-04-07T...
 #    >>> for submission in pomsgset.getCurrentSubmissions(1):
-#    ...     print submission.datecreated.isoformat()
+#    ...     print(submission.datecreated.isoformat())
 #    2005-04-07T...
 
 The POMsgSet we're looking at had its submissions cache pre-populated by the
@@ -326,11 +326,11 @@
 #    >>> pomsgset.potmsgset.id
 #    144
 #    >>> for text in pomsgset.active_texts:
-#    ...     print text
+#    ...     print(text)
 #    %d contacto
 #    %d contactos
 #    >>> for text in pomsgset.published_texts:
-#    ...     print text
+#    ...     print(text)
 #    %d contacto
 #    %d contactos
 #    >>> list(pomsgset.getNewSubmissions(0))
@@ -338,10 +338,10 @@
 #    >>> list(pomsgset.getNewSubmissions(1))
 #    []
 #    >>> for submission in pomsgset.getCurrentSubmissions(0):
-#    ...     print submission.datecreated.isoformat()
+#    ...     print(submission.datecreated.isoformat())
 #    2005-04-07T...
 #    >>> for submission in pomsgset.getCurrentSubmissions(1):
-#    ...     print submission.datecreated.isoformat()
+#    ...     print(submission.datecreated.isoformat())
 #    2005-04-07T...
 
 Now, we are going to check the alternative language submission.
@@ -369,16 +369,19 @@
     >>> translationimportqueue.countEntries()
     2
     >>> for entry in translationimportqueue.getAllEntries():
-    ...     print entry.id, entry.content.filename
+    ...     print(entry.id, entry.content.filename)
     1 evolution-2.2-test.pot
     2 pt_BR.po
 
 The FileUpload class needs a class with the attributes: filename, file and
 headers.
 
+XXX cjwatson 2018-06-02: FileUploadArgument.filename can become a native
+string again once we're on zope.publisher >= 4.0.0a1.
+
     >>> class FileUploadArgument:
-    ...     filename='po/es.po'
-    ...     file=StringIO('foos')
+    ...     filename=b'po/es.po'
+    ...     file=StringIO(b'foos')
     ...     headers=''
 
 Now, we do the upload.
@@ -497,5 +500,5 @@
 
 And we are redirected to the index page, as expected:
 
-    >>> print pofile_view.request.response.getHeader('Location')
+    >>> print(pofile_view.request.response.getHeader('Location'))
     http://trans.../ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es

=== modified file 'lib/lp/translations/browser/tests/potemplate-views.txt'
--- lib/lp/translations/browser/tests/potemplate-views.txt	2015-10-05 08:36:52 +0000
+++ lib/lp/translations/browser/tests/potemplate-views.txt	2018-06-03 00:41:33 +0000
@@ -42,9 +42,12 @@
 The FileUpload class needs a class with the attributes: filename, file and
 headers.
 
+XXX cjwatson 2018-06-02: FileUploadArgument.filename can become a native
+string again once we're on zope.publisher >= 4.0.0a1.
+
     >>> class FileUploadArgument:
-    ...     filename='po/foo.pot'
-    ...     file=StringIO('foos')
+    ...     filename=b'po/foo.pot'
+    ...     file=StringIO(b'foos')
     ...     headers=''
 
 Now, we do the upload.
@@ -73,9 +76,12 @@
 From the IPOTemplate upload form, we can also upload .po files. Let's check
 that feature...
 
+XXX cjwatson 2018-06-02: FileUploadArgument.filename can become a native
+string again once we're on zope.publisher >= 4.0.0a1.
+
     >>> class FileUploadArgument:
-    ...     filename='po/es.po'
-    ...     file=StringIO('foos')
+    ...     filename=b'po/es.po'
+    ...     file=StringIO(b'foos')
     ...     headers=''
 
 We do the upload...

=== modified file 'lib/lp/translations/browser/tests/productseries-views.txt'
--- lib/lp/translations/browser/tests/productseries-views.txt	2012-11-08 04:22:30 +0000
+++ lib/lp/translations/browser/tests/productseries-views.txt	2018-06-03 00:41:33 +0000
@@ -75,16 +75,16 @@
     ...           'field.actions.save_settings': 'Save Settings'})
     >>> view = ProductSeriesTranslationsSettingsView(
     ...     productseries, request)
-    >>> print productseries.translations_autoimport_mode.title
+    >>> print(productseries.translations_autoimport_mode.title)
     None
     >>> view.initialize()
-    >>> print productseries.translations_autoimport_mode.title
+    >>> print(productseries.translations_autoimport_mode.title)
     Import template files
 
 Also, a branch job to upload the full set of translation files has been
 created to create the initial database entries from the files in the branch.
 
-    >>> print isUploadJobCreatedForBranch(productseries)
+    >>> print(isUploadJobCreatedForBranch(productseries))
     True
 
 
@@ -104,5 +104,5 @@
     >>> view = ProductSeriesTranslationsBzrImportView(
     ...     productseries, request)
     >>> view.initialize()
-    >>> print isUploadJobCreatedForBranch(productseries, True)
+    >>> print(isUploadJobCreatedForBranch(productseries, True))
     True

=== modified file 'lib/lp/translations/browser/tests/test_views.py'
--- lib/lp/translations/browser/tests/test_views.py	2011-12-28 17:03:06 +0000
+++ lib/lp/translations/browser/tests/test_views.py	2018-06-03 00:41:33 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """
@@ -33,7 +33,8 @@
     for filename in filenames:
         path = filename
         one_test = LayeredDocFileSuite(
-            path, setUp=setUp, tearDown=tearDown,
+            path,
+            setUp=lambda test: setUp(test, future=True), tearDown=tearDown,
             layer=LaunchpadFunctionalLayer,
             stdout_logging_level=logging.WARNING)
         suite.addTest(one_test)

=== modified file 'lib/lp/translations/browser/tests/translationimportqueue-views.txt'
--- lib/lp/translations/browser/tests/translationimportqueue-views.txt	2014-08-20 06:59:52 +0000
+++ lib/lp/translations/browser/tests/translationimportqueue-views.txt	2018-06-03 00:41:33 +0000
@@ -19,7 +19,7 @@
 
     >>> from lp.registry.interfaces.person import IPersonSet
     >>> importer = getUtility(IPersonSet).getByEmail('foo.bar@xxxxxxxxxxxxx')
-    >>> print importer.displayname
+    >>> print(importer.displayname)
     Foo Bar
 
 A real entry would have been uploaded in the context of productseries or a
@@ -29,18 +29,18 @@
     >>> from lp.registry.interfaces.productseries import (
     ...     IProductSeriesSet)
     >>> productseries = getUtility(IProductSeriesSet).get(3)
-    >>> print productseries.name
+    >>> print(productseries.name)
     trunk
-    >>> print productseries.summary
+    >>> print(productseries.summary)
     The primary "trunk" of development for this product...
 
 Now we create the entry in the queue.
 
     >>> queue = getUtility(ITranslationImportQueue)
     >>> pot_entry = queue.addOrUpdateEntry(
-    ...     'demo.pot', '# foo', False,
+    ...     'demo.pot', b'# foo', False,
     ...     importer, productseries=productseries)
-    >>> print pot_entry.path
+    >>> print(pot_entry.path)
     demo.pot
 
 The view is named "+index" and it is found on the TranslationsLayer. We want
@@ -53,7 +53,7 @@
 
 Upon initialization the view sets the file type.
 
-    >>> print view.initial_values['file_type']
+    >>> print(view.initial_values['file_type'])
     Template
 
 Validating correct data should not produce an error.
@@ -68,7 +68,7 @@
     >>> def validate(view, data):
     ...     view.validate(data)
     ...     for err in view.errors:
-    ...         print err
+    ...         print(err)
     >>> validate(view, data)
 
 But declaring a different file type would require different data, so we get
@@ -154,7 +154,7 @@
 We can upload to a sourcepackagename in the Ubuntu series.
 
     >>> ubuntu_entry = queue.addOrUpdateEntry(
-    ...     'demo.pot', '# foo', True,
+    ...     'demo.pot', b'# foo', True,
     ...     importer, distroseries=ubuntuseries,
     ...     sourcepackagename=packagename)
     >>> ubuntu_view = create_initialized_view(ubuntu_entry, '+index')
@@ -195,7 +195,7 @@
 
     >>> other_series = factory.makeProductSeries()
     >>> other_entry = queue.addOrUpdateEntry(
-    ...     'demo.pot', '# foo', True,
+    ...     'demo.pot', b'# foo', True,
     ...     importer, productseries=other_series)
     >>> other_view = create_initialized_view(other_entry, '+index')
     >>> 'languagepack' in other_view.field_names
@@ -205,9 +205,9 @@
 available for selection.
 
     >>> po_entry = queue.addOrUpdateEntry(
-    ...     'demo.po', '# foo', False,
+    ...     'demo.po', b'# foo', False,
     ...     importer, productseries=productseries)
-    >>> print po_entry.path
+    >>> print(po_entry.path)
     demo.po
 
 The drop-down list is fed from a vocabulary.
@@ -215,7 +215,7 @@
     >>> from lp.translations.vocabularies import TranslationTemplateVocabulary
     >>> vocab = TranslationTemplateVocabulary(po_entry)
     >>> for term in vocab:
-    ...     print term.title
+    ...     print(term.title)
     demo-name
     evolution-2.2
     evolution-2.2-test
@@ -229,6 +229,6 @@
 
     >>> vocab = TranslationTemplateVocabulary(po_entry)
     >>> for term in vocab:
-    ...     print term.title
+    ...     print(term.title)
     evolution-2.2
     evolution-2.2-test

=== modified file 'lib/lp/translations/browser/tests/translationmessage-views.txt'
--- lib/lp/translations/browser/tests/translationmessage-views.txt	2015-10-05 08:36:52 +0000
+++ lib/lp/translations/browser/tests/translationmessage-views.txt	2018-06-03 00:41:33 +0000
@@ -37,7 +37,7 @@
 
 Here we can see that it's lacking that information.
 
-    >>> print translationmessage_page_view.context.language.pluralforms
+    >>> print(translationmessage_page_view.context.language.pluralforms)
     None
 
 And the view class detects it correctly.
@@ -61,7 +61,7 @@
 
 We have the plural form information for this language.
 
-    >>> print translationmessage_page_view.context.language.pluralforms
+    >>> print(translationmessage_page_view.context.language.pluralforms)
     2
 
 And thus, the view class should know that it doesn't lacks the plural forms
@@ -118,7 +118,7 @@
 As we didn't submit the form, the getSubmittedTranslation method will
 return None.
 
-    >>> print subview.getSubmittedTranslation(0)
+    >>> print(subview.getSubmittedTranslation(0))
     None
 
 If we request a plural form that is not valid, we get an AssertionError.
@@ -287,10 +287,10 @@
     >>> translationmessage_page_view.initialize()
     >>> for notification in (
     ...     translationmessage_page_view.request.notifications):
-    ...     print notification.message
+    ...     print(notification.message)
     There is an error in the translation you provided. Please correct it
     before continuing.
-    >>> print translationmessage_page_view.error
+    >>> print(translationmessage_page_view.error)
     This translation has changed since you last saw it.  To avoid
     accidentally reverting work done by others, we added your
     translations as suggestions.  Please review the current values.
@@ -424,7 +424,7 @@
 Which produces no suggestions, because Japanese only has one form but
 Spanish has two.
 
-    >>> print suggestions.submissions
+    >>> print(suggestions.submissions)
     []
 
 However, when we use the first plural form, which exists in both
@@ -444,7 +444,7 @@
     >>> submission = suggestions.submissions[0]
     >>> for attr in sorted(dir(submission)):
     ...     if not attr.startswith('_'):
-    ...         print "%s: %s" % (attr, getattr(submission, attr))
+    ...         print("%s: %s" % (attr, getattr(submission, attr)))
     date_created: ...
     id: ...
     is_empty: False
@@ -548,7 +548,7 @@
 A shared translation is not explicitely shown, since the current one is
 the shared translation.
 
-    >>> print subview.shared_translationmessage
+    >>> print(subview.shared_translationmessage)
     None
 
 When looking at the entire POFile, diverging is not allowed.

=== modified file 'lib/lp/translations/browser/tests/translator-views.txt'
--- lib/lp/translations/browser/tests/translator-views.txt	2014-02-19 04:01:46 +0000
+++ lib/lp/translations/browser/tests/translator-views.txt	2018-06-03 00:41:33 +0000
@@ -24,16 +24,16 @@
 Translator +admin view provides a nice page title and a form label.
 
     >>> view = create_initialized_view(translator, '+admin')
-    >>> print view.label
+    >>> print(view.label)
     Edit Serbian translation team in Test translators
 
-    >>> print view.page_title
+    >>> print(view.page_title)
     Edit Serbian translation team
 
 Canceling changes to the Translator takes one back to the group
 page.
 
-    >>> print view.cancel_url
+    >>> print(view.cancel_url)
     http://translations.launchpad.dev/+groups/test-translators
 
 TranslatorEditView
@@ -44,15 +44,15 @@
 describe that appropriately.
 
     >>> view = create_initialized_view(translator, '+edit')
-    >>> print view.label
+    >>> print(view.label)
     Set Serbian guidelines for Test translators
 
-    >>> print view.page_title
+    >>> print(view.page_title)
     Set Serbian guidelines
 
 Canceling changes to the guidelines takes one back to the team page.
 
-    >>> print view.cancel_url
+    >>> print(view.cancel_url)
     http://translations.launchpad.dev/~bad-translators
 
 
@@ -63,13 +63,13 @@
 for a language in a TranslationGroup.
 
     >>> view = create_initialized_view(translator, '+remove')
-    >>> print view.label
+    >>> print(view.label)
     Unset 'Bad translators' as the Serbian translator in Test translators
 
-    >>> print view.page_title
+    >>> print(view.page_title)
     Remove translation team
 
 Canceling removal of a translation team takes one back to the group page.
 
-    >>> print view.cancel_url
+    >>> print(view.cancel_url)
     http://translations.launchpad.dev/+groups/test-translators

=== modified file 'lib/lp/translations/stories/buildfarm/xx-build-summary.txt'
--- lib/lp/translations/stories/buildfarm/xx-build-summary.txt	2014-11-27 07:48:25 +0000
+++ lib/lp/translations/stories/buildfarm/xx-build-summary.txt	2018-06-03 00:41:33 +0000
@@ -72,7 +72,7 @@
 
     >>> user_browser.mech_browser.set_handle_equiv(False)
     >>> user_browser.open(builder_page)
-    >>> print extract_text(find_build_summary(user_browser))
+    >>> print(extract_text(find_build_summary(user_browser)))
     Working on TranslationTemplatesBuild for branch ...
 
     >>> user_browser.getLink(branch_url).click()
@@ -93,7 +93,7 @@
     >>> logout()
 
     >>> user_browser.open(build_url)
-    >>> print extract_text(find_main_content(user_browser.contents))
+    >>> print(extract_text(find_main_content(user_browser.contents)))
     created ...
     Build status
         Currently building
@@ -119,7 +119,7 @@
     >>> logout()
 
     >>> user_browser.open(build_url)
-    >>> print extract_text(find_main_content(user_browser.contents))
+    >>> print(extract_text(find_main_content(user_browser.contents)))
     created ...
     Build status
         Currently building
@@ -151,7 +151,7 @@
     >>> user_browser.open(builder_page)
     >>> user_browser.getLink('View full history').click()
 
-    >>> print extract_text(find_main_content(user_browser.contents))
+    >>> print(extract_text(find_main_content(user_browser.contents)))
     Build history for ...
     1 ... 1 of 1 result
     ...

=== modified file 'lib/lp/translations/stories/distribution/xx-distribution-change-language-pack-admins.txt'
--- lib/lp/translations/stories/distribution/xx-distribution-change-language-pack-admins.txt	2009-09-14 13:47:28 +0000
+++ lib/lp/translations/stories/distribution/xx-distribution-change-language-pack-admins.txt	2018-06-03 00:41:33 +0000
@@ -34,7 +34,7 @@
     >>> browser.getControl('Language Pack Administrator').value = (
     ...     'martin.pitt@xxxxxxxxxxxxx')
     >>> browser.getControl('Change').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/ubuntu/+select-language-pack-admin
     >>> browser.getControl('Language Pack Administrator').value
     'martin.pitt@xxxxxxxxxxxxx'

=== modified file 'lib/lp/translations/stories/distribution/xx-distribution-translations.txt'
--- lib/lp/translations/stories/distribution/xx-distribution-translations.txt	2012-01-15 11:06:57 +0000
+++ lib/lp/translations/stories/distribution/xx-distribution-translations.txt	2018-06-03 00:41:33 +0000
@@ -9,7 +9,7 @@
 
     >>> browser.open('http://launchpad.dev/ubuntu')
     >>> browser.getLink('Translations').click()
-    >>> print browser.title
+    >>> print(browser.title)
     Translations : Ubuntu
 
 Check that there aren't disabled languages shown here:
@@ -43,20 +43,20 @@
 Now, we are going to check that the language list we got is pointing
 to the right translation focus.
 
-    >>> print browser.getLink('Spanish').url
+    >>> print(browser.getLink('Spanish').url)
     http://translations.launchpad.dev/ubuntu/hoary/+lang/es
-    >>> print browser.getLink('Italian').url
+    >>> print(browser.getLink('Italian').url)
     http://translations.launchpad.dev/ubuntu/hoary/+lang/it
-    >>> print browser.getLink('Portuguese (Brazil)').url
+    >>> print(browser.getLink('Portuguese (Brazil)').url)
     http://translations.launchpad.dev/ubuntu/hoary/+lang/pt_BR
 
 And the other Ubuntu distributions should be there too.
 
     >>> content = find_main_content(browser.contents)
-    >>> print extract_text(content.findAll('h2')[1])
+    >>> print(extract_text(content.findAll('h2')[1]))
     Other versions of Ubuntu
 
-    >>> print extract_text(content.find(id='distroseries-list'))
+    >>> print(extract_text(content.find(id='distroseries-list')))
     Breezy Badger Autotest (6.6.6)
     Grumpy (5.10)
     Warty (4.10)
@@ -64,11 +64,11 @@
 We are not showing its translation status here, so we should have
 links to their particular translation status.
 
-    >>> print browser.getLink('Breezy Badger Autotest (6.6.6)').url
+    >>> print(browser.getLink('Breezy Badger Autotest (6.6.6)').url)
     http://translations.launchpad.dev/ubuntu/breezy-autotest
-    >>> print browser.getLink('Grumpy (5.10)').url
+    >>> print(browser.getLink('Grumpy (5.10)').url)
     http://translations.launchpad.dev/ubuntu/grumpy
-    >>> print browser.getLink('Warty (4.10)').url
+    >>> print(browser.getLink('Warty (4.10)').url)
     http://translations.launchpad.dev/ubuntu/warty
 
 But we are already showing the status for the translation focus one,
@@ -100,25 +100,25 @@
 It doesn't have any translation, so we will get the default GeoIP
 languages pointing to the latest release, Hoary.
 
-    >>> print browser.getLink('Zulu').url
+    >>> print(browser.getLink('Zulu').url)
     http://translations.launchpad.dev/debian/sarge/+lang/zu
 
 And the other Ubuntu distributions should be there too.
 
     >>> content = find_main_content(browser.contents)
-    >>> print extract_text(content.findAll('h2')[1])
+    >>> print(extract_text(content.findAll('h2')[1]))
     Other versions of Debian
 
-    >>> print extract_text(content.find(id='distroseries-list'))
+    >>> print(extract_text(content.find(id='distroseries-list')))
     Sid (3.2)
     Woody (3.0)
 
 We are not showing its translation status here, so we should have
 links to their particular translation status.
 
-    >>> print browser.getLink('Sid (3.2)').url
+    >>> print(browser.getLink('Sid (3.2)').url)
     http://translations.launchpad.dev/debian/sid
-    >>> print browser.getLink('Woody (3.0)').url
+    >>> print(browser.getLink('Woody (3.0)').url)
     http://translations.launchpad.dev/debian/woody
 
 But we are already showing the status for the translation focus one,

=== modified file 'lib/lp/translations/stories/distroseries/xx-distroseries-language-packs.txt'
--- lib/lp/translations/stories/distroseries/xx-distroseries-language-packs.txt	2013-09-27 04:13:23 +0000
+++ lib/lp/translations/stories/distroseries/xx-distroseries-language-packs.txt	2018-06-03 00:41:33 +0000
@@ -23,33 +23,33 @@
 base language pack.
 
     >>> base = admin_browser.getControl('Language pack base')
-    >>> print base.displayValue
+    >>> print(base.displayValue)
     ['(nothing selected)']
     >>> base.displayOptions
     ['(nothing selected)', '2007-09-10 19:16:01 UTC', '2007-09-10 19:14:26 UTC']
     >>> delta = admin_browser.getControl('Language pack update')
-    >>> print delta.displayValue
+    >>> print(delta.displayValue)
     ['(nothing selected)']
     >>> proposed = admin_browser.getControl('Proposed language pack update')
-    >>> print proposed.displayValue
+    >>> print(proposed.displayValue)
     ['(nothing selected)']
 
 Let's select a base one:
 
     >>> base.displayValue = ['2007-09-10 19:14:26 UTC']
     >>> admin_browser.getControl('Change').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/ubuntu/hoary/+language-packs
 
 Now the admin page will show us that language pack selected and a list of
 available update packages.
 
     >>> base = admin_browser.getControl('Language pack base')
-    >>> print base.displayValue
+    >>> print(base.displayValue)
     ['2007-09-10 19:14:26 UTC']
 
     >>> update = admin_browser.getControl('Language pack update')
-    >>> print update.displayValue
+    >>> print(update.displayValue)
     ['(nothing selected)']
     >>> update.displayOptions
     ['(nothing selected)', '2007-09-10 19:15:19 UTC', '2007-09-10 19:15:01 UTC']
@@ -58,7 +58,8 @@
 
     >>> browser.open('http://translations.launchpad.dev/ubuntu/hoary')
     >>> browser.getLink('See all language packs').click()
-    >>> print extract_text(find_tag_by_id(browser.contents, "language_packs"))
+    >>> print(extract_text(
+    ...     find_tag_by_id(browser.contents, "language_packs")))
     A language pack...
     Active language packs
     Base pack: 2007-09-10 19:14:26 UTC
@@ -86,9 +87,9 @@
 The active base language pack URL is linking to an archive, while the latest
 URL uses '+latest-full-language-pack'.
 
-    >>> print browser.getLink('2007-09-10 19:14:26 UTC').url
+    >>> print(browser.getLink('2007-09-10 19:14:26 UTC').url)
     http.../71/ubuntu-hoary-translations.tar.gz
-    >>> print browser.getLink('2007-09-10 19:16:01 UTC').url
+    >>> print(browser.getLink('2007-09-10 19:16:01 UTC').url)
     http://translations.launchpad.dev/ubuntu/hoary/+latest-full-language-pack
 
 An administrator can choose the current update pack and there is also an
@@ -102,7 +103,7 @@
     >>> admin_browser.getControl(
     ...     'Request a full language pack export').selected = True
     >>> admin_browser.getControl('Change').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/ubuntu/hoary/+language-packs
     >>> print_feedback_messages(admin_browser.contents)
     Your request has been noted. Next language pack export will include
@@ -122,7 +123,8 @@
 
     >>> browser.open(
     ...     'http://translations.launchpad.dev/ubuntu/hoary/+language-packs')
-    >>> print extract_text(find_tag_by_id(browser.contents, "language_packs"))
+    >>> print(extract_text(
+    ...     find_tag_by_id(browser.contents, "language_packs")))
     A language pack...
     Active language packs
     Base pack: 2007-09-10 19:14:26 UTC
@@ -149,8 +151,7 @@
 The active update language pack URL is linking to an archive, while the latest
 URL uses '+latest-full-language-pack'.
 
-    >>> print browser.getLink('2007-09-10 19:15:01 UTC').url
+    >>> print(browser.getLink('2007-09-10 19:15:01 UTC').url)
     http.../72/ubuntu-hoary-translations-update.tar.gz
-    >>> print browser.getLink('2007-09-10 19:15:19 UTC').url
+    >>> print(browser.getLink('2007-09-10 19:15:19 UTC').url)
     http://translations.launchpad.dev/ubuntu/hoary/+latest-delta-language-pack
-

=== modified file 'lib/lp/translations/stories/distroseries/xx-distroseries-translations.txt'
--- lib/lp/translations/stories/distroseries/xx-distroseries-translations.txt	2011-11-28 12:00:28 +0000
+++ lib/lp/translations/stories/distroseries/xx-distroseries-translations.txt	2018-06-03 00:41:33 +0000
@@ -24,7 +24,7 @@
 
 But it shows the ones not hidden:
 
-    >>> print anon_browser.getLink('Spanish').url
+    >>> print(anon_browser.getLink('Spanish').url)
     http://translations.launchpad.dev/ubuntu/hoary/+lang/es
 
 Launchpad has an option to hide all of the translations for a distribution
@@ -55,7 +55,7 @@
     >>> dtc_browser.getControl(
     ...     'Hide translations for this release').selected = True
     >>> dtc_browser.getControl('Change').click()
-    >>> print dtc_browser.url
+    >>> print(dtc_browser.url)
     http://translations.launchpad.dev/ubuntu/hoary
 
 ...a notice about the fact shows up on the overview page.
@@ -63,7 +63,7 @@
     >>> notices = find_tags_by_class(
     ...     dtc_browser.contents, 'visibility-notice')
     >>> for notice in notices:
-    ...     print extract_text(notice)
+    ...     print(extract_text(notice))
     Translations for this series are currently hidden.
 
 Now, the translation status page will no longer display any languages to
@@ -84,7 +84,7 @@
     ...
     HTTPError: HTTP Error 503: Service Unavailable
     >>> main_content = find_main_content(user_browser.contents)
-    >>> print main_content.findNext('p').renderContents()
+    >>> print(main_content.findNext('p').renderContents())
     Translations for this release series are not available yet.
 
     >>> user_browser.handleErrors = False
@@ -139,7 +139,7 @@
     >>> dtc_browser.getControl(
     ...     'Defer translation imports').selected = True
     >>> dtc_browser.getControl('Change').click()
-    >>> print dtc_browser.url
+    >>> print(dtc_browser.url)
     http://translations.launchpad.dev/ubuntu/hoary
 
 Once the system accepts the submission, we can see such change applied.

=== modified file 'lib/lp/translations/stories/importqueue/xx-entry-details.txt'
--- lib/lp/translations/stories/importqueue/xx-entry-details.txt	2014-11-24 09:16:35 +0000
+++ lib/lp/translations/stories/importqueue/xx-entry-details.txt	2018-06-03 00:41:33 +0000
@@ -19,14 +19,14 @@
     >>> trunk = product.getSeries('trunk')
     >>> uploader = factory.makePerson()
     >>> entry = queue.addOrUpdateEntry(
-    ...     filename, '# empty', False, uploader, productseries=trunk)
+    ...     filename, b'# empty', False, uploader, productseries=trunk)
     >>> entry_url = canonical_url(entry, rootsite='translations')
     >>> logout()
 
     >>> admin_browser.open(entry_url)
     >>> details = find_tag_by_id(admin_browser.contents, 'portlet-details')
     >>> details_text = extract_text(details)
-    >>> print details_text
+    >>> print(details_text)
     Upload attached to ... trunk series.
     This project...s licence is open source.
     Release series has no templates.
@@ -44,9 +44,9 @@
 
 There's also a link to the file's contents.
 
-    >>> print admin_browser.getLink(filename).text
+    >>> print(admin_browser.getLink(filename).text)
     po/foo.pot
-    >>> print admin_browser.getLink(filename).url
+    >>> print(admin_browser.getLink(filename).url)
     http://...foo.pot
 
 
@@ -63,22 +63,22 @@
     >>> admin_browser.open(entry_url)
     >>> details = find_tag_by_id(admin_browser.contents, 'portlet-details')
     >>> details_text = extract_text(details)
-    >>> print details_text
+    >>> print(details_text)
     Upload attached to
     ...
     Release series has 1 template.
     ...
 
-    >>> print admin_browser.getLink('1 template').url
+    >>> print(admin_browser.getLink('1 template').url)
     http...://translations.launchpad.dev/.../trunk/+templates
 
     >>> admin_browser.getLink('1 template').click()
-    >>> print admin_browser.title
+    >>> print(admin_browser.title)
     All templates : Series trunk : Translations : ...
 
 In that case, the product is also shown to have translatable series.
 
-    >>> print details_text
+    >>> print(details_text)
     Upload attached to
     ...
     Project has translatable series: trunk.
@@ -98,7 +98,7 @@
     >>> distroseries = distro.getSeries('hoary')
     >>> packagename = factory.makeSourcePackageName(name='xpad')
     >>> entry = queue.addOrUpdateEntry(
-    ...     filename, '# nothing', True, uploader, distroseries=distroseries,
+    ...     filename, b'# nothing', True, uploader, distroseries=distroseries,
     ...     sourcepackagename=packagename)
     >>> entry_url = canonical_url(entry, rootsite='translations')
     >>> logout()
@@ -108,6 +108,6 @@
     >>> admin_browser.open(entry_url)
     >>> details = find_tag_by_id(admin_browser.contents, 'portlet-details')
     >>> details_text = extract_text(details)
-    >>> print details_text
+    >>> print(details_text)
     Upload attached to xpad in Ubuntu Hoary.
     File po/foo.pot uploaded by ...

=== modified file 'lib/lp/translations/stories/importqueue/xx-entry-error-output.txt'
--- lib/lp/translations/stories/importqueue/xx-entry-error-output.txt	2010-02-10 13:04:10 +0000
+++ lib/lp/translations/stories/importqueue/xx-entry-error-output.txt	2018-06-03 00:41:33 +0000
@@ -17,13 +17,13 @@
     >>> product = factory.makeProduct()
     >>> trunk = product.getSeries('trunk')
     >>> entry = queue.addOrUpdateEntry(
-    ...     'la.po', '# contents', False, product.owner, productseries=trunk)
+    ...     'la.po', b'# contents', False, product.owner, productseries=trunk)
     >>> entry_url = canonical_url(entry, rootsite='translations')
     >>> logout()
 
     >>> admin_browser.open(entry_url)
     >>> output_panel = find_error_output(admin_browser)
-    >>> print output_panel
+    >>> print(output_panel)
     None
 
 The section showing the output only shows up when there is output to
@@ -32,7 +32,7 @@
     >>> entry.error_output = "Things went horribly wrong."
     >>> admin_browser.open(entry_url)
     >>> output_panel = find_error_output(admin_browser)
-    >>> print extract_text(output_panel)
+    >>> print(extract_text(output_panel))
     Error output for this entry:
     Things went horribly wrong.
 
@@ -41,6 +41,6 @@
     >>> entry.error_output = "<h1>Injection &amp; subterfuge</h1>"
     >>> admin_browser.open(entry_url)
     >>> output_panel = find_error_output(admin_browser)
-    >>> print output_panel.renderContents()
+    >>> print(output_panel.renderContents())
     Error output for this entry:
     ...&lt;h1&gt;Injection &amp;amp; subterfuge&lt;/h1&gt;...

=== modified file 'lib/lp/translations/stories/importqueue/xx-translation-import-queue-edit-autofilling.txt'
--- lib/lp/translations/stories/importqueue/xx-translation-import-queue-edit-autofilling.txt	2016-01-26 15:47:37 +0000
+++ lib/lp/translations/stories/importqueue/xx-translation-import-queue-edit-autofilling.txt	2018-06-03 00:41:33 +0000
@@ -21,7 +21,7 @@
   >>> browser.url
   'http://translations.launchpad.dev/alsa-utils/trunk/+translations-upload'
   >>> for tag in find_tags_by_class(browser.contents, 'message'):
-  ...     print tag
+  ...     print(tag)
   <div...Thank you for your upload. 2 files from the tarball...
 
 Let's check the values we get by default from the .pot file. The name field

=== modified file 'lib/lp/translations/stories/importqueue/xx-translation-import-queue-entry.txt'
--- lib/lp/translations/stories/importqueue/xx-translation-import-queue-entry.txt	2009-09-01 20:06:37 +0000
+++ lib/lp/translations/stories/importqueue/xx-translation-import-queue-entry.txt	2018-06-03 00:41:33 +0000
@@ -12,10 +12,10 @@
 
     >>> admin_browser.open('http://translations.launchpad.dev/+imports')
     >>> admin_browser.getLink(url='imports/1').click()
-    >>> print admin_browser.getLink('Cancel').url
+    >>> print(admin_browser.getLink('Cancel').url)
     http://translations.launchpad.dev/+imports
     >>> admin_browser.getControl('Approve').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/+imports
 
 Going to the same entry from the Evolution import queue, and then approving
@@ -24,10 +24,10 @@
     >>> admin_browser.open(
     ...     'http://translations.launchpad.dev/evolution/+imports')
     >>> admin_browser.getLink(url='imports/1').click()
-    >>> print admin_browser.getLink('Cancel').url
+    >>> print(admin_browser.getLink('Cancel').url)
     http://translations.launchpad.dev/evolution/+imports
     >>> admin_browser.getControl('Approve').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/evolution/+imports
 
 Similarly, if we go to an import queue entry through the user's import
@@ -36,8 +36,8 @@
     >>> admin_browser.open(
     ...     'http://translations.launchpad.dev/~name16/+imports')
     >>> admin_browser.getLink(url='imports/1').click()
-    >>> print admin_browser.getLink('Cancel').url
+    >>> print(admin_browser.getLink('Cancel').url)
     http://translations.launchpad.dev/~name16/+imports
     >>> admin_browser.getControl('Approve').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/~name16/+imports

=== modified file 'lib/lp/translations/stories/importqueue/xx-translation-import-queue-filtering.txt'
--- lib/lp/translations/stories/importqueue/xx-translation-import-queue-filtering.txt	2015-10-05 08:36:52 +0000
+++ lib/lp/translations/stories/importqueue/xx-translation-import-queue-filtering.txt	2018-06-03 00:41:33 +0000
@@ -28,7 +28,7 @@
     ...     tarball, 'application/x-gzip', 'foo.tar.gz')
     >>> browser.getControl('Upload').click()
     >>> for tag in find_tags_by_class(browser.contents, 'message'):
-    ...     print tag
+    ...     print(tag)
     <div...Thank you for your upload. 100 files from the tarball will be
     automatically reviewed...
 
@@ -52,13 +52,14 @@
     ...     """Print "x -> y of z results" batch navigator heading."""
     ...     heading = find_tags_by_class(
     ...         browser.contents, 'batch-navigation-index')[0]
-    ...     print extract_text(heading).encode('us-ascii', 'backslashreplace')
+    ...     print(
+    ...         extract_text(heading).encode('us-ascii', 'backslashreplace'))
 
     >>> def print_dropdown(browser, name, index=0):
     ...     """Print contents of named dropdown."""
     ...     dropdown = browser.getControl(name=name, index=index)
     ...     for item in dropdown.displayOptions:
-    ...         print item
+    ...         print(item)
 
     >>> def print_targets(browser):
     ...     """Print contents of the import target dropdown."""
@@ -165,7 +166,7 @@
     ['APPROVED']
     >>> browser.getControl(name='field.filter_extension', index=0).value
     ['all']
-    >>> print browser.contents
+    >>> print(browser.contents)
     <!DOCTYPE...
     ...There are no entries that match this filtering...
 
@@ -185,7 +186,7 @@
     ['NEEDS_REVIEW']
     >>> browser.getControl(name='field.status_%d' % qid1).value = ['BLOCKED']
     >>> browser.getControl('Change status').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/+imports/+index
 
 The entry now shows up in the Blocked filter.
@@ -285,7 +286,7 @@
     ...     StringIO('foo'), 'application/x-po', 'foo.pot')
     >>> admin_browser.getControl('Upload').click()
     >>> for tag in find_tags_by_class(admin_browser.contents, 'message'):
-    ...     print extract_text(tag.renderContents())
+    ...     print(extract_text(tag.renderContents()))
     Thank you for your upload. It will be automatically reviewed...
 
     # Commit the transaction so librarian stores the uploaded file.
@@ -299,7 +300,7 @@
     >>> user_browser.getControl(
     ...     name='field.filter_target', index=0).value = ['ubuntu/hoary']
     >>> user_browser.getControl('Filter').click()
-    >>> print user_browser.url
+    >>> print(user_browser.url)
     http://.../+imports/+index?field.filter_target=ubuntu/hoary&;...
 
 The only entry that shows up now is the one Carlos just uploaded.
@@ -311,7 +312,7 @@
     >>> import_list = find_tag_by_id(
     ...     user_browser.contents, 'import-entries-list')
     >>> first_entry = import_list.findNext('tr')
-    >>> print represent_queue_entry(first_entry)
+    >>> print(represent_queue_entry(first_entry))
     foo.pot in evolution in Ubuntu Hoary
     Needs Review
 
@@ -354,7 +355,7 @@
 The "Any project" filter here shows only those Evolution uploads.
 
     >>> displayed_entries = summarize_displayed_queue_entries(user_browser)
-    >>> print displayed_entries
+    >>> print(displayed_entries)
     po/evolution-2.2-test.pot in Evolution trunk series ...
     po/pt_BR.po in Evolution trunk series ...
     foo-01.po in Evolution trunk series ...
@@ -363,7 +364,7 @@
 
 None of the Hoary uploads are shown.
 
-    >>> print re.findall('Hoary', displayed_entries)
+    >>> print(re.findall('Hoary', displayed_entries))
     []
 
 The "Any distribution" filter on the other hand shows the Hoary upload
@@ -375,10 +376,10 @@
     >>> user_browser.getControl('Filter').click()
 
     >>> displayed_entries = summarize_displayed_queue_entries(user_browser)
-    >>> print displayed_entries
+    >>> print(displayed_entries)
     foo.pot in evolution in Ubuntu Hoary ...
 
-    >>> print re.findall('Evolution', displayed_entries)
+    >>> print(re.findall('Evolution', displayed_entries))
     []
 
 
@@ -393,4 +394,3 @@
     Traceback (most recent call last):
     ...
     UnexpectedFormData: Unknown target.
-

=== modified file 'lib/lp/translations/stories/importqueue/xx-translation-import-queue-targets.txt'
--- lib/lp/translations/stories/importqueue/xx-translation-import-queue-targets.txt	2009-09-14 19:00:45 +0000
+++ lib/lp/translations/stories/importqueue/xx-translation-import-queue-targets.txt	2018-06-03 00:41:33 +0000
@@ -14,12 +14,12 @@
 
 There is no content for Ubuntu.
 
-    >>> print find_tag_by_id(
-    ...     user_browser.contents, 'description').renderContents().strip()
+    >>> print(find_tag_by_id(
+    ...     user_browser.contents, 'description').renderContents().strip())
     These translation related entries are imported, blocked, deleted or
     waiting to be imported in Launchpad for Ubuntu.
-    >>> print find_tag_by_id(
-    ...     user_browser.contents, 'no-entries').renderContents()
+    >>> print(find_tag_by_id(
+    ...     user_browser.contents, 'no-entries').renderContents())
     There are no entries that match this filtering.
     >>> find_tag_by_id(user_browser.contents, 'import-entries-list') is None
     True
@@ -35,12 +35,12 @@
 And obviously, given that the ubuntu distribution had no content, Hoary, an
 Ubuntu distro series has also no content.
 
-    >>> print find_tag_by_id(
-    ...     user_browser.contents, 'description').renderContents().strip()
+    >>> print(find_tag_by_id(
+    ...     user_browser.contents, 'description').renderContents().strip())
     These translation related entries are imported, blocked, deleted or
     waiting to be imported in Launchpad for Hoary.
-    >>> print find_tag_by_id(
-    ...     user_browser.contents, 'no-entries').renderContents()
+    >>> print(find_tag_by_id(
+    ...     user_browser.contents, 'no-entries').renderContents())
     There are no entries that match this filtering.
     >>> find_tag_by_id(user_browser.contents, 'import-entries-list') is None
     True
@@ -55,8 +55,8 @@
 
 This time, we do have content for this product:
 
-    >>> print find_tag_by_id(
-    ...     user_browser.contents, 'description').renderContents().strip()
+    >>> print(find_tag_by_id(
+    ...     user_browser.contents, 'description').renderContents().strip())
     These translation related entries are imported, blocked, deleted or
     waiting to be imported in Launchpad for Evolution.
     >>> find_tag_by_id(user_browser.contents, 'no-entries') is None
@@ -67,13 +67,13 @@
     >>> import_list = find_tag_by_id(
     ...     user_browser.contents, 'import-entries-list')
     >>> first_entry = import_list.findNext('tr')
-    >>> print extract_text(first_entry)
+    >>> print(extract_text(first_entry))
     po/evolution-2.2-test.pot in
     Evolution trunk series
     Imported
     >>> second_entry = first_entry.findNext(
     ...     'tr').findNext('tr').findNext('tr')
-    >>> print extract_text(second_entry)
+    >>> print(extract_text(second_entry))
     po/pt_BR.po in
     Evolution trunk series
     Imported
@@ -88,8 +88,8 @@
 
 This time, we do have content for this product:
 
-    >>> print find_tag_by_id(
-    ...     user_browser.contents, 'description').renderContents().strip()
+    >>> print(find_tag_by_id(
+    ...     user_browser.contents, 'description').renderContents().strip())
     These translation related entries are imported, blocked, deleted or
     waiting to be imported in Launchpad for trunk.
     >>> find_tag_by_id(user_browser.contents, 'no-entries') is None
@@ -101,13 +101,13 @@
     >>> import_list = find_tag_by_id(
     ...     user_browser.contents, 'import-entries-list')
     >>> first_entry = import_list.findNext('tr')
-    >>> print extract_text(first_entry)
+    >>> print(extract_text(first_entry))
     po/evolution-2.2-test.pot in
     Evolution trunk series
     Imported
     >>> second_entry = first_entry.findNext(
     ...     'tr').findNext('tr').findNext('tr')
-    >>> print extract_text(second_entry)
+    >>> print(extract_text(second_entry))
     po/pt_BR.po in
     Evolution trunk series
     Imported
@@ -122,8 +122,8 @@
 
 This time, we do have content for this product:
 
-    >>> print find_tag_by_id(
-    ...     user_browser.contents, 'description').renderContents().strip()
+    >>> print(find_tag_by_id(
+    ...     user_browser.contents, 'description').renderContents().strip())
     These translation related entries are imported, blocked, deleted or
     waiting to be imported in Launchpad for Foo Bar.
     >>> find_tag_by_id(user_browser.contents, 'no-entries') is None
@@ -135,23 +135,23 @@
     >>> import_list = find_tag_by_id(
     ...     user_browser.contents, 'import-entries-list')
     >>> first_entry = import_list.findNext('tr')
-    >>> print extract_text(first_entry)
+    >>> print(extract_text(first_entry))
     po/evolution-2.2-test.pot in
     Evolution trunk series
     Imported
     >>> first_entry_importer = first_entry.findNext('tr')
-    >>> print extract_text(first_entry_importer)
+    >>> print(extract_text(first_entry_importer))
     Uploaded by
     Foo Bar
     on 2006-12-13 22:17:56 CET
     >>> second_entry = first_entry_importer.findNext(
     ...     'tr').findNext('tr')
-    >>> print extract_text(second_entry)
+    >>> print(extract_text(second_entry))
     po/pt_BR.po in
     Evolution trunk series
     Imported
     >>> second_entry_importer = second_entry.findNext('tr')
-    >>> print extract_text(second_entry_importer)
+    >>> print(extract_text(second_entry_importer))
     Uploaded by
     Foo Bar
     on 2006-12-13 22:18:28 CET

=== modified file 'lib/lp/translations/stories/importqueue/xx-translation-import-queue.txt'
--- lib/lp/translations/stories/importqueue/xx-translation-import-queue.txt	2016-01-26 15:47:37 +0000
+++ lib/lp/translations/stories/importqueue/xx-translation-import-queue.txt	2018-06-03 00:41:33 +0000
@@ -31,7 +31,7 @@
     True
     >>> for tag in find_tags_by_class(ff_owner_browser.contents,
     ...                               'informational message'):
-    ...     print extract_text(tag.renderContents())
+    ...     print(extract_text(tag.renderContents()))
     Thank you for your upload. 2 files from the tarball will be automatically
     reviewed in the next few hours...
 
@@ -39,7 +39,7 @@
 import status:
 
     >>> ff_owner_browser.getLink('Translation Import Queue').click()
-    >>> print ff_owner_browser.url
+    >>> print(ff_owner_browser.url)
     http://translations.launchpad.dev/firefox/1.0/+imports
 
 Once the upload has completed, its templates and translations show up on
@@ -48,12 +48,12 @@
 
     >>> anon_browser.open(
     ...     'http://translations.launchpad.dev/firefox/1.0/+imports')
-    >>> print anon_browser.url
+    >>> print(anon_browser.url)
     http://translations.launchpad.dev/firefox/1.0/+imports
     >>> row = find_tags_by_class(anon_browser.contents, 'import_entry_row')[1]
-    >>> print extract_text(row.find('', 'import_source'))
+    >>> print(extract_text(row.find('', 'import_source')))
     po/es.po in Mozilla Firefox 1.0 series
-    >>> print extract_text(row.find('', 'import_status'))
+    >>> print(extract_text(row.find('', 'import_status')))
     Needs Review
 
 Some tarballs contain files whose names look like PO or POT files, but
@@ -116,10 +116,10 @@
   >>> upload.add_file(StringIO('# foo\n'),
   ...   'text/x-gettext-translation-template', 'evolution.pot')
   >>> browser.getControl('Upload').click()
-  >>> print browser.url
+  >>> print(browser.url)
   http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/+upload
   >>> for tag in find_tags_by_class(browser.contents, 'message'):
-  ...     print tag.renderContents()
+  ...     print(tag.renderContents())
   Thank you for your upload.  It will be automatically reviewed...
 
 The import queue should have three additional entries with the last upload as
@@ -128,10 +128,10 @@
   >>> anon_browser.open('http://translations.launchpad.dev/+imports')
   >>> nav_index = first_tag_by_class(anon_browser.contents,
   ...     'batch-navigation-index')
-  >>> print extract_text(nav_index)
+  >>> print(extract_text(nav_index))
   1 &rarr; 5 of 5 results
   >>> rows = find_tags_by_class(anon_browser.contents, 'import_entry_row')
-  >>> print extract_text(rows[4])
+  >>> print(extract_text(rows[4]))
   evolution.pot in
   evolution in Ubuntu Hoary
   Needs Review
@@ -149,17 +149,17 @@
   >>> browser.getControl('Name').value = '.InvalidName'
   >>> browser.getControl('Translation domain').value = 'pkgconf-mozilla'
   >>> browser.getControl('Approve').click()
-  >>> print browser.url
+  >>> print(browser.url)
   http://translations.launchpad.dev/+imports/.../+index
   >>> message = find_tags_by_class(browser.contents, 'message')[1]
-  >>> print message.string
+  >>> print(message.string)
   Please specify a valid name...
 
 So we'd better specify a valid name.
 
   >>> browser.getControl('Name').value = 'pkgconf-mozilla'
   >>> browser.getControl('Approve').click()
-  >>> print browser.url
+  >>> print(browser.url)
   http://translations.launchpad.dev/+imports
 
 Open the edit form for the fourth entry.
@@ -179,7 +179,7 @@
   ...     'pkgconf-mozilla']
   >>> browser.getControl('Language').value = ['es']
   >>> browser.getControl('Approve').click()
-  >>> print browser.url
+  >>> print(browser.url)
   http://translations.launchpad.dev/+imports
 
 The entries are approved, and now have the place where they will be
@@ -188,7 +188,7 @@
   >>> anon_browser.open('http://translations.launchpad.dev/+imports')
   >>> imports_table = find_tag_by_id(
   ...     anon_browser.contents, 'import-entries-list')
-  >>> print extract_text(imports_table)
+  >>> print(extract_text(imports_table))
   pkgconf-mozilla.pot in
   Mozilla Firefox 1.0 series
   Approved
@@ -226,7 +226,7 @@
   ...     'http://translations.launchpad.dev/+imports',
   ...     data=post_data)
   >>> for status in find_tags_by_class(user_browser.contents, 'import_status'):
-  ...     print extract_text(status)
+  ...     print(extract_text(status))
   Approved
   Approved
   Imported
@@ -242,7 +242,7 @@
   >>> jordi_browser.url
   'http://translations.launchpad.dev/+imports/+index'
 
-  >>> print find_main_content(jordi_browser.contents)
+  >>> print(find_main_content(jordi_browser.contents))
   <...po/evolution-2.2-test.pot...
   ...Evolution trunk series...
   ...field.status_1...
@@ -258,7 +258,7 @@
   >>> admin_browser.url
   'http://translations.launchpad.dev/+imports/+index'
 
-  >>> print find_main_content(admin_browser.contents)
+  >>> print(find_main_content(admin_browser.contents))
   <...po/pt_BR.po...
   ...Evolution trunk series...
   ...field.status_2...
@@ -279,7 +279,7 @@
 
 The entry now appears deleted.
 
-  >>> print find_main_content(ff_owner_browser.contents)
+  >>> print(find_main_content(ff_owner_browser.contents))
   <...po/es.po...
   ...Mozilla Firefox 1.0 series...
   ...field.status_...
@@ -315,7 +315,7 @@
     >>> uploader = factory.makePerson()
     >>> ubuntu.translationgroup = factory.makeTranslationGroup(group_owner)
     >>> ubuntu_upload = queue.addOrUpdateEntry(
-    ...     'messages.pot', '(content)', False, uploader,
+    ...     'messages.pot', b'(content)', False, uploader,
     ...     sourcepackagename=package, distroseries=hoary)
 
     >>> logout()
@@ -354,7 +354,7 @@
   >>> evo_owner_browser.url
   'http://translations.launchpad.dev/evolution/trunk/+translations-upload'
   >>> for tag in find_tags_by_class(evo_owner_browser.contents, 'message'):
-  ...     print extract_text(tag)
+  ...     print(extract_text(tag))
   Thank you for your upload. 2 files from the tarball will be automatically
   reviewed...
 
@@ -366,7 +366,7 @@
   ...     '+source/evolution/+pots/evolution-2.2/+upload')
   >>> browser.getControl('Upload').click()
   >>> for tag in find_tags_by_class(browser.contents, 'message'):
-  ...     print tag
+  ...     print(tag)
   <div...Your upload was ignored because you didn&#x27;t select a file....
   ...Please select a file and try again.</div>...
 
@@ -387,7 +387,7 @@
   >>> evo_owner_browser.url
   'http://translations.launchpad.dev/evolution/trunk/+translations-upload'
   >>> for tag in find_tags_by_class(evo_owner_browser.contents, 'message'):
-  ...     print extract_text(tag)
+  ...     print(extract_text(tag))
   Thank you for your upload. 1 file from the tarball will be automatically
   reviewed...
 
@@ -408,7 +408,7 @@
   >>> evo_owner_browser.url
   'http://translations.launchpad.dev/evolution/trunk/+translations-upload'
   >>> for tag in find_tags_by_class(evo_owner_browser.contents, 'message'):
-  ...     print extract_text(tag)
+  ...     print(extract_text(tag))
   Upload ignored.  The tarball you uploaded did not contain...
 
 And also a truncated tarball inside a bzip2 wrapper:
@@ -428,7 +428,7 @@
   >>> evo_owner_browser.url
   'http://translations.launchpad.dev/evolution/trunk/+translations-upload'
   >>> for tag in find_tags_by_class(evo_owner_browser.contents, 'message'):
-  ...     print extract_text(tag)
+  ...     print(extract_text(tag))
   Upload ignored.  The tarball you uploaded did not contain...
 
 Or even files that are not really tar.gz files even if the filename

=== modified file 'lib/lp/translations/stories/navigation-links/pofile.txt'
--- lib/lp/translations/stories/navigation-links/pofile.txt	2014-11-26 23:50:26 +0000
+++ lib/lp/translations/stories/navigation-links/pofile.txt	2018-06-03 00:41:33 +0000
@@ -8,7 +8,7 @@
     >>> admin_browser.open(
     ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
     ...     'evolution-2.2/es')
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2/es
 
 The Application tabs should point to IProduct URLs.
@@ -26,7 +26,7 @@
     >>> admin_browser.open(
     ...     'http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/'
     ...     '+pots/evolution-2.2/es')
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es
 
 The Application tabs should point to IDistributionSourcePackage URLs.
@@ -38,4 +38,3 @@
     * Blueprints - not linked
     * Translations (selected) - http://translations.launchpad.dev/ubuntu/+source/evolution
     * Answers - http://answers.launchpad.dev/ubuntu/+source/evolution
-

=== modified file 'lib/lp/translations/stories/navigation-links/pomsgset.txt'
--- lib/lp/translations/stories/navigation-links/pomsgset.txt	2014-11-26 23:50:26 +0000
+++ lib/lp/translations/stories/navigation-links/pomsgset.txt	2018-06-03 00:41:33 +0000
@@ -10,7 +10,7 @@
 We get a +translate page because that's the only page for an IPOMsgSet and the
 system forwards automatically there.
 
-  >>> print browser.url
+  >>> print(browser.url)
   http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2/es/1/+translate
 
 The Application tabs should point to IProductSeries URLs.
@@ -32,7 +32,7 @@
 We get a +translate page because that's the only page for an IPOMsgSet and the
 system forwards automatically there.
 
-  >>> print browser.url
+  >>> print(browser.url)
   http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es/1/+translate
 
 The Application tabs should point to ISourcePackage URLs.

=== modified file 'lib/lp/translations/stories/navigation-links/potemplate.txt'
--- lib/lp/translations/stories/navigation-links/potemplate.txt	2014-11-26 23:50:26 +0000
+++ lib/lp/translations/stories/navigation-links/potemplate.txt	2018-06-03 00:41:33 +0000
@@ -6,7 +6,7 @@
   >>> admin_browser.open(
   ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
   ...     'evolution-2.2')
-  >>> print admin_browser.url
+  >>> print(admin_browser.url)
   http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2
 
 The Application tabs should point to IProduct URLs.
@@ -24,7 +24,7 @@
   >>> admin_browser.open(
   ...     'http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/'
   ...     '+pots/evolution-2.2')
-  >>> print admin_browser.url
+  >>> print(admin_browser.url)
   http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2
 
 The Application tabs should point to IDistributionSourcePackage URLs.

=== modified file 'lib/lp/translations/stories/productseries/xx-productseries-export-to-branch.txt'
--- lib/lp/translations/stories/productseries/xx-productseries-export-to-branch.txt	2012-05-24 20:25:54 +0000
+++ lib/lp/translations/stories/productseries/xx-productseries-export-to-branch.txt	2018-06-03 00:41:33 +0000
@@ -52,37 +52,37 @@
 
 The settings page currently shows that no branch has been selected.
 
-    >>> print extract_text(get_translations_branch_paragraph(owner_browser))
+    >>> print(extract_text(get_translations_branch_paragraph(owner_browser)))
     Currently not exporting translations to a branch.
     Choose a target branch.
 
 The notice links to a page where a translations branch can be selected.
 
     >>> owner_browser.getLink('Choose a target branch').click()
-    >>> print owner_browser.url
+    >>> print(owner_browser.url)
     http://translations.launchpad.dev/.../trunk/+link-translations-branch
 
     >>> set_translations_branch(owner_browser, branch_name)
 
 After setting the branch, the form returns to the settings page.
 
-    >>> print owner_browser.url
+    >>> print(owner_browser.url)
     http://translations.launchpad.dev/.../trunk/+translations-settings
 
 It shows the changed setting.
 
-    >>> print extract_text(get_translations_branch_paragraph(owner_browser))
+    >>> print(extract_text(get_translations_branch_paragraph(owner_browser)))
     Exporting translations to branch: lp:...
 
 The notice links to the branch, and to the page where the setting can be
 changed.
 
     >>> edit_link = owner_browser.getLink(id='translations-branch-edit-link')
-    >>> print edit_link.url
+    >>> print(edit_link.url)
     http://translations.launchpad.dev/.../trunk/+link-translations-branch
 
     >>> branch_link = owner_browser.getLink(url=branch_name)
-    >>> print branch_link.url
+    >>> print(branch_link.url)
     http://code.launchpad.dev/~.../.../...
 
     >>> branch_link.click()
@@ -91,7 +91,7 @@
 
     >>> back_reference = find_tag_by_id(
     ...     owner_browser.contents, 'translations-sources')
-    >>> print back_reference
+    >>> print(back_reference)
     <div ...>
     <h2>Automatic translations commits</h2>
     <ul>
@@ -108,12 +108,12 @@
     >>> owner_browser.open(link_page)
     >>> set_translations_branch(owner_browser, '')
 
-    >>> print owner_browser.url
+    >>> print(owner_browser.url)
     http://translations.launchpad.dev/.../trunk/+translations-settings
 
 The settings page then goes back to showing the original message.
 
-    >>> print extract_text(get_translations_branch_paragraph(owner_browser))
+    >>> print(extract_text(get_translations_branch_paragraph(owner_browser)))
     Currently not exporting translations to a branch.
     Choose a target branch.
 
@@ -123,7 +123,7 @@
     >>> owner_browser.open(branch_page)
     >>> back_reference = find_tag_by_id(
     ...     owner_browser.contents, 'translations-sources')
-    >>> print back_reference
+    >>> print(back_reference)
     None
 
 
@@ -140,7 +140,7 @@
 This leaves the translations_branch unchanged.
 
     >>> owner_browser.open(settings_page)
-    >>> print extract_text(get_translations_branch_paragraph(owner_browser))
+    >>> print(extract_text(get_translations_branch_paragraph(owner_browser)))
     Currently not exporting translations to a branch.
     Choose a target branch.
 

=== modified file 'lib/lp/translations/stories/productseries/xx-productseries-translation-export.txt'
--- lib/lp/translations/stories/productseries/xx-productseries-translation-export.txt	2014-11-24 09:16:35 +0000
+++ lib/lp/translations/stories/productseries/xx-productseries-translation-export.txt	2018-06-03 00:41:33 +0000
@@ -13,7 +13,7 @@
 
     >>> user_browser.open('http://translations.launchpad.dev/evolution/trunk/')
     >>> user_browser.getLink('download').click()
-    >>> print user_browser.url
+    >>> print(user_browser.url)
     http://translations.launchpad.dev/evolution/trunk/+export
 
 Another way of getting there is by going to the product's +translate page.
@@ -23,7 +23,7 @@
     >>> user_browser.open(
     ...     'http://translations.launchpad.dev/evolution/+translations')
     >>> user_browser.getLink('download').click()
-    >>> print user_browser.url
+    >>> print(user_browser.url)
     http://translations.launchpad.dev/evolution/trunk/+export
 
 
@@ -44,7 +44,7 @@
 The logged-in user sees a page that lets them select an export format, and
 request the download.
 
-    >>> print user_browser.title
+    >>> print(user_browser.title)
     Download : Series trunk : Translations...
 
 
@@ -63,7 +63,7 @@
     >>> user_browser.getControl('Format:').value = ['PO']
     >>> user_browser.getControl('Request Download').click()
 
-    >>> print user_browser.url
+    >>> print(user_browser.url)
     http://translations.launchpad.dev/evolution/trunk
 
     >>> print_feedback_messages(user_browser.contents)
@@ -74,7 +74,7 @@
     >>> user_browser.getLink('download').click()
     >>> user_browser.getControl('Format:').value = ['PO']
     >>> user_browser.getControl('Request Download').click()
-    >>> print user_browser.url
+    >>> print(user_browser.url)
     http://translations.launchpad.dev/evolution/trunk
 
     >>> print_feedback_messages(user_browser.contents)
@@ -98,4 +98,3 @@
     Traceback (most recent call last):
     ...
     LinkNotFoundError
-

=== modified file 'lib/lp/translations/stories/productseries/xx-productseries-translations-bzr-request.txt'
--- lib/lp/translations/stories/productseries/xx-productseries-translations-bzr-request.txt	2013-09-27 04:13:23 +0000
+++ lib/lp/translations/stories/productseries/xx-productseries-translations-bzr-request.txt	2018-06-03 00:41:33 +0000
@@ -13,7 +13,7 @@
     >>> browser.open(
     ...     'http://translations.launchpad.dev/evolution/trunk/')
     >>> browser.getLink('Request an import from bazaar').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.l...d.dev/evolution/trunk/+request-bzr-import
 
 == The request page with a branch ==
@@ -21,7 +21,7 @@
 to request an import. The current branch is displayed on the page.
 
     >>> branch = find_tag_by_id(browser.contents, 'branch-display')
-    >>> print extract_text(branch)
+    >>> print(extract_text(branch))
     The official Bazaar branch is:
     lp://dev/evolution
 
@@ -29,10 +29,10 @@
 continuously, the user is reminded of that here.
 
     >>> settings = find_tag_by_id(browser.contents, 'settings-display')
-    >>> print extract_text(settings)
+    >>> print(extract_text(settings))
     To enable continuous imports please change the settings here.
     >>> browser.getLink('here').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.l...d.dev/evolution/trunk/+translations-settings
 
 Changing that setting will make that message disappear from the page.
@@ -45,7 +45,7 @@
     ...     'http://translations.launchpad.dev/evolution/trunk/'
     ...     '+request-bzr-import')
     >>> settings = find_tag_by_id(browser.contents, 'settings-display')
-    >>> print settings
+    >>> print(settings)
     None
 
 The request is made by clicking on a button labeled
@@ -53,10 +53,10 @@
 
     >>> request_button = find_tag_by_id(
     ...     browser.contents, 'field.actions.request_import')
-    >>> print request_button
+    >>> print(request_button)
     <input type="submit"...value="Request one-time import"...
     >>> browser.getControl("Request one-time import").click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/evolution/trunk
     >>> print_feedback_messages(browser.contents)
     The import has been requested.
@@ -76,11 +76,11 @@
     ...     'http://translations.launchpad.dev/evolution/trunk/'
     ...     '+request-bzr-import')
     >>> branch = find_tag_by_id(browser.contents, 'no-branch-display')
-    >>> print extract_text(branch)
+    >>> print(extract_text(branch))
     This series does not have an official Bazaar branch.
     Please set it first.
     >>> browser.getLink('Please set it first.').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://launchpad.dev/evolution/trunk/+setbranch
 
 The request button is missing completely from the page.
@@ -90,5 +90,5 @@
     ...     '+request-bzr-import')
     >>> request_button = find_tag_by_id(
     ...     browser.contents, 'field.actions.request_import')
-    >>> print request_button
+    >>> print(request_button)
     None

=== modified file 'lib/lp/translations/stories/productseries/xx-productseries-translations-settings.txt'
--- lib/lp/translations/stories/productseries/xx-productseries-translations-settings.txt	2016-01-26 15:47:37 +0000
+++ lib/lp/translations/stories/productseries/xx-productseries-translations-settings.txt	2018-06-03 00:41:33 +0000
@@ -17,7 +17,7 @@
     >>> browser.open(
     ...     'http://translations.launchpad.dev/evolution/trunk/')
     >>> browser.getLink('Set up branch synchronization').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.l...d.dev/evolution/trunk/+translations-settings
 
 The branch display
@@ -27,7 +27,7 @@
 displayed on the page.
 
     >>> branch = find_tag_by_id(browser.contents, 'branch-display')
-    >>> print extract_text(branch)
+    >>> print(extract_text(branch))
     The official Bazaar branch is:
     lp://dev/evolution
 
@@ -43,11 +43,11 @@
     ...     'http://translations.launchpad.dev/evolution/trunk/'
     ...     '+translations-settings')
     >>> branch = find_tag_by_id(browser.contents, 'no-branch-display')
-    >>> print extract_text(branch)
+    >>> print(extract_text(branch))
     This series does not have an official Bazaar branch.
     Set it now!
     >>> browser.getLink('Set it now!').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://launchpad.dev/evolution/trunk/+setbranch
 
 Pointer to one-time import
@@ -60,10 +60,10 @@
     ...     'http://translations.launchpad.dev/evolution/trunk/'
     ...     '+translations-settings')
     >>> settings = find_tag_by_id(browser.contents, 'bzr-request-display')
-    >>> print extract_text(settings)
+    >>> print(extract_text(settings))
     You can request a one-time import.
     >>> browser.getLink('request a one-time import').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.l...d.dev/evolution/trunk/+request-bzr-import
 
 Changing the setting
@@ -86,7 +86,7 @@
 
 The user is automatically redirected to the page they came from.
 
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/evolution/trunk/
     >>> print_feedback_messages(browser.contents)
     The settings have been updated.

=== modified file 'lib/lp/translations/stories/productseries/xx-productseries-translations.txt'
--- lib/lp/translations/stories/productseries/xx-productseries-translations.txt	2015-06-15 08:35:10 +0000
+++ lib/lp/translations/stories/productseries/xx-productseries-translations.txt	2018-06-03 00:41:33 +0000
@@ -27,30 +27,30 @@
     >>> def print_language_stats(browser):
     ...     table = find_tag_by_id(browser.contents, 'languagestats')
     ...     if table is None:
-    ...         print "No translations."
+    ...         print("No translations.")
     ...         return
     ...     language_rows = find_tags_by_class(str(table), 'stats')
-    ...     print "%-25s %13s %13s" % (
-    ...         "Language", "Untranslated", "Unreviewed")
+    ...     print("%-25s %13s %13s" % (
+    ...         "Language", "Untranslated", "Unreviewed"))
     ...     for row in language_rows:
     ...         cols = row.findAll('td')
     ...         language = extract_text(cols[0])
     ...         untranslated = extract_link_info(cols[2])
     ...         unreviewed = extract_link_info(cols[3])
-    ...         print "%-25s %13d %13d\n" % (
-    ...             language, untranslated[0], unreviewed[0])
-    ...         print "Untranslated link: %s\n" % untranslated[1]
-    ...         print "Unreviewed link: %s\n" % unreviewed[1]
+    ...         print("%-25s %13d %13d\n" % (
+    ...             language, untranslated[0], unreviewed[0]))
+    ...         print("Untranslated link: %s\n" % untranslated[1])
+    ...         print("Unreviewed link: %s\n" % unreviewed[1])
 
 When there are no translatable templates, series is considered as not
 being set up for translation.
 
     >>> anon_browser.open(frobnicator_trunk_url)
-    >>> print anon_browser.title
+    >>> print(anon_browser.title)
     Series trunk : Translations : Frobnicator
 
     >>> main_content = find_main_content(anon_browser.contents)
-    >>> print extract_text(main_content.findAll('h1')[0])
+    >>> print(extract_text(main_content.findAll('h1')[0]))
     Translation status by language
 
     >>> print_language_stats(anon_browser)
@@ -59,7 +59,7 @@
 Explanation is shown to indicate that there are no translations for
 this series.
 
-    >>> print extract_text(main_content.findAll('p')[0])
+    >>> print(extract_text(main_content.findAll('p')[0]))
     There are no translations for this release series.
 
 Administrator will also see instructions on how to set up a project for
@@ -68,7 +68,7 @@
     >>> admin_browser.open(frobnicator_trunk_url)
     >>> main_content = find_main_content(admin_browser.contents)
     >>> paragraphs = main_content.findAll('p')
-    >>> print extract_text(main_content.findAll('p')[1])
+    >>> print(extract_text(main_content.findAll('p')[1]))
     To start translating your project...
 
 With one translatable template (with fake stats for 10 messages), a listing
@@ -145,8 +145,8 @@
     >>> owner_browser.open(
     ...     'http://translations.launchpad.dev/'
     ...     'evolution/trunk/+translations-upload')
-    >>> print find_tag_by_id(
-    ...     owner_browser.contents, 'not-translated-in-launchpad')
+    >>> print(find_tag_by_id(
+    ...     owner_browser.contents, 'not-translated-in-launchpad'))
     None
 
 Nor does it appear on the template upload pages.
@@ -154,8 +154,8 @@
     >>> owner_browser.open(
     ...     'http://translations.launchpad.dev/'
     ...     'evolution/trunk/+pots/evolution-2.2/+upload')
-    >>> print find_tag_by_id(
-    ...     owner_browser.contents, 'not-translated-in-launchpad')
+    >>> print(find_tag_by_id(
+    ...     owner_browser.contents, 'not-translated-in-launchpad'))
     None
 
 Now this is changed: Evolution's owner configures it not to use
@@ -171,13 +171,13 @@
     >>> owner_browser.open(
     ...     'http://translations.launchpad.dev/'
     ...     'bazaar/trunk/+translations-upload')
-    >>> print extract_text(
+    >>> print(extract_text(
     ...     find_tag_by_id(
-    ...         owner_browser.contents, 'not-translated-in-launchpad'))
+    ...         owner_browser.contents, 'not-translated-in-launchpad')))
     trunk does not translate its messages.
-    >>> print extract_text(
+    >>> print(extract_text(
     ...     find_tag_by_id(
-    ...         owner_browser.contents, 'translations-explanation'))
+    ...         owner_browser.contents, 'translations-explanation')))
     Launchpad allows communities to translate projects using
     imports or a branch.
     Getting started with translating your project in Launchpad
@@ -186,7 +186,7 @@
 The notice links to the page for configuring translations on the project.
 
     >>> owner_browser.getLink('Translations', index=1).click()
-    >>> print owner_browser.url
+    >>> print(owner_browser.url)
     http://.../bazaar/+configure-translations
 
 An administrator also sees the notice.
@@ -194,13 +194,13 @@
     >>> admin_browser.open(
     ...     'http://translations.launchpad.dev/'
     ...     'bazaar/trunk/+translations-upload')
-    >>> print extract_text(
+    >>> print(extract_text(
     ...     find_tag_by_id(
-    ...         admin_browser.contents, 'not-translated-in-launchpad'))
+    ...         admin_browser.contents, 'not-translated-in-launchpad')))
     trunk does not translate its messages.
-    >>> print extract_text(
+    >>> print(extract_text(
     ...     find_tag_by_id(
-    ...         admin_browser.contents, 'translations-explanation'))
+    ...         admin_browser.contents, 'translations-explanation')))
     Launchpad allows communities to translate projects using
     imports or a branch.
     Getting started with translating your project in Launchpad
@@ -235,9 +235,9 @@
     ...     'http://translations.launchpad.dev/'
     ...     'bazaar/trunk/+translations-upload')
 
-    >>> print extract_text(
+    >>> print(extract_text(
     ...     find_tag_by_id(
-    ...         jtv_browser.contents, 'not-translated-in-launchpad'))
+    ...         jtv_browser.contents, 'not-translated-in-launchpad')))
     trunk does not translate its messages.
 
 Branch synchronization options
@@ -248,7 +248,7 @@
     >>> browser.open(frobnicator_trunk_url)
     >>> sync_settings = first_tag_by_class(
     ...     browser.contents, 'automatic-synchronization')
-    >>> print extract_text(sync_settings)
+    >>> print(extract_text(sync_settings))
     Automatic synchronization
     This project is currently not using any synchronization
     with bazaar branches.
@@ -269,7 +269,7 @@
     >>> browser.open(frobnicator_trunk_url)
     >>> sync_settings = first_tag_by_class(
     ...     browser.contents, 'automatic-synchronization')
-    >>> print extract_text(sync_settings)
+    >>> print(extract_text(sync_settings))
     Automatic synchronization
     Translations are exported daily to branch
     lp://dev/~person-name.../frobnicator/branch....
@@ -287,7 +287,7 @@
     >>> browser.open(frobnicator_trunk_url)
     >>> sync_settings = first_tag_by_class(
     ...     browser.contents, 'automatic-synchronization')
-    >>> print extract_text(sync_settings)
+    >>> print(extract_text(sync_settings))
     Automatic synchronization
     This project is currently not using any synchronization
     with bazaar branches.
@@ -301,7 +301,7 @@
     >>> browser.open(frobnicator_trunk_url)
     >>> sync_settings = first_tag_by_class(
     ...     browser.contents, 'automatic-synchronization')
-    >>> print extract_text(sync_settings)
+    >>> print(extract_text(sync_settings))
     Automatic synchronization
     Translations are imported with every update from branch
     lp://dev/frobnicator.
@@ -317,11 +317,11 @@
     >>> distribution = factory.makeDistribution(name='earthian')
     >>> distroseries = factory.makeDistroSeries(
     ...     name='1.4', distribution=distribution)
-    >>> print distribution.translation_focus
+    >>> print(distribution.translation_focus)
     None
     >>> logout()
     >>> admin_browser.open('http://translations.launchpad.dev/earthian/1.4')
-    >>> print find_tag_by_id(admin_browser.contents, 'translation-focus')
+    >>> print(find_tag_by_id(admin_browser.contents, 'translation-focus'))
     None
 
 If focus is set, nice explanatory text is displayed.
@@ -332,8 +332,8 @@
     >>> distribution.translation_focus = focus_series
     >>> logout()
     >>> admin_browser.open('http://translations.launchpad.dev/earthian/1.4')
-    >>> print extract_text(
-    ...     find_tag_by_id(admin_browser.contents, 'translation-focus'))
+    >>> print(extract_text(
+    ...     find_tag_by_id(admin_browser.contents, 'translation-focus')))
     Launchpad currently recommends translating 1.6.
 
 
@@ -374,7 +374,7 @@
     ...     'http://translations.launchpad.dev/evolution')
     >>> untranslatable = find_tag_by_id(
     ...     admin_browser.contents, 'portlet-untranslatable-branches')
-    >>> print extract_text(untranslatable)
+    >>> print(extract_text(untranslatable))
     Set up translations for a series...
     evo-new series — manual or automatic...
 
@@ -382,13 +382,13 @@
 page togheter with link for uploading a template from that series
 (manual) and setting automatic imports.
 
-    >>> print admin_browser.getLink('Evolution evo-new series').url
+    >>> print(admin_browser.getLink('Evolution evo-new series').url)
     http://translations.launchpad.dev/evolution/evo-new/+translations
 
-    >>> print admin_browser.getLink(
-    ... 'manual', url='/evolution/evo-new/+translations-upload').url
+    >>> print(admin_browser.getLink(
+    ... 'manual', url='/evolution/evo-new/+translations-upload').url)
     http://translations.launchpad.dev/evolution/evo-new/+translations-upload
 
-    >>> print admin_browser.getLink(
-    ... 'automatic', url='/evolution/evo-new/+translations-settings').url
+    >>> print(admin_browser.getLink(
+    ... 'automatic', url='/evolution/evo-new/+translations-settings').url)
     http://translations.launchpad.dev/evolution/evo-new/+translations-settings

=== modified file 'lib/lp/translations/stories/project/xx-project-translations.txt'
--- lib/lp/translations/stories/project/xx-project-translations.txt	2011-09-20 01:33:04 +0000
+++ lib/lp/translations/stories/project/xx-project-translations.txt	2018-06-03 00:41:33 +0000
@@ -4,17 +4,17 @@
 GNOME is a good example, it has products with translations.
 
     >>> browser.open('http://translations.launchpad.dev/gnome')
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/gnome
 
 Evolution being one with translations and being used officially
 
     >>> evo_link = browser.getLink('Evolution')
-    >>> print evo_link.url
+    >>> print(evo_link.url)
     http://launchpad.dev/evolution/+translations
 
     >>> browser.open('http://translations.launchpad.dev/gnome')
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/gnome
 
 Netapplet is another product of GNOME project. It has translations,
@@ -24,7 +24,7 @@
     >>> translated_projects = find_tag_by_id(
     ...     browser.contents, 'translatable-projects')
     >>> link = translated_projects.find(text='Network Applet')
-    >>> print link
+    >>> print(link)
     None
 
 It does show up among untranslated projects.
@@ -39,13 +39,13 @@
 
     >>> anon_browser.open('http://launchpad.dev/gnome')
     >>> anon_browser.getLink('NetApplet').click()
-    >>> print anon_browser.title
+    >>> print(anon_browser.title)
     NetApplet in Launchpad
 
 Anonymous users don't see the translations
 
     >>> anon_browser.getLink('Translations').click()
-    >>> print anon_browser.title
+    >>> print(anon_browser.title)
     Translations : NetApplet
 
     >>> find_tag_by_id(
@@ -66,7 +66,7 @@
 Let's confirm what we just stated.
 
     >>> browser.open('http://launchpad.dev/gnome')
-    >>> print browser.url
+    >>> print(browser.url)
     http://launchpad.dev/gnome
 
 alsa-utils does not belong to GNOME project.
@@ -79,9 +79,9 @@
 It's using Launchpad Translations officially. And it has translations.
 
     >>> browser.open('http://translations.launchpad.dev/alsa-utils')
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/alsa-utils
 
     >>> alsa_utils_spanish = browser.getLink('Spanish')
-    >>> print alsa_utils_spanish.url
+    >>> print(alsa_utils_spanish.url)
     http://translations.../alsa-utils/trunk/+pots/alsa-utils/es/+translate

=== modified file 'lib/lp/translations/stories/standalone/custom-language-codes.txt'
--- lib/lp/translations/stories/standalone/custom-language-codes.txt	2014-11-27 07:48:25 +0000
+++ lib/lp/translations/stories/standalone/custom-language-codes.txt	2018-06-03 00:41:33 +0000
@@ -41,7 +41,7 @@
 
     >>> owner_browser.open(product_page)
     >>> tag = find_custom_language_codes_link(owner_browser)
-    >>> print extract_text(tag.renderContents())
+    >>> print(extract_text(tag.renderContents()))
     If necessary, you may
     define custom language codes
     for this project.
@@ -50,7 +50,7 @@
 
     >>> rosetta_admin_browser.open(product_page)
     >>> tag = find_custom_language_codes_link(rosetta_admin_browser)
-    >>> print extract_text(tag.renderContents())
+    >>> print(extract_text(tag.renderContents()))
     If necessary, you may
     define custom language codes
     for this project.
@@ -63,13 +63,13 @@
 Other users don't see this link.
 
     >>> user_browser.open(product_page)
-    >>> print find_custom_language_codes_link(user_browser)
+    >>> print(find_custom_language_codes_link(user_browser))
     None
 
 Initially the page shows no custom language codes for the project.
 
     >>> tag = find_tag_by_id(owner_browser.contents, 'empty')
-    >>> print extract_text(tag.renderContents())
+    >>> print(extract_text(tag.renderContents()))
     No custom language codes have been defined.
 
 There is a link to add a custom language code.
@@ -88,7 +88,7 @@
     True
 
     >>> tag = find_tag_by_id(owner_browser.contents, 'nonempty')
-    >>> print extract_text(tag.renderContents())
+    >>> print(extract_text(tag.renderContents()))
     Foo uses the following custom language codes:
     Code...     ...maps to language
     no          Norwegian Nynorsk
@@ -98,7 +98,7 @@
 
     >>> owner_browser.getLink("no").click()
     >>> main = find_main_content(owner_browser.contents)
-    >>> print extract_text(main.renderContents())
+    >>> print(extract_text(main.renderContents()))
     Custom language code  ...no... for Foo
     For Foo, uploads with the language code
     &ldquo;no&rdquo;
@@ -130,7 +130,7 @@
     True
 
     >>> tag = find_tag_by_id(owner_browser.contents, 'empty')
-    >>> print extract_text(tag.renderContents())
+    >>> print(extract_text(tag.renderContents()))
     No custom language codes have been defined.
 
 
@@ -143,7 +143,7 @@
     >>> user_browser.open(custom_language_codes_page)
 
     >>> tag = find_tag_by_id(user_browser.contents, 'empty')
-    >>> print extract_text(tag.renderContents())
+    >>> print(extract_text(tag.renderContents()))
     No custom language codes have been defined.
 
 However all they get is a read-only version of the page.
@@ -170,7 +170,7 @@
 
     >>> user_browser.open(custom_language_codes_page)
     >>> tag = find_tag_by_id(user_browser.contents, 'nonempty')
-    >>> print extract_text(tag.renderContents())
+    >>> print(extract_text(tag.renderContents()))
     Foo uses the following custom language codes:
     Code...     ...maps to language
     no          Norwegian Nynorsk
@@ -231,7 +231,7 @@
 codes talks about a package, not a project.
 
     >>> tag = find_custom_language_codes_link(translations_browser)
-    >>> print extract_text(tag.renderContents())
+    >>> print(extract_text(tag.renderContents()))
     If necessary, you may
     define custom language codes
     for this package.
@@ -240,7 +240,7 @@
     >>> custom_language_codes_page = translations_browser.url
 
     >>> tag = find_tag_by_id(translations_browser.contents, 'empty')
-    >>> print extract_text(tag.renderContents())
+    >>> print(extract_text(tag.renderContents()))
     No custom language codes have been defined.
 
 A translations admin can add a language code.
@@ -255,7 +255,7 @@
 The language code is displayed.
 
     >>> tag = find_tag_by_id(translations_browser.contents, 'nonempty')
-    >>> print extract_text(tag.renderContents())
+    >>> print(extract_text(tag.renderContents()))
     bar in Distro uses the following custom language codes:
     Code...     ...maps to language
     pt-br       Portuguese (Brazil)
@@ -265,14 +265,14 @@
 
     >>> translations_browser.open(page_in_other_series)
     >>> tag = find_custom_language_codes_link(translations_browser)
-    >>> print extract_text(tag.renderContents())
+    >>> print(extract_text(tag.renderContents()))
     If necessary, you may
     define custom language codes
     for this package.
 
     >>> translations_browser.getLink("define custom language codes").click()
     >>> tag = find_tag_by_id(translations_browser.contents, 'nonempty')
-    >>> print extract_text(tag.renderContents())
+    >>> print(extract_text(tag.renderContents()))
     bar in Distro uses the following custom language codes:
     Code...     ...maps to language
     pt-br       Portuguese (Brazil)
@@ -288,5 +288,5 @@
     >>> translations_browser.getControl("Remove").click()
 
     >>> tag = find_tag_by_id(translations_browser.contents, 'empty')
-    >>> print extract_text(tag.renderContents())
+    >>> print(extract_text(tag.renderContents()))
     No custom language codes have been defined.

=== modified file 'lib/lp/translations/stories/standalone/xx-language.txt'
--- lib/lp/translations/stories/standalone/xx-language.txt	2011-12-22 05:09:10 +0000
+++ lib/lp/translations/stories/standalone/xx-language.txt	2018-06-03 00:41:33 +0000
@@ -17,7 +17,7 @@
 There we can find a link to browse and manage languages.
 
     >>> admin_browser.getLink('18 languages').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/+languages
 
 
@@ -28,14 +28,14 @@
 add new languages.
 
     >>> admin_browser.getLink('Add new language').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/+languages/+add
 
 Which detects an attempt to create duplicate Languages, such as Spanish,
 which is already registered:
 
     >>> browser.open('http://translations.launchpad.dev/+languages/es')
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/+languages/es
 
 If someone tries to create a new language with the same language code,
@@ -44,11 +44,11 @@
     >>> admin_browser.getControl('The ISO 639').value = 'es'
     >>> admin_browser.getControl('English name').value = 'Foos'
     >>> admin_browser.getControl('Add').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/+languages/+add
 
     >>> for tag in find_tags_by_class(admin_browser.contents, 'message'):
-    ...     print tag.renderContents()
+    ...     print(tag.renderContents())
     There is 1 error.
     There is already a language with that code.
 
@@ -65,13 +65,13 @@
 
 And the system forwards us to its main page:
 
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/+languages/foos
 
 A normal user will not be able to see or use the url to add languages.
 
     >>> user_browser.open('http://translations.launchpad.dev/+languages')
-    >>> print user_browser.url
+    >>> print(user_browser.url)
     http://translations.launchpad.dev/+languages
 
     >>> user_browser.getLink('Add new language')
@@ -92,13 +92,13 @@
 From the top languages page, anyone can find languages.
 
     >>> browser.open('http://translations.launchpad.dev/+languages')
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/+languages
 
     >>> text_search = browser.getControl(name='field.search_lang')
     >>> text_search.value = 'Spanish'
     >>> browser.getControl('Find language', index=0).click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/+languages/+index?field.search_lang=Spanish
 
 
@@ -109,11 +109,11 @@
 about the selected language.
 
     >>> browser.getLink('Spanish').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/+languages/es
 
-    >>> print extract_text(find_portlet(browser.contents, 'Plural forms'
-    ...     ).renderContents())
+    >>> print(extract_text(find_portlet(browser.contents, 'Plural forms'
+    ...     ).renderContents()))
     Plural forms
     Spanish has 2 plural forms:
     Form 0 for 1.
@@ -122,13 +122,13 @@
 
     >>> translationteams_portlet = find_portlet(
     ...     browser.contents, 'Translation teams')
-    >>> print translationteams_portlet
+    >>> print(translationteams_portlet)
     <...
     ...testing Spanish team...
     ...Just a testing team...
 
     >>> countries_portlet = find_portlet(browser.contents, 'Countries')
-    >>> print countries_portlet
+    >>> print(countries_portlet)
     <...
     ...Argentina...
     ...Bolivia...
@@ -153,7 +153,7 @@
 
     >>> topcontributors_portlet = find_portlet(
     ...     browser.contents, 'Top contributors')
-    >>> print topcontributors_portlet
+    >>> print(topcontributors_portlet)
     <...
     ...Carlos Perelló Marín...
 
@@ -165,13 +165,13 @@
 form.
 
     >>> browser.open('http://translations.launchpad.dev/+languages/ab')
-    >>> print extract_text(find_portlet(browser.contents, 'Plural forms'
-    ...     ).renderContents())
+    >>> print(extract_text(find_portlet(browser.contents, 'Plural forms'
+    ...     ).renderContents()))
     Plural forms
     Unfortunately, Launchpad doesn't know the plural
     form information for this language...
 
-    >>> print browser.getLink(id='plural_question').url
+    >>> print(browser.getLink(id='plural_question').url)
     http://answers.launchpad.dev/launchpad/+addquestion
 
 We will see a note that Launchpad does not know in which countries
@@ -179,12 +179,12 @@
 Rosetta admin about the countries where this page is officially spoken.
 
     >>> countries_portlet = find_portlet(browser.contents, 'Countries')
-    >>> print countries_portlet
+    >>> print(countries_portlet)
     <...
     Abkhazian is not registered as being spoken in any
     country...
 
-    >>> print browser.getLink(id='country_question').url
+    >>> print(browser.getLink(id='country_question').url)
     http://answers.launchpad.dev/launchpad/+addquestion
 
 
@@ -195,7 +195,7 @@
 
     >>> user_browser.open(
     ...     'http://translations.launchpad.dev/+languages/es')
-    >>> print user_browser.url
+    >>> print(user_browser.url)
     http://translations.launchpad.dev/+languages/es
 
 A plain user is not able to reach it.
@@ -217,35 +217,35 @@
 
     >>> admin_browser.open(
     ...     'http://translations.launchpad.dev/+languages/es')
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/+languages/es
 
     >>> admin_browser.getLink('Administer').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/+languages/es/+admin
 
-    >>> print admin_browser.getControl('ISO 639').value
+    >>> print(admin_browser.getControl('ISO 639').value)
     es
 
-    >>> print admin_browser.getControl('English name').value
+    >>> print(admin_browser.getControl('English name').value)
     Spanish
 
-    >>> print admin_browser.getControl('Native name').value
+    >>> print(admin_browser.getControl('Native name').value)
 
-    >>> print admin_browser.getControl('Number of plural forms').value
+    >>> print(admin_browser.getControl('Number of plural forms').value)
     2
 
-    >>> print admin_browser.getControl('Plural form expression').value
+    >>> print(admin_browser.getControl('Plural form expression').value)
     n != 1
 
-    >>> print admin_browser.getControl('Visible').optionValue
+    >>> print(admin_browser.getControl('Visible').optionValue)
     on
 
-    >>> print admin_browser.getControl('Text direction').displayValue
+    >>> print(admin_browser.getControl('Text direction').displayValue)
     ['Left to Right']
 
     >>> control = admin_browser.getControl(name='field.countries')
-    >>> print [strip_label(country) for country in control.displayValue]
+    >>> print([strip_label(country) for country in control.displayValue])
     ['Argentina', 'Bolivia', 'Chile', 'Colombia',
      'Costa Rica', 'Dominican Republic', 'Ecuador',
      'El Salvador', 'Guatemala', 'Honduras', 'Mexico',
@@ -261,11 +261,11 @@
 
     >>> admin_browser.getControl('ISO 639').value = 'fr'
     >>> admin_browser.getControl('Admin Language').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/+languages/es/+admin
 
     >>> for tag in find_tags_by_class(admin_browser.contents, 'message'):
-    ...     print tag.renderContents()
+    ...     print(tag.renderContents())
     There is 1 error.
     There is already a language with that code.
 
@@ -274,26 +274,26 @@
     >>> admin_browser.getControl('ISO 639').value = 'bars'
     >>> admin_browser.getControl('English name').value = 'Changed field'
     >>> spokenin_control = admin_browser.getControl(name='field.countries')
-    >>> spokenin_control.getControl('Argentina').selected = False
-    >>> spokenin_control.getControl('France').selected = True
+    >>> spokenin_control.getControl(b'Argentina').selected = False
+    >>> spokenin_control.getControl(b'France').selected = True
     >>> admin_browser.getControl('Admin Language').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/+languages/bars
 
 And we can validate it:
 
     >>> admin_browser.getLink('Administer').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/+languages/bars/+admin
 
-    >>> print admin_browser.getControl('ISO 639').value
+    >>> print(admin_browser.getControl('ISO 639').value)
     bars
 
-    >>> print admin_browser.getControl('English name').value
+    >>> print(admin_browser.getControl('English name').value)
     Changed field
 
     >>> control = admin_browser.getControl(name='field.countries')
-    >>> print [strip_label(country) for country in control.displayValue]
+    >>> print([strip_label(country) for country in control.displayValue])
     ['Bolivia', 'Chile', 'Colombia', 'Costa Rica',
      'Dominican Republic', 'Ecuador', 'El Salvador', 'France',
      'Guatemala', 'Honduras', 'Mexico', 'Nicaragua',
@@ -307,5 +307,3 @@
     Traceback (most recent call last):
     ...
     NotFound:...
-
-

=== modified file 'lib/lp/translations/stories/standalone/xx-licensing.txt'
--- lib/lp/translations/stories/standalone/xx-licensing.txt	2016-01-26 15:47:37 +0000
+++ lib/lp/translations/stories/standalone/xx-licensing.txt	2018-06-03 00:41:33 +0000
@@ -14,9 +14,9 @@
 Karl realises that he is not on the translate page, he can see that
 that this page has information about relicensing.
 
-    >>> print browser.title
+    >>> print(browser.title)
     Licensing : Translations...
-    >>> print extract_text(find_main_content(browser.contents))
+    >>> print(extract_text(find_main_content(browser.contents)))
     Translations licensing by Karl Tilbury
     ...
 
@@ -50,10 +50,10 @@
     >>> main_content = find_tag_by_id(
     ...     browser.contents, 'messages_to_translate')
     >>> for textarea in main_content.findAll('textarea'):
-    ...     print 'Found textarea:\n%s' % textarea
+    ...     print('Found textarea:\n%s' % textarea)
 
     >>> for input in main_content.findAll('input'):
-    ...     print 'Found input:\n%s' % input
+    ...     print('Found input:\n%s' % input)
 
 Karl changes his mind. He returns to the licensing page.
 
@@ -61,13 +61,13 @@
     >>> browser.getLink('Translations licensing').click()
     >>> browser.url
     'http://translations.launchpad.dev/~karl/+licensing'
-    >>> print browser.title
+    >>> print(browser.title)
     Licensing : Translations...
 
 Karl sees that the current value is 'no', which he set before.
 
     >>> radiobuttons = browser.getControl(name='field.allow_relicensing')
-    >>> print radiobuttons.value
+    >>> print(radiobuttons.value)
     ['REMOVE']
 
 He changes it again.
@@ -84,7 +84,7 @@
     ...              '+pots/alsa-utils/es/+translate')
     >>> browser.url
     'http://.../alsa-utils/trunk/+pots/alsa-utils/es/+translate'
-    >>> print browser.title
+    >>> print(browser.title)
     Spanish (es) : Template ...alsa-utils... : Series trunk :
     Translations : alsa-utils
 

=== modified file 'lib/lp/translations/stories/standalone/xx-person-activity.txt'
--- lib/lp/translations/stories/standalone/xx-person-activity.txt	2016-01-26 15:47:37 +0000
+++ lib/lp/translations/stories/standalone/xx-person-activity.txt	2018-06-03 00:41:33 +0000
@@ -21,7 +21,7 @@
     # Prints a heading and formatted list of POFiles and latest submissions.
     >>> def print_activity_list(listing):
     ...     for row in listing.findAll('tr'):
-    ...         print extract_text(row)
+    ...         print(extract_text(row))
 
     >>> listing = find_tag_by_id(anon_browser.contents, 'activity-table')
     >>> print_activity_list(listing)
@@ -44,7 +44,7 @@
     >>> alsa_utils_link = (
     ...     'Spanish (es) translation of alsa-utils in alsa-utils trunk')
     >>> anon_browser.getLink(alsa_utils_link).click()
-    >>> print anon_browser.title
+    >>> print(anon_browser.title)
     Translations by ...Spanish (es)...
 
 
@@ -71,11 +71,11 @@
     >>> table = find_tag_by_id(user_browser.contents, 'activity-table')
     >>> link = table.find('a')
     >>> url = link['href']
-    >>> print url.split('/')[-1]
+    >>> print(url.split('/')[-1])
     +filter?person=a%2Bb
 
 Because of this, the link actually works.
 
     >>> user_browser.open(url.encode('ascii'))
-    >>> print user_browser.title
+    >>> print(user_browser.title)
     Translations by A+b in...Serbian (sr) translation...

=== modified file 'lib/lp/translations/stories/standalone/xx-person-editlanguages.txt'
--- lib/lp/translations/stories/standalone/xx-person-editlanguages.txt	2017-10-23 00:16:39 +0000
+++ lib/lp/translations/stories/standalone/xx-person-editlanguages.txt	2018-06-03 00:41:33 +0000
@@ -15,7 +15,7 @@
     ...     tag = find_tag_by_id(page, 'preferred-languages')
     ...     return extract_text(tag)
 
-    >>> print find_languages_section(browser.contents)
+    >>> print(find_languages_section(browser.contents))
     Your preferred languages
     Catalan
     Spanish
@@ -25,7 +25,7 @@
 +editlanguages page.
 
     >>> browser.getLink(id="change-languages").click()
-    >>> print extract_text(find_main_content(browser.contents).find('h1'))
+    >>> print(extract_text(find_main_content(browser.contents).find('h1')))
     Your language preferences
 
 So far, he has Spanish selected as one of his preferred languages, but
@@ -54,12 +54,12 @@
 confirming his changes.
 
     >>> for message in find_tags_by_class(browser.contents, 'message'):
-    ...     print message.renderContents()
+    ...     print(message.renderContents())
     Added Welsh to your preferred languages.<br
     />Removed Spanish from your preferred languages.
 
     >>> browser.open('http://translations.launchpad.dev/')
-    >>> print find_languages_section(browser.contents)
+    >>> print(find_languages_section(browser.contents))
     Your preferred languages
     Catalan
     Welsh
@@ -72,7 +72,7 @@
     >>> # Fake request from a Liechtenstein IP address by setting
     >>> # X-Forwarded-For in the http header, like a proxy would.
     >>> browser.addHeader('X-Forwarded-For', '80.66.224.0')
-    >>> browser.addHeader('Accept-Language', 'pt_br, Espa\xf1ol')
+    >>> browser.addHeader('Accept-Language', b'pt_br, Espa\xf1ol')
     >>> browser.open(
     ...     'http://translations.launchpad.dev/~name12/+editlanguages')
 
@@ -82,7 +82,7 @@
 pt_br language code is recognized properly.
 
     >>> portlet = find_tag_by_id(browser.contents, 'portlet-browser-languages')
-    >>> print extract_text(portlet)
+    >>> print(extract_text(portlet))
     Your browser languages:
     Portuguese (Brazil)
 
@@ -91,7 +91,7 @@
 
     >>> country_portlet = find_tag_by_id(browser.contents,
     ...                                  'portlet-country-languages')
-    >>> print extract_text(country_portlet.dt)
+    >>> print(extract_text(country_portlet.dt))
     Languages in Liechtenstein
 
 The system has no information about languages spoken in Liechtenstein,
@@ -103,9 +103,9 @@
     ...     list_items = find_tags_by_class(str(spoken_in), 'language')
     ...     return [li.contents for li in list_items]
 
-    >>> print find_spoken_languages(country_portlet)
+    >>> print(find_spoken_languages(country_portlet))
     []
-    >>> print country_portlet.find('a')['href']
+    >>> print(country_portlet.find('a')['href'])
     http://answers.launchpad.dev/launchpad
 
 Back home in Brazil, Joao gets the equivalent for Brazil, where the
@@ -117,7 +117,7 @@
     ...     'http://translations.launchpad.dev/~name12/+editlanguages')
     >>> country_portlet = find_tag_by_id(browser.contents,
     ...                                  'portlet-country-languages')
-    >>> print find_spoken_languages(country_portlet)
+    >>> print(find_spoken_languages(country_portlet))
     [[u'Portuguese (Brazil)']]
 
 We also have a page under the launchpad root, called +editmylanguages,
@@ -154,13 +154,13 @@
     'Landscape Developers in Launchpad'
 
     >>> browser.getLink('Set preferred languages').click()
-    >>> print extract_text(find_main_content(browser.contents).find('h1'))
+    >>> print(extract_text(find_main_content(browser.contents).find('h1')))
     Landscape Developers's language preferences
 
     >>> browser.getControl('Spanish').selected = True
     >>> browser.getControl('Save').click()
     >>> for message in find_tags_by_class(browser.contents, 'message'):
-    ...     print extract_text(message)
+    ...     print(extract_text(message))
     Added Spanish to Landscape Developers&#x27;s preferred languages.
 
 
@@ -175,14 +175,14 @@
     >>> admin_browser.title
     'No Privileges Person in Launchpad'
     >>> admin_browser.getLink('Set preferred languages').click()
-    >>> print extract_text(
-    ...     find_main_content(admin_browser.contents).find('h1'))
+    >>> print(extract_text(
+    ...     find_main_content(admin_browser.contents).find('h1')))
     No Privileges Person's language preferences
 
     >>> admin_browser.getControl('Esperanto').selected = True
     >>> admin_browser.getControl('Save').click()
     >>> for message in find_tags_by_class(admin_browser.contents, 'message'):
-    ...     print extract_text(message)
+    ...     print(extract_text(message))
     Added Esperanto to No Privileges Person&#x27;s preferred languages.
 
 
@@ -212,20 +212,20 @@
 
 The home page reminds her to set her preferred languages.
 
-    >>> print find_nag(noi_browser)
+    >>> print(find_nag(noi_browser))
     You have not selected your preferred languages.
     Please <a ...>set them now</a>.
 
 The message does not appear for other users looking at Noi's home page.
 
     >>> user_browser.open(noi_home)
-    >>> print find_nag(user_browser)
+    >>> print(find_nag(user_browser))
     None
 
 The nag message links to the languages editing page.
 
     >>> noi_browser.getLink(id='set-languages').click()
-    >>> print noi_browser.title
+    >>> print(noi_browser.title)
     Language preferences...
 
 Once Noi has set one or more preferred languages, the nag message goes
@@ -237,5 +237,5 @@
     >>> logout()
 
     >>> noi_browser.open(noi_home)
-    >>> print find_nag(noi_browser)
+    >>> print(find_nag(noi_browser))
     None

=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-auto-alt-languages.txt'
--- lib/lp/translations/stories/standalone/xx-pofile-auto-alt-languages.txt	2009-07-01 20:45:39 +0000
+++ lib/lp/translations/stories/standalone/xx-pofile-auto-alt-languages.txt	2018-06-03 00:41:33 +0000
@@ -11,7 +11,7 @@
   >>> browser.open(
   ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
   ...     'evolution-2.2/es_MX/+translate')
-  >>> print browser.url
+  >>> print(browser.url)
   http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2/es_MX/+translate
 
   >>> browser.getControl(name='field.alternative_language', index=0).value
@@ -23,7 +23,7 @@
   >>> browser.open(
   ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
   ...     'evolution-2.2/pt_BR/+translate')
-  >>> print browser.url
+  >>> print(browser.url)
   http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2/pt_BR/+translate
 
   >>> browser.getControl(name='field.alternative_language', index=0).value
@@ -35,10 +35,8 @@
   >>> browser.open(
   ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
   ...     'evolution-2.2/fr/+translate')
-  >>> print browser.url
+  >>> print(browser.url)
   http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2/fr/+translate
 
   >>> browser.getControl(name='field.alternative_language', index=0).value
   ['']
-
-

=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-details.txt'
--- lib/lp/translations/stories/standalone/xx-pofile-details.txt	2013-09-27 04:13:23 +0000
+++ lib/lp/translations/stories/standalone/xx-pofile-details.txt	2018-06-03 00:41:33 +0000
@@ -8,8 +8,8 @@
     >>> anon_browser.open(
     ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
     ...     'evolution-2.2/es/+details')
-    >>> print extract_text(find_main_content(anon_browser.contents)).encode(
-    ...     'ascii', 'backslashreplace')
+    >>> print(extract_text(find_main_content(anon_browser.contents)).encode(
+    ...     'ascii', 'backslashreplace'))
     Details for Spanish translation
     ...
     Latest contributor:
@@ -25,7 +25,7 @@
 appearing among contributors), by choosing the 'filter' link:
 
     >>> anon_browser.getLink('filter').click()
-    >>> print anon_browser.title
+    >>> print(anon_browser.title)
     Translations by Carlos ... in ...
 
 To display all the translations submitted by Carlos, and allow easier
@@ -63,7 +63,7 @@
     ...         contents = get_columns(cells)
     ...         if len(contents) > 50:
     ...             contents = contents[:47] + '...'
-    ...         print "%-10s %s" % (types[type], contents)
+    ...         print("%-10s %s" % (types[type], contents))
 
 A user can see all the submissions Carlos has made to this POFile.
 Note that 'english' in the first column indicates a msgid, and 'used',
@@ -102,9 +102,9 @@
 A POTMsgSet sequence number is also a link to edit a translation.
 
     >>> anon_browser.getLink('14.').click()
-    >>> print anon_browser.url
+    >>> print(anon_browser.url)
     http://.../evolution/trunk/+pots/evolution-2.2/es/14/+translate
-    >>> print anon_browser.title
+    >>> print(anon_browser.title)
     Browsing Spanish translation...
 
 
@@ -164,7 +164,7 @@
     ...     "ubuntu/hoary/+source/%s/+pots/%s/%s/+details") % (
     ...     package.name, template.name, language_code))
     >>> main_text = extract_text(find_main_content(browser.contents))
-    >>> print main_text
+    >>> print(main_text)
     Details for ...
     Contributors to this translation
     The following people have made some contribution to this specific
@@ -192,7 +192,7 @@
 
     >>> browser.open(pofile_url)
     >>> stats_portlet = find_tag_by_id(browser.contents, 'portlet-stats')
-    >>> print extract_text(stats_portlet)
+    >>> print(extract_text(stats_portlet))
     Statistics
     Messages: 10
     Translated: 7 (70.0%)

=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-export.txt'
--- lib/lp/translations/stories/standalone/xx-pofile-export.txt	2017-10-23 00:16:39 +0000
+++ lib/lp/translations/stories/standalone/xx-pofile-export.txt	2018-06-03 00:41:33 +0000
@@ -18,11 +18,11 @@
     ...     '/+source/evolution/+pots/evolution-2.2/es')
     >>> user_browser.getLink('Download').click()
 
-    >>> print user_browser.title
+    >>> print(user_browser.title)
     Download translation : Spanish (es)... : Hoary (5.04) :
     Translations : evolution package : Ubuntu
 
-    >>> print find_main_content(user_browser.contents)
+    >>> print(find_main_content(user_browser.contents))
     <...
     ...Download Spanish translation...
     Once the file is ready for download, Launchpad will email
@@ -33,7 +33,7 @@
 
     >>> user_browser.getControl(name='format').value = ['PO']
     >>> user_browser.getControl('Request Download').click()
-    >>> print user_browser.url
+    >>> print(user_browser.url)
     http://translatio.../ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es
 
     >>> for tag in find_tags_by_class(user_browser.contents, 'informational'):
@@ -50,12 +50,12 @@
 
     >>> user_browser.getControl(name='format').value = ['PO']
     >>> user_browser.getControl('Request Download').click()
-    >>> print user_browser.url
+    >>> print(user_browser.url)
     http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2/cy
 
     >>> for tag in find_tags_by_class(
     ...     user_browser.contents, 'informational'):
-    ...     print tag.renderContents()
+    ...     print(tag.renderContents())
     Your request has been received. Expect to receive an email shortly.
 
 If the POFile first has to be created, the requester becomes its owner.
@@ -72,7 +72,7 @@
 
     >>> browser.getControl(name='format').value = ['PO']
     >>> browser.getControl('Request Download').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2/sv
 
     >>> for tag in find_tags_by_class(browser.contents, 'informational'):
@@ -97,7 +97,7 @@
 
     >>> browser.getControl(name='format').value = ['PO']
     >>> browser.getControl('Request Download').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2/sv
 
     >>> for tag in find_tags_by_class(browser.contents, 'informational'):

=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-translate-alternative-language.txt'
--- lib/lp/translations/stories/standalone/xx-pofile-translate-alternative-language.txt	2016-01-26 15:47:37 +0000
+++ lib/lp/translations/stories/standalone/xx-pofile-translate-alternative-language.txt	2018-06-03 00:41:33 +0000
@@ -86,8 +86,8 @@
 "cards" might translate to Catalan as "targetas," the Spanish equivalent is
 "tarjetas."
 
-    >>> print extract_text(find_main_content(
-    ...     browser.contents)).encode('ASCII', 'backslashreplace')
+    >>> print(extract_text(find_main_content(
+    ...     browser.contents)).encode('ASCII', 'backslashreplace'))
     Translating into Catalan...
     ...
     English: cards
@@ -123,7 +123,7 @@
 
 It even presents a link to where the user can set the preferred languages.
 
-    >>> print browser.getLink("add it to your preferred languages").url
+    >>> print(browser.getLink("add it to your preferred languages").url)
     http...~carlos/+editlanguages
 
 This distinction between alternative languages from the user's preferred set
@@ -134,8 +134,8 @@
     >>> anon_browser.getControl('Spanish (es)').selected = True
     >>> anon_browser.getControl('Change').click()
 
-    >>> print extract_text(find_main_content(
-    ...     anon_browser.contents)).encode('ASCII', 'backslashreplace')
+    >>> print(extract_text(find_main_content(
+    ...     anon_browser.contents)).encode('ASCII', 'backslashreplace'))
     Browsing Catalan translation
     ...
     English: cards
@@ -164,16 +164,17 @@
     >>> browser.getControl('Catalan (ca)').selected = True
     >>> browser.getControl('untranslated').selected = True
     >>> browser.getControl('Change').click()
-    >>> print extract_url_parameter(browser.url, 'field.alternative_language')
+    >>> print(extract_url_parameter(
+    ...     browser.url, 'field.alternative_language'))
     field.alternative_language=ca
-    >>> print extract_url_parameter(browser.url, 'show')
+    >>> print(extract_url_parameter(browser.url, 'show'))
     show=untranslated
 
 Carlos can see that he is viewing the first page of results in the
 navigation bar between the translation controls and the messages.
 
     >>> navigation = find_tags_by_class(browser.contents, 'results')[0].td
-    >>> print extract_text(navigation).decode('utf-8')
+    >>> print(extract_text(navigation).decode('utf-8'))
     1 ... 10  of 15 results ...
 
 Carlos uses the 'Save & Continue' button to see the next page of
@@ -181,7 +182,7 @@
 
     >>> browser.getControl('Save & Continue').click()
     >>> navigation = find_tags_by_class(browser.contents, 'results')[0].td
-    >>> print extract_text(navigation)
+    >>> print(extract_text(navigation))
     11 ... 15  of 15 results ...
 
 
@@ -273,4 +274,3 @@
     >>> browser.getControl(
     ...     name='field.alternative_language').displayValue
     ['(nothing selected)']
-

=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-translate-dismiss-suggestions.txt'
--- lib/lp/translations/stories/standalone/xx-pofile-translate-dismiss-suggestions.txt	2015-10-05 08:36:52 +0000
+++ lib/lp/translations/stories/standalone/xx-pofile-translate-dismiss-suggestions.txt	2018-06-03 00:41:33 +0000
@@ -25,10 +25,10 @@
     ...     name='msgset_198_de_translation_0_radiobutton').value = [
     ...         'msgset_198_de_translation_0_new']
     >>> admin_browser.getControl('Save & Continue').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.../alsa-utils/trunk/+pots/alsa-utils/de/+translate
-    >>> print extract_text(find_tag_by_id(admin_browser.contents,
-    ...                                   'msgset_198_de_translation_0'))
+    >>> print(extract_text(find_tag_by_id(admin_browser.contents,
+    ...                                   'msgset_198_de_translation_0')))
     The great new translation.
 
 Now somebody else comes and makes a really bad suggestion.
@@ -44,18 +44,18 @@
     >>> user_browser.getControl(
     ...     name='msgset_198_de_needsreview').value = 'force_suggestion'
     >>> user_browser.getControl('Save & Continue').click()
-    >>> print user_browser.url
+    >>> print(user_browser.url)
     http://translations.../alsa-utils/trunk/+pots/alsa-utils/de/+translate
 
 But it's only a suggestion, so the translation remains unchanged.
 
     >>> import re
-    >>> print extract_text(find_tag_by_id(user_browser.contents,
-    ...                                   'msgset_198_de_translation_0'))
+    >>> print(extract_text(find_tag_by_id(user_browser.contents,
+    ...                                   'msgset_198_de_translation_0')))
     The great new translation.
-    >>> print extract_text(find_tag_by_id(
+    >>> print(extract_text(find_tag_by_id(
     ...     user_browser.contents,
-    ...     re.compile(r'^msgset_198_de_suggestion_\d+_0$')))
+    ...     re.compile(r'^msgset_198_de_suggestion_\d+_0$'))))
     The really bad suggestion.
 
 In order to get rid of this, the admin chooses to keep the great new
@@ -66,19 +66,19 @@
     >>> admin_browser.getControl(name='msgset_198_dismiss').value = (
     ...         'dismiss_suggestions')
     >>> admin_browser.getControl('Save & Continue').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.../alsa-utils/trunk/+pots/alsa-utils/de/+translate
 
 The great new translation is still intact.
 
-    >>> print extract_text(find_tag_by_id(admin_browser.contents,
-    ...                                   'msgset_198_de_translation_0'))
+    >>> print(extract_text(find_tag_by_id(admin_browser.contents,
+    ...                                   'msgset_198_de_translation_0')))
     The great new translation.
 
 But the really bad suggestion is gone.
 
-    >>> print find_tag_by_id(admin_browser.contents,
-    ...                      'msgset_198_de_suggestion_702_0')
+    >>> print(find_tag_by_id(admin_browser.contents,
+    ...                      'msgset_198_de_suggestion_702_0'))
     None
 
 == External suggestions ==
@@ -91,19 +91,18 @@
     >>> admin_browser.getControl(name='msgset_5_dismiss').value = (
     ...         'dismiss_suggestions')
     >>> admin_browser.getControl('Save & Continue').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.../evolution/trunk/+pots/evolution-2.2/es/6/+translate
 
 There are still suggestions because they are external.
 
     >>> admin_browser.open('http://translations.launchpad.dev/evolution/'
     ...                    'trunk/+pots/evolution-2.2/es/5/+translate')
-    >>> print extract_text(find_tag_by_id(admin_browser.contents,
-    ...                                   'msgset_5_es_suggestion_686_0'))
+    >>> print(extract_text(find_tag_by_id(admin_browser.contents,
+    ...                                   'msgset_5_es_suggestion_686_0')))
     caratas
 
 But the checkbox for dismissal is gone.
 
-    >>> print find_tag_by_id(admin_browser.contents, 'msgset_5_dismiss')
+    >>> print(find_tag_by_id(admin_browser.contents, 'msgset_5_dismiss'))
     None
-

=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-translate-empty-strings-without-validation.txt'
--- lib/lp/translations/stories/standalone/xx-pofile-translate-empty-strings-without-validation.txt	2011-07-13 06:08:16 +0000
+++ lib/lp/translations/stories/standalone/xx-pofile-translate-empty-strings-without-validation.txt	2018-06-03 00:41:33 +0000
@@ -9,7 +9,7 @@
 translation should use it too. If the translation is empty, our validation
 system should not detect that as an error.
 
-  >>> print browser.contents
+  >>> print(browser.contents)
   <!DOCTYPE...
   ...Migrating `...%s...':...
 
@@ -26,6 +26,5 @@
 We should be redirected to the next page because the validation didn't get
 it as an error.
 
-  >>> print browser.url
+  >>> print(browser.url)
   http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es/+translate?batch=1&memo=13&start=13
-

=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-translate-gettext-error-middle-page.txt'
--- lib/lp/translations/stories/standalone/xx-pofile-translate-gettext-error-middle-page.txt	2009-07-01 20:45:39 +0000
+++ lib/lp/translations/stories/standalone/xx-pofile-translate-gettext-error-middle-page.txt	2018-06-03 00:41:33 +0000
@@ -15,7 +15,7 @@
   ...     name='msgset_142_es_translation_0_radiobutton').value = [
   ...         'msgset_142_es_translation_0_new']
   >>> browser.getControl(name='msgset_142_es_translation_0_new').value = (
-  ...     'Migrando \xc2\xab%i\xc2\xbb')
+  ...     b'Migrando \xc2\xab%i\xc2\xbb')
 
 We are going to add a valid translation too so we are sure that it's stored
 even when there is an error in another message of the same form.
@@ -31,19 +31,19 @@
 
 We remain at the same page:
 
-  >>> print browser.url
+  >>> print(browser.url)
   http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es/+translate?start=10&batch=5
 
 The valid translation is stored:
 
-  >>> print find_tag_by_id(
-  ...     browser.contents, 'msgset_140_es_translation_0').renderContents()
+  >>> print(find_tag_by_id(
+  ...     browser.contents, 'msgset_140_es_translation_0').renderContents())
   Foo
 
 And the error is noted in the page.
 
   >>> for tag in find_tags_by_class(browser.contents, 'error'):
-  ...     print tag
+  ...     print(tag)
   <div class="error message">There is an error in a translation you provided.
     Please correct it before continuing.</div>
   <tr class="error translation">

=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-translate-html-tags-escape.txt'
--- lib/lp/translations/stories/standalone/xx-pofile-translate-html-tags-escape.txt	2011-07-13 06:08:16 +0000
+++ lib/lp/translations/stories/standalone/xx-pofile-translate-html-tags-escape.txt	2018-06-03 00:41:33 +0000
@@ -19,7 +19,7 @@
 
 We are in next form page.
 
-  >>> print user_browser.url
+  >>> print(user_browser.url)
   http://translations.launchpad.dev/ubuntu/hoary/+source/pmount/+pots/pmount/hr/+translate?memo=10&start=10
 
 Let's go back to the modified message.
@@ -30,6 +30,6 @@
 
   >>> text = find_tag_by_id(
   ...     user_browser.contents, 'msgset_67_hr_translation_0')
-  >>> print extract_text(text.renderContents())
+  >>> print(extract_text(text.renderContents()))
   Upotreba:
   %s [opcije] &lt;foo&gt; [&lt;etiketa&gt;]%s%s%s

=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-translate-lang-direction.txt'
--- lib/lp/translations/stories/standalone/xx-pofile-translate-lang-direction.txt	2017-12-24 15:45:26 +0000
+++ lib/lp/translations/stories/standalone/xx-pofile-translate-lang-direction.txt	2018-06-03 00:41:33 +0000
@@ -46,7 +46,8 @@
 But suggestion text is tagged with its language code and its own text
 direction:
 
-  >>> print find_tag_by_id(browser.contents, 'msgset_130_es_suggestion_562_0')
+  >>> print(find_tag_by_id(
+  ...     browser.contents, 'msgset_130_es_suggestion_562_0'))
   <label style="white-space: normal" dir="ltr"
     for="msgset_130_es_suggestion_562_0_radiobutton"
     id="msgset_130_es_suggestion_562_0" lang="es">libreta de

=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-translate-legal-warning.txt'
--- lib/lp/translations/stories/standalone/xx-pofile-translate-legal-warning.txt	2010-10-26 10:31:37 +0000
+++ lib/lp/translations/stories/standalone/xx-pofile-translate-legal-warning.txt	2018-06-03 00:41:33 +0000
@@ -20,8 +20,8 @@
 
     >>> browser = setupBrowser(auth='Basic carlos@xxxxxxxxxxxxx:test')
     >>> browser.open('http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es/3/+translate')
-    >>> print extract_text(find_tag_by_id(
-    ...     browser.contents, 'msgset_132_es_suggestion_3_0'))
+    >>> print(extract_text(find_tag_by_id(
+    ...     browser.contents, 'msgset_132_es_suggestion_3_0')))
     tiene
 
 A warning icon about the legal situation is shown alongside the suggestion

=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-translate-message-filtering.txt'
--- lib/lp/translations/stories/standalone/xx-pofile-translate-message-filtering.txt	2017-12-24 15:45:26 +0000
+++ lib/lp/translations/stories/standalone/xx-pofile-translate-message-filtering.txt	2018-06-03 00:41:33 +0000
@@ -34,8 +34,8 @@
     ...     # Print matching HTML tags, in numeric order of msgset id
     ...     for msgset_id in sorted(translations.keys()):
     ...         translation = translations[msgset_id]
-    ...         print "%d: '%s'" % (
-    ...             msgset_id, translation.renderContents().strip())
+    ...         print("%d: '%s'" % (
+    ...             msgset_id, translation.renderContents().strip()))
 
 
 Filters
@@ -47,7 +47,7 @@
     >>> user_browser.open(
     ...     'http://translations.launchpad.dev/ubuntu/hoary/'
     ...     '+source/evolution/+pots/evolution-2.2/es/+translate')
-    >>> print user_browser.title
+    >>> print(user_browser.title)
     Spanish (es) : Template ...evolution-2.2... :
     Hoary (5.04) : Translations : evolution package : Ubuntu
 
@@ -61,7 +61,7 @@
 this page, and which alternate language to get suggestions from (tested
 in xx-pofile-translate-alternative-language.txt).
 
-    >>> print contents
+    >>> print(contents)
     <... Translating ... using ... as a guide...
 
 
@@ -77,10 +77,10 @@
     >>> re.match('[^?]*', user_browser.url).group()
     'http://.../evolution-2.2/es/+translate'
 
-    >>> print extract_url_parameter(user_browser.url, 'batch')
+    >>> print(extract_url_parameter(user_browser.url, 'batch'))
     batch=10
 
-    >>> print extract_url_parameter(user_browser.url, 'show')
+    >>> print(extract_url_parameter(user_browser.url, 'show'))
     show=untranslated
 
     >>> contents = find_main_content(user_browser.contents)
@@ -124,7 +124,7 @@
     ...     '+source/evolution/+pots/evolution-2.2/en_AU/+translate')
     >>> user_browser.getControl(name='show', index=1).value = ['untranslated']
     >>> user_browser.getControl('Change').click()
-    >>> print user_browser.title
+    >>> print(user_browser.title)
     English (Australia) (en_AU) : Template ...evolution-2.2... :
     Hoary (5.04) : Translations : evolution package : Ubuntu
 
@@ -156,8 +156,8 @@
     >>> print_batch_header(contents)
     1 ... 10  of 21 results
 
-    >>> print find_tag_by_id(
-    ...     user_browser.contents, 'msgset_130_en_AU_translation_0')
+    >>> print(find_tag_by_id(
+    ...     user_browser.contents, 'msgset_130_en_AU_translation_0'))
     None
 
 Projects can restrict translation to privileged users. The messages that
@@ -192,7 +192,7 @@
     ...     'evolution/trunk/+pots/evolution-2.2/en_AU/+translate')
     >>> user_browser.getControl(name='show', index=1).value = ['untranslated']
     >>> user_browser.getControl('Change').click()
-    >>> print user_browser.title
+    >>> print(user_browser.title)
     English (Australia) (en_AU) : Template ...evolution-2.2... :
     Series trunk : Translations : Evolution
 
@@ -235,10 +235,10 @@
     >>> user_browser.getControl(name='show', index=1).displayValue = [
     ...     'changed in Ubuntu']
     >>> user_browser.getControl('Change').click()
-    >>> print extract_url_parameter(user_browser.url, 'batch')
+    >>> print(extract_url_parameter(user_browser.url, 'batch'))
     batch=10
 
-    >>> print extract_url_parameter(user_browser.url, 'show')
+    >>> print(extract_url_parameter(user_browser.url, 'show'))
     show=changed_in_ubuntu
 
     >>> contents = find_main_content(user_browser.contents)
@@ -253,10 +253,10 @@
 the browser is redirected to the first batch.
 
     >>> user_browser.getControl('Save & Continue').click()
-    >>> print extract_url_parameter(user_browser.url, 'batch')
+    >>> print(extract_url_parameter(user_browser.url, 'batch'))
     batch=10
 
-    >>> print extract_url_parameter(user_browser.url, 'show')
+    >>> print(extract_url_parameter(user_browser.url, 'show'))
     show=changed_in_ubuntu
 
     >>> print_shown_messages(user_browser)
@@ -276,10 +276,10 @@
     >>> user_browser.getControl(name='show', index=1).displayValue = [
     ...     'with new suggestions']
     >>> user_browser.getControl('Change').click()
-    >>> print extract_url_parameter(user_browser.url, 'batch')
+    >>> print(extract_url_parameter(user_browser.url, 'batch'))
     batch=10
 
-    >>> print extract_url_parameter(user_browser.url, 'show')
+    >>> print(extract_url_parameter(user_browser.url, 'show'))
     show=new_suggestions
 
     >>> print_shown_messages(user_browser)
@@ -300,7 +300,7 @@
 
     >>> description = first_tag_by_class(user_browser.contents,
     ...     'documentDescription')
-    >>> print extract_text(description)
+    >>> print(extract_text(description))
     There are no messages that match this filtering.
 
 
@@ -358,8 +358,8 @@
 conversion specifications the original message has, and is shown an
 error.
 
-    >>> print find_tag_by_id(
-    ...     user_browser.contents, 'msgset_142_singular').renderContents()
+    >>> print(find_tag_by_id(
+    ...     user_browser.contents, 'msgset_142_singular').renderContents())
     Migrating ...%s...
 
     >>> user_browser.getControl(
@@ -391,7 +391,7 @@
     142: '(no translation yet)'
 
     >>> for tag in find_tags_by_class(user_browser.contents, 'error'):
-    ...     print tag.renderContents()
+    ...     print(tag.renderContents())
     There is an error in a translation you provided.
     Please correct it before continuing.
     ...Error in Translation:...
@@ -428,7 +428,7 @@
     >>> user_browser.getLink("Previous").click()
 
     >>> text = extract_text(find_main_content(user_browser.contents))
-    >>> print text.encode('UTF-8')
+    >>> print(text.encode('UTF-8'))
     Translating...
     English: current addressbook folder
     Current Chinese (China): (no translation yet)

=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-translate-needs-review-flags-preserved.txt'
--- lib/lp/translations/stories/standalone/xx-pofile-translate-needs-review-flags-preserved.txt	2015-10-05 08:36:52 +0000
+++ lib/lp/translations/stories/standalone/xx-pofile-translate-needs-review-flags-preserved.txt	2018-06-03 00:41:33 +0000
@@ -18,7 +18,7 @@
     ...     'Someone should review this translation')
     Traceback (most recent call last):
     ...
-    LookupError: label 'Someone...
+    LookupError: label u'Someone...
 
 If the same user tries translating for another, unrestricted project,
 they get to see the checkbox:
@@ -41,7 +41,7 @@
     >>> user_browser.getControl(
     ...     name='msgset_130_es_translation_0_new').value = "New suggestion"
     >>> user_browser.getControl('Save & Continue').click()
-    >>> print user_browser.url
+    >>> print(user_browser.url)
     http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es/2/+translate
 
 The needs review flag is unset when we go back to the previous message.
@@ -55,9 +55,9 @@
 But a new suggestion is provided for this message.
 
     >>> import re
-    >>> print extract_text(find_tag_by_id(
+    >>> print(extract_text(find_tag_by_id(
     ...     user_browser.contents,
-    ...     re.compile(r'^msgset_130_es_suggestion_\d+_0$')))
+    ...     re.compile(r'^msgset_130_es_suggestion_\d+_0$'))))
     New suggestion
 
 
@@ -82,8 +82,8 @@
     ...     name='msgset_166_es_translation_0_new')
     >>> inputfield.value = 'New test translation'
     >>> admin_browser.getControl('Save & Continue').click()
-    >>> print extract_text(find_tag_by_id(admin_browser.contents,
-    ...                             'messages_to_translate'))
+    >>> print(extract_text(find_tag_by_id(admin_browser.contents,
+    ...                             'messages_to_translate')))
     1. English: test man page
     Current Spanish: New test translation Translated and reviewed ...
     New translation:
@@ -101,8 +101,8 @@
     ...    name='msgset_166_es_translation_0_radiobutton')
     >>> inputradio.value = ['msgset_166_es_translation_0_new']
     >>> admin_browser.getControl('Save & Continue').click()
-    >>> print extract_text(find_tag_by_id(admin_browser.contents,
-    ...                             'messages_to_translate'))
+    >>> print(extract_text(find_tag_by_id(admin_browser.contents,
+    ...                             'messages_to_translate')))
     1. English: test man page
     Current Spanish: (no translation yet)
     Suggestions:

=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-translate-newlines-check.txt'
--- lib/lp/translations/stories/standalone/xx-pofile-translate-newlines-check.txt	2012-12-11 02:19:13 +0000
+++ lib/lp/translations/stories/standalone/xx-pofile-translate-newlines-check.txt	2018-06-03 00:41:33 +0000
@@ -12,7 +12,7 @@
     ...     """Find and print [tags] in browser.contents. End each with '--'."""
     ...     soup = find_main_content(browser.contents)
     ...     for tag in soup.findAll(attrs={'id': tags}):
-    ...         print "%s\n--\n" % tag.renderContents()
+    ...         print("%s\n--\n" % tag.renderContents(encoding=None))
 
     >>> browser = setupBrowser(auth='Basic carlos@xxxxxxxxxxxxx:test')
     >>> browser.open(
@@ -40,9 +40,10 @@
     >>> browser.getControl(
     ...     name='msgset_149_es_translation_0_new').value = '\r\nfoo\r\n\r\n'
     >>> browser.getControl(name='submit_translations').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es/+translate?start=19&batch=1
-    >>> print find_tag_by_id(browser.contents, 'msgset_149_es_translation_0_new')
+    >>> print(find_tag_by_id(
+    ...     browser.contents, 'msgset_149_es_translation_0_new'))
     <textarea ... name="msgset_149_es_translation_0_new"...>
 
     foo
@@ -58,11 +59,11 @@
     ...         'msgset_149_es_translation_0_new']
     >>> browser.getControl(name='msgset_149_es_translation_0_new').value = 'foo'
     >>> browser.getControl(name='submit_translations').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es/+translate?start=19&batch=1
-    >>> print find_tag_by_id(
+    >>> print(find_tag_by_id(
     ...     browser.contents,
-    ...     'msgset_149_es_translation_0_new') #doctest: -NORMALIZE_WHITESPACE
+    ...     'msgset_149_es_translation_0_new')) #doctest: -NORMALIZE_WHITESPACE
     <textarea ... name="msgset_149_es_translation_0_new"...>
     foo</textarea>
 
@@ -76,12 +77,12 @@
     ...     name='msgset_165_es_translation_0_radiobutton').value = [
     ...         'msgset_165_es_translation_0_new']
     >>> browser.getControl(name='msgset_165_es_translation_0_new').value = (
-    ...     '%s: la opcion \xc2\xab%s\xc2\xbb es ambigua')
+    ...     b'%s: la opcion \xc2\xab%s\xc2\xbb es ambigua')
     >>> browser.getControl(name='submit_translations').click()
 
 We were redirected to the next form, the translation was accepted.
 
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2/es/+translate?batch=1
 
 Get previous page to check that the save translation is the right one.
@@ -106,12 +107,12 @@
     ...     name='msgset_165_es_translation_0_radiobutton').value = [
     ...         'msgset_165_es_translation_0_new']
     >>> browser.getControl(name='msgset_165_es_translation_0_new').value = (
-    ...     '%s: la opcion \xc2\xab%s\xc2\xbb es ambigua\r\n')
+    ...     b'%s: la opcion \xc2\xab%s\xc2\xbb es ambigua\r\n')
     >>> browser.getControl(name='submit_translations').click()
 
 We were redirected to the next form, the translation was accepted.
 
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2/es/+translate?batch=1
 
 Get previous page to check that the save translation is the right one.
@@ -136,12 +137,12 @@
     ...     name='msgset_165_es_translation_0_radiobutton').value = [
     ...         'msgset_165_es_translation_0_new']
     >>> browser.getControl(name='msgset_165_es_translation_0_new').value = (
-    ...     '%s: la opcion \xc2\xab%s\xc2\xbb es ambigua\r\n\r\n')
+    ...     b'%s: la opcion \xc2\xab%s\xc2\xbb es ambigua\r\n\r\n')
     >>> browser.getControl(name='submit_translations').click()
 
 We were redirected to the next form, the translation was accepted.
 
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2/es/+translate?batch=1
 
 Get previous page to check that the save translation is the right one.

=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-translate-performance.txt'
--- lib/lp/translations/stories/standalone/xx-pofile-translate-performance.txt	2015-09-29 01:38:34 +0000
+++ lib/lp/translations/stories/standalone/xx-pofile-translate-performance.txt	2018-06-03 00:41:33 +0000
@@ -20,7 +20,7 @@
     ...     '+pots/evolution-2.2/es/+translate')
     >>> anon_browser.url
     'http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es/+translate'
-    >>> print anon_browser.contents
+    >>> print(anon_browser.contents)
     <...
     >>> statement_count = query_counter.count
     >>> 0 < statement_count < 120
@@ -47,4 +47,3 @@
 
     >>> # Cleanup.
     >>> query_counter.unregister()
-

=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-translate-private-issues.txt'
--- lib/lp/translations/stories/standalone/xx-pofile-translate-private-issues.txt	2009-07-01 20:45:39 +0000
+++ lib/lp/translations/stories/standalone/xx-pofile-translate-private-issues.txt	2018-06-03 00:41:33 +0000
@@ -10,12 +10,12 @@
 The GNOME standard string for credits is well handled.
 
   >>> msgid = find_tag_by_id(browser.contents, 'msgset_199_singular')
-  >>> print msgid.renderContents()
+  >>> print(msgid.renderContents())
   translation-credits
 
   >>> translation = find_tag_by_id(
   ...     browser.contents, 'msgset_199_es_translation_0')
-  >>> print translation.renderContents()
+  >>> print(translation.renderContents())
   To prevent privacy issues, this translation is not available to anonymous
   users,<br /> if you want to see it, please, <a href="+login">log in</a>
   first.
@@ -23,13 +23,13 @@
 And the same for KDE one.
 
   >>> msgid = find_tag_by_id(browser.contents, 'msgset_200_singular')
-  >>> print msgid.renderContents()
+  >>> print(msgid.renderContents())
   _: EMAIL OF TRANSLATORS<img alt="" src="/@@/translation-newline" /><br />
   Your emails
 
   >>> translation = find_tag_by_id(
   ...     browser.contents, 'msgset_200_es_translation_0')
-  >>> print translation.renderContents()
+  >>> print(translation.renderContents())
   To prevent privacy issues, this translation is not available to anonymous
   users,<br /> if you want to see it, please, <a href="+login">log in</a>
   first.
@@ -50,30 +50,30 @@
 The GNOME standard string for credits is now available:
 
   >>> msgid = find_tag_by_id(user_browser.contents, 'msgset_199_singular')
-  >>> print msgid.renderContents()
+  >>> print(msgid.renderContents())
   translation-credits
 
   >>> translation = find_tag_by_id(
   ...     user_browser.contents, 'msgset_199_es_translation_0')
-  >>> print extract_text(translation.renderContents())
+  >>> print(extract_text(translation.renderContents()))
   Launchpad Contributions:
   Carlos ... http://translations.launchpad.dev/~carlos
 
 And the same for KDE one.
 
   >>> msgid = find_tag_by_id(user_browser.contents, 'msgset_200_singular')
-  >>> print msgid.renderContents()
+  >>> print(msgid.renderContents())
   _: EMAIL OF TRANSLATORS<img alt="" src="/@@/translation-newline" /><br />
   Your emails
 
   >>> translation = find_tag_by_id(
   ...     user_browser.contents, 'msgset_200_es_translation_0')
-  >>> print translation.renderContents()
+  >>> print(translation.renderContents())
   ,,carlos@xxxxxxxxxxxxx
 
 Also, suggestions should not appear.
 
   >>> suggestion = find_tag_by_id(
   ...     user_browser.contents, 'msgset_199_es_suggestion_709_0')
-  >>> print suggestion
+  >>> print(suggestion)
   None

=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-translate-search.txt'
--- lib/lp/translations/stories/standalone/xx-pofile-translate-search.txt	2009-07-01 20:45:39 +0000
+++ lib/lp/translations/stories/standalone/xx-pofile-translate-search.txt	2018-06-03 00:41:33 +0000
@@ -30,7 +30,7 @@
 A warning is displayed.
 
     >>> tags = find_tags_by_class(user_browser.contents, 'warning message')
-    >>> print extract_text(tags[0])
+    >>> print(extract_text(tags[0]))
     Please try searching for a longer string.
 
 And no filtering is applied: all messages are shown.

=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-translate.txt'
--- lib/lp/translations/stories/standalone/xx-pofile-translate.txt	2016-01-26 15:47:37 +0000
+++ lib/lp/translations/stories/standalone/xx-pofile-translate.txt	2018-06-03 00:41:33 +0000
@@ -39,12 +39,12 @@
     >>> main_content = find_tag_by_id(
     ...     browser.contents, 'messages_to_translate')
     >>> for textarea in main_content.findAll('textarea'):
-    ...     print 'Found textarea:\n%s' % textarea
+    ...     print('Found textarea:\n%s' % textarea)
 
 In fact, no input widgets at all are displayed.
 
     >>> for input in main_content.findAll('input'):
-    ...     print 'Found input:\n%s' % input
+    ...     print('Found input:\n%s' % input)
 
 As an anynoymous user you will have access to the download and details
 pages for the pofile this message belongs to. The link to upload page
@@ -52,15 +52,15 @@
 translator and reviewer working mode.
 
     >>> nav = find_tag_by_id(browser.contents, 'nav-pofile-subpages')
-    >>> print extract_text(nav)
+    >>> print(extract_text(nav))
     Download translation Translation details
 
 Download translations and Translation details should linked to the proper
 pages
 
-    >> print nav.getLink("Download translation").url
+    >> print(nav.getLink("Download translation").url)
     https://.../hoary/+source/evolution/+pots/evolution-2.2/es/+export
-    >> print nav.getLink("Translation details").url
+    >> print(nav.getLink("Translation details").url)
     https://.../hoary/+source/evolution/+pots/evolution-2.2/es/+details
 
 Rendering the form in read-only mode does not actually stop an anonymous
@@ -84,17 +84,17 @@
 working mode
 
     >>> nav = find_tag_by_id(admin_browser.contents, 'nav-pofile-subpages')
-    >>> print extract_text(nav)
+    >>> print(extract_text(nav))
     Download translation Upload translation Translation details
     Reviewer mode (What's this?)
 
 All those links should linked the proper pages
 
-    >> print nav.getLink("Download translation").url
+    >> print(nav.getLink("Download translation").url)
     https://.../hoary/+source/evolution/+pots/evolution-2.2/es/+export
-    >> print nav.getLink("Upload translation").url
+    >> print(nav.getLink("Upload translation").url)
     https://.../hoary/+source/evolution/+pots/evolution-2.2/es/+upload
-    >> print nav.getLink("Translation details").url
+    >> print(nav.getLink("Translation details").url)
     https://.../hoary/+source/evolution/+pots/evolution-2.2/es/+details
 
 
@@ -154,7 +154,7 @@
 
     >>> msgset_130 = get_tags(browser, 'id', 'msgset_130')
     >>> for id in msgset_130:
-    ...     print id
+    ...     print(id)
     msgset_130
     ...
     msgset_130_singular...
@@ -170,7 +170,7 @@
     * optional suffix describing the element, such as 'radiobutton.'
 
     >>> for id in msgset_130:
-    ...     print id
+    ...     print(id)
     msgset_130
     msgset_130_en_AU_translation_0
     msgset_130_en_AU_translation_0_new
@@ -196,7 +196,7 @@
     ...     '+pots/alsa-utils/es/+translate')
     >>> msgset_198 = get_tags(browser, 'name', 'msgset_198')
     >>> for name in msgset_198:
-    ...     print name
+    ...     print(name)
     msgset_198
     msgset_198_es_needsreview
     msgset_198_es_translation_0_new
@@ -212,8 +212,8 @@
     >>> browser.open(
     ...     'http://translations.launchpad.dev/ubuntu/hoary/+source/'
     ...     'evolution/+pots/evolution-2.2/es/5/+translate')
-    >>> print extract_text(find_tag_by_id(
-    ...     browser.contents, 'msgset_134_es_suggestion_694_0'))
+    >>> print(extract_text(find_tag_by_id(
+    ...     browser.contents, 'msgset_134_es_suggestion_694_0')))
     tarjetas
 
 
@@ -236,4 +236,3 @@
     ...     '+source/evolution/+pots/evolution-2.2/ab/5/+translate')
     >>> print_feedback_messages(browser.contents)
     Launchpad can&#8217;t handle the plural items ...
-

=== modified file 'lib/lp/translations/stories/standalone/xx-potemplate-admin.txt'
--- lib/lp/translations/stories/standalone/xx-potemplate-admin.txt	2016-06-21 17:01:35 +0000
+++ lib/lp/translations/stories/standalone/xx-potemplate-admin.txt	2018-06-03 00:41:33 +0000
@@ -23,7 +23,7 @@
     >>> browser.open(
     ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
     ...     'evolution-2.2/+admin')
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations...dev/evolution/trunk/+pots/evolution-2.2/+admin
 
 Now, we should be sure that the admin form gets all required fields to allow
@@ -35,7 +35,7 @@
     'evolution-2.2'
     >>> browser.getControl(name='field.description').value
     'Template for evolution in hoary'
-    >>> print browser.getControl(name='field.header').value
+    >>> print(browser.getControl(name='field.header').value)
     Project-Id-Version: PACKAGE VERSION
     Report-Msgid-Bugs-To:
     POT-Creation-Date: 2005-08-25 14:56+0200
@@ -69,7 +69,7 @@
     >>> admin_browser.open(
     ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
     ...     'evolution-2.2/+admin')
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations...dev/evolution/trunk/+pots/evolution-2.2/+admin
 
 Now, we should be sure that the admin form gets all required fields to allow
@@ -81,7 +81,7 @@
     'evolution-2.2'
     >>> admin_browser.getControl(name='field.description').value
     'Template for evolution in hoary'
-    >>> print admin_browser.getControl(name='field.header').value
+    >>> print(admin_browser.getControl(name='field.header').value)
     Project-Id-Version: PACKAGE VERSION
     Report-Msgid-Bugs-To:
     POT-Creation-Date: 2005-08-25 14:56+0200
@@ -119,7 +119,7 @@
 
     >>> admin_browser.getControl('Translation domain').value = 'foo'
     >>> admin_browser.getControl('Change').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2
 
 Going back to the form we can see that the changes are saved and also,
@@ -156,7 +156,7 @@
     >>> admin_browser.getControl(name='field.name').value = (
     ...     'evolution-renamed')
     >>> admin_browser.getControl('Change').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/evolution/trunk/+pots/evolution-renamed
 
 Administrators can disable and then make changes to a disabled template.
@@ -166,7 +166,7 @@
     ...     'evolution-renamed/+admin')
     >>> admin_browser.getControl(name='field.iscurrent').value = False
     >>> admin_browser.getControl('Change').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/evolution/trunk/+pots/evolution-renamed
 
 Now we will reenable the template.
@@ -176,7 +176,7 @@
     ...     'evolution-renamed/+admin')
     >>> admin_browser.getControl(name='field.iscurrent').value = True
     >>> admin_browser.getControl('Change').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/evolution/trunk/+pots/evolution-renamed
 
 
@@ -220,7 +220,7 @@
     >>> distro_owner_browser.getControl(name='field.path').value = 'bar.pot'
     >>> distro_owner_browser.getControl('Change').click()
 
-    >>> print template.path
+    >>> print(template.path)
     bar.pot
 
 This privilege also extends to items that require "edit" permissions.
@@ -238,7 +238,7 @@
     >>> group_owner_browser.getControl(name='field.priority').value = '543'
     >>> group_owner_browser.getControl('Change').click()
 
-    >>> print template.path
+    >>> print(template.path)
     splat.pot
 
 Distribution translation coordinators can disable and manage disabled

=== modified file 'lib/lp/translations/stories/standalone/xx-potemplate-edit.txt'
--- lib/lp/translations/stories/standalone/xx-potemplate-edit.txt	2016-06-09 20:13:17 +0000
+++ lib/lp/translations/stories/standalone/xx-potemplate-edit.txt	2018-06-03 00:41:33 +0000
@@ -33,7 +33,7 @@
     ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
     ...     'evolution-2.2/')
     >>> browser.getLink('Change details').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.../evolution/trunk/+pots/evolution-2.2/+edit
 
 The owner of a product has access to edit page for PO templates.
@@ -42,7 +42,7 @@
     >>> browser.open(
     ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
     ...     'evolution-2.2/+edit')
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.../evolution/trunk/+pots/evolution-2.2/+edit
 
 Owner will not see admin fields, only those fields designated for the
@@ -121,7 +121,7 @@
     >>> browser.getControl(name='field.owner').value = u'name12'
     >>> browser.getControl(name='field.description').value = u'foo'
     >>> browser.getControl('Change').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/evolution/trunk/+pots/evo
 
 The changed values will be stored and visible by accesing again the edit
@@ -172,7 +172,7 @@
     ...     'evolution-2.2/+edit')
     >>> admin_browser.getControl(name='field.priority').value = '-1'
     >>> admin_browser.getControl('Change').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.../evolution/trunk/+pots/evolution-2.2/+edit
 
     >>> print_feedback_messages(admin_browser.contents)
@@ -184,7 +184,7 @@
     ...     'evolution-2.2/+edit')
     >>> admin_browser.getControl(name='field.priority').value = '100001'
     >>> admin_browser.getControl('Change').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.../evolution/trunk/+pots/evolution-2.2/+edit
 
     >>> print_feedback_messages(admin_browser.contents)
@@ -216,7 +216,5 @@
     ...   'http://translations.launchpad.dev/evolution/trunk/+pots/'
     ...   'evolution-2.2/+edit')
     >>> admin_browser.getLink('Cancel').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2
-
-

=== modified file 'lib/lp/translations/stories/standalone/xx-potemplate-export.txt'
--- lib/lp/translations/stories/standalone/xx-potemplate-export.txt	2013-09-27 04:13:23 +0000
+++ lib/lp/translations/stories/standalone/xx-potemplate-export.txt	2018-06-03 00:41:33 +0000
@@ -47,7 +47,7 @@
   >>> browser.getControl('Everything').selected = True
   >>> browser.getControl('Format:').value = ['PO']
   >>> browser.getControl('Request Download').click()
-  >>> print browser.url
+  >>> print(browser.url)
   http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2
 
   >>> print_feedback_messages(browser.contents)
@@ -62,9 +62,8 @@
   >>> browser.getControl('The PO template').selected = True
   >>> browser.getControl('Format:').value = ['PO']
   >>> browser.getControl('Request Download').click()
-  >>> print browser.url
+  >>> print(browser.url)
   http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2
 
   >>> print_feedback_messages(browser.contents)
   Your request has been received. Expect to receive an email shortly.
-

=== modified file 'lib/lp/translations/stories/standalone/xx-potemplate-index.txt'
--- lib/lp/translations/stories/standalone/xx-potemplate-index.txt	2014-11-27 22:13:36 +0000
+++ lib/lp/translations/stories/standalone/xx-potemplate-index.txt	2018-06-03 00:41:33 +0000
@@ -11,7 +11,7 @@
 
     >>> anon_browser.open("http://translations.launchpad.dev/";
     ...     "ubuntu/hoary/+source/evolution/+pots/evolution-2.2/")
-    >>> print anon_browser.title
+    >>> print(anon_browser.title)
     Template ...evolution-2.2... : Hoary (5.04) :
     Translations : evolution package : Ubuntu
 
@@ -19,7 +19,7 @@
 
     >>> owner_display = find_tag_by_id(anon_browser.contents,
     ...                                'potemplate-owner')
-    >>> print extract_text(owner_display)
+    >>> print(extract_text(owner_display))
     Owner: Rosetta Administrators
 
 The page lists the status of all the translations. It merges the
@@ -30,12 +30,12 @@
 that represents, when the translation was updated, and by whom.
 
     >>> content = find_main_content(anon_browser.contents)
-    >>> print extract_text(content.findAll('h1')[0])
+    >>> print(extract_text(content.findAll('h1')[0]))
     Translation status
 
     >>> table = content.findAll('table')[0]
     >>> for row in table.findAll('tr'):
-    ...     print extract_text(row).encode('us-ascii', 'backslashreplace')
+    ...     print(extract_text(row).encode('us-ascii', 'backslashreplace'))
     Language        Status  Untranslated Need review Changed Last    Edited By
     Afrikaans               22           ...         ...     &mdash; &mdash;
     Japanese                21           ...         ...     ...     Carlos...
@@ -54,7 +54,7 @@
     ...     '+source/mozilla/+pots/pkgconf-mozilla')
     >>> table = find_tag_by_id(anon_browser.contents, 'language-chart')
     >>> for row in table.findAll('tr')[0:6]:
-    ...     print extract_text(row).encode('us-ascii', 'backslashreplace')
+    ...     print(extract_text(row).encode('us-ascii', 'backslashreplace'))
     Language    Status  Untranslated Need review Changed Last  Edited By
     Afrikaans             9            ...         ...   ...   &mdash;
     Czech                 ...          ...         ...   ...   Miroslav Kure
@@ -73,12 +73,12 @@
     ...     '/trunk/+pots/evolution-2.2')
     >>> sharing_info = find_tag_by_id(
     ...     anon_browser.contents, 'sharing-information')
-    >>> print extract_text(sharing_info)
+    >>> print(extract_text(sharing_info))
     Sharing Information
     This template is sharing translations with
     evolution in Ubuntu Hoary template evolution-2.2.
     View sharing details
-    >>> print sharing_info
+    >>> print(sharing_info)
     <div...<a href="/ubuntu/hoary/+source/evolution/+pots/evolution-2.2"...
 
 Likewise, the Ubuntu template gives information about how it is sharing
@@ -88,12 +88,12 @@
     ...     '/hoary/+source/evolution/+pots/evolution-2.2')
     >>> sharing_info = find_tag_by_id(
     ...     anon_browser.contents, 'sharing-information')
-    >>> print extract_text(sharing_info)
+    >>> print(extract_text(sharing_info))
     Sharing Information
     This template is sharing translations with
     Evolution trunk series template evolution-2.2.
     View sharing details
-    >>> print sharing_info
+    >>> print(sharing_info)
     <div...<a href="/evolution/trunk/+pots/evolution-2.2"...
 
 If the user has the right permissions, they are offered to edit the sharing
@@ -103,9 +103,9 @@
     ...     '/trunk/+pots/evolution-2.2')
     >>> sharing_details = find_tag_by_id(
     ...     admin_browser.contents, 'sharing-details')
-    >>> print extract_text(sharing_details)
+    >>> print(extract_text(sharing_details))
     Edit sharing details
-    >>> print sharing_details['href']
+    >>> print(sharing_details['href'])
     http://.../ubuntu/hoary/+source/evolution/+sharing-details
 
 
@@ -120,12 +120,12 @@
     ...     "evolution/trunk/+pots/evolution-2.2-test")
     >>> alternate_notice = find_tag_by_id(anon_browser.contents,
     ...                                   'potemplate-relatives')
-    >>> print extract_text(alternate_notice)
+    >>> print(extract_text(alternate_notice))
     Other templates here: evolution-2.2.
 
 The notice links to the alternate template.
 
-    >>> print alternate_notice
+    >>> print(alternate_notice)
     <p...>
     <span>Other templates here:</span>
     <a href="/evolution/trunk/+pots/evolution-2.2">evolution-2.2</a>...
@@ -170,7 +170,7 @@
     ...     package.name, template.name))
     >>> relatives = find_tag_by_id(
     ...     browser.contents, 'potemplate-relatives')
-    >>> print extract_text(relatives)
+    >>> print(extract_text(relatives))
     Other templates here: first, forth, second, third.
 
 For five templates, the page displays the first four templates in
@@ -190,7 +190,7 @@
     ...     package.name, template.name))
     >>> relatives = find_tag_by_id(
     ...     browser.contents, 'potemplate-relatives')
-    >>> print extract_text(relatives)
+    >>> print(extract_text(relatives))
     Other templates here: fifth, first, forth, second
     and one other template.
 
@@ -214,7 +214,7 @@
     ...     package.name, template.name))
     >>> relatives = find_tag_by_id(
     ...     browser.contents, 'potemplate-relatives')
-    >>> print extract_text(relatives)
+    >>> print(extract_text(relatives))
     Other templates here: fifth, first, forth, second
     and 2 other templates.
 
@@ -256,7 +256,7 @@
     ...     "fusa/trunk/+pots/%s") % template.name)
     >>> relatives = find_tag_by_id(
     ...     browser.contents, 'potemplate-relatives')
-    >>> print extract_text(relatives)
+    >>> print(extract_text(relatives))
     Other templates here: fifth, first, forth, second
     and 2 other templates.
 
@@ -298,7 +298,7 @@
     LinkNotFoundError
 
     >>> user_browser.getLink('download').click()
-    >>> print user_browser.url
+    >>> print(user_browser.url)
     http://trans.../ubuntu/hoary/+source/evolution/+pots/evolution-2.2/+export
 
 Translation administrators will see both download and upload links.
@@ -309,28 +309,26 @@
     ...     'http://translations.launchpad.dev/'
     ...     'ubuntu/hoary/+source/evolution/+pots/evolution-2.2')
     >>> admin_browser.getLink('upload').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://trans.../ubuntu/hoary/+source/evolution/+pots/evolution-2.2/+upload
 
     >>> admin_browser.open(
     ...     'http://translations.launchpad.dev/'
     ...     'ubuntu/hoary/+source/evolution/+pots/evolution-2.2')
     >>> admin_browser.getLink('download').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://trans.../ubuntu/hoary/+source/evolution/+pots/evolution-2.2/+export
 
     >>> admin_browser.open(
     ...     'http://translations.launchpad.dev/'
     ...     'ubuntu/hoary/+source/evolution/+pots/evolution-2.2')
     >>> admin_browser.getLink('Administer this template').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://trans.../ubuntu/hoary/+source/evolution/+pots/evolution-2.2/+admin
 
     >>> admin_browser.open(
     ...     'http://translations.launchpad.dev/'
     ...     'ubuntu/hoary/+source/evolution/+pots/evolution-2.2')
     >>> admin_browser.getLink('Change details').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://trans.../ubuntu/hoary/+source/evolution/+pots/evolution-2.2/+edit
-
-

=== modified file 'lib/lp/translations/stories/standalone/xx-product-export.txt'
--- lib/lp/translations/stories/standalone/xx-product-export.txt	2014-11-24 09:16:35 +0000
+++ lib/lp/translations/stories/standalone/xx-product-export.txt	2018-06-03 00:41:33 +0000
@@ -25,7 +25,7 @@
 The translations export is implemented by the same machinery that does
 it for source packages (tested and documented separately).
 
-    >>> print user_browser.title
+    >>> print(user_browser.title)
     Download : Series trunk : Translations...
 
     >>> user_browser.getControl('Request Download').click()
@@ -80,7 +80,7 @@
     >>> anon_browser.open('http://translations.launchpad.dev/evolution/')
     >>> for tag in find_tags_by_class(
     ...     anon_browser.contents, 'menu-link-translationdownload'):
-    ...     print tag.renderContents()
+    ...     print(tag.renderContents())
 
     # Reset global configuration...
     >>> config.devmode = True

=== modified file 'lib/lp/translations/stories/standalone/xx-product-translations.txt'
--- lib/lp/translations/stories/standalone/xx-product-translations.txt	2015-06-15 08:35:10 +0000
+++ lib/lp/translations/stories/standalone/xx-product-translations.txt	2018-06-03 00:41:33 +0000
@@ -4,9 +4,9 @@
 Each product in Launchpad has a Translations page.
 
     >>> anon_browser.open('http://translations.launchpad.dev/evolution')
-    >>> print anon_browser.title
+    >>> print(anon_browser.title)
     Translations : Evolution
-    >>> print extract_text(find_main_content(anon_browser.contents))
+    >>> print(extract_text(find_main_content(anon_browser.contents)))
     Translation overview
     ...
 
@@ -15,18 +15,18 @@
     >>> def print_language_stats(browser):
     ...     table = find_tag_by_id(browser.contents, 'languagestats')
     ...     if table is None:
-    ...         print "No translations."
+    ...         print("No translations.")
     ...         return
     ...     language_rows = find_tags_by_class(str(table), 'stats')
-    ...     print "%-25s %13s %13s" % (
-    ...         "Language", "Untranslated", "Unreviewed")
+    ...     print("%-25s %13s %13s" % (
+    ...         "Language", "Untranslated", "Unreviewed"))
     ...     for row in language_rows:
     ...         cols = row.findAll('td')
     ...         language = extract_text(cols[0])
     ...         untranslated = extract_text(cols[2])
     ...         unreviewed = extract_text(cols[3])
-    ...         print "%-25s %13d %13d" % (
-    ...             language, int(untranslated), int(unreviewed))
+    ...         print("%-25s %13d %13d" % (
+    ...             language, int(untranslated), int(unreviewed)))
 
 We even have a language chart table.
 
@@ -42,14 +42,14 @@
     >>> registrant = setupBrowser(auth='Basic mark@xxxxxxxxxxx:test')
     >>> registrant.open(
     ...     'http://translations.launchpad.dev/gnomebaker')
-    >>> print extract_text(
+    >>> print(extract_text(
     ...     find_tag_by_id(
-    ...         registrant.contents, 'not-translated-in-launchpad'))
+    ...         registrant.contents, 'not-translated-in-launchpad')))
     Launchpad does not know where gnomebaker translates its messages.
 
-    >>> print extract_text(
+    >>> print(extract_text(
     ...     find_tag_by_id(
-    ...         registrant.contents, 'translations-explanation'))
+    ...         registrant.contents, 'translations-explanation')))
     Launchpad allows communities to translate projects using imports or a
     branch.
     Getting started with translating your project in Launchpad
@@ -65,7 +65,7 @@
 Launchpad for translations if desired.
 
     >>> registrant.getLink('Translations').click()
-    >>> print registrant.url
+    >>> print(registrant.url)
     http://.../gnomebaker/+configure-translations
 
 (The template upload process is tested in xx-translation-import-queue.txt.)
@@ -76,7 +76,7 @@
 
     >>> unprivileged = setupBrowser(auth='Basic no-priv@xxxxxxxxxxxxx:test')
     >>> unprivileged.open('http://translations.launchpad.dev/gnomebaker')
-    >>> print extract_text(find_main_content(unprivileged.contents))
+    >>> print(extract_text(find_main_content(unprivileged.contents)))
     Translation overview
     Help for translations
     Launchpad does not know where
@@ -107,7 +107,7 @@
 options, either.
 
     >>> anon_browser.open('http://translations.launchpad.dev/gnomebaker')
-    >>> print extract_text(find_main_content(anon_browser.contents))
+    >>> print(extract_text(find_main_content(anon_browser.contents)))
     Translation overview
     Help for translations
     Launchpad does not know where
@@ -118,16 +118,16 @@
 
     >>> anon_browser.open('http://launchpad.dev/netapplet')
     >>> anon_browser.getLink('Translations').click()
-    >>> print anon_browser.title
+    >>> print(anon_browser.title)
     Translations : NetApplet
-    >>> print find_main_content(anon_browser.contents)
+    >>> print(find_main_content(anon_browser.contents))
     <...
     ...Translation overview...
 
 And since the Network Applet isn't currently using Launchpad for
 Translations, there is no language chart shown.
 
-    >>> print find_tag_by_id(anon_browser.contents, 'language-chart')
+    >>> print(find_tag_by_id(anon_browser.contents, 'language-chart'))
     None
 
 If the netapplet project is updated to use Launchpad for translations...
@@ -145,16 +145,16 @@
 ...there are no longer any obsolete entries.
 
     >>> admin_browser.getLink('Translations', index=1).click()
-    >>> print admin_browser.title
+    >>> print(admin_browser.title)
     Configure translations : Translations : NetApplet
-    >>> print find_tag_by_id(admin_browser.contents,
-    ...                'portlet-obsolete-translatable-series')
+    >>> print(find_tag_by_id(admin_browser.contents,
+    ...                'portlet-obsolete-translatable-series'))
     None
 
 Also, we will get some translation status for network applet.
 
     >>> anon_browser.open('http://translations.launchpad.dev/netapplet')
-    >>> print find_main_content(anon_browser.contents)
+    >>> print(find_main_content(anon_browser.contents))
     <...
     ...Translation overview...
     >>> print_language_stats(anon_browser)
@@ -179,13 +179,13 @@
 That's all an anonymous user will see.
 
     >>> anon_browser.open(product_url)
-    >>> print find_translation_recommendation(anon_browser)
+    >>> print(find_translation_recommendation(anon_browser))
     Launchpad currently recommends translating Evolution trunk series.
 
 A logged-in user is also invited to download translations.
 
     >>> user_browser.open(product_url)
-    >>> print find_translation_recommendation(user_browser)
+    >>> print(find_translation_recommendation(user_browser))
     Launchpad currently recommends translating Evolution trunk series.
     You can also download translations for trunk.
 
@@ -193,7 +193,7 @@
 to upload as well.
 
     >>> admin_browser.open(product_url)
-    >>> print find_translation_recommendation(admin_browser)
+    >>> print(find_translation_recommendation(admin_browser))
     Launchpad currently recommends translating Evolution trunk series.
     You can also download or upload translations for trunk.
 
@@ -213,7 +213,7 @@
     ...     potemplate.iscurrent = False
     >>> logout()
     >>> admin_browser.open(product_url)
-    >>> print find_translation_recommendation(admin_browser)
+    >>> print(find_translation_recommendation(admin_browser))
     None
 
 At the moment, translatable source packages are not recommended, although
@@ -221,14 +221,14 @@
 
     >>> source_package = find_tag_by_id(
     ...     admin_browser.contents, 'portlet-translatable-packages')
-    >>> print extract_text(source_package)
+    >>> print(extract_text(source_package))
     All translatable distribution packages
     evolution source package in Hoary
 
 Instead a notice is displayed that the product has no translations.
 
     >>> notice = first_tag_by_class(admin_browser.contents, 'notice')
-    >>> print extract_text(notice)
+    >>> print(extract_text(notice))
     Getting started with translating your project in Launchpad
     Configure Translations
     There are no translations for this project.

=== modified file 'lib/lp/translations/stories/standalone/xx-products-with-translations.txt'
--- lib/lp/translations/stories/standalone/xx-products-with-translations.txt	2015-06-26 12:57:00 +0000
+++ lib/lp/translations/stories/standalone/xx-products-with-translations.txt	2018-06-03 00:41:33 +0000
@@ -24,9 +24,8 @@
     ...     'http://translations.launchpad.dev/'
     ...     'translations/+products-with-translations')
 
-    >>> print find_main_content(browser.contents).renderContents()
+    >>> print(find_main_content(browser.contents).renderContents())
     <...>
     ... of 2 results
     ...Evolution...
     ...alsa-utils...
-

=== modified file 'lib/lp/translations/stories/standalone/xx-rosetta-distributionsourcepackage-list.txt'
--- lib/lp/translations/stories/standalone/xx-rosetta-distributionsourcepackage-list.txt	2014-11-25 05:54:32 +0000
+++ lib/lp/translations/stories/standalone/xx-rosetta-distributionsourcepackage-list.txt	2018-06-03 00:41:33 +0000
@@ -8,14 +8,14 @@
     'Translations : ...evolution...package : Ubuntu'
 
     >>> content = find_main_content(anon_browser.contents)
-    >>> print extract_text(content.find(attrs='top-portlet'))
+    >>> print(extract_text(content.find(attrs='top-portlet')))
     Launchpad currently recommends translating evolution in Ubuntu Hoary.
 
 The focus' two templates are shown.
 
     >>> template_names = content.findAll('h2')
     >>> for name in template_names:
-    ...     print extract_text(name)
+    ...     print(extract_text(name))
     Template "evolution-2.2" in Ubuntu Hoary package "evolution"
     Template "man" in Ubuntu Hoary package "evolution"
     Other versions of evolution in Ubuntu
@@ -23,7 +23,7 @@
 Other series are also listed.
 
     >>> for other in content.find(id='distroseries-list').findAll('li'):
-    ...     print extract_text(other)
+    ...     print(extract_text(other))
     Breezy Badger Autotest (6.6.6)
     Grumpy (5.10)
     Warty (4.10)

=== modified file 'lib/lp/translations/stories/standalone/xx-rosetta-homepage.txt'
--- lib/lp/translations/stories/standalone/xx-rosetta-homepage.txt	2011-12-08 18:47:23 +0000
+++ lib/lp/translations/stories/standalone/xx-rosetta-homepage.txt	2018-06-03 00:41:33 +0000
@@ -18,7 +18,7 @@
 --------------
 
     >>> browser.open('http://translations.launchpad.dev/')
-    >>> print browser.title
+    >>> print(browser.title)
     Launchpad Translations
 
 The page includes a list of translatable distribution series
@@ -26,7 +26,7 @@
 
     >>> left_column = find_tags_by_class(
     ...     browser.contents, 'three column left')[0]
-    >>> print extract_text(left_column)
+    >>> print(extract_text(left_column))
     Translatable operating systems
     Ubuntu Hoary (5.04)
 
@@ -36,12 +36,12 @@
     >>> middle_column = find_tags_by_class(
     ...     browser.contents, 'three column middle')[0]
     >>> heading = middle_column.findAll('h2')[0]
-    >>> print extract_text(heading)
+    >>> print(extract_text(heading))
     Translatable projects
     >>> for project in middle_column.findAll('span'):
-    ...     print extract_text(project)
+    ...     print(extract_text(project))
     Evolution
-    >>> print extract_text(middle_column.findAll('div')[0])
+    >>> print(extract_text(middle_column.findAll('div')[0]))
     &raquo; List all translatable projects...
 
 The translation front page list of the user's translatable languages.
@@ -49,7 +49,7 @@
 
     >>> right_column = find_tags_by_class(
     ...     browser.contents, 'three column right')[0]
-    >>> print extract_text(right_column)
+    >>> print(extract_text(right_column))
     Your preferred languages
     Afrikaans
     Sotho, Southern

=== modified file 'lib/lp/translations/stories/standalone/xx-rosetta-source-package-redirects.txt'
--- lib/lp/translations/stories/standalone/xx-rosetta-source-package-redirects.txt	2012-02-20 00:45:53 +0000
+++ lib/lp/translations/stories/standalone/xx-rosetta-source-package-redirects.txt	2018-06-03 00:41:33 +0000
@@ -1,10 +1,10 @@
 Checks that the '+pots/' page redirects always to the '+translations' one.
 
-  >>> print http(r"""
+  >>> print(http(r"""
   ... GET /ubuntu/hoary/+source/evolution/+pots/ HTTP/1.1
   ... Accept-Language: en-gb,en;q=0.5
   ... Host: translations.launchpad.dev
-  ... """)
+  ... """))
   HTTP/1.1 303 See Other
   Content-Length: 0
   Content-Type: text/plain;charset=utf-8
@@ -13,11 +13,11 @@
 
 Checks that the '+pots' page redirects always to the '+translations' one.
 
-  >>> print http(r"""
+  >>> print(http(r"""
   ... GET /ubuntu/hoary/+source/evolution/+pots HTTP/1.1
   ... Accept-Language: en-gb,en;q=0.5
   ... Host: translations.launchpad.dev
-  ... """)
+  ... """))
   HTTP/1.1 303 See Other
   ...
   Location: .../ubuntu/hoary/+source/evolution/+pots...
@@ -30,14 +30,13 @@
 Hardy, which is 2013-04.  Please consult with the Ubuntu Desktop team before
 removing.
 
-  >>> print http(r"""
+  >>> print(http(r"""
   ... GET /ubuntu/hoary/+sources/evolution/+translate HTTP/1.1
   ... Accept-Language: en-gb,en;q=0.5
   ... Host: translations.launchpad.dev
-  ... """)
+  ... """))
   HTTP/1.1 301 Moved Permanently
   Content-Length: 0
   Content-Type: text/plain;charset=utf-8
   Location: .../+source/evolution/+translations
   ...
-

=== modified file 'lib/lp/translations/stories/standalone/xx-rosetta-sourcepackage-list.txt'
--- lib/lp/translations/stories/standalone/xx-rosetta-sourcepackage-list.txt	2014-11-24 09:16:35 +0000
+++ lib/lp/translations/stories/standalone/xx-rosetta-sourcepackage-list.txt	2018-06-03 00:41:33 +0000
@@ -11,15 +11,15 @@
     'Hoary (5.04) : Translations : ...evolution...package : Ubuntu'
 
     >>> content = find_main_content(anon_browser.contents)
-    >>> print extract_text(content.findAll('h1')[0]).encode(
-    ...     'ascii','backslashreplace')
+    >>> print(extract_text(content.findAll('h1')[0]).encode(
+    ...     'ascii','backslashreplace'))
     Translations for evolution in Ubuntu Hoary
 
 There are two templates for evolution in Ubuntu Hoary
 
     >>> template_names = content.findAll('h2')
     >>> for name in template_names:
-    ...     print extract_text(name)
+    ...     print(extract_text(name))
     Template "evolution-2.2" in Ubuntu Hoary package "evolution"
     Template "man" in Ubuntu Hoary package "evolution"
 
@@ -32,7 +32,7 @@
 
     >>> table = content.findAll('table')[0]
     >>> for row in table.findAll('tr'):
-    ...     print extract_text(row)
+    ...     print(extract_text(row))
     Language        Status Untranslated Need review Changed Last    Edited By
     Afrikaans              22             ...         ...   &mdash; &mdash;
     Sotho, Southern        22             ...         ...   &mdash; &mdash;

=== modified file 'lib/lp/translations/stories/standalone/xx-series-templates.txt'
--- lib/lp/translations/stories/standalone/xx-series-templates.txt	2012-06-16 14:07:41 +0000
+++ lib/lp/translations/stories/standalone/xx-series-templates.txt	2018-06-03 00:41:33 +0000
@@ -19,7 +19,7 @@
     >>> user_browser.open(
     ...     'http://translations.launchpad.dev/ubuntu/hoary')
     >>> user_browser.getLink('full list of templates').click()
-    >>> print user_browser.url
+    >>> print(user_browser.url)
     http://translations.launchpad.dev/ubuntu/hoary/+templates
 
 
@@ -37,7 +37,7 @@
 length, languages and the date of last update for this distribution series.
 
     >>> table = find_tag_by_id(anon_browser.contents, 'templates_table')
-    >>> print extract_text(table)
+    >>> print(extract_text(table))
     Priority    Source package Template name  Shared with     Length   Updated
     0           evolution      man            ...not shared   1     2006-08-14
     100         evolution      evolution-2.2  ...evolution/trunk 22 2005-05-06
@@ -53,7 +53,7 @@
 the active templates.
 
     >>> table = find_tag_by_id(user_browser.contents, 'templates_table')
-    >>> print extract_text(table)
+    >>> print(extract_text(table))
     Priority    Source package  Template name ...   Updated     Actions...
     100         evolution       evolution-2.2 ...   2005-05-06  Download...
     0           mozilla         pkgconf-mozilla ... 2005-05-06  Download...
@@ -67,7 +67,7 @@
 The page shows a table of all templates and links to their subpages.
 
     >>> table = find_tag_by_id(admin_browser.contents, 'templates_table')
-    >>> print extract_text(table)
+    >>> print(extract_text(table))
     Priority  Source package  ... Updated     Actions
     0         evolution       ... 2007-01-05  Edit Upload Download Administer
     ...
@@ -87,7 +87,7 @@
     ...     'http://translations.launchpad.dev/ubuntu/hoary/+templates')
     >>> utc_browser.getLink(
     ...     url='+source/evolution/+pots/evolution-2.2/+edit').click()
-    >>> print utc_browser.url
+    >>> print(utc_browser.url)
     http://.../ubuntu/hoary/+source/evolution/+pots/evolution-2.2/+edit
 
 Administration page is inaccessible.
@@ -106,7 +106,7 @@
     ...     'http://translations.launchpad.dev/ubuntu/hoary/+templates')
     >>> utc_browser.getLink(
     ...     url='+source/evolution/+pots/disabled-template/+edit').click()
-    >>> print utc_browser.url
+    >>> print(utc_browser.url)
     http://.../ubuntu/hoary/+source/evolution/+pots/disabled-template/+edit
 
 Administration page is inaccessible.
@@ -127,7 +127,7 @@
 page.
 
     >>> admin_browser.getLink('pmount').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/ubuntu/hoary/+source/pmount/+pots/pmount
 
 Clicking on 'Edit' will take the user to the page to edit the template
@@ -136,7 +136,7 @@
     >>> admin_browser.open(
     ...     'http://translations.launchpad.dev/ubuntu/hoary/+templates')
     >>> admin_browser.getLink('Edit', index=1).click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.../evolution/+pots/disabled-template/+edit
 
 
@@ -173,7 +173,7 @@
     >>> user_browser.open(
     ...     'http://translations.launchpad.dev/evolution/trunk')
     >>> user_browser.getLink('full list of templates').click()
-    >>> print user_browser.url
+    >>> print(user_browser.url)
     http://translations.launchpad.dev/evolution/trunk/+templates
 
 
@@ -183,7 +183,7 @@
 The page shows a table of all templates and links to their subpages.
 
     >>> table = find_tag_by_id(user_browser.contents, 'templates_table')
-    >>> print extract_text(table)
+    >>> print(extract_text(table))
     Priority    Template name   Length  Updated     Actions
     0           at-the-top      0       ...         Download
     0           evolution-2.2   22      2005-08-25  Download
@@ -195,7 +195,7 @@
     >>> admin_browser.open(
     ...     'http://translations.launchpad.dev/evolution/trunk/+templates')
     >>> table = find_tag_by_id(admin_browser.contents, 'templates_table')
-    >>> print extract_text(table)
+    >>> print(extract_text(table))
     Priority  Template name ...   Updated     Actions
     0         at-the-top    ...   ...         Edit Upload Download Administer
     0         evolution-2.2 ...   2005-08-25  Edit Upload Download Administer
@@ -209,7 +209,7 @@
 page.
 
     >>> admin_browser.getLink('evolution-2.2').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2
 
 Clicking on 'Edit' will take the user to the page to edit the template
@@ -218,5 +218,5 @@
     >>> admin_browser.open(
     ...     'http://translations.launchpad.dev/evolution/trunk/+templates')
     >>> admin_browser.getLink('Edit').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/evolution/trunk/+pots/at-the-top/+edit

=== modified file 'lib/lp/translations/stories/standalone/xx-serieslanguage-index.txt'
--- lib/lp/translations/stories/standalone/xx-serieslanguage-index.txt	2014-11-24 09:16:35 +0000
+++ lib/lp/translations/stories/standalone/xx-serieslanguage-index.txt	2018-06-03 00:41:33 +0000
@@ -10,26 +10,26 @@
 
     >>> browser.open('http://translations.launchpad.dev/evolution/trunk/')
     >>> browser.getLink('Portuguese (Brazil)').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/evolution/trunk/+lang/pt_BR
 
-    >>> print browser.title
+    >>> print(browser.title)
     Portuguese (Brazil) (pt_BR) : Series trunk : Translations : Evolution
 
 Since there is no translation team to manage Portuguese (Brazil) language
 in the Evolution's translation group, all users will be informed about it
 and pointed to the translation group owner.
 
-    >>> print extract_text(find_tag_by_id(
-    ...     browser.contents, 'group-team-info'))
+    >>> print(extract_text(find_tag_by_id(
+    ...     browser.contents, 'group-team-info')))
     There is no team to manage Evolution translations to ...
     To set one up, please get in touch with Carlos Perelló Marín.
 
 Anonymous users are informed that in order to make translations they
 need to login first.
 
-    >>> print extract_text(
-    ...     find_tag_by_id(browser.contents, 'translation-access-level'))
+    >>> print(extract_text(
+    ...     find_tag_by_id(browser.contents, 'translation-access-level')))
     You are not logged in. Please log in to work on translations...
 
 Authenticated users will see information about what they can do in
@@ -42,8 +42,8 @@
 
     >>> user_browser.open(
     ...     'http://translations.launchpad.dev/ubuntu/hoary/+lang/es')
-    >>> print extract_text(
-    ...     find_tag_by_id(user_browser.contents, 'group-team-info'))
+    >>> print(extract_text(
+    ...     find_tag_by_id(user_browser.contents, 'group-team-info')))
     There is no translation group to manage Ubuntu translations.
 
 Create a translation group for Ubuntu, together with a translation
@@ -81,27 +81,26 @@
     >>> user_browser.open(
     ...     'http://translations.launchpad.dev/ubuntu/hoary/')
     >>> user_browser.getLink('Spanish').click()
-    >>> print user_browser.url
+    >>> print(user_browser.url)
     http://translations.launchpad.dev/ubuntu/hoary/+lang/es
 
-    >>> print extract_text(
-    ...     find_tag_by_id(user_browser.contents, 'group-team-info'))
+    >>> print(extract_text(
+    ...     find_tag_by_id(user_browser.contents, 'group-team-info')))
     These Ubuntu translations are managed by Ubuntu Spanish Translators.
 
 Authenticated users can add suggestion but will be held for review by
 the members of Spanish translations team.
 
-    >>> print extract_text(
+    >>> print(extract_text(
     ...     find_tag_by_id(
-    ...         user_browser.contents, 'translation-access-level'))
+    ...         user_browser.contents, 'translation-access-level')))
     Your suggestions will be held for review by
     Ubuntu Spanish Translator...
     please get in touch with Ubuntu Spanish Translators...
 
 Users will see three references to the team managing these translations.
 
-    >>> print user_browser.getLink(
-    ...     'Ubuntu Spanish Translator').url
+    >>> print(user_browser.getLink('Ubuntu Spanish Translator').url)
     http://launchpad.dev/~ubuntu-l10n-es
 
 Catalan has no translation team for managing translations and since
@@ -111,22 +110,21 @@
     >>> user_browser.open(
     ...     'http://translations.launchpad.dev/ubuntu/hoary/')
     >>> user_browser.getLink('Catalan').click()
-    >>> print user_browser.url
+    >>> print(user_browser.url)
     http://translations.launchpad.dev/ubuntu/hoary/+lang/ca
 
-    >>> print extract_text(
-    ...     find_tag_by_id(user_browser.contents, 'group-team-info'))
+    >>> print(extract_text(
+    ...     find_tag_by_id(user_browser.contents, 'group-team-info')))
     There is no team to manage ... To set one up, please get in touch
     with Ubuntu Translation Coordinators.
 
-    >>> print extract_text(find_tag_by_id(
-    ...     user_browser.contents, 'translation-access-level'))
+    >>> print(extract_text(find_tag_by_id(
+    ...     user_browser.contents, 'translation-access-level')))
     Since there is nobody to manage translation ...
     you cannot add new suggestions. If you are interested in making
     translations, please contact Ubuntu Translation Coordinators...
 
-    >>> print user_browser.getLink(
-    ...     'Ubuntu Translation Coordinators').url
+    >>> print(user_browser.getLink('Ubuntu Translation Coordinators').url)
     http://launchpad.dev/~utc-team
 
 Members of translation team and translations admins have full access to
@@ -134,8 +132,8 @@
 
     >>> admin_browser.open(
     ...     'http://translations.launchpad.dev/ubuntu/hoary/+lang/ro')
-    >>> print extract_text(find_tag_by_id(
-    ...     admin_browser.contents, 'translation-access-level'))
+    >>> print(extract_text(find_tag_by_id(
+    ...     admin_browser.contents, 'translation-access-level')))
     You can add and review translations...
 
 For projects using closed translations policy, a translator that is not
@@ -148,8 +146,8 @@
 
     >>> user_browser.open(
     ...     'http://translations.launchpad.dev/ubuntu/hoary/+lang/ro')
-    >>> print extract_text(find_tag_by_id(
-    ...     user_browser.contents, 'translation-access-level'))
+    >>> print(extract_text(find_tag_by_id(
+    ...     user_browser.contents, 'translation-access-level')))
     These templates can be translated only by their managers...
 
 Translation policy is rolled back to not affect other tests.
@@ -165,13 +163,12 @@
     ...     auth='Basic dude@xxxxxx:test')
     >>> no_license_browser.open(
     ...     'http://translations.launchpad.dev/ubuntu/hoary/+lang/ro')
-    >>> print extract_text(find_tag_by_id(
-    ...     no_license_browser.contents, 'translation-access-level'))
+    >>> print(extract_text(find_tag_by_id(
+    ...     no_license_browser.contents, 'translation-access-level')))
     To make translations in Launchpad you need to agree with
     the Translations licensing...
 
-    >>> print no_license_browser.getLink(
-    ...     'Translations licensing').url
+    >>> print(no_license_browser.getLink('Translations licensing').url)
     http://translations.launchpad.dev/~dude/+licensing
 
 For projects with no translation group, translators see a note stating
@@ -183,12 +180,12 @@
 
     >>> user_browser.open(
     ...     'http://translations.launchpad.dev/ubuntu/hoary/+lang/ro')
-    >>> print extract_text(
-    ...     find_tag_by_id(user_browser.contents, 'group-team-info'))
+    >>> print(extract_text(
+    ...     find_tag_by_id(user_browser.contents, 'group-team-info')))
     There is no translation group to manage Ubuntu translations.
 
-    >>> print extract_text(find_tag_by_id(
-    ...     user_browser.contents, 'translation-access-level'))
+    >>> print(extract_text(find_tag_by_id(
+    ...     user_browser.contents, 'translation-access-level')))
     Templates which are more important to translate are listed first.
 
 Translation group configuration is rolled back to not affect other tests.

=== modified file 'lib/lp/translations/stories/standalone/xx-sourcepackage-export.txt'
--- lib/lp/translations/stories/standalone/xx-sourcepackage-export.txt	2014-11-27 22:13:36 +0000
+++ lib/lp/translations/stories/standalone/xx-sourcepackage-export.txt	2018-06-03 00:41:33 +0000
@@ -20,7 +20,7 @@
     >>> download = browser.getLink('download a full tarball')
     >>> download_url = download.url
     >>> download.click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/ubuntu/hoary/+source/mozilla/+export
 
 
@@ -170,7 +170,7 @@
 
     >>> browser.getControl('Request Download').click()
 
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/ubuntu/hoary/+source/mozilla
 
     >>> print_feedback_messages(browser.contents)

=== modified file 'lib/lp/translations/stories/standalone/xx-template-description-escaping.txt'
--- lib/lp/translations/stories/standalone/xx-template-description-escaping.txt	2010-08-31 18:10:51 +0000
+++ lib/lp/translations/stories/standalone/xx-template-description-escaping.txt	2018-06-03 00:41:33 +0000
@@ -26,7 +26,7 @@
     >>> description = find_description(template_url)
     >>> len(description)
     1
-    >>> print description[0]
+    >>> print(description[0])
     See <a ... href="http://example.com/";>...</a> for an example!
 
 The same description also shows up on the +translate page for the
@@ -35,7 +35,5 @@
     >>> description = find_description(package_url + '/+translations')
     >>> len(description)
     1
-    >>> print description[0]
+    >>> print(description[0])
     See <a ... href="http://example.com/";>...</a> for an example!
-
-

=== modified file 'lib/lp/translations/stories/standalone/xx-test-potlists.txt'
--- lib/lp/translations/stories/standalone/xx-test-potlists.txt	2009-09-09 12:41:48 +0000
+++ lib/lp/translations/stories/standalone/xx-test-potlists.txt	2018-06-03 00:41:33 +0000
@@ -1,9 +1,9 @@
 Check that we can get a potlist for a source pacakge that has potemplates:
 
-  >>> print http(r"""
+  >>> print(http(br"""
   ... GET /ubuntu/hoary/+source/evolution/+potlist HTTP/1.1
   ... Host: translations.launchpad.dev
-  ... """)
+  ... """))
   HTTP/1.1 200 Ok
   Content-Length: ...
   Content-Type: text/html;charset=utf-8

=== modified file 'lib/lp/translations/stories/standalone/xx-translation-access-display.txt'
--- lib/lp/translations/stories/standalone/xx-translation-access-display.txt	2015-01-29 18:43:52 +0000
+++ lib/lp/translations/stories/standalone/xx-translation-access-display.txt	2018-06-03 00:41:33 +0000
@@ -8,9 +8,9 @@
     ...     """Find and print tag with given id."""
     ...     tag = find_tag_by_id(page, id)
     ...     if tag is None:
-    ...         print 'None'
+    ...         print('None')
     ...     else:
-    ...         print tag.renderContents()
+    ...         print(tag.renderContents())
 
     >>> admin_browser.open(
     ...     'http://translations.launchpad.dev/'
@@ -69,7 +69,7 @@
     ...     'evolution/trunk/+pots/evolution-2.2/es/+translate')
     >>> managers_tag = find_tag_by_id(
     ...     admin_browser.contents, 'translation-managers').renderContents()
-    >>> print re.search(',\s+and', managers_tag)
+    >>> print(re.search(',\s+and', managers_tag))
     None
 
 If no translation group is assigned, the page also mentions that.

=== modified file 'lib/lp/translations/stories/standalone/xx-translation-credits.txt'
--- lib/lp/translations/stories/standalone/xx-translation-credits.txt	2009-07-01 20:45:39 +0000
+++ lib/lp/translations/stories/standalone/xx-translation-credits.txt	2018-06-03 00:41:33 +0000
@@ -25,8 +25,8 @@
 
 And there is no input field allowing changing this message.
 
-   >>> print find_tag_by_id(browser.contents,
-   ...                      'msgset_199_sr_translation_0_new')
+   >>> print(find_tag_by_id(browser.contents,
+   ...                      'msgset_199_sr_translation_0_new'))
    None
 
 KDE-style translation credits are split into two messages, with emails
@@ -39,11 +39,11 @@
 
 These are locked as well:
 
-    >>> print find_tag_by_id(browser.contents,
-    ...                      'msgset_200_sr_translation_0_new')
+    >>> print(find_tag_by_id(browser.contents,
+    ...                      'msgset_200_sr_translation_0_new'))
     None
-    >>> print find_tag_by_id(browser.contents,
-    ...                      'msgset_201_sr_translation_0_new')
+    >>> print(find_tag_by_id(browser.contents,
+    ...                      'msgset_201_sr_translation_0_new'))
     None
 
 We can translate a non-translator credits message, which will update
@@ -55,7 +55,7 @@
     >>> inputfield = browser.getControl(name='msgset_198_sr_translation_0_new')
     >>> inputfield.value = 'Test translation'
     >>> browser.getControl('Save & Continue').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/alsa-utils/trunk/+pots/alsa-utils/sr/+translate
 
 Translation has been updated.

=== modified file 'lib/lp/translations/stories/standalone/xx-translationmessage-translate.txt'
--- lib/lp/translations/stories/standalone/xx-translationmessage-translate.txt	2015-10-05 08:36:52 +0000
+++ lib/lp/translations/stories/standalone/xx-translationmessage-translate.txt	2018-06-03 00:41:33 +0000
@@ -43,15 +43,15 @@
 will not be displayed in that list.
 
     >>> nav = find_tag_by_id(browser.contents, 'nav-pofile-subpages')
-    >>> print extract_text(nav)
+    >>> print(extract_text(nav))
     Download translation Translation details
 
 Download translations and Translation details should linked to the
 proper pages
 
-  >> print nav.getLink("Download translation").url
+  >> print(nav.getLink("Download translation").url)
   https://.../hoary/+source/evolution/+pots/evolution-2.2/es/+export
-  >> print nav.getLink("Translation details").url
+  >> print(nav.getLink("Translation details").url)
   https://.../hoary/+source/evolution/+pots/evolution-2.2/es/+details
 
 Let's log in.
@@ -86,48 +86,48 @@
     LinkNotFoundError
 
     >>> next = browser.getLink('Next')
-    >>> print next.url
+    >>> print(next.url)
     http://.../hoary/+source/evolution/+pots/evolution-2.2/es/2/+translate
 
-    >>> print browser.getLink('Last').url
+    >>> print(browser.getLink('Last').url)
     http://.../hoary/+source/evolution/+pots/evolution-2.2/es/22/+translate
 
 And the link to the IPOFile view should be there too:
 
     >>> zoom_link = browser.getLink(id="zoom-out")
-    >>> print zoom_link.url
+    >>> print(zoom_link.url)
     http://.../+source/evolution/+pots/evolution-2.2/es/+translate?start=0
 
 when we choose the next entry, all links should appear.
 
     >>> next.click()
-    >>> print browser.getLink('First').url
-    http://.../hoary/+source/evolution/+pots/evolution-2.2/es/1/+translate
-
-    >>> print browser.getLink('Previous').url
-    http://.../hoary/+source/evolution/+pots/evolution-2.2/es/1/+translate
-
-    >>> print browser.getLink('Next').url
+    >>> print(browser.getLink('First').url)
+    http://.../hoary/+source/evolution/+pots/evolution-2.2/es/1/+translate
+
+    >>> print(browser.getLink('Previous').url)
+    http://.../hoary/+source/evolution/+pots/evolution-2.2/es/1/+translate
+
+    >>> print(browser.getLink('Next').url)
     http://.../hoary/+source/evolution/+pots/evolution-2.2/es/3/+translate
 
     >>> last = browser.getLink('Last')
-    >>> print last.url
+    >>> print(last.url)
     http://.../hoary/+source/evolution/+pots/evolution-2.2/es/22/+translate
 
 And the link to the IPOFile view should be there too:
 
     >>> zoom_link = browser.getLink(id="zoom-out")
-    >>> print zoom_link.url
+    >>> print(zoom_link.url)
     http://.../+source/evolution/+pots/evolution-2.2/es/+translate?start=1
 
 And the last one.
 
     >>> last.click()
-    >>> print browser.getLink('First').url
+    >>> print(browser.getLink('First').url)
     http://.../hoary/+source/evolution/+pots/evolution-2.2/es/1/+translate
 
     >>> prev = browser.getLink('Previous')
-    >>> print prev.url
+    >>> print(prev.url)
     http://.../hoary/+source/evolution/+pots/evolution-2.2/es/21/+translate
 
     >>> browser.getLink('Next')
@@ -143,22 +143,22 @@
 And the link to the IPOFile view should be there too:
 
     >>> zoom_link = browser.getLink(id="zoom-out")
-    >>> print zoom_link.url
+    >>> print(zoom_link.url)
     http://.../+source/evolution/+pots/evolution-2.2/es/+translate?start=21
 
 Let's test the ones at the end of the form.
 
     >>> prev.click()
-    >>> print browser.getLink('First').url
+    >>> print(browser.getLink('First').url)
     http://.../hoary/+source/evolution/+pots/evolution-2.2/es/1/+translate
 
-    >>> print browser.getLink('Previous').url
+    >>> print(browser.getLink('Previous').url)
     http://.../hoary/+source/evolution/+pots/evolution-2.2/es/20/+translate
 
-    >>> print browser.getLink('Next').url
+    >>> print(browser.getLink('Next').url)
     http://.../hoary/+source/evolution/+pots/evolution-2.2/es/22/+translate
 
-    >>> print browser.getLink('Last').url
+    >>> print(browser.getLink('Last').url)
     http://.../hoary/+source/evolution/+pots/evolution-2.2/es/22/+translate
 
 As a translation admin you will have access to the download and details
@@ -167,17 +167,17 @@
 mode
 
     >>> nav = find_tag_by_id(browser.contents, 'nav-pofile-subpages')
-    >>> print extract_text(nav)
+    >>> print(extract_text(nav))
     Download translation Translation details
     Reviewer mode (What's this?)
 
 All those links should linked the proper pages
 
-  >> print nav.getLink("Download translation").url
+  >> print(nav.getLink("Download translation").url)
   https://.../hoary/+source/evolution/+pots/evolution-2.2/es/+export
-  >> print nav.getLink("Upload translation").url
+  >> print(nav.getLink("Upload translation").url)
   https://.../hoary/+source/evolution/+pots/evolution-2.2/es/+upload
-  >> print nav.getLink("Translation details").url
+  >> print(nav.getLink("Translation details").url)
   https://.../hoary/+source/evolution/+pots/evolution-2.2/es/+details
 
 Now, we are going to check a message submission.
@@ -190,16 +190,16 @@
 
 First what we represent in the form when there is no translation:
 
-    >>> print find_tag_by_id(browser.contents, 'msgset_142').renderContents()
+    >>> print(find_tag_by_id(browser.contents, 'msgset_142').renderContents())
     13.
     <input type="hidden" name="msgset_142" />
 
-    >>> print find_tag_by_id(
-    ...     browser.contents, 'msgset_142_singular').renderContents()
+    >>> print(find_tag_by_id(
+    ...     browser.contents, 'msgset_142_singular').renderContents())
     Migrating `<code>%s</code>&#x27;:
 
-    >>> print find_tag_by_id(
-    ...     browser.contents, 'msgset_142_es_translation_0').renderContents()
+    >>> print(find_tag_by_id(
+    ...     browser.contents, 'msgset_142_es_translation_0').renderContents())
     (no translation yet)
 
 And also, we don't get anyone as the Last translator because there is no
@@ -222,11 +222,11 @@
     >>> browser.getControl(
     ...     name='msgset_142_es_translation_0_new').value = 'foo %i'
     >>> browser.getControl(name='submit_translations').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://.../hoary/+source/evolution/+pots/evolution-2.2/es/13/+translate
 
     >>> for tag in find_tags_by_class(browser.contents, 'error'):
-    ...     print tag
+    ...     print(tag)
     <div class="error message">There is an error in the translation you
       provided. Please correct it before continuing.</div>
     <tr class="error translation">
@@ -244,16 +244,16 @@
 
 The message is still without translation:
 
-    >>> print find_tag_by_id(browser.contents, 'msgset_142').renderContents()
+    >>> print(find_tag_by_id(browser.contents, 'msgset_142').renderContents())
     13.
     <input type="hidden" name="msgset_142" />
 
-    >>> print find_tag_by_id(
-    ...     browser.contents, 'msgset_142_singular').renderContents()
+    >>> print(find_tag_by_id(
+    ...     browser.contents, 'msgset_142_singular').renderContents())
     Migrating `<code>%s</code>&#x27;:
 
-    >>> print find_tag_by_id(
-    ...     browser.contents, 'msgset_142_es_translation_0').renderContents()
+    >>> print(find_tag_by_id(
+    ...     browser.contents, 'msgset_142_es_translation_0').renderContents())
     (no translation yet)
 
 And now a good submit.
@@ -267,7 +267,7 @@
 
 We moved to the next message, that means this submission worked.
 
-    >>> print browser.url
+    >>> print(browser.url)
     http:/.../hoary/+source/evolution/+pots/evolution-2.2/es/14/+translate
 
 Now, it has the submitted value.
@@ -278,16 +278,16 @@
 
 Check that the message #13 has the new value we submitted.
 
-    >>> print find_tag_by_id(browser.contents, 'msgset_142').renderContents()
+    >>> print(find_tag_by_id(browser.contents, 'msgset_142').renderContents())
     13.
     <input type="hidden" name="msgset_142" />
 
-    >>> print find_tag_by_id(
-    ...     browser.contents, 'msgset_142_singular').renderContents()
+    >>> print(find_tag_by_id(
+    ...     browser.contents, 'msgset_142_singular').renderContents())
     Migrating `<code>%s</code>&#x27;:
 
-    >>> print find_tag_by_id(
-    ...     browser.contents, 'msgset_142_es_translation_0').renderContents()
+    >>> print(find_tag_by_id(
+    ...     browser.contents, 'msgset_142_es_translation_0').renderContents())
     foo <code>%s</code>
 
 And now, we get the translator and reviewer, who happen to be the same
@@ -325,7 +325,7 @@
 
 Check that suggestions come in from other contexts:
 
-    >>> "Suggested in" in browser.contents
+    >>> b"Suggested in" in browser.contents
     True
 
     >>> find_tag_by_id(browser.contents, 'msgset_143_es_suggestion_697_0')
@@ -334,15 +334,15 @@
 Check that no other suggestions are presented (since no others are
 relevant for this message):
 
-    >>> "Suggested by" in browser.contents
+    >>> b"Suggested by" in browser.contents
     False
 
-    >>> "Used in" in browser.contents
+    >>> b"Used in" in browser.contents
     False
 
 Check for the translator note:
 
-    >>> note = "This is an example of commenttext for a multiline"
+    >>> note = b"This is an example of commenttext for a multiline"
     >>> note in browser.contents
     True
 
@@ -396,13 +396,13 @@
 And submit it.
 
     >>> fast_submission.getControl(name='submit_translations').click()
-    >>> print fast_submission.url
+    >>> print(fast_submission.url)
     http://.../hoary/+source/evolution/+pots/evolution-2.2/es/15/+translate
 
 Now, we check that the translation we are going to add is not yet in the
 form, so we can check later that it's added as a suggestion:
 
-    >>> 'foo!!' in fast_submission.contents
+    >>> b'foo!!' in fast_submission.contents
     False
 
 Now, we update the translation in slow_submission.
@@ -416,11 +416,11 @@
 We submit it
 
     >>> slow_submission.getControl(name='submit_translations').click()
-    >>> print slow_submission.url
+    >>> print(slow_submission.url)
     http://.../hoary/+source/evolution/+pots/evolution-2.2/es/14/+translate
 
     >>> for tag in find_tags_by_class(slow_submission.contents, 'error'):
-    ...     print tag
+    ...     print(tag)
     <div class="error message">There is an error in the translation you
       provided. Please correct it before continuing.</div>
     <tr class="error translation">
@@ -439,18 +439,18 @@
 
 Also, we should still have previous translation:
 
-    >>> print find_tag_by_id(
-    ...     slow_submission.contents, 'msgset_143').renderContents()
+    >>> print(find_tag_by_id(
+    ...     slow_submission.contents, 'msgset_143').renderContents())
     14.
     <input type="hidden" name="msgset_143" />
 
-    >>> print find_tag_by_id(
-    ...     slow_submission.contents, 'msgset_143_singular').renderContents()
+    >>> print(find_tag_by_id(
+    ...     slow_submission.contents, 'msgset_143_singular').renderContents())
     The location and hierarchy of the Evolution contact...
 
-    >>> print find_tag_by_id(
+    >>> print(find_tag_by_id(
     ...     slow_submission.contents,
-    ...     'msgset_143_es_translation_0').renderContents()
+    ...     'msgset_143_es_translation_0').renderContents())
     blah
 
 But also, the new one should appear in the form.
@@ -459,7 +459,7 @@
     >>> elements = find_main_content(slow_submission.contents).findAll(
     ...     True, {'id': re.compile(r'^msgset_143_es_suggestion_\d+_0$')})
     >>> for element in elements:
-    ...     print element.renderContents()
+    ...     print(element.renderContents())
     La ubicación ...
     Tenga paciencia ...
     foo!!
@@ -476,14 +476,14 @@
 
     >>> browser.open('http://translations.launchpad.dev/ubuntu/hoary/+source/'
     ...              'mozilla/+pots/pkgconf-mozilla/de/1/+translate')
-    >>> print extract_text(
-    ...     find_tag_by_id(browser.contents, "translated_by").parent)
+    >>> print(extract_text(
+    ...     find_tag_by_id(browser.contents, "translated_by").parent))
     Translated by Helge Kreutzmann on 2005-05-06
 
-    >>> print find_tag_by_id(browser.contents, "reviewed_by")
+    >>> print(find_tag_by_id(browser.contents, "reviewed_by"))
     None
 
-    >>> print find_tag_by_id(browser.contents, "translated_and_reviewed_by")
+    >>> print(find_tag_by_id(browser.contents, "translated_and_reviewed_by"))
     None
 
 
@@ -496,8 +496,8 @@
     >>> browser.open(
     ...     'http://translations.launchpad.dev/alsa-utils/trunk/+pots/'
     ...     'alsa-utils/sr/+translate')
-    >>> print extract_text(find_tag_by_id(
-    ...     browser.contents, "msgset_198_context").parent)
+    >>> print(extract_text(find_tag_by_id(
+    ...     browser.contents, "msgset_198_context").parent))
     Something
 
 We can change a translation for messages with context.
@@ -511,13 +511,13 @@
 And submit it.
 
     >>> browser.getControl(name='submit_translations').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://.../alsa-utils/trunk/+pots/alsa-utils/sr/+translate
 
 And the translation is now updated.
 
-    >>> print extract_text(
-    ...     find_tag_by_id(browser.contents, "msgset_198_sr_translation_0"))
+    >>> print(extract_text(
+    ...     find_tag_by_id(browser.contents, "msgset_198_sr_translation_0")))
     blah
 
 
@@ -532,7 +532,7 @@
     >>> browser.open('http://translations.launchpad.dev/ubuntu/hoary/'
     ...              '+source/evolution/+pots/evolution-2.2/es/5/+translate')
     >>> packaged = find_tag_by_id(browser.contents, 'msgset_134_other')
-    >>> print extract_text(packaged)
+    >>> print(extract_text(packaged))
     In upstream: tarjetas
 
 First, we look for an existing imported translation in evolution PO file
@@ -588,7 +588,7 @@
 
 Also, the page now displays a "(not translated yet)" message.
 
-    >>> print extract_text(packaged)
+    >>> print(extract_text(packaged))
     In upstream: (not translated yet)
 
 
@@ -653,7 +653,7 @@
     >>> shared_html_id = 'msgset_%d_%s_suggestion_%d_0' % (
     ...     potmsgset.id, pofile.language.code, translationmessage.id)
     >>> shared_message_tag = find_tag_by_id(browser.contents, shared_html_id)
-    >>> print extract_text(shared_message_tag)
+    >>> print(extract_text(shared_message_tag))
     shared translation
 
     >>> extract_text(find_tag_by_id(browser.contents, html_id))

=== modified file 'lib/lp/translations/stories/standalone/xx-translations-to-complete.txt'
--- lib/lp/translations/stories/standalone/xx-translations-to-complete.txt	2012-01-15 13:32:27 +0000
+++ lib/lp/translations/stories/standalone/xx-translations-to-complete.txt	2018-06-03 00:41:33 +0000
@@ -38,14 +38,14 @@
 
     >>> tag = find_tag_by_id(
     ...     jean_browser.contents, 'translations-to-complete-table')
-    >>> print tag.renderContents()
+    >>> print(tag.renderContents())
 
 Only Jean sees his personal listing.
 
     >>> user_browser.open(jean_home)
     >>> tag = find_tag_by_id(
     ...     user_browser.contents, 'translations-to-complete-table')
-    >>> print tag
+    >>> print(tag)
     None
 
 Pierre is not a translator.  Pierre does not get such a listing either.
@@ -58,7 +58,7 @@
     >>> pierre_browser.open('http://translations.launchpad.dev/~pierre')
     >>> tag = find_tag_by_id(
     ...     pierre_browser.contents, 'translations-to-complete-table')
-    >>> print tag
+    >>> print(tag)
     None
 
 
@@ -82,5 +82,5 @@
     >>> experts_home = 'http://translations.launchpad.dev/~rosetta-admins'
     >>> carlos_browser.open(experts_home)
 
-    >>> print extract_text(find_tag_by_id(
-    ...     jean_browser.contents, 'translations-to-complete-table'))
+    >>> print(extract_text(find_tag_by_id(
+    ...     jean_browser.contents, 'translations-to-complete-table')))

=== modified file 'lib/lp/translations/stories/standalone/xx-translations-to-review.txt'
--- lib/lp/translations/stories/standalone/xx-translations-to-review.txt	2018-02-02 10:06:24 +0000
+++ lib/lp/translations/stories/standalone/xx-translations-to-review.txt	2018-06-03 00:41:33 +0000
@@ -56,20 +56,20 @@
     ...         count = 0
     ...         for tr in listing.findAll('tr'):
     ...             tds = [td.renderContents() for td in tr.findAll('td')]
-    ...             print '    '.join(tds)
+    ...             print('    '.join(tds))
     ...             count += 1
-    ...         print "Listing contains %d translation(s)." % count
+    ...         print("Listing contains %d translation(s)." % count)
     ...     else:
-    ...         print "No listing found."
+    ...         print("No listing found.")
 
     >>> def show_reviewables_link(browser):
     ...     """Show the "view all n unreviewed translations" link."""
     ...     soup = BeautifulSoup(browser.contents)
     ...     link = soup.find(id="translations-to-review-link")
     ...     if link:
-    ...         print link.renderContents()
+    ...         print(link.renderContents())
     ...     else:
-    ...         print "No link."
+    ...         print("No link.")
 
 There are no translations for xowxz to review, so the listing does not
 show up.

=== modified file 'lib/lp/translations/stories/standalone/xx-translations-xpi-import.txt'
--- lib/lp/translations/stories/standalone/xx-translations-xpi-import.txt	2015-10-05 08:36:52 +0000
+++ lib/lp/translations/stories/standalone/xx-translations-xpi-import.txt	2018-06-03 00:41:33 +0000
@@ -12,7 +12,7 @@
 translations upload link.
 
   >>> browser.getLink('upload').click()
-  >>> print browser.url
+  >>> print(browser.url)
   http://translations.launchpad.dev/firefox/trunk/+translations-upload
 
 Get the XPI file we are going to upload.
@@ -26,19 +26,19 @@
   ...     xpifile, 'application/zip', 'en-US.xpi')
   >>> browser.getControl('Upload').click()
 
-  >>> print browser.url
+  >>> print(browser.url)
   http://translations.launchpad.dev/firefox/trunk/+translations-upload
   >>> for tag in find_tags_by_class(browser.contents, 'message'):
-  ...     print extract_text(tag.renderContents())
+  ...     print(extract_text(tag.renderContents()))
   Thank you for your upload.  It will be automatically reviewed...
 
 Lets check the import queue to edit this entry and set the name.
 
   >>> browser.getLink('Translation Import Queue').click()
-  >>> print browser.getLink(url='en-US.xpi').url
+  >>> print(browser.getLink(url='en-US.xpi').url)
   http://.../en-US.xpi
   >>> browser.getLink(url='/+imports/').click()
-  >>> print browser.url
+  >>> print(browser.url)
   http://translations.launchpad.dev/+imports/...
   >>> qid = int(browser.url.rsplit('/', 1)[-1])
 
@@ -49,7 +49,7 @@
   >>> browser.getControl('Name').value = 'firefox'
   >>> browser.getControl('Translation domain').value = 'firefox'
   >>> browser.getControl('Approve').click()
-  >>> print browser.url
+  >>> print(browser.url)
   http://translations.launchpad.dev/firefox/trunk/+imports
   >>> browser.getControl(name='field.status_%d' % qid).value
   ['APPROVED']

=== modified file 'lib/lp/translations/stories/translationfocus/xx-product-translationfocus.txt'
--- lib/lp/translations/stories/translationfocus/xx-product-translationfocus.txt	2015-06-15 08:35:10 +0000
+++ lib/lp/translations/stories/translationfocus/xx-product-translationfocus.txt	2018-06-03 00:41:33 +0000
@@ -15,14 +15,14 @@
 but have no access to the 'Configure translations' menu.
 
     >>> admin_browser.open(fooproject_url)
-    >>> print extract_text(
+    >>> print(extract_text(
     ...     find_tags_by_class(admin_browser.contents,
-    ...                        'menu-link-configure_translations')[0])
+    ...                        'menu-link-configure_translations')[0]))
     Configure Translations
 
     >>> browser.open(fooproject_url)
-    >>> print extract_text(
-    ...     find_tags_by_class(browser.contents, 'edit sprite')[0])
+    >>> print(extract_text(
+    ...     find_tags_by_class(browser.contents, 'edit sprite')[0]))
     Traceback (most recent call last):
     ...
     IndexError: list index out of range
@@ -41,12 +41,13 @@
 development focus as the current series to be translated.
 It needs to be translatable.
 
-    >>> print fooproject.translation_focus
+    >>> print(fooproject.translation_focus)
     None
 
     >>> logout()
     >>> browser.open(fooproject_url)
-    >>> print extract_text(find_tags_by_class(browser.contents, 'portlet')[0])
+    >>> print(extract_text(
+    ...     find_tags_by_class(browser.contents, 'portlet')[0]))
     Translation details...
     Launchpad currently recommends translating... Fooproject trunk series.
     ...
@@ -59,12 +60,13 @@
     ...     product=fooproject,
     ...     name="untranslatable-series")
     >>> fooproject.translation_focus = fooproject_untranslatableseries
-    >>> print removeSecurityProxy(fooproject.translation_focus.name)
+    >>> print(removeSecurityProxy(fooproject.translation_focus.name))
     untranslatable-series
 
     >>> logout()
     >>> browser.open(fooproject_url)
-    >>> print extract_text(find_tags_by_class(browser.contents, 'portlet')[0])
+    >>> print(extract_text(
+    ...     find_tags_by_class(browser.contents, 'portlet')[0]))
     Translation details...
     Launchpad currently recommends translating... Fooproject trunk series.
     ...
@@ -83,7 +85,8 @@
     >>> logout()
 
     >>> browser.open(fooproject_url)
-    >>> print extract_text(find_tags_by_class(browser.contents, 'portlet')[0])
+    >>> print(extract_text(
+    ...     find_tags_by_class(browser.contents, 'portlet')[0]))
     Translation details...
     Launchpad currently recommends translating...
     Fooproject other-series series.

=== modified file 'lib/lp/translations/stories/translationgroups/xx-change-translation-policy.txt'
--- lib/lp/translations/stories/translationgroups/xx-change-translation-policy.txt	2015-06-15 08:35:10 +0000
+++ lib/lp/translations/stories/translationgroups/xx-change-translation-policy.txt	2018-06-03 00:41:33 +0000
@@ -24,20 +24,20 @@
     >>> re_browser.open(
     ...     'http://translations.launchpad.dev/chestii')
     >>> re_browser.getLink('Configure Translations').click()
-    >>> print re_browser.url
+    >>> print(re_browser.url)
     http://translations.launchpad.dev/chestii/+configure-translations
 
     >>> po_browser.open(
     ...     'http://translations.launchpad.dev/chestii')
     >>> po_browser.getLink('Configure Translations').click()
-    >>> print po_browser.url
+    >>> print(po_browser.url)
     http://translations.launchpad.dev/chestii/+configure-translations
 
 From the settings page, translations group and translation permissions
 can be changed.
 
     >>> hint = find_tag_by_id(re_browser.contents, 'form_extra_info')
-    >>> print extract_text(hint)
+    >>> print(extract_text(hint))
     Select the translation group that will be managing...
 
     >>> re_browser.getControl('Translation group').value = [
@@ -45,11 +45,11 @@
     >>> re_browser.getControl('Translation permissions policy').value = [
     ...     'CLOSED']
     >>> re_browser.getControl('Change').click()
-    >>> print re_browser.url
+    >>> print(re_browser.url)
     http://translations.launchpad.dev/chestii
     >>> permissions = find_tag_by_id(
     ...     re_browser.contents, 'translation-permissions')
-    >>> print extract_text(permissions)
+    >>> print(extract_text(permissions))
     Chestii is translated by Ubuntu Translators with Closed permissions.
 
 Other persons, including the translation group owners, will not see the link
@@ -81,7 +81,7 @@
     >>> dtc_browser.open(
     ...     'http://translations.launchpad.dev/ubuntu')
     >>> dtc_browser.getLink('Configure translations').click()
-    >>> print dtc_browser.url
+    >>> print(dtc_browser.url)
     http://translations.launchpad.dev/ubuntu/+configure-translations
 
     >>> dtc_browser.getControl('Translation group').value = [
@@ -89,9 +89,9 @@
     >>> dtc_browser.getControl('Translation permissions policy').value = [
     ...     'CLOSED']
     >>> dtc_browser.getControl('Change').click()
-    >>> print dtc_browser.url
+    >>> print(dtc_browser.url)
     http://translations.launchpad.dev/ubuntu
     >>> permissions = find_tag_by_id(
     ...     dtc_browser.contents, 'translation-permissions')
-    >>> print extract_text(permissions)
+    >>> print(extract_text(permissions))
     Ubuntu is translated by Ubuntu Translators with Closed permissions.

=== modified file 'lib/lp/translations/stories/translationgroups/xx-link-to-documentation.txt'
--- lib/lp/translations/stories/translationgroups/xx-link-to-documentation.txt	2016-01-26 15:47:37 +0000
+++ lib/lp/translations/stories/translationgroups/xx-link-to-documentation.txt	2018-06-03 00:41:33 +0000
@@ -7,7 +7,7 @@
     >>> anon_browser.open(
     ...     'http://translations.launchpad.dev/evolution/trunk/'
     ...     '+pots/evolution-2.2/es/+translate')
-    >>> print first_tag_by_class(anon_browser.contents, 'style_guide_url')
+    >>> print(first_tag_by_class(anon_browser.contents, 'style_guide_url'))
     None
 
 Carlos is the owner of the testing-translation-team group and can change
@@ -17,7 +17,7 @@
     >>> carlos_browser.open(
     ...     'http://translations.launchpad.dev/'
     ...     '+groups/testing-translation-team/es/+admin')
-    >>> print carlos_browser.title
+    >>> print(carlos_browser.title)
     Just a testing team...
 
 This way he can also set the documentation URL.
@@ -25,17 +25,17 @@
     >>> carlos_browser.getControl('Translation guidelines').value = (
     ...     'http://www.ubuntu.com/')
     >>> carlos_browser.getControl('Change').click()
-    >>> print carlos_browser.url
+    >>> print(carlos_browser.url)
     http://translations.launchpad.dev/+groups/testing-translation-team
 
 The link now appears in the table next to the name of the team.
 
     >>> team = first_tag_by_class(carlos_browser.contents, 'translator-team')
-    >>> print extract_text(team.findAll('a')[0])
+    >>> print(extract_text(team.findAll('a')[0]))
     testing Spanish team
 
     >>> link = first_tag_by_class(carlos_browser.contents, 'translator-link')
-    >>> print link.findAll('a')[0]['href']
+    >>> print(link.findAll('a')[0]['href'])
     http://www.ubuntu.com/
 
 Back on the translations page, the link is now present, too.
@@ -45,9 +45,9 @@
     ...     '+pots/evolution-2.2/es/+translate')
     >>> style_guide_url = first_tag_by_class(
     ...     anon_browser.contents, 'style-guide-url')
-    >>> print extract_text(style_guide_url)
+    >>> print(extract_text(style_guide_url))
     testing Spanish team guidelines
-    >>> print style_guide_url['href']
+    >>> print(style_guide_url['href'])
     http://www.ubuntu.com/
 
 Carlos appoints Sample Person as a translator for Esperanto.
@@ -58,7 +58,7 @@
     >>> carlos_browser.getControl('Language').value = ['eo']
     >>> carlos_browser.getControl('Translator').value = 'name12'
     >>> carlos_browser.getControl('Appoint').click()
-    >>> print carlos_browser.url
+    >>> print(carlos_browser.url)
     http://translations.launchpad.dev/+groups/testing-translation-team
 
 Sample Person can not administer their ITranslator record but they can edit
@@ -71,22 +71,22 @@
     ...         'http://translations.launchpad.dev/'
     ...         '+groups/testing-translation-team/eo/+admin')
     ... except Unauthorized as e:
-    ...     print e
+    ...     print(e)
     (...'launchpad.Admin')
     >>> test_browser.open(
     ...     'http://translations.launchpad.dev/'
     ...     '+groups/testing-translation-team/eo/+edit')
-    >>> print test_browser.title
+    >>> print(test_browser.title)
     Set Esperanto guidelines...
 
     >>> test_browser.getControl('Translation guidelines').value = (
     ...     'http://www.launchpad.net/')
     >>> test_browser.getControl('Set guidelines').click()
-    >>> print test_browser.url
+    >>> print(test_browser.url)
     http://translations.launchpad.dev/~name12
     >>> translator_listing = find_tag_by_id(
     ...     test_browser.contents, 'translation-group-memberships')
-    >>> print extract_text(translator_listing)
+    >>> print(extract_text(translator_listing))
     Translation group
     Language
     Translation guidelines
@@ -135,7 +135,7 @@
     >>> set_team_url(carlos_browser, '')
 
     >>> browser.open(evolution_spanish_url)
-    >>> print get_notification_content(browser)
+    >>> print(get_notification_content(browser))
     None
 
 Setting a group documentation URL will show the notification with the link
@@ -144,7 +144,7 @@
     >>> set_group_url(carlos_browser, u'https://help.launchpad.net/')
     >>> browser.open(evolution_spanish_url)
     >>> notification = get_notification_content(browser)
-    >>> print extract_text(notification)
+    >>> print(extract_text(notification))
     Before translating, be sure to go through Just a testing team
     instructions.
 
@@ -157,7 +157,7 @@
     >>> set_team_url(carlos_browser, 'https://help.launchpad.net/Spanish')
     >>> browser.open(evolution_spanish_url)
     >>> notification = get_notification_content(browser)
-    >>> print extract_text(notification)
+    >>> print(extract_text(notification))
     Before translating, be sure to go through Just a testing team
     instructions and Spanish guidelines.
 
@@ -173,7 +173,7 @@
     >>> set_group_url(carlos_browser, '')
     >>> browser.open(evolution_spanish_url)
     >>> notification = get_notification_content(browser)
-    >>> print extract_text(notification)
+    >>> print(extract_text(notification))
     Before translating, be sure to go through testing Spanish team
     guidelines.
 

=== modified file 'lib/lp/translations/stories/translationgroups/xx-translationgroups.txt'
--- lib/lp/translations/stories/translationgroups/xx-translationgroups.txt	2017-10-23 00:16:39 +0000
+++ lib/lp/translations/stories/translationgroups/xx-translationgroups.txt	2018-06-03 00:41:33 +0000
@@ -4,14 +4,14 @@
 Make sure we can actually display the Translation Groups page.
 
     >>> anon_browser.open('http://translations.launchpad.dev/')
-    >>> print anon_browser.url
+    >>> print(anon_browser.url)
     http://translations.launchpad.dev/
 
     >>> anon_browser.getLink('translation groups').click()
-    >>> print anon_browser.url
+    >>> print(anon_browser.url)
     http://translations.launchpad.dev/+groups
 
-    >>> print anon_browser.title
+    >>> print(anon_browser.title)
     Translation groups
 
 Only Rosetta experts and Launchpad administrators can create translation
@@ -36,8 +36,8 @@
 
     >>> admin_browser.open(
     ...     'http://translations.launchpad.dev/+groups/+new')
-    >>> print find_main_content(
-    ...     admin_browser.contents).find('h1').renderContents()
+    >>> print(find_main_content(
+    ...     admin_browser.contents).find('h1').renderContents())
     Create a new translation group
 
 Translation group names must meet certain conditions.  For example, they
@@ -53,7 +53,7 @@
     ...     "each specialising in their own language.")
     >>> admin_browser.getControl('Create').click()
     >>> for message in find_tags_by_class(admin_browser.contents, 'message'):
-    ...     print message.renderContents()
+    ...     print(message.renderContents())
     There is 1 error.
     Invalid name 'PolYglot'. Names must be at least two characters ...
 
@@ -61,19 +61,19 @@
 translation-team.
 
     >>> browser.open('http://translations.launchpad.dev/+groups')
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/+groups
 
-    >>> print browser.getLink('Just a testing team').url
+    >>> print(browser.getLink('Just a testing team').url)
     http://translations.launchpad.dev/+groups/testing-translation-team
 
     >>> admin_browser.getControl('Name').value = 'testing-translation-team'
     >>> admin_browser.getControl('Create').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/+groups/+new
 
     >>> for message in find_tags_by_class(admin_browser.contents, 'message'):
-    ...     print message.renderContents()
+    ...     print(message.renderContents())
     There is 1 error.
     There is already a translation group with such name
 
@@ -84,7 +84,7 @@
     >>> admin_browser.getControl('Translation instructions').value=(
     ...     u'https://help.launchpad.net/Translations/PolyglotPolicies')
     >>> admin_browser.getControl('Create').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/+groups/polyglot
 
 After creating a translation group, the user automatically ends up on
@@ -97,7 +97,7 @@
     '...The PolyGlot Translation Group...'
 
     >>> docs = find_tag_by_id(admin_browser.contents, 'documentation')
-    >>> print extract_text(docs)
+    >>> print(extract_text(docs))
     Please read the translation instructions...
     >>> docs_url = docs.find('a')
     >>> extract_link_from_tag(docs_url)
@@ -114,7 +114,7 @@
     ...     "Since each of us only speaks one language, we work out software "
     ...     "translations through drawings and hand signals.")
     >>> browser.getControl('Create').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/+groups/monolingua
 
     >>> browser.title
@@ -123,7 +123,7 @@
 By default, when a group is created, the creator is its owner.
 
     >>> for t in find_tags_by_class(browser.contents, 'link'):
-    ...     print t.renderContents()
+    ...     print(t.renderContents())
     Jordi Mallach
 
 The Rosetta administrator assigns ownership of the group to Sample
@@ -146,7 +146,7 @@
 But Sample Person is now listed as its owner:
 
     >>> for t in find_tags_by_class(browser.contents, 'link'):
-    ...     print t.renderContents()
+    ...     print(t.renderContents())
     Sample Person
 
 That means that Sample Person is allowed to administer "their" group.
@@ -161,7 +161,7 @@
 The new groups should show up on the "Translation groups" page.
 
     >>> anon_browser.open('http://translations.launchpad.dev/+groups')
-    >>> print anon_browser.url
+    >>> print(anon_browser.url)
     http://translations.launchpad.dev/+groups
 
     >>> groups_table = find_tag_by_id(
@@ -169,7 +169,7 @@
     >>> groups = groups_table.find('tbody').findAll('tr')
     >>> for group_row in groups:
     ...     group = group_row.findNext('td')
-    ...     print '%s: %s' % (group.a.string, group.a['href'])
+    ...     print('%s: %s' % (group.a.string, group.a['href']))
     Just a testing team: ...testing-translation-team
     Single-language Translators: ...monolingua
     The PolyGlot Translation Group: ...polyglot
@@ -179,25 +179,25 @@
 
     >>> admin_browser.open(
     ...     'http://translations.launchpad.dev/+groups')
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/+groups
 
 We can see that the translation group that we are going to duplicate
 exists already:
 
-    >>> print admin_browser.getLink('The PolyGlot Translation Group').url
+    >>> print(admin_browser.getLink('The PolyGlot Translation Group').url)
     http://translations.launchpad.dev/+groups/polyglot
 
 Navigate to the one we are going to rename.
 
     >>> admin_browser.getLink('Just a testing team').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/+groups/testing-translation-team
 
 And select to edit its details.
 
     >>> admin_browser.getLink('Change details').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/+groups/testing-translation-team/+edit
 
 Change the name.
@@ -208,11 +208,11 @@
 The system detected that we tried to use an already existing name, so we
 didn't move away from this form.
 
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/+groups/testing-translation-team/+edit
 
     >>> for tag in find_tags_by_class(admin_browser.contents, 'message'):
-    ...     print tag.renderContents()
+    ...     print(tag.renderContents())
     There is 1 error.
     There is already a translation group with this name
 
@@ -220,11 +220,11 @@
 
     >>> admin_browser.getControl('Name').value = u'renamed-group'
     >>> admin_browser.getControl('Change').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/+groups/renamed-group
 
     >>> for tag in find_tags_by_class(admin_browser.contents, 'message'):
-    ...     print tag.renderContents()
+    ...     print(tag.renderContents())
 
 You can also edit the generic translation instructions for the team
 
@@ -242,11 +242,11 @@
 
     >>> anon_browser.open('http://launchpad.dev/ubuntu')
     >>> anon_browser.getLink('Translations').click()
-    >>> print anon_browser.title
+    >>> print(anon_browser.title)
     Translations : Ubuntu
 
-    >>> print extract_text(
-    ...     find_tag_by_id(anon_browser.contents, 'translation-permissions'))
+    >>> print(extract_text(
+    ...     find_tag_by_id(anon_browser.contents, 'translation-permissions')))
     Ubuntu is translated with Open permissions...
 
 And now make sure we can see the form to change the translation group
@@ -257,7 +257,7 @@
     ...     auth='Basic colin.watson@xxxxxxxxxxxxxxx:test')
     >>> ubuntu_owner_browser.open(anon_browser.url)
     >>> ubuntu_owner_browser.getLink('Configure translations').click()
-    >>> print ubuntu_owner_browser.title
+    >>> print(ubuntu_owner_browser.title)
     Settings : Translations : Ubuntu
 
 Other users cannot access this page, nor see the menu link to it.
@@ -278,8 +278,8 @@
 
     >>> ubuntu_owner_browser.getControl(
     ...     'Translation permissions policy').displayValue = ['Closed']
-    >>> print ubuntu_owner_browser.getControl(
-    ...     'Translation group').displayOptions
+    >>> print(ubuntu_owner_browser.getControl(
+    ...     'Translation group').displayOptions)
     ['(nothing selected)', 'Single-language Translators',
      'The PolyGlot Translation Group', 'Just a testing team']
 
@@ -287,23 +287,23 @@
     ...     'Translation group').displayValue = [
     ...         'The PolyGlot Translation Group']
     >>> ubuntu_owner_browser.getControl('Change').click()
-    >>> print ubuntu_owner_browser.title
+    >>> print(ubuntu_owner_browser.title)
     Translations : Ubuntu
 
-    >>> print extract_text(
+    >>> print(extract_text(
     ...     find_tag_by_id(ubuntu_owner_browser.contents,
-    ...                    'translation-permissions'))
+    ...                    'translation-permissions')))
     Ubuntu is translated by The PolyGlot Translation Group...
 
 These changes are now reflected in the Ubuntu translations page for
 everybody else as well.
 
     >>> anon_browser.reload()
-    >>> print anon_browser.title
+    >>> print(anon_browser.title)
     Translations : Ubuntu
 
-    >>> print extract_text(
-    ...     find_tag_by_id(anon_browser.contents, 'translation-permissions'))
+    >>> print(extract_text(
+    ...     find_tag_by_id(anon_browser.contents, 'translation-permissions')))
     Ubuntu is translated by The PolyGlot Translation Group
     with Closed permissions...
 
@@ -316,12 +316,12 @@
     >>> netapplet_owner_browser.open('http://launchpad.dev/netapplet')
     >>> netapplet_owner_browser.getLink(
     ...   'Translations', index=1).click()
-    >>> print netapplet_owner_browser.title
+    >>> print(netapplet_owner_browser.title)
     Configure translations : Translations : NetApplet
 
     >>> netapplet_owner_browser.getControl('Launchpad').click()
     >>> netapplet_owner_browser.getControl('Change').click()
-    >>> print netapplet_owner_browser.title
+    >>> print(netapplet_owner_browser.title)
     NetApplet in Launchpad
 
 Netapplet doesn't have TranslationGroup and uses open permissions. We
@@ -329,12 +329,12 @@
 
     >>> netapplet_owner_browser.open('http://launchpad.dev/netapplet')
     >>> netapplet_owner_browser.getLink('Translations').click()
-    >>> print netapplet_owner_browser.title
+    >>> print(netapplet_owner_browser.title)
     Translations : NetApplet
 
-    >>> print extract_text(
+    >>> print(extract_text(
     ...     find_tag_by_id(netapplet_owner_browser.contents,
-    ...                    'translation-permissions'))
+    ...                    'translation-permissions')))
     NetApplet is translated with Open permissions.
 
 Now let's make sure we can see the page to let us change translation
@@ -344,16 +344,16 @@
     >>> netapplet_owner_browser.getLink('Configure Translations').click()
     >>> change_translators_url = netapplet_owner_browser.url
 
-    >>> print netapplet_owner_browser.title
+    >>> print(netapplet_owner_browser.title)
     Configure translations : Translations : NetApplet
 
-    >>> print netapplet_owner_browser.getControl(
-    ...     'Translation group').displayOptions
+    >>> print(netapplet_owner_browser.getControl(
+    ...     'Translation group').displayOptions)
     ['(nothing selected)', 'Single-language Translators',
      'The PolyGlot Translation Group', 'Just a testing team']
 
-    >>> print netapplet_owner_browser.getControl(
-    ...     'Translation group').displayValue
+    >>> print(netapplet_owner_browser.getControl(
+    ...     'Translation group').displayValue)
     ['(nothing selected)']
 
 Ordinary users cannot see the "Configure Translations" link or the page it
@@ -376,7 +376,7 @@
     ...     'Translation group').displayValue = [
     ...         'The PolyGlot Translation Group']
     >>> netapplet_owner_browser.getControl('Change').click()
-    >>> print netapplet_owner_browser.title
+    >>> print(netapplet_owner_browser.title)
     Translations : NetApplet
 
 Now these changes show up in the product page. (XXX mpt 20070126:
@@ -392,14 +392,14 @@
     >>> gnome_owner_browser.open('http://launchpad.dev/gnome')
     >>> gnome_owner_browser.getLink('Translations').click()
     >>> translations_page_url = gnome_owner_browser.url
-    >>> print gnome_owner_browser.title
+    >>> print(gnome_owner_browser.title)
     Translations : GNOME
 
 And now make sure we can see the form to change the translation group
 and permissions on a project.
 
     >>> gnome_owner_browser.getLink('Change permissions').click()
-    >>> print gnome_owner_browser.title
+    >>> print(gnome_owner_browser.title)
     Permissions and policies...
 
 Other users don't see the "Change translators" link and aren't allowed
@@ -421,8 +421,8 @@
 
     >>> gnome_owner_browser.getControl(
     ...     'Translation permissions policy').displayValue = ['Closed']
-    >>> print gnome_owner_browser.getControl(
-    ...     'Translation group').displayOptions
+    >>> print(gnome_owner_browser.getControl(
+    ...     'Translation group').displayOptions)
     ['(nothing selected)', 'Single-language Translators',
      'The PolyGlot Translation Group', 'Just a testing team']
 
@@ -436,7 +436,7 @@
 
     >>> gnome_owner_browser.url
     'http://translations.launchpad.dev/gnome'
-    >>> print gnome_owner_browser.title
+    >>> print(gnome_owner_browser.title)
     Translations : GNOME
 
 We should now see the various distro's, projects and products that the
@@ -444,7 +444,7 @@
 
     >>> browser.open(
     ...     'http://translations.launchpad.dev/+groups/polyglot')
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/+groups/polyglot
 
     >>> def find_projects_portlet(browser):
@@ -454,7 +454,7 @@
 
     >>> portlet = find_projects_portlet(browser)
     >>> for link in portlet.findAll('a'):
-    ...     print '%s: %s' % (link.find(text=True), link['href'])
+    ...     print('%s: %s' % (link.find(text=True), link['href']))
     Ubuntu: http://launchpad.dev/ubuntu
     GNOME: http://launchpad.dev/gnome
     NetApplet: http://launchpad.dev/netapplet
@@ -484,12 +484,12 @@
 
     >>> browser.open(
     ...     'http://translations.launchpad.dev/+groups/polyglot')
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/+groups/polyglot
 
     >>> portlet = find_projects_portlet(browser)
     >>> for link in portlet.findAll('a'):
-    ...     print '%s: %s' % (link.string, link['href'])
+    ...     print('%s: %s' % (link.string, link['href']))
     Ubuntu: http://launchpad.dev/ubuntu
 
 Let's undo this so we don't get in trouble with other tests in this
@@ -520,7 +520,7 @@
     >>> browser.addHeader('Authorization', 'Basic jordi@xxxxxxxxxx:test')
     >>> browser.open(
     ...   'http://translations.launchpad.dev/+groups/polyglot/')
-    >>> print find_tag_by_id(browser.contents, "translation-teams-listing")
+    >>> print(find_tag_by_id(browser.contents, "translation-teams-listing"))
     <...
     No translation teams or supervisors have been appointed in this
     group yet.
@@ -577,7 +577,7 @@
     'http://translations.launchpad.dev/+groups/polyglot/+appoint'
 
     >>> for message in find_tags_by_class(browser.contents, 'message'):
-    ...     print message.renderContents()
+    ...     print(message.renderContents())
     There is 1 error.
     There is already a translator for this language
 
@@ -630,7 +630,7 @@
 
     >>> anon_browser.open(
     ...     'http://translations.launchpad.dev/+groups/polyglot')
-    >>> print anon_browser.url
+    >>> print(anon_browser.url)
     http://translations.launchpad.dev/+groups/polyglot
 
     >>> portlet = find_tag_by_id(
@@ -640,20 +640,20 @@
     ...     cell = language_row.findNext('td')
     ...     lang_name = extract_text(cell)
     ...     lang_team = extract_text(cell.findNext('td').findNext('a'))
-    ...     print '%s: %s' % (lang_name, lang_team)
+    ...     print('%s: %s' % (lang_name, lang_team))
     Abkhazian (ab): Hoary Gnome Team
     Afrikaans (af): No Privileges Person
 
     >>> browser.addHeader('Authorization', 'Basic jordi@xxxxxxxxxx:test')
     >>> browser.open(
     ...   'http://translations.launchpad.dev/+groups/polyglot/')
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/+groups/polyglot/
 
     # We are going to change the Afrikaans (af) translator.
 
     >>> browser.getLink(id='edit-af-translator').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/+groups/polyglot/af
 
 Let's change the language it translates to Afrikaans, which already
@@ -663,7 +663,7 @@
 
     >>> admin_browser.open(
     ...     'http://translations.launchpad.dev/+groups/polyglot/ab')
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/+groups/polyglot/ab
 
     # And we change the one we are editing from Afrikaans to Abkhazian
@@ -674,13 +674,13 @@
     # We stay in the same page (+admin is the default view for
     # polyglot/af/).
 
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/+groups/polyglot/af/+admin
 
 the system detects it and notify the user that is not possible.
 
     >>> for message in find_tags_by_class(browser.contents, 'message'):
-    ...     print message.renderContents()
+    ...     print(message.renderContents())
     There is 1 error.
     <a href="http://translations.launchpad.dev/~name21";>Hoary Gnome Team</a>
     is already a translator for this language
@@ -699,7 +699,7 @@
 
     # We are back to the translation group summary page.
 
-    >>> print browser.url
+    >>> print(browser.url)
     http://translations.launchpad.dev/+groups/polyglot
 
     # And the 'Translation Teams' portlet shows the new information.
@@ -711,7 +711,7 @@
     ...     cell = language_row.findNext('td')
     ...     lang_name = extract_text(cell)
     ...     lang_team = extract_text(cell.findNext('td').findNext('a'))
-    ...     print '%s: %s' % (lang_name, lang_team)
+    ...     print('%s: %s' % (lang_name, lang_team))
     Abkhazian (ab): Hoary Gnome Team
     Welsh (cy): No Privileges Person
 
@@ -722,17 +722,17 @@
     >>> admin_browser.open(
     ...     'http://translations.launchpad.dev/+groups/' +
     ...         'polyglot/ab/+remove')
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/+groups/polyglot/ab/+remove
 
     >>> admin_browser.getControl('Remove').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/+groups/polyglot
 
 And on that page, we should see the removal message.
 
     >>> for tag in find_tags_by_class(admin_browser.contents, 'message'):
-    ...     print tag.renderContents()
+    ...     print(tag.renderContents())
     Removed Hoary Gnome Team as the Abkhazian translator for The PolyGlot
     Translation Group.
 
@@ -758,7 +758,7 @@
     ...     'http://translations.launchpad.dev/'
     ...     'ubuntu/hoary/+source/evolution/'
     ...     '+pots/evolution-2.2/st/+translate')
-    >>> print browser.url
+    >>> print(browser.url)
     http://.../ubuntu/.../evolution/+pots/evolution-2.2/st/+translate
 
 We are in read only mode, so there shouldn't be any textareas:
@@ -766,12 +766,12 @@
     >>> main_content = find_tag_by_id(
     ...     browser.contents, 'messages_to_translate')
     >>> for textarea in main_content.findAll('textarea'):
-    ...     print 'Found textarea:\n%s' % textarea
+    ...     print('Found textarea:\n%s' % textarea)
 
 Neither any input widget:
 
     >>> for input in main_content.findAll('input'):
-    ...     print 'Found input:\n%s' % input
+    ...     print('Found input:\n%s' % input)
 
 However, in Welsh, No Privileges Person does have the ability to edit
 directly.
@@ -780,14 +780,14 @@
     ...     'http://translations.launchpad.dev/'
     ...     'ubuntu/hoary/+source/evolution/'
     ...     '+pots/evolution-2.2/cy/19/+translate')
-    >>> print browser.url
+    >>> print(browser.url)
     http://.../ubuntu/.../evolution/+pots/evolution-2.2/cy/19/+translate
 
 No Privileges is going to do some translation here.  Right now, message
 number 148 is not translated.
 
     >>> tag = find_tag_by_id(browser.contents, 'msgset_148_cy_translation_0')
-    >>> print tag.renderContents()
+    >>> print(tag.renderContents())
     (no translation yet)
 
 After No posts a translation, however, it is.
@@ -798,18 +798,18 @@
     >>> browser.getControl(
     ...     name='msgset_148_cy_translation_0_new').value = 'foo\n%i%i%i\n'
     >>> browser.getControl('Save & Continue').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://.../ubuntu/.../evolution/+pots/evolution-2.2/cy/20/+translate
 
 And finally, let's take a look again, and we should have a translation
 added (with some extra html code, but the same content we wanted to add)
 
     >>> browser.getLink('Previous').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://.../ubuntu/.../evolution/+pots/evolution-2.2/cy/19/+translate
 
     >>> tag = find_tag_by_id(browser.contents, 'msgset_148_cy_translation_0')
-    >>> print tag.renderContents()
+    >>> print(tag.renderContents())
     foo<img alt="" src="/@@/translation-newline" /><br />
     %i%i%i
 
@@ -830,11 +830,12 @@
     >>> admin_browser.getControl(
     ...     'Translation permissions policy').value = ['RESTRICTED']
     >>> admin_browser.getControl('Change').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/ubuntu
 
-    >>> print extract_text(
-    ...     find_tag_by_id(admin_browser.contents, 'translation-permissions'))
+    >>> print(extract_text(
+    ...     find_tag_by_id(admin_browser.contents,
+    ...                    'translation-permissions')))
     Ubuntu is translated by ... with Restricted permissions...
 
 The translation group does not assign anyone to tend to the Southern
@@ -861,10 +862,10 @@
     ...     """Print given navigation menu on given page, if present."""
     ...     found = False
     ...     for item in find_tags_by_class(contents, 'menu-link-%s' % option):
-    ...         print item.renderContents()
+    ...         print(item.renderContents())
     ...         found = True
     ...     if not found:
-    ...         print "Not found."
+    ...         print("Not found.")
 
     >>> browser.open(
     ...     'http://translations.launchpad.dev/'
@@ -882,15 +883,15 @@
     ...     'ubuntu/hoary/+source/evolution/'
     ...     '+pots/evolution-2.2/st/+translate')
 
-    >>> print find_translation_input_label(browser.contents)
+    >>> print(find_translation_input_label(browser.contents))
     None
 
     >>> managers = get_detail_tag(browser, 'translation-managers')
-    >>> print managers
+    >>> print(managers)
     This translation is managed by <...> translation group
     <...>polyglot<...>.
 
-    >>> print get_detail_tag(browser, 'translation-access')
+    >>> print(get_detail_tag(browser, 'translation-access'))
     There is nobody to manage translation into this particular language.  If
     you are interested in working on it, please contact the translation group.
 
@@ -914,11 +915,11 @@
     ...     'ubuntu/hoary/+source/evolution/'
     ...     '+pots/evolution-2.2/st/+translate')
 
-    >>> print find_translation_input_label(browser.contents)
+    >>> print(find_translation_input_label(browser.contents))
     New suggestion:
 
     >>> managers = get_detail_tag(browser, 'translation-managers')
-    >>> print managers
+    >>> print(managers)
     This translation is managed by <...>...Hoary Gnome Team<...>, assigned
     by <...>The PolyGlot Translation Group<...>.
 
@@ -932,10 +933,10 @@
 since there is only one item in this case, we don't see that.
 
     >>> import re
-    >>> print re.search('\band\b', managers)
+    >>> print(re.search('\band\b', managers))
     None
 
-    >>> print get_detail_tag(browser, 'translation-access')
+    >>> print(get_detail_tag(browser, 'translation-access'))
     Your suggestions will be held for review...
 
 In Welsh, No Privileges Person does have the ability to edit directly,
@@ -965,14 +966,14 @@
     ...     'ubuntu/hoary/+source/evolution/'
     ...     '+pots/evolution-2.2/cy/8/+translate')
 
-    >>> print get_detail_tag(browser, 'translation-managers')
+    >>> print(get_detail_tag(browser, 'translation-managers'))
     This translation is managed by <...No Privileges Person<...>, assigned by
     <...>The PolyGlot Translation Group<...>.
 
-    >>> print get_detail_tag(browser, 'translation-access')
+    >>> print(get_detail_tag(browser, 'translation-access'))
     You have full access to this translation.
 
-    >>> print find_no_translation_marker(browser.contents)
+    >>> print(find_no_translation_marker(browser.contents))
     (no translation yet)
 
 Now, we need to show that it is translated after a post. Let's go ahead and
@@ -985,17 +986,17 @@
     >>> msg_137.value = 'evolution minikaart'
 
     >>> browser.getControl(name='submit_translations').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://.../ubuntu/.../+pots/evolution-2.2/cy/9/+translate
 
 And finally, let's take a look again, and we see that the translation
 has been added.
 
     >>> browser.getLink('Previous').click()
-    >>> print find_no_translation_marker(browser.contents)
+    >>> print(find_no_translation_marker(browser.contents))
     None
 
-    >>> print find_main_content(browser.contents).renderContents()
+    >>> print(find_main_content(browser.contents).renderContents())
     <...evolution minikaart...
 
 First, we verify that netapplet is using Launchpad Translations.
@@ -1008,7 +1009,7 @@
     ( ) External
     ( ) Not Applicable
     >>> admin_browser.getLink('Cancel').click()
-    >>> print admin_browser.title
+    >>> print(admin_browser.title)
     NetApplet in Launchpad
 
 We set the 'Structured' permission and select the 'Just a testing team'
@@ -1025,7 +1026,7 @@
     >>> admin_browser.getControl(
     ...     'Translation permissions policy').displayValue = ['Structured']
     >>> admin_browser.getControl('Change').click()
-    >>> print admin_browser.title
+    >>> print(admin_browser.title)
     Translations : NetApplet
 
 ... and its associated project, GNOME.
@@ -1048,7 +1049,7 @@
     ...     'http://translations.launchpad.dev/netapplet/trunk/+pots/' +
     ...         'netapplet/es/+translate')
     >>> content = find_main_content(tsukimi_browser.contents)
-    >>> print content
+    >>> print(content)
     <...
     ...Translating into Spanish...
     ...Dial-up connection...
@@ -1063,7 +1064,7 @@
     ...     'http://translations.launchpad.dev/netapplet/trunk/+pots/' +
     ...         'netapplet/es/+translate')
     >>> content = find_main_content(no_priv_browser.contents)
-    >>> print content
+    >>> print(content)
     <...
     ...Translating into Spanish...
     ...Your suggestions will be held for review...
@@ -1075,7 +1076,7 @@
     ...     'http://translations.launchpad.dev/netapplet/trunk/+pots/' +
     ...         'netapplet/fr/+translate')
     >>> content = find_main_content(no_priv_browser.contents)
-    >>> print content
+    >>> print(content)
     <...
     ...Translating into French...
 
@@ -1095,25 +1096,25 @@
     >>> admin_browser.open(
     ...     'http://translations.launchpad.dev/ubuntu/hoary/+source/' +
     ...         'evolution/+pots/evolution-2.2/af/+upload')
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://.../ubuntu/hoary/+source/evolution/+pots/evolution-2.2/af/+upload
 
 Now hit the upload button, but without giving a file for upload. We get
 an error message back.
 
     >>> admin_browser.getControl('Upload').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://.../ubuntu/hoary/+source/evolution/+pots/evolution-2.2/af/+upload
 
     >>> for tag in find_tags_by_class(admin_browser.contents, 'error'):
-    ...     print tag.renderContents()
+    ...     print(tag.renderContents())
     Ignored your upload because you didn&#x27;t select a file to upload.
 
 Uploading files with an unkown file format notifies the user that it
 cannot be handled.
 
     >>> from StringIO import StringIO
-    >>> af_file = '''
+    >>> af_file = b'''
     ... # Afrikaans translation for Silky
     ... # Copyright (C) 2004 Free Software Foundation, Inc.
     ... # This file is distributed under the same license as the silky package.
@@ -1142,11 +1143,11 @@
     >>> upload = admin_browser.getControl(name='file')
     >>> upload.add_file(StringIO(af_file), 'application/msword', 'af.doc')
     >>> admin_browser.getControl('Upload').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/af/+upload
 
     >>> for tag in find_tags_by_class(admin_browser.contents, 'error'):
-    ...     print tag.renderContents()
+    ...     print(tag.renderContents())
     Ignored your upload because the file you uploaded was not recognised as
     a file that can be imported.
 
@@ -1155,11 +1156,11 @@
     >>> upload = admin_browser.getControl(name='file')
     >>> upload.add_file(StringIO(af_file), 'application/x-po', 'af.po')
     >>> admin_browser.getControl('Upload').click()
-    >>> print admin_browser.url
+    >>> print(admin_browser.url)
     http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/af/+upload
 
     >>> for tag in find_tags_by_class(admin_browser.contents, 'message'):
-    ...     print tag.renderContents()
+    ...     print(tag.renderContents())
     Thank you for your upload.  It will be automatically reviewed...
 
 
@@ -1209,7 +1210,7 @@
     >>> browser.getControl(
     ...     name='msgset_134_es_translation_0_new').value = 'new suggestion'
     >>> browser.getControl(name='submit_translations').click()
-    >>> print browser.url
+    >>> print(browser.url)
     http://.../ubuntu/.../evolution/+pots/evolution-2.2/es/+translate?...
 
     >>> browser.getLink('Previous').click()
@@ -1218,7 +1219,7 @@
 
     >>> for suggestion in find_main_content(browser.contents).findAll(
     ...         True, {'id': re.compile('msgset_134_es_suggestion_.*')}):
-    ...     print suggestion
+    ...     print(suggestion)
     <...<samp> </samp>new suggestion...
     <...
     ...Suggested by...No Privileges Person...
@@ -1232,10 +1233,9 @@
 
 And there's also a separate translation coming from upstream:
 
-    >>> print find_tag_by_id(browser.contents, "msgset_134_other")
+    >>> print(find_tag_by_id(browser.contents, "msgset_134_other"))
     <...<samp> </samp>tarjetas...
 
-    >>> print find_tag_by_id(browser.contents, "msgset_134_other_origin")
+    >>> print(find_tag_by_id(browser.contents, "msgset_134_other_origin"))
     <...
     ...Suggested by...Carlos Perelló Marín...2005-05-06...
-

=== modified file 'lib/lp/translations/stories/translations/xx-translations.txt'
--- lib/lp/translations/stories/translations/xx-translations.txt	2016-01-26 15:47:37 +0000
+++ lib/lp/translations/stories/translations/xx-translations.txt	2018-06-03 00:41:33 +0000
@@ -11,18 +11,18 @@
 We are going to change message #21, but first, we see that this messages
 has no translations at all.
 
-  >>> print find_tag_by_id(
-  ...     user_browser.contents, 'msgset_150').renderContents()
+  >>> print(find_tag_by_id(
+  ...     user_browser.contents, 'msgset_150').renderContents())
   21.
   <input type="hidden" name="msgset_150" />
-  >>> print find_tag_by_id(
-  ...     user_browser.contents, 'msgset_150_singular').renderContents()
+  >>> print(find_tag_by_id(
+  ...     user_browser.contents, 'msgset_150_singular').renderContents())
   Found <code>%i</code> invalid file.
-  >>> print find_tag_by_id(user_browser.contents,
-  ...                      'msgset_150_es_translation_0').renderContents()
+  >>> print(find_tag_by_id(user_browser.contents,
+  ...                      'msgset_150_es_translation_0').renderContents())
   (no translation yet)
-  >>> print find_tag_by_id(user_browser.contents,
-  ...                      'msgset_150_es_translation_1').renderContents()
+  >>> print(find_tag_by_id(user_browser.contents,
+  ...                      'msgset_150_es_translation_1').renderContents())
   (no translation yet)
 
 We are going to submit now translations for the singular and plural forms.
@@ -45,13 +45,13 @@
 
 Because of the error, we're still in on the same page.
 
-  >>> print user_browser.url
+  >>> print(user_browser.url)
   http://.../hoary/+source/evo.../+pots/evo...-2.2/es/+translate?start=20
 
 And we can see the error.
 
   >>> for tag in find_tags_by_class(user_browser.contents, 'error'):
-  ...     print extract_text(tag)
+  ...     print(extract_text(tag))
   There is an error in a translation you provided.
   Please correct it before continuing.
   Error in Translation:
@@ -70,18 +70,18 @@
 
 Also, we can see that the message has no active translation yet:
 
-  >>> print find_tag_by_id(
-  ...     user_browser.contents, 'msgset_150').renderContents()
+  >>> print(find_tag_by_id(
+  ...     user_browser.contents, 'msgset_150').renderContents())
   21.
   <input type="hidden" name="msgset_150" />
-  >>> print find_tag_by_id(
-  ...     user_browser.contents, 'msgset_150_singular').renderContents()
+  >>> print(find_tag_by_id(
+  ...     user_browser.contents, 'msgset_150_singular').renderContents())
   Found <code>%i</code> invalid file.
-  >>> print find_tag_by_id(user_browser.contents,
-  ...                      'msgset_150_es_translation_0').renderContents()
+  >>> print(find_tag_by_id(user_browser.contents,
+  ...                      'msgset_150_es_translation_0').renderContents())
   (no translation yet)
-  >>> print find_tag_by_id(user_browser.contents,
-  ...                      'msgset_150_es_translation_1').renderContents()
+  >>> print(find_tag_by_id(user_browser.contents,
+  ...                      'msgset_150_es_translation_1').renderContents())
   (no translation yet)
 
 = Translations for DistroSeries =
@@ -92,11 +92,11 @@
   >>> from lp.testing.pages import extract_url_parameter
   >>> browser.open('http://translations.launchpad.dev/ubuntu/hoary/'
   ...     '+translations')
-  >>> 'Translation status by language' in browser.contents
+  >>> b'Translation status by language' in browser.contents
   True
-  >>> print browser.getLink('Catalan').url
+  >>> print(browser.getLink('Catalan').url)
   http://translations.launchpad.dev/ubuntu/hoary/+lang/ca
-  >>> print browser.getLink('Xhosa').url
+  >>> print(browser.getLink('Xhosa').url)
   http://translations.launchpad.dev/ubuntu/hoary/+lang/xh
   >>> browser.getLink('Afrihili')
   Traceback (most recent call last):
@@ -111,13 +111,13 @@
   >>> browser.addHeader('Accept-Language', 'en-us,en;q=0.7,afh;q=0.3')
   >>> browser.open('http://translations.launchpad.dev/ubuntu/hoary/'
   ...     '+translations')
-  >>> 'Translation status by language' in browser.contents
+  >>> b'Translation status by language' in browser.contents
   True
-  >>> print browser.getLink('Catalan').url
+  >>> print(browser.getLink('Catalan').url)
   http://translations.launchpad.dev/ubuntu/hoary/+lang/ca
-  >>> print browser.getLink('Xhosa').url
+  >>> print(browser.getLink('Xhosa').url)
   http://translations.launchpad.dev/ubuntu/hoary/+lang/xh
-  >>> print browser.getLink('Afrihili').url
+  >>> print(browser.getLink('Afrihili').url)
   http://translations.launchpad.dev/ubuntu/hoary/+lang/afh
 
 If we select Croatian, we would expect to see the list of source package
@@ -127,13 +127,13 @@
 
   >>> browser.open(
   ...     'http://translations.launchpad.dev/ubuntu/hoary/+lang/hr?batch=2')
-  >>> 'Croatian' in browser.contents
-  True
-  >>> 'Translatable templates' in browser.contents
-  True
-  >>> print browser.getLink('evolution-2.2').url
+  >>> b'Croatian' in browser.contents
+  True
+  >>> b'Translatable templates' in browser.contents
+  True
+  >>> print(browser.getLink('evolution-2.2').url)
   http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/hr/+translate
-  >>> print browser.getLink('man').url
+  >>> print(browser.getLink('man').url)
   http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/man/hr/+translate
 
 pmount and pkgconf-mozilla are not in this page, because it belongs to the next batch.
@@ -153,9 +153,9 @@
 
 Now, we have the other man and pkgconf-mozilla:
 
-  >>> print browser.getLink('man').url
+  >>> print(browser.getLink('man').url)
   http://translations.launchpad.dev/ubuntu/hoary/+source/pmount/+pots/man/hr/+translate
-  >>> print browser.getLink('pkgconf-mozilla').url
+  >>> print(browser.getLink('pkgconf-mozilla').url)
   http://translations.launchpad.dev/ubuntu/hoary/+source/mozilla/+pots/pkgconf-mozilla/hr/+translate
 
 Let's go to next page.
@@ -164,12 +164,12 @@
 
 And finally, we will get pmount.
 
-  >>> print browser.getLink('pmount').url
+  >>> print(browser.getLink('pmount').url)
   http://translations.launchpad.dev/ubuntu/hoary/+source/pmount/+pots/pmount/hr/+translate
 
 With its latest translator.
 
-  >>> 'Edgar Bursic' in browser.contents
+  >>> b'Edgar Bursic' in browser.contents
   True
 
 == Last translator ==
@@ -179,11 +179,11 @@
 last translator is displayed.
 
   >>> browser.open('http://translations.launchpad.dev/ubuntu/hoary/+lang/de')
-  >>> print extract_text(
-  ...     find_tag_by_id(browser.contents, "pkgconf-mozilla-time"))
+  >>> print(extract_text(
+  ...     find_tag_by_id(browser.contents, "pkgconf-mozilla-time")))
   2005-05-06
-  >>> print extract_text(
-  ...     find_tag_by_id(browser.contents, "pkgconf-mozilla-person"))
+  >>> print(extract_text(
+  ...     find_tag_by_id(browser.contents, "pkgconf-mozilla-person")))
   Helge Kreutzmann
 
 == DummyPOFile ==
@@ -202,11 +202,11 @@
 
   >>> browser.getControl(name='show', index=1).value = ['untranslated']
   >>> browser.getControl('Change').click()
-  >>> print extract_url_parameter(browser.url, 'batch')
+  >>> print(extract_url_parameter(browser.url, 'batch'))
   batch=10
-  >>> print extract_url_parameter(browser.url, 'show')
+  >>> print(extract_url_parameter(browser.url, 'show'))
   show=untranslated
-  >>> '10.' in browser.contents
+  >>> b'10.' in browser.contents
   True
 
 If everything works out ok, that means that DummyPOFile has actually
@@ -216,9 +216,9 @@
 
   >>> browser.getControl(name='show', index=1).value = ['translated']
   >>> browser.getControl('Change').click()
-  >>> print extract_url_parameter(browser.url, 'show')
+  >>> print(extract_url_parameter(browser.url, 'show'))
   show=translated
-  >>> "There are no messages that match this filtering." in browser.contents
+  >>> b"There are no messages that match this filtering." in browser.contents
   True
 
 == Links to filtered pages ==
@@ -234,7 +234,7 @@
 
     >>> browser.open('http://translations.launchpad.dev/ubuntu/hoary/+lang/es')
     >>> evolution_line = find_tag_by_id(browser.contents, 'evolution-2.2')
-    >>> print extract_text(evolution_line)
+    >>> print(extract_text(evolution_line))
     evolution-2.2
     ...
     15 1 1
@@ -248,9 +248,9 @@
     >>> all_links = evolution_line.findAll('a')
     >>> base_href = browser.url
     >>> unfiltered = all_links[0]
-    >>> print extract_text(unfiltered)
+    >>> print(extract_text(unfiltered))
     evolution-2.2
-    >>> print extract_link_from_tag(unfiltered, base_href)
+    >>> print(extract_link_from_tag(unfiltered, base_href))
     http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es/+translate
 
 The number of untranslated entries points to the same page, which now show
@@ -258,39 +258,39 @@
 has the right filter preselected.
 
     >>> untranslated = all_links[1]
-    >>> print extract_text(untranslated)
+    >>> print(extract_text(untranslated))
     15
     >>> untranslated_link = extract_link_from_tag(untranslated, base_href)
     >>> browser.open(untranslated_link.encode('UTF-8'))
     >>> browser.url
     'http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es/+translate?show=untranslated'
-    >>> print browser.getControl(name='show', index=1).value
+    >>> print(browser.getControl(name='show', index=1).value)
     ['untranslated']
 
 Similarly, the number of unreviewed entries points to the translation page
 with the 'with new suggestions' filter selected.
 
     >>> unreviewed = all_links[2]
-    >>> print extract_text(unreviewed)
+    >>> print(extract_text(unreviewed))
     1
     >>> unreviewed_link = extract_link_from_tag(unreviewed, base_href)
     >>> browser.open(unreviewed_link.encode('UTF-8'))
     >>> browser.url
     'http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es/+translate?show=new_suggestions'
-    >>> print browser.getControl(name='show', index=1).value
+    >>> print(browser.getControl(name='show', index=1).value)
     ['new_suggestions']
 
 The number of updated entries points to the translation page with the
 'changed in Ubuntu' filter selected.
 
     >>> updated = all_links[3]
-    >>> print extract_text(updated)
+    >>> print(extract_text(updated))
     1
     >>> updated_link = extract_link_from_tag(updated, base_href)
     >>> browser.open(updated_link.encode('UTF-8'))
     >>> browser.url
     'http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es/+translate?show=changed_in_ubuntu'
-    >>> print browser.getControl(name='show', index=1).value
+    >>> print(browser.getControl(name='show', index=1).value)
     ['changed_in_ubuntu']
 
 ==========================
@@ -375,20 +375,20 @@
     >>> browser.open(
     ...     'http://translations.launchpad.dev/ubuntu/hoary/+source/'
     ...     'evolution/+translations')
-    >>> 'Catalan' in browser.contents
+    >>> b'Catalan' in browser.contents
     True
 
 But also, he doesn't want to see other languages in the list.  So, he
 shouldn't see eg. Japanese.
 
-    >>> 'Japanese' in browser.contents
+    >>> b'Japanese' in browser.contents
     False
 
 Next, if he chooses to view all the languages, he should see Japanese
 among the languages on the page.
 
     >>> browser.getLink('View template & all languages...').click()
-    >>> 'Japanese' in browser.contents
+    >>> b'Japanese' in browser.contents
     True
 
 So, everything is fine, and Carlos can sleep calmly.
@@ -408,7 +408,7 @@
     >>> browser.open('http://translations.launchpad.dev/ubuntu/hoary/'+
     ...              '+source/evolution/+pots/evolution-2.2')
     >>> spanish_line = find_tag_by_id(browser.contents, 'evolution-2.2_es')
-    >>> print extract_text(spanish_line)
+    >>> print(extract_text(spanish_line))
     Spanish
     15 1 1
     ...
@@ -421,9 +421,9 @@
     >>> all_links = spanish_line.findAll('a')
     >>> base_href = browser.url
     >>> unfiltered = all_links[0]
-    >>> print extract_text(unfiltered)
+    >>> print(extract_text(unfiltered))
     Spanish
-    >>> print extract_link_from_tag(unfiltered, base_href)
+    >>> print(extract_link_from_tag(unfiltered, base_href))
     http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es/+translate
 
 The number of untranslated entries points to the same page, which now shows
@@ -431,37 +431,37 @@
 the right filter preselected.
 
     >>> untranslated = all_links[1]
-    >>> print extract_text(untranslated)
+    >>> print(extract_text(untranslated))
     15
     >>> untranslated_link = extract_link_from_tag(untranslated, base_href)
     >>> browser.open(untranslated_link.encode('UTF-8'))
     >>> browser.url
     'http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es/+translate?show=untranslated'
-    >>> print browser.getControl(name='show', index=2).value
+    >>> print(browser.getControl(name='show', index=2).value)
     untranslated
 
 Similarly, the number of unreviewed entries points to the translation page
 with the 'with new suggestions' filter selected.
 
     >>> unreviewed = all_links[2]
-    >>> print extract_text(unreviewed)
+    >>> print(extract_text(unreviewed))
     1
     >>> unreviewed_link = extract_link_from_tag(unreviewed, base_href)
     >>> browser.open(unreviewed_link.encode('UTF-8'))
     >>> browser.url
     'http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es/+translate?show=new_suggestions'
-    >>> print browser.getControl(name='show', index=2).value
+    >>> print(browser.getControl(name='show', index=2).value)
     new_suggestions
 
 The number of updated entries points to the translation page with the
 'changed in Ubuntu' filter selected.
 
     >>> updated = all_links[3]
-    >>> print extract_text(updated)
+    >>> print(extract_text(updated))
     1
     >>> updated_link = extract_link_from_tag(updated, base_href)
     >>> browser.open(updated_link.encode('UTF-8'))
     >>> browser.url
     'http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es/+translate?show=changed_in_ubuntu'
-    >>> print browser.getControl(name='show', index=2).value
+    >>> print(browser.getControl(name='show', index=2).value)
     changed_in_ubuntu

=== modified file 'lib/lp/translations/stories/webservice/xx-potemplate.txt'
--- lib/lp/translations/stories/webservice/xx-potemplate.txt	2012-10-18 15:20:48 +0000
+++ lib/lp/translations/stories/webservice/xx-potemplate.txt	2018-06-03 00:41:33 +0000
@@ -34,7 +34,7 @@
 
     >>> translation_files = anon_webservice.get(
     ...     potemplate['translation_files_collection_link']).jsonBody()
-    >>> print translation_files['total_size']
+    >>> print(translation_files['total_size'])
     9
     >>> print(translation_files['entries'][0]['resource_type_link'])
     http://.../#translation_file
@@ -118,4 +118,3 @@
     True
     >>> print(all_translation_templates['entries'][0]['resource_type_link'])
     http://.../#translation_template
-

=== modified file 'lib/lp/translations/stories/webservice/xx-translationfocus.txt'
--- lib/lp/translations/stories/webservice/xx-translationfocus.txt	2010-01-20 20:24:06 +0000
+++ lib/lp/translations/stories/webservice/xx-translationfocus.txt	2018-06-03 00:41:33 +0000
@@ -6,29 +6,30 @@
 outside the scope of this test.
 
     >>> evolution = webservice.get('/evolution').jsonBody()
-    >>> print evolution['development_focus_link']
+    >>> print(evolution['development_focus_link'])
     http://.../evolution/trunk
-    >>> print evolution['translation_focus_link']
+    >>> print(evolution['translation_focus_link'])
     None
 
 It's possible to set the translation focus through the API
 if you're an admin. The translation focus should be a project series.
 
     >>> from simplejson import dumps
-    >>> print webservice.patch(
+    >>> print(webservice.patch(
     ...     evolution['self_link'], 'application/json',
     ...     dumps({'translation_focus_link' :
-    ...         evolution['development_focus_link']}))
+    ...         evolution['development_focus_link']})))
     HTTP/1.1 209 Content Returned
     ...
 
-    >>> print webservice.get('/evolution').jsonBody()['translation_focus_link']
+    >>> print(webservice.get(
+    ...     '/evolution').jsonBody()['translation_focus_link'])
     http://.../evolution/trunk
 
 Unprivileged users cannot set the translation focus.
 
-    >>> print user_webservice.patch(
+    >>> print(user_webservice.patch(
     ...     evolution['self_link'], 'application/json',
-    ...     dumps({'translation_focus_link': None}))
+    ...     dumps({'translation_focus_link': None})))
     HTTP... 401 Unauthorized
     ...

=== modified file 'lib/lp/translations/stories/webservice/xx-translationimportqueue.txt'
--- lib/lp/translations/stories/webservice/xx-translationimportqueue.txt	2010-10-21 12:50:05 +0000
+++ lib/lp/translations/stories/webservice/xx-translationimportqueue.txt	2018-06-03 00:41:33 +0000
@@ -28,10 +28,10 @@
     ...     :param shown_keys: optional set of keys that should be
     ...         shown.  If omitted, all keys are shown.
     ...     """
-    ...     print 'Entry:'
+    ...     print('Entry:')
     ...     for key in sorted(a_dict.iterkeys()):
     ...         if shown_keys is None or key in shown_keys:
-    ...             print '', key, a_dict[key]
+    ...             print('', key, a_dict[key])
 
     >>> def print_list_of_dicts(a_list, shown_keys=None):
     ...     """Print entries from a list of dicts."""
@@ -90,19 +90,19 @@
 An entry's file path can be changed by the entry's owner or an admin.
 
     >>> first_entry = queue['entries'][0]['self_link']
-    >>> print webservice.patch(
-    ...     first_entry, 'application/json', dumps({'path': 'foo.pot'}))
+    >>> print(webservice.patch(
+    ...     first_entry, 'application/json', dumps({'path': 'foo.pot'})))
     HTTP/1.1 209 Content Returned
     ...
 
-    >>> print webservice.get(first_entry).jsonBody()['path']
+    >>> print(webservice.get(first_entry).jsonBody()['path'])
     foo.pot
 
 A regular user is not allowed to make this change.
 
     >>> first_entry = queue['entries'][0]['self_link']
-    >>> print user_webservice.patch(
-    ...     first_entry, 'application/json', dumps({'path': 'bar.pot'}))
+    >>> print(user_webservice.patch(
+    ...     first_entry, 'application/json', dumps({'path': 'bar.pot'})))
     HTTP... Unauthorized
     ...
 
@@ -113,29 +113,29 @@
 For now, it is not possible to set an entry's status through the API.
 
     >>> first_entry = queue['entries'][0]['self_link']
-    >>> print webservice.patch(
-    ...     first_entry, 'application/json', dumps({'status': 'Approved'}))
+    >>> print(webservice.patch(
+    ...     first_entry, 'application/json', dumps({'status': 'Approved'})))
     HTTP... Bad Request
     ...
     status: You tried to modify a read-only attribute.
 
 But you can set the status using the setStatus method.
 
-    >>> print webservice.named_post(
-    ...     first_entry, 'setStatus', {}, new_status='Approved')
+    >>> print(webservice.named_post(
+    ...     first_entry, 'setStatus', {}, new_status='Approved'))
     HTTP/1.1 200 Ok
     ...
 
 The entry's status is changed.
 
     >>> queue = webservice.get("/+imports").jsonBody()
-    >>> print queue['entries'][0]['status']
+    >>> print(queue['entries'][0]['status'])
     Approved
 
 Unprivileged users cannot change the status.
 
-    >>> print user_webservice.named_post(
-    ...     first_entry, 'setStatus', {}, new_status='Deleted')
+    >>> print(user_webservice.named_post(
+    ...     first_entry, 'setStatus', {}, new_status='Deleted'))
     HTTP/1.1 401 Unauthorized
     ...
 
@@ -160,8 +160,8 @@
 
     >>> target_queue = webservice.named_get(
     ...     target_url, 'getTranslationImportQueueEntries').jsonBody()
-    >>> print target_queue['total_size']
+    >>> print(target_queue['total_size'])
     1
 
-    >>> print target_queue['entries'][0]['path']
+    >>> print(target_queue['entries'][0]['path'])
     matching-entry.pot

=== modified file 'lib/lp/translations/tests/test_doc.py'
--- lib/lp/translations/tests/test_doc.py	2018-06-02 13:37:18 +0000
+++ lib/lp/translations/tests/test_doc.py	2018-06-03 00:41:33 +0000
@@ -15,7 +15,10 @@
     LaunchpadFunctionalLayer,
     LaunchpadZopelessLayer,
     )
-from lp.testing.pages import PageTestSuite
+from lp.testing.pages import (
+    PageTestSuite,
+    setUpGlobs,
+    )
 from lp.testing.systemdocs import (
     LayeredDocFileSuite,
     setGlobs,
@@ -60,13 +63,15 @@
     suite = unittest.TestSuite()
 
     stories_dir = os.path.join(os.path.pardir, 'stories')
-    suite.addTest(PageTestSuite(stories_dir))
+    suite.addTest(PageTestSuite(
+        stories_dir, setUp=lambda test: setUpGlobs(test, future=True)))
     stories_path = os.path.join(here, stories_dir)
     for story_entry in scandir.scandir(stories_path):
         if not story_entry.is_dir():
             continue
         story_path = os.path.join(stories_dir, story_entry.name)
-        suite.addTest(PageTestSuite(story_path))
+        suite.addTest(PageTestSuite(
+            story_path, setUp=lambda test: setUpGlobs(test, future=True)))
 
     testsdir = os.path.abspath(
         os.path.normpath(os.path.join(here, os.path.pardir, 'doc')))


Follow ups