Merge lp:~mikemc/ubuntu-sso-client/cross-platform-translations-1074116 into lp:ubuntu-sso-client

Proposed by Mike McCracken
Status: Merged
Approved by: dobey
Approved revision: 1018
Merged at revision: 1014
Proposed branch: lp:~mikemc/ubuntu-sso-client/cross-platform-translations-1074116
Merge into: lp:ubuntu-sso-client
Diff against target: 350 lines (+320/-7)
3 files modified
ubuntu_sso/utils/tests/test_translation.py (+221/-0)
ubuntu_sso/utils/translation.py (+91/-0)
ubuntu_sso/utils/ui.py (+8/-7)
To merge this branch: bzr merge lp:~mikemc/ubuntu-sso-client/cross-platform-translations-1074116
Reviewer Review Type Date Requested Status
Brian Curtin (community) Approve
Michał Karnicki (community) Approve
Review via email: mp+139536@code.launchpad.net

Commit message

- Add util func returning translation function for platform/pyversion/frozen status. (LP: #1074116)

Description of the change

- Add util func returning translation function for platform/pyversion/frozen status. (LP: #1074116)

Consolidates checking for python version and giving gettext platform-specific search paths for translation files on frozen mac/win.

Also adds fallback to built translation files in source tree for testing translation support from source.

Windows frozen support is TBD.

Testing IRL from source:
- 'setup.py build' should've been run (as part of tests, or just run it now) to get compiled translation files.
- set language:
 -- on OSX, reorder the list in system preferences > Language & Text
 -- on linux, just set LANG='es' or whatever in the shell
- run sso-login-qt
- observe translated strings in UI.

Testing IRL mac frozen requires a currently unlanded setup-mac branch.
I will be pushing that soon, and will update this MP when I do.

To post a comment you must log in.
Revision history for this message
Michał Karnicki (karni) wrote :

Looks good.

review: Approve
Revision history for this message
Brian Curtin (brian.curtin) wrote :

134 + if fallback:
135 + g_func = translation.get_gettext(TEST_DOMAIN, fallback)
136 + else:
137 + g_func = translation.get_gettext(TEST_DOMAIN)

There's nothing functionally wrong with this, but it's a bit repetitive. Would something like the below part work?

args = [TEST_DOMAIN]
args.append(fallback) if fallback else None
gfunc = translation.get_gettext(*args)

** I don't know if that's readable to everyone so I'm not marking this "Needs Fixing". I prefer to avoid if/else blocks that only choose one line or a very similar other line.

review: Approve
Revision history for this message
Ubuntu One Auto Pilot (otto-pilot) wrote :
Download full text (102.7 KiB)

The attempt to merge lp:~mikemc/ubuntu-sso-client/cross-platform-translations-1074116 into lp:ubuntu-sso-client failed. Below is the output from the failed tests.

*** Running QT test suite for ubuntu_sso ***
running build
Compiled data/qt/ssl_dialog.ui into ubuntu_sso/qt/ui/ssl_dialog_ui.py
compiled data/qt/resources.qrc into ubuntu_sso/qt/ui/resources_rc.py
Compiled data/qt/loadingoverlay.ui into ubuntu_sso/qt/ui/loadingoverlay_ui.py
Compiled data/qt/proxy_credentials_dialog.ui into ubuntu_sso/qt/ui/proxy_credentials_dialog_ui.py
Compiled data/qt/reset_password.ui into ubuntu_sso/qt/ui/reset_password_ui.py
Compiled data/qt/network_detection.ui into ubuntu_sso/qt/ui/network_detection_ui.py
Compiled data/qt/email_verification.ui into ubuntu_sso/qt/ui/email_verification_ui.py
Compiled data/qt/setup_account.ui into ubuntu_sso/qt/ui/setup_account_ui.py
Compiled data/qt/forgotten_password.ui into ubuntu_sso/qt/ui/forgotten_password_ui.py
Compiled data/qt/current_user_sign_in.ui into ubuntu_sso/qt/ui/current_user_sign_in_ui.py
Compiled data/qt/success_message.ui into ubuntu_sso/qt/ui/success_message_ui.py
Compiled data/qt/error_message.ui into ubuntu_sso/qt/ui/error_message_ui.py
running build_py
creating build
creating build/lib.linux-x86_64-2.7
creating build/lib.linux-x86_64-2.7/ubuntu_sso
copying ubuntu_sso/account.py -> build/lib.linux-x86_64-2.7/ubuntu_sso
copying ubuntu_sso/logger.py -> build/lib.linux-x86_64-2.7/ubuntu_sso
copying ubuntu_sso/__init__.py -> build/lib.linux-x86_64-2.7/ubuntu_sso
copying ubuntu_sso/credentials.py -> build/lib.linux-x86_64-2.7/ubuntu_sso
creating build/lib.linux-x86_64-2.7/ubuntu_sso/tests
copying ubuntu_sso/tests/linux.py -> build/lib.linux-x86_64-2.7/ubuntu_sso/tests
copying ubuntu_sso/tests/__init__.py -> build/lib.linux-x86_64-2.7/ubuntu_sso/tests
copying ubuntu_sso/tests/test_credentials.py -> build/lib.linux-x86_64-2.7/ubuntu_sso/tests
copying ubuntu_sso/tests/test_account.py -> build/lib.linux-x86_64-2.7/ubuntu_sso/tests
creating build/lib.linux-x86_64-2.7/ubuntu_sso/keyring
copying ubuntu_sso/keyring/linux.py -> build/lib.linux-x86_64-2.7/ubuntu_sso/keyring
copying ubuntu_sso/keyring/__init__.py -> build/lib.linux-x86_64-2.7/ubuntu_sso/keyring
copying ubuntu_sso/keyring/pykeyring.py -> build/lib.linux-x86_64-2.7/ubuntu_sso/keyring
creating build/lib.linux-x86_64-2.7/ubuntu_sso/keyring/tests
copying ubuntu_sso/keyring/tests/__init__.py -> build/lib.linux-x86_64-2.7/ubuntu_sso/keyring/tests
copying ubuntu_sso/keyring/tests/test_pykeyring.py -> build/lib.linux-x86_64-2.7/ubuntu_sso/keyring/tests
copying ubuntu_sso/keyring/tests/test_linux.py -> build/lib.linux-x86_64-2.7/ubuntu_sso/keyring/tests
copying ubuntu_sso/keyring/tests/test_common.py -> build/lib.linux-x86_64-2.7/ubuntu_sso/keyring/tests
creating build/lib.linux-x86_64-2.7/ubuntu_sso/main
copying ubuntu_sso/main/glib.py -> build/lib.linux-x86_64-2.7/ubuntu_sso/main
copying ubuntu_sso/main/perspective_broker.py -> build/lib.linux-x86_64-2.7/ubuntu_sso/main
copying ubuntu_sso/main/linux.py -> build/lib.linux-x86_64-2.7/ubuntu_sso/main
copying ubuntu_sso/main/windows.py -> build/lib.linux-x86_64-2.7/ubuntu_sso/main
copying ubuntu_sso/main/qt.py -> b...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'ubuntu_sso/utils/tests/test_translation.py'
2--- ubuntu_sso/utils/tests/test_translation.py 1970-01-01 00:00:00 +0000
3+++ ubuntu_sso/utils/tests/test_translation.py 2012-12-12 18:25:24 +0000
4@@ -0,0 +1,221 @@
5+# -*- coding: utf-8 -*-
6+#
7+# Copyright 2012 Canonical Ltd.
8+#
9+# This program is free software: you can redistribute it and/or modify it
10+# under the terms of the GNU General Public License version 3, as published
11+# by the Free Software Foundation.
12+#
13+# This program is distributed in the hope that it will be useful, but
14+# WITHOUT ANY WARRANTY; without even the implied warranties of
15+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
16+# PURPOSE. See the GNU General Public License for more details.
17+#
18+# You should have received a copy of the GNU General Public License along
19+# with this program. If not, see <http://www.gnu.org/licenses/>.
20+#
21+# In addition, as a special exception, the copyright holders give
22+# permission to link the code of portions of this program with the
23+# OpenSSL library under certain conditions as described in each
24+# individual source file, and distribute linked combinations
25+# including the two.
26+# You must obey the GNU General Public License in all respects
27+# for all of the code used other than OpenSSL. If you modify
28+# file(s) with this exception, you may extend this exception to your
29+# version of the file(s), but you are not obligated to do so. If you
30+# do not wish to do so, delete this exception statement from your
31+# version. If you delete this exception statement from all source
32+# files in the program, then also delete it here.
33+"""Test the platform-specific translation functions."""
34+
35+import os
36+import sys
37+
38+from twisted.internet import defer
39+from ubuntuone.devtools.testcases import TestCase, skipIfNotOS
40+from ubuntu_sso.utils import translation
41+
42+TEST_DOMAIN = 'test-domain'
43+TEST_LANG_DEFAULTS_EN_FIRST = ['en', 'pt-PT']
44+TEST_LANG_DEFAULTS = ['es', 'en', 'pt-PT']
45+TEST_FALLBACK_PATH = 'test-path/to/mofiles'
46+TEST_FROZEN_PATH = 'frozen-test-path/to/mofiles'
47+
48+
49+class MockGettextTranslations(object):
50+ """Mock translations to test properties"""
51+ ugettext = 'ugettext'
52+ gettext = 'gettext'
53+
54+
55+class MockNSUserDefaults(object):
56+ """Mock defaults for _get_languages on darwin."""
57+
58+ def standardUserDefaults(self):
59+ return {'AppleLanguages': TEST_LANG_DEFAULTS}
60+
61+
62+class TranslationsTestCase(TestCase):
63+ """Test getting the right gettext translations."""
64+
65+ @defer.inlineCallbacks
66+ def setUp(self):
67+ yield super(TranslationsTestCase, self).setUp()
68+ self._called = []
69+
70+ def _set_called(self, *args, **kwargs):
71+ """call recorder"""
72+ self._called.append((args, kwargs))
73+ return MockGettextTranslations()
74+
75+ def test_import(self):
76+ """Test whether import translation defines _ as a builtin."""
77+ import ubuntu_sso.utils.translation
78+ assert ubuntu_sso.utils.translation
79+ self.assertFalse('_' in __builtins__)
80+
81+ @skipIfNotOS('darwin', 'Test requires pyobjc-Cocoa')
82+ def test_get_languages_darwin(self):
83+ """Test getting the user's list of preferred languages."""
84+ import Cocoa
85+ assert Cocoa
86+ self.patch(Cocoa, 'NSUserDefaults', MockNSUserDefaults())
87+ langs = translation._get_languages()
88+ self.assertEqual(langs, TEST_LANG_DEFAULTS)
89+
90+ def test_get_languages_linux(self):
91+ """Test that we will use gettext defaults on linux."""
92+ self.patch(sys, 'platform', 'linux2')
93+ langs = translation._get_languages()
94+ self.assertEqual(langs, None)
95+
96+ def test_get_translations_data_path_darwin_frozen(self):
97+ """Test getting the location of the compiled translation files."""
98+ self.patch(sys, 'platform', 'darwin')
99+ sys.frozen = 'yes'
100+ self.addCleanup(delattr, sys, 'frozen')
101+ self.patch(translation, '__file__',
102+ os.path.join('path', 'to', 'Main.app', 'ignore', 'me'))
103+
104+ path = translation._get_translations_data_path()
105+
106+ self.assertEqual(path, os.path.join('path', 'to', 'Main.app',
107+ 'Contents', 'Resources',
108+ 'translations'))
109+
110+ def test_get_translations_data_path_darwin_unfrozen_nofallback(self):
111+ """Test that we use gettext defaults on darwin when not frozen."""
112+ self.patch(sys, 'platform', 'darwin')
113+ path = translation._get_translations_data_path()
114+ self.assertEqual(path, None)
115+
116+ def test_get_translations_data_path_darwin_unfrozen_fallback(self):
117+ """Test that we use fallback on darwin when not frozen."""
118+ self.patch(sys, 'platform', 'darwin')
119+ expected = "a-test-path"
120+ path = translation._get_translations_data_path(expected)
121+ self.assertEqual(path, expected)
122+
123+ def test_get_translations_data_path_linux(self):
124+ """Test that we use gettext defaults on linux."""
125+ path = translation._get_translations_data_path()
126+ self.assertEqual(path, None)
127+
128+ def _call_get_gettext(self, platform, py_version, fallback=None):
129+ """Helper function to patch and call translation.get_gettext."""
130+ self.patch(sys, 'platform', platform)
131+ self.patch(sys, 'version_info', py_version)
132+ self.patch(translation.gettext, 'translation', self._set_called)
133+
134+ if fallback:
135+ g_func = translation.get_gettext(TEST_DOMAIN, fallback)
136+ else:
137+ g_func = translation.get_gettext(TEST_DOMAIN)
138+
139+ if py_version == (2,):
140+ self.assertEqual(g_func, 'ugettext')
141+ else:
142+ self.assertEqual(g_func, 'gettext')
143+
144+ def test_get_gettext_linux_py2(self):
145+ """test get_gettext on linux py2"""
146+ self._call_get_gettext('linux2', py_version=(2,))
147+
148+ def test_get_gettext_linux_py3(self):
149+ """test get_gettext on linux py3"""
150+ self._call_get_gettext('linux2', py_version=(3,))
151+
152+ def _call_get_gettext_nonlinux(self, frozen, py_version,
153+ lang_en_first=False):
154+ """Helper function for non-linux runs of get_gettext."""
155+ if lang_en_first:
156+ lang_rv = TEST_LANG_DEFAULTS_EN_FIRST
157+ else:
158+ lang_rv = TEST_LANG_DEFAULTS
159+ self.patch(translation, '_get_languages', lambda: lang_rv)
160+ if frozen:
161+ expected_path = TEST_FROZEN_PATH
162+ sys.frozen = 'yes'
163+ self.addCleanup(delattr, sys, 'frozen')
164+ else:
165+ expected_path = TEST_FALLBACK_PATH
166+
167+ self.patch(translation, '_get_translations_data_path',
168+ lambda x: expected_path)
169+
170+ self._call_get_gettext('notlinux', py_version)
171+
172+ # This tests a special case if 'en' is the first language, we
173+ # don't give the path, so we will get the fallback
174+ # NullTranslation instance that will use the keys and not look
175+ # for en translations, which we do not ship.
176+ if lang_en_first:
177+ expected_arg = ((TEST_DOMAIN,), {'fallback': True})
178+ self.assertEqual(self._called, [expected_arg])
179+ else:
180+ # Check for lang_rv[:1] (must be a list) because we only
181+ # send first language, in order to fall back to 'en' even
182+ # though we don't ship an en.mo
183+ expected_arg = ((TEST_DOMAIN, expected_path),
184+ {'languages': lang_rv[:1],
185+ 'fallback': True})
186+
187+ self.assertEqual(self._called, [expected_arg])
188+
189+ def test_get_gettext_nonlinux_frozen_py2(self):
190+ """test get_gettext on nonlinux frozen and py2"""
191+ self._call_get_gettext_nonlinux(frozen=True, py_version=(2,))
192+
193+ def test_get_gettext_nonlinux_frozen_py3(self):
194+ """test get_gettext on nonlinux frozen and py3"""
195+ self._call_get_gettext_nonlinux(frozen=True, py_version=(3,))
196+
197+ def test_get_gettext_nonlinux_unfrozen_py2(self):
198+ """test get_gettext on nonlinux un-frozen and py2"""
199+ self._call_get_gettext_nonlinux(frozen=False, py_version=(2,))
200+
201+ def test_get_gettext_nonlinux_unfrozen_py3(self):
202+ """test get_gettext on nonlinux un-frozen and py3"""
203+ self._call_get_gettext_nonlinux(frozen=False, py_version=(3,))
204+
205+ def test_get_gettext_nonlinux_frozen_py2_enfirst(self):
206+ """test get_gettext returning nulltranslations when lang[0] = en"""
207+ self._call_get_gettext_nonlinux(frozen=True,
208+ py_version=(2,),
209+ lang_en_first=True)
210+
211+ def test_get_gettext_darwin_unfrozen_fallback(self):
212+ """test using fallback path from source"""
213+ self.patch(translation, '_get_languages', lambda: ['not-en'])
214+ self._call_get_gettext('darwin', (2,), TEST_FALLBACK_PATH)
215+ self.assertEqual(self._called, [((TEST_DOMAIN, TEST_FALLBACK_PATH),
216+ {'languages': ['not-en'],
217+ 'fallback': True})])
218+
219+ def test_get_gettext_win32_unfrozen_fallback(self):
220+ """test using fallback path from source"""
221+ self.patch(translation, '_get_languages', lambda: ['not-en'])
222+ self._call_get_gettext('win32', (2,), TEST_FALLBACK_PATH)
223+ self.assertEqual(self._called, [((TEST_DOMAIN, TEST_FALLBACK_PATH),
224+ {'languages': ['not-en'],
225+ 'fallback': True})])
226
227=== added file 'ubuntu_sso/utils/translation.py'
228--- ubuntu_sso/utils/translation.py 1970-01-01 00:00:00 +0000
229+++ ubuntu_sso/utils/translation.py 2012-12-12 18:25:24 +0000
230@@ -0,0 +1,91 @@
231+# -*- coding: utf-8 -*-
232+#
233+# Copyright 2012 Canonical Ltd.
234+#
235+# This program is free software: you can redistribute it and/or modify it
236+# under the terms of the GNU General Public License version 3, as published
237+# by the Free Software Foundation.
238+#
239+# This program is distributed in the hope that it will be useful, but
240+# WITHOUT ANY WARRANTY; without even the implied warranties of
241+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
242+# PURPOSE. See the GNU General Public License for more details.
243+#
244+# You should have received a copy of the GNU General Public License along
245+# with this program. If not, see <http://www.gnu.org/licenses/>.
246+#
247+# In addition, as a special exception, the copyright holders give
248+# permission to link the code of portions of this program with the
249+# OpenSSL library under certain conditions as described in each
250+# individual source file, and distribute linked combinations
251+# including the two.
252+# You must obey the GNU General Public License in all respects
253+# for all of the code used other than OpenSSL. If you modify
254+# file(s) with this exception, you may extend this exception to your
255+# version of the file(s), but you are not obligated to do so. If you
256+# do not wish to do so, delete this exception statement from your
257+# version. If you delete this exception statement from all source
258+# files in the program, then also delete it here.
259+"""Platform-specific translation functions."""
260+
261+import gettext
262+import os
263+import sys
264+
265+from ubuntu_sso.logger import setup_logging
266+
267+logger = setup_logging('ubuntu_sso.utils.translation')
268+
269+
270+def _get_languages():
271+ """list of langs ordered by preference, or None for gettext defaults."""
272+ if sys.platform == 'darwin':
273+ from Cocoa import NSUserDefaults
274+ su = NSUserDefaults.standardUserDefaults()
275+ return su['AppleLanguages']
276+ else:
277+ if sys.platform == 'win32':
278+ return None
279+ return None
280+
281+
282+def _get_translations_data_path(fallback_path=None):
283+ """path to compiled translation files, or None for gettext defaults"""
284+ if getattr(sys, 'frozen', None) is not None:
285+ if sys.platform == 'darwin':
286+ main_app_dir = ''.join(__file__.partition('.app')[:-1])
287+ path = os.path.join(main_app_dir, 'Contents', 'Resources',
288+ 'translations')
289+ return path
290+ elif sys.platform == 'win32':
291+ return None # TODO
292+
293+ exists = (os.path.exists(fallback_path)
294+ if fallback_path else False)
295+ logger.debug("Using fallback translation path %r "
296+ "which does %s exist." %
297+ (fallback_path, "" if exists else "**NOT**"))
298+
299+ return fallback_path
300+
301+
302+def get_gettext(translation_domain, fallback_path=None):
303+ """Get proper gettext translation function for platform and py version."""
304+ languages = _get_languages()
305+ translations_path = _get_translations_data_path(fallback_path)
306+
307+ if languages is None or translations_path is None or languages[0] == 'en':
308+ logger.debug('Using default gettext translation search paths')
309+
310+ translation = gettext.translation(translation_domain, fallback=True)
311+ else:
312+ translation = gettext.translation(translation_domain,
313+ translations_path,
314+ languages=languages[:1],
315+ fallback=True)
316+ if isinstance(translation, gettext.NullTranslations):
317+ logger.warn('Translations not found, using null translator.')
318+ if sys.version_info < (3,):
319+ return translation.ugettext
320+ else:
321+ return translation.gettext
322
323=== modified file 'ubuntu_sso/utils/ui.py'
324--- ubuntu_sso/utils/ui.py 2012-09-18 20:45:03 +0000
325+++ ubuntu_sso/utils/ui.py 2012-12-12 18:25:24 +0000
326@@ -29,17 +29,18 @@
327 """Utils to be used by the UI modules."""
328
329 import argparse
330+import os
331 import re
332-import gettext
333-import sys
334
335 from ubuntu_sso.logger import setup_logging
336
337-TRANSLATION = gettext.translation('ubuntu-sso-client', fallback=True)
338-if sys.version_info < (3,):
339- _ = TRANSLATION.ugettext
340-else:
341- _ = TRANSLATION.gettext
342+from ubuntu_sso.utils.translation import get_gettext
343+
344+source_path = os.path.join(os.path.dirname(__file__),
345+ os.path.pardir, os.path.pardir,
346+ 'build', 'mo')
347+_ = get_gettext('ubuntu-sso-client',
348+ fallback_path=os.path.abspath(source_path))
349
350 logger = setup_logging('ubuntu_sso.utils.ui')
351

Subscribers

People subscribed via source and target branches