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
=== added file 'ubuntu_sso/utils/tests/test_translation.py'
--- ubuntu_sso/utils/tests/test_translation.py 1970-01-01 00:00:00 +0000
+++ ubuntu_sso/utils/tests/test_translation.py 2012-12-12 18:25:24 +0000
@@ -0,0 +1,221 @@
1# -*- coding: utf-8 -*-
2#
3# Copyright 2012 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16#
17# In addition, as a special exception, the copyright holders give
18# permission to link the code of portions of this program with the
19# OpenSSL library under certain conditions as described in each
20# individual source file, and distribute linked combinations
21# including the two.
22# You must obey the GNU General Public License in all respects
23# for all of the code used other than OpenSSL. If you modify
24# file(s) with this exception, you may extend this exception to your
25# version of the file(s), but you are not obligated to do so. If you
26# do not wish to do so, delete this exception statement from your
27# version. If you delete this exception statement from all source
28# files in the program, then also delete it here.
29"""Test the platform-specific translation functions."""
30
31import os
32import sys
33
34from twisted.internet import defer
35from ubuntuone.devtools.testcases import TestCase, skipIfNotOS
36from ubuntu_sso.utils import translation
37
38TEST_DOMAIN = 'test-domain'
39TEST_LANG_DEFAULTS_EN_FIRST = ['en', 'pt-PT']
40TEST_LANG_DEFAULTS = ['es', 'en', 'pt-PT']
41TEST_FALLBACK_PATH = 'test-path/to/mofiles'
42TEST_FROZEN_PATH = 'frozen-test-path/to/mofiles'
43
44
45class MockGettextTranslations(object):
46 """Mock translations to test properties"""
47 ugettext = 'ugettext'
48 gettext = 'gettext'
49
50
51class MockNSUserDefaults(object):
52 """Mock defaults for _get_languages on darwin."""
53
54 def standardUserDefaults(self):
55 return {'AppleLanguages': TEST_LANG_DEFAULTS}
56
57
58class TranslationsTestCase(TestCase):
59 """Test getting the right gettext translations."""
60
61 @defer.inlineCallbacks
62 def setUp(self):
63 yield super(TranslationsTestCase, self).setUp()
64 self._called = []
65
66 def _set_called(self, *args, **kwargs):
67 """call recorder"""
68 self._called.append((args, kwargs))
69 return MockGettextTranslations()
70
71 def test_import(self):
72 """Test whether import translation defines _ as a builtin."""
73 import ubuntu_sso.utils.translation
74 assert ubuntu_sso.utils.translation
75 self.assertFalse('_' in __builtins__)
76
77 @skipIfNotOS('darwin', 'Test requires pyobjc-Cocoa')
78 def test_get_languages_darwin(self):
79 """Test getting the user's list of preferred languages."""
80 import Cocoa
81 assert Cocoa
82 self.patch(Cocoa, 'NSUserDefaults', MockNSUserDefaults())
83 langs = translation._get_languages()
84 self.assertEqual(langs, TEST_LANG_DEFAULTS)
85
86 def test_get_languages_linux(self):
87 """Test that we will use gettext defaults on linux."""
88 self.patch(sys, 'platform', 'linux2')
89 langs = translation._get_languages()
90 self.assertEqual(langs, None)
91
92 def test_get_translations_data_path_darwin_frozen(self):
93 """Test getting the location of the compiled translation files."""
94 self.patch(sys, 'platform', 'darwin')
95 sys.frozen = 'yes'
96 self.addCleanup(delattr, sys, 'frozen')
97 self.patch(translation, '__file__',
98 os.path.join('path', 'to', 'Main.app', 'ignore', 'me'))
99
100 path = translation._get_translations_data_path()
101
102 self.assertEqual(path, os.path.join('path', 'to', 'Main.app',
103 'Contents', 'Resources',
104 'translations'))
105
106 def test_get_translations_data_path_darwin_unfrozen_nofallback(self):
107 """Test that we use gettext defaults on darwin when not frozen."""
108 self.patch(sys, 'platform', 'darwin')
109 path = translation._get_translations_data_path()
110 self.assertEqual(path, None)
111
112 def test_get_translations_data_path_darwin_unfrozen_fallback(self):
113 """Test that we use fallback on darwin when not frozen."""
114 self.patch(sys, 'platform', 'darwin')
115 expected = "a-test-path"
116 path = translation._get_translations_data_path(expected)
117 self.assertEqual(path, expected)
118
119 def test_get_translations_data_path_linux(self):
120 """Test that we use gettext defaults on linux."""
121 path = translation._get_translations_data_path()
122 self.assertEqual(path, None)
123
124 def _call_get_gettext(self, platform, py_version, fallback=None):
125 """Helper function to patch and call translation.get_gettext."""
126 self.patch(sys, 'platform', platform)
127 self.patch(sys, 'version_info', py_version)
128 self.patch(translation.gettext, 'translation', self._set_called)
129
130 if fallback:
131 g_func = translation.get_gettext(TEST_DOMAIN, fallback)
132 else:
133 g_func = translation.get_gettext(TEST_DOMAIN)
134
135 if py_version == (2,):
136 self.assertEqual(g_func, 'ugettext')
137 else:
138 self.assertEqual(g_func, 'gettext')
139
140 def test_get_gettext_linux_py2(self):
141 """test get_gettext on linux py2"""
142 self._call_get_gettext('linux2', py_version=(2,))
143
144 def test_get_gettext_linux_py3(self):
145 """test get_gettext on linux py3"""
146 self._call_get_gettext('linux2', py_version=(3,))
147
148 def _call_get_gettext_nonlinux(self, frozen, py_version,
149 lang_en_first=False):
150 """Helper function for non-linux runs of get_gettext."""
151 if lang_en_first:
152 lang_rv = TEST_LANG_DEFAULTS_EN_FIRST
153 else:
154 lang_rv = TEST_LANG_DEFAULTS
155 self.patch(translation, '_get_languages', lambda: lang_rv)
156 if frozen:
157 expected_path = TEST_FROZEN_PATH
158 sys.frozen = 'yes'
159 self.addCleanup(delattr, sys, 'frozen')
160 else:
161 expected_path = TEST_FALLBACK_PATH
162
163 self.patch(translation, '_get_translations_data_path',
164 lambda x: expected_path)
165
166 self._call_get_gettext('notlinux', py_version)
167
168 # This tests a special case if 'en' is the first language, we
169 # don't give the path, so we will get the fallback
170 # NullTranslation instance that will use the keys and not look
171 # for en translations, which we do not ship.
172 if lang_en_first:
173 expected_arg = ((TEST_DOMAIN,), {'fallback': True})
174 self.assertEqual(self._called, [expected_arg])
175 else:
176 # Check for lang_rv[:1] (must be a list) because we only
177 # send first language, in order to fall back to 'en' even
178 # though we don't ship an en.mo
179 expected_arg = ((TEST_DOMAIN, expected_path),
180 {'languages': lang_rv[:1],
181 'fallback': True})
182
183 self.assertEqual(self._called, [expected_arg])
184
185 def test_get_gettext_nonlinux_frozen_py2(self):
186 """test get_gettext on nonlinux frozen and py2"""
187 self._call_get_gettext_nonlinux(frozen=True, py_version=(2,))
188
189 def test_get_gettext_nonlinux_frozen_py3(self):
190 """test get_gettext on nonlinux frozen and py3"""
191 self._call_get_gettext_nonlinux(frozen=True, py_version=(3,))
192
193 def test_get_gettext_nonlinux_unfrozen_py2(self):
194 """test get_gettext on nonlinux un-frozen and py2"""
195 self._call_get_gettext_nonlinux(frozen=False, py_version=(2,))
196
197 def test_get_gettext_nonlinux_unfrozen_py3(self):
198 """test get_gettext on nonlinux un-frozen and py3"""
199 self._call_get_gettext_nonlinux(frozen=False, py_version=(3,))
200
201 def test_get_gettext_nonlinux_frozen_py2_enfirst(self):
202 """test get_gettext returning nulltranslations when lang[0] = en"""
203 self._call_get_gettext_nonlinux(frozen=True,
204 py_version=(2,),
205 lang_en_first=True)
206
207 def test_get_gettext_darwin_unfrozen_fallback(self):
208 """test using fallback path from source"""
209 self.patch(translation, '_get_languages', lambda: ['not-en'])
210 self._call_get_gettext('darwin', (2,), TEST_FALLBACK_PATH)
211 self.assertEqual(self._called, [((TEST_DOMAIN, TEST_FALLBACK_PATH),
212 {'languages': ['not-en'],
213 'fallback': True})])
214
215 def test_get_gettext_win32_unfrozen_fallback(self):
216 """test using fallback path from source"""
217 self.patch(translation, '_get_languages', lambda: ['not-en'])
218 self._call_get_gettext('win32', (2,), TEST_FALLBACK_PATH)
219 self.assertEqual(self._called, [((TEST_DOMAIN, TEST_FALLBACK_PATH),
220 {'languages': ['not-en'],
221 'fallback': True})])
0222
=== added file 'ubuntu_sso/utils/translation.py'
--- ubuntu_sso/utils/translation.py 1970-01-01 00:00:00 +0000
+++ ubuntu_sso/utils/translation.py 2012-12-12 18:25:24 +0000
@@ -0,0 +1,91 @@
1# -*- coding: utf-8 -*-
2#
3# Copyright 2012 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16#
17# In addition, as a special exception, the copyright holders give
18# permission to link the code of portions of this program with the
19# OpenSSL library under certain conditions as described in each
20# individual source file, and distribute linked combinations
21# including the two.
22# You must obey the GNU General Public License in all respects
23# for all of the code used other than OpenSSL. If you modify
24# file(s) with this exception, you may extend this exception to your
25# version of the file(s), but you are not obligated to do so. If you
26# do not wish to do so, delete this exception statement from your
27# version. If you delete this exception statement from all source
28# files in the program, then also delete it here.
29"""Platform-specific translation functions."""
30
31import gettext
32import os
33import sys
34
35from ubuntu_sso.logger import setup_logging
36
37logger = setup_logging('ubuntu_sso.utils.translation')
38
39
40def _get_languages():
41 """list of langs ordered by preference, or None for gettext defaults."""
42 if sys.platform == 'darwin':
43 from Cocoa import NSUserDefaults
44 su = NSUserDefaults.standardUserDefaults()
45 return su['AppleLanguages']
46 else:
47 if sys.platform == 'win32':
48 return None
49 return None
50
51
52def _get_translations_data_path(fallback_path=None):
53 """path to compiled translation files, or None for gettext defaults"""
54 if getattr(sys, 'frozen', None) is not None:
55 if sys.platform == 'darwin':
56 main_app_dir = ''.join(__file__.partition('.app')[:-1])
57 path = os.path.join(main_app_dir, 'Contents', 'Resources',
58 'translations')
59 return path
60 elif sys.platform == 'win32':
61 return None # TODO
62
63 exists = (os.path.exists(fallback_path)
64 if fallback_path else False)
65 logger.debug("Using fallback translation path %r "
66 "which does %s exist." %
67 (fallback_path, "" if exists else "**NOT**"))
68
69 return fallback_path
70
71
72def get_gettext(translation_domain, fallback_path=None):
73 """Get proper gettext translation function for platform and py version."""
74 languages = _get_languages()
75 translations_path = _get_translations_data_path(fallback_path)
76
77 if languages is None or translations_path is None or languages[0] == 'en':
78 logger.debug('Using default gettext translation search paths')
79
80 translation = gettext.translation(translation_domain, fallback=True)
81 else:
82 translation = gettext.translation(translation_domain,
83 translations_path,
84 languages=languages[:1],
85 fallback=True)
86 if isinstance(translation, gettext.NullTranslations):
87 logger.warn('Translations not found, using null translator.')
88 if sys.version_info < (3,):
89 return translation.ugettext
90 else:
91 return translation.gettext
092
=== modified file 'ubuntu_sso/utils/ui.py'
--- ubuntu_sso/utils/ui.py 2012-09-18 20:45:03 +0000
+++ ubuntu_sso/utils/ui.py 2012-12-12 18:25:24 +0000
@@ -29,17 +29,18 @@
29"""Utils to be used by the UI modules."""29"""Utils to be used by the UI modules."""
3030
31import argparse31import argparse
32import os
32import re33import re
33import gettext
34import sys
3534
36from ubuntu_sso.logger import setup_logging35from ubuntu_sso.logger import setup_logging
3736
38TRANSLATION = gettext.translation('ubuntu-sso-client', fallback=True)37from ubuntu_sso.utils.translation import get_gettext
39if sys.version_info < (3,):38
40 _ = TRANSLATION.ugettext39source_path = os.path.join(os.path.dirname(__file__),
41else:40 os.path.pardir, os.path.pardir,
42 _ = TRANSLATION.gettext41 'build', 'mo')
42_ = get_gettext('ubuntu-sso-client',
43 fallback_path=os.path.abspath(source_path))
4344
44logger = setup_logging('ubuntu_sso.utils.ui')45logger = setup_logging('ubuntu_sso.utils.ui')
4546

Subscribers

People subscribed via source and target branches