""" Tests for validate Internationalization and Module i18n service. """ import gettext from unittest import skip import mock from django.contrib.auth.models import User from django.utils import translation from django.utils.translation import get_language from contentstore.tests.utils import AjaxEnabledTestClient from contentstore.views.preview import _preview_module_system from xmodule.modulestore.django import ModuleI18nService from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory class FakeTranslations(ModuleI18nService): """A test GNUTranslations class that takes a map of msg -> translations.""" def __init__(self, translations): # pylint: disable=super-init-not-called self.translations = translations def ugettext(self, msgid): """ Mock override for ugettext translation operation """ return self.translations.get(msgid, msgid) @staticmethod def translator(locales_map): # pylint: disable=method-hidden """Build mock translator for the given locales. Returns a mock gettext.translation function that uses individual TestTranslations to translate in the given locales. :param locales_map: A map from locale name to a translations map. { 'es': {'Hi': 'Hola', 'Bye': 'Adios'}, 'zh': {'Hi': 'Ni Hao', 'Bye': 'Zaijian'} } """ def _translation(domain, localedir=None, languages=None): # pylint: disable=unused-argument """ return gettext.translation for given language """ if languages: language = languages[0] if language in locales_map: return FakeTranslations(locales_map[language]) return gettext.NullTranslations() return _translation class TestModuleI18nService(ModuleStoreTestCase): """ Test ModuleI18nService """ def setUp(self): """ Setting up tests """ super(TestModuleI18nService, self).setUp() self.test_language = 'dummy language' self.request = mock.Mock() self.course = CourseFactory.create() self.field_data = mock.Mock() self.descriptor = ItemFactory(category="pure", parent=self.course) self.runtime = _preview_module_system( self.request, self.descriptor, self.field_data, ) self.addCleanup(translation.deactivate) def get_module_i18n_service(self, descriptor): """ return the module i18n service. """ i18n_service = self.runtime.service(descriptor, 'i18n') self.assertIsNotNone(i18n_service) self.assertIsInstance(i18n_service, ModuleI18nService) return i18n_service def test_django_service_translation_works(self): """ Test django translation service works fine. """ class wrap_ugettext_with_xyz(object): # pylint: disable=invalid-name """ A context manager function that just adds 'XYZ ' to the front of all strings of the module ugettext function. """ def __init__(self, module): self.module = module self.old_ugettext = module.ugettext def __enter__(self): def new_ugettext(*args, **kwargs): """ custom function """ output = self.old_ugettext(*args, **kwargs) return "XYZ " + output self.module.ugettext = new_ugettext def __exit__(self, _type, _value, _traceback): self.module.ugettext = self.old_ugettext i18n_service = self.get_module_i18n_service(self.descriptor) # Activate french, so that if the fr files haven't been loaded, they will be loaded now. with translation.override("fr"): french_translation = translation.trans_real._active.value # pylint: disable=protected-access # wrap the ugettext functions so that 'XYZ ' will prefix each translation with wrap_ugettext_with_xyz(french_translation): self.assertEqual(i18n_service.ugettext(self.test_language), 'XYZ dummy language') # Check that the old ugettext has been put back into place self.assertEqual(i18n_service.ugettext(self.test_language), 'dummy language') @mock.patch('django.utils.translation.ugettext', mock.Mock(return_value='XYZ-TEST-LANGUAGE')) def test_django_translator_in_use_with_empty_block(self): """ Test: Django default translator should in use if we have an empty block """ i18n_service = ModuleI18nService(None) self.assertEqual(i18n_service.ugettext(self.test_language), 'XYZ-TEST-LANGUAGE') @mock.patch('django.utils.translation.ugettext', mock.Mock(return_value='XYZ-TEST-LANGUAGE')) def test_message_catalog_translations(self): """ Test: Message catalog from FakeTranslation should return required translations. """ _translator = FakeTranslations.translator( { 'es': {'Hello': 'es-hello-world'}, 'fr': {'Hello': 'fr-hello-world'}, }, ) localedir = '/translations' translation.activate("es") with mock.patch('gettext.translation', return_value=_translator(domain='text', localedir=localedir, languages=[get_language()])): i18n_service = self.get_module_i18n_service(self.descriptor) self.assertEqual(i18n_service.ugettext('Hello'), 'es-hello-world') translation.activate("ar") with mock.patch('gettext.translation', return_value=_translator(domain='text', localedir=localedir, languages=[get_language()])): i18n_service = self.get_module_i18n_service(self.descriptor) self.assertEqual(i18n_service.ugettext('Hello'), 'Hello') self.assertNotEqual(i18n_service.ugettext('Hello'), 'fr-hello-world') self.assertNotEqual(i18n_service.ugettext('Hello'), 'es-hello-world') translation.activate("fr") with mock.patch('gettext.translation', return_value=_translator(domain='text', localedir=localedir, languages=[get_language()])): i18n_service = self.get_module_i18n_service(self.descriptor) self.assertEqual(i18n_service.ugettext('Hello'), 'fr-hello-world') def test_i18n_service_callable(self): """ Test: i18n service should be callable in studio. """ self.assertTrue(callable(self.runtime._services.get('i18n'))) # pylint: disable=protected-access class InternationalizationTest(ModuleStoreTestCase): """ Tests to validate Internationalization. """ CREATE_USER = False def setUp(self): """ These tests need a user in the DB so that the django Test Client can log them in. They inherit from the ModuleStoreTestCase class so that the mongodb collection will be cleared out before each test case execution and deleted afterwards. """ super(InternationalizationTest, self).setUp() self.uname = 'testuser' self.email = 'test+courses@edx.org' self.password = 'foo' # Create the use so we can log them in. self.user = User.objects.create_user(self.uname, self.email, self.password) # Note that we do not actually need to do anything # for registration if we directly mark them active. self.user.is_active = True # Staff has access to view all courses self.user.is_staff = True self.user.save() self.course_data = { 'org': 'MITx', 'number': '999', 'display_name': 'Robot Super Course', } def test_course_plain_english(self): """Test viewing the index page with no courses""" self.client = AjaxEnabledTestClient() self.client.login(username=self.uname, password=self.password) resp = self.client.get_html('/home/') self.assertContains(resp, '<h1 class="page-header">Studio Home</h1>', status_code=200, html=True) def test_course_explicit_english(self): """Test viewing the index page with no courses""" self.client = AjaxEnabledTestClient() self.client.login(username=self.uname, password=self.password) resp = self.client.get_html( '/home/', {}, HTTP_ACCEPT_LANGUAGE='en', ) self.assertContains(resp, '<h1 class="page-header">Studio Home</h1>', status_code=200, html=True) # **** # NOTE: # **** # # This test will break when we replace this fake 'test' language # with actual Esperanto. This test will need to be updated with # actual Esperanto at that time. # Test temporarily disable since it depends on creation of dummy strings @skip def test_course_with_accents(self): """Test viewing the index page with no courses""" self.client = AjaxEnabledTestClient() self.client.login(username=self.uname, password=self.password) resp = self.client.get_html( '/home/', {}, HTTP_ACCEPT_LANGUAGE='eo' ) TEST_STRING = ( u'<h1 class="title-1">' u'My \xc7\xf6\xfcrs\xe9s L#' u'</h1>' ) self.assertContains(resp, TEST_STRING, status_code=200, html=True)