Unverified Commit 436576de by Brian Beggs Committed by GitHub

Merge pull request #48 from open-craft/MCKIN-7023-template-i18n

[MCKIN-7023] Allows translated strings in XBlock Django templates
parents c868900f ba54084a
...@@ -35,7 +35,7 @@ def package_data(pkg, root_list): ...@@ -35,7 +35,7 @@ def package_data(pkg, root_list):
setup( setup(
name='xblock-utils', name='xblock-utils',
version='1.0.5', version='1.1.0',
description='Various utilities for XBlocks', description='Various utilities for XBlocks',
packages=[ packages=[
'xblockutils', 'xblockutils',
...@@ -43,5 +43,5 @@ setup( ...@@ -43,5 +43,5 @@ setup(
install_requires=[ install_requires=[
'XBlock', 'XBlock',
], ],
package_data=package_data("xblockutils", ["public", "templates"]), package_data=package_data("xblockutils", ["public", "templates", "templatetags"]),
) )
{% load i18n %}
{% trans "Translate 1" %}
{% trans "Translate 2" as var %}
{{ var }}
{% blocktrans %}
Multi-line translation
with variable: {{name}}
{% endblocktrans %}
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2016-03-30 16:54+0500\n"
"PO-Revision-Date: 2016-03-30 16:54+0500\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: eo\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: data/trans_django_template.txt
msgid "Translate 1"
msgstr "tRaNsLaTe !"
#: data/trans_django_template.txt
msgid "Translate 2"
msgstr ""
#: data/trans_django_template.txt
msgid ""
"\n"
"Multi-line translation"
"\n"
"with variable: %(name)s"
"\n"
msgstr "\nmUlTi_LiNe TrAnSlAtIoN: %(name)s\n"
...@@ -19,8 +19,11 @@ ...@@ -19,8 +19,11 @@
# #
import unittest import unittest
import gettext
from mock import patch, DEFAULT from mock import patch, DEFAULT
from pkg_resources import resource_filename
from django.utils.translation import get_language, to_locale
from xblockutils.resources import ResourceLoader from xblockutils.resources import ResourceLoader
...@@ -66,6 +69,27 @@ Although it is simple, it can also contain non-ASCII characters: ...@@ -66,6 +69,27 @@ Although it is simple, it can also contain non-ASCII characters:
Thé Fütüré øf Ønlïné Édüçätïøn Ⱡσяєм ι# Før änýøné, änýwhéré, änýtïmé Ⱡσяєм # Thé Fütüré øf Ønlïné Édüçätïøn Ⱡσяєм ι# Før änýøné, änýwhéré, änýtïmé Ⱡσяєм #
""" """
expected_not_translated_template = u"""\
Translate 1
Translate 2
Multi-line translation
with variable: This is a fine name
"""
expected_translated_template = u"""\
tRaNsLaTe !
Translate 2
mUlTi_LiNe TrAnSlAtIoN: This is a fine name
"""
example_id = "example-unique-id" example_id = "example-unique-id"
expected_filled_js_template = u"""\ expected_filled_js_template = u"""\
...@@ -99,6 +123,25 @@ expected_scenarios_with_identifiers = [ ...@@ -99,6 +123,25 @@ expected_scenarios_with_identifiers = [
expected_scenarios = [(t, c) for (i, t, c) in expected_scenarios_with_identifiers] expected_scenarios = [(t, c) for (i, t, c) in expected_scenarios_with_identifiers]
class MockI18nService(object):
"""
I18n service used for testing translations.
"""
def __init__(self):
locale_dir = 'data/translations'
locale_path = resource_filename(__name__, locale_dir)
domain = 'text'
self.mock_translator = gettext.translation(
domain,
locale_path,
['eo'],
)
def __getattr__(self, name):
return getattr(self.mock_translator, name)
class TestResourceLoader(unittest.TestCase): class TestResourceLoader(unittest.TestCase):
def test_load_unicode(self): def test_load_unicode(self):
s = ResourceLoader(__name__).load_unicode("data/simple_django_template.txt") s = ResourceLoader(__name__).load_unicode("data/simple_django_template.txt")
...@@ -113,6 +156,17 @@ class TestResourceLoader(unittest.TestCase): ...@@ -113,6 +156,17 @@ class TestResourceLoader(unittest.TestCase):
s = loader.render_django_template("data/simple_django_template.txt", example_context) s = loader.render_django_template("data/simple_django_template.txt", example_context)
self.assertEquals(s, expected_filled_template) self.assertEquals(s, expected_filled_template)
def test_render_django_template_translated(self):
loader = ResourceLoader(__name__)
s = loader.render_django_template("data/trans_django_template.txt",
context=example_context,
i18n_service=MockI18nService())
self.assertEquals(s, expected_translated_template)
# Test that the language changes were reverted
s = loader.render_django_template("data/trans_django_template.txt", example_context)
self.assertEquals(s, expected_not_translated_template)
def test_render_mako_template(self): def test_render_mako_template(self):
loader = ResourceLoader(__name__) loader = ResourceLoader(__name__)
s = loader.render_mako_template("data/simple_mako_template.txt", example_context) s = loader.render_mako_template("data/simple_mako_template.txt", example_context)
......
...@@ -8,7 +8,7 @@ envlist = py{27}-django{18,111} ...@@ -8,7 +8,7 @@ envlist = py{27}-django{18,111}
deps = deps =
-rrequirements.txt -rrequirements.txt
-rtest_requirements.txt -rtest_requirements.txt
-egit+https://github.com/edx/xblock-sdk.git#egg=xblock-sdk -egit+https://github.com/edx/xblock-sdk.git@48cb0d5066a1f5ab8eecba41bb3d63c78c985a81#egg=xblock-sdk==0.1.5
commands = commands =
pip install -r {envdir}/src/xblock-sdk/requirements/test.txt pip install -r {envdir}/src/xblock-sdk/requirements/test.txt
pip install -r {envdir}/src/xblock-sdk/requirements/base.txt pip install -r {envdir}/src/xblock-sdk/requirements/base.txt
...@@ -31,5 +31,5 @@ deps = ...@@ -31,5 +31,5 @@ deps =
commands = commands =
{[base]commands} {[base]commands}
pip install Django>=1.11,<2.0 pip install Django>=1.11,<2.0
pep8 xblockutils --max-line-length=120 pycodestyle xblockutils --max-line-length=120
pylint xblockutils pylint xblockutils
...@@ -28,7 +28,8 @@ import warnings ...@@ -28,7 +28,8 @@ import warnings
import pkg_resources import pkg_resources
from django.template import Context, Template import django
from django.template import Context, Template, Engine, base as TemplateBase
from mako.template import Template as MakoTemplate from mako.template import Template as MakoTemplate
from mako.lookup import TemplateLookup as MakoTemplateLookup from mako.lookup import TemplateLookup as MakoTemplateLookup
...@@ -46,14 +47,38 @@ class ResourceLoader(object): ...@@ -46,14 +47,38 @@ class ResourceLoader(object):
resource_content = pkg_resources.resource_string(self.module_name, resource_path) resource_content = pkg_resources.resource_string(self.module_name, resource_path)
return unicode(resource_content, 'utf-8') return unicode(resource_content, 'utf-8')
def render_django_template(self, template_path, context=None): def render_django_template(self, template_path, context=None, i18n_service=None):
""" """
Evaluate a django template by resource path, applying the provided context Evaluate a django template by resource path, applying the provided context.
""" """
context = context or {} context = context or {}
context['_i18n_service'] = i18n_service
libraries = {
'i18n': 'xblockutils.templatetags.i18n',
}
# For django 1.8, we have to load the libraries manually, and restore them once the template is rendered.
_libraries = None
if django.VERSION[0] == 1 and django.VERSION[1] == 8:
_libraries = TemplateBase.libraries.copy()
for library_name in libraries:
library = TemplateBase.import_library(libraries[library_name])
if library:
TemplateBase.libraries[library_name] = library
engine = Engine()
else:
# Django>1.8 Engine can load the extra templatetag libraries itself
engine = Engine(libraries=libraries)
template_str = self.load_unicode(template_path) template_str = self.load_unicode(template_path)
template = Template(template_str) template = Template(template_str, engine=engine)
return template.render(Context(context)) rendered = template.render(Context(context))
# Restore the original TemplateBase.libraries
if _libraries is not None:
TemplateBase.libraries = _libraries
return rendered
def render_mako_template(self, template_path, context=None): def render_mako_template(self, template_path, context=None):
""" """
......
"""
Template tags for handling i18n translations for xblocks
Based on: https://github.com/eduNEXT/django-xblock-i18n
"""
from contextlib import contextmanager
from django.template import Library, Node
from django.templatetags import i18n
from django.utils.translation import trans_real, get_language
register = Library()
class ProxyTransNode(Node):
"""
This node is a proxy of a django TranslateNode.
"""
def __init__(self, do_translate_node):
"""
Initialize the ProxyTransNode
"""
self.do_translate = do_translate_node
self._translations = {}
@contextmanager
def merge_translation(self, context):
"""
Context wrapper which modifies the given language's translation catalog using the i18n service, if found.
"""
language = get_language()
i18n_service = context.get('_i18n_service', None)
if i18n_service:
# Cache the original translation object to reduce overhead
if language not in self._translations:
self._translations[language] = trans_real.DjangoTranslation(language)
translation = trans_real.translation(language)
translation.merge(i18n_service)
yield
# Revert to original translation object
if language in self._translations:
trans_real._translations[language] = self._translations[language]
# Re-activate the current language to reset translation caches
trans_real.activate(language)
def render(self, context):
"""
Renders the translated text using the XBlock i18n service, if available.
"""
with self.merge_translation(context):
django_translated = self.do_translate.render(context)
return django_translated
@register.tag('trans')
def xblock_translate(parser, token):
"""
Proxy implementation of the i18n `trans` tag.
"""
return ProxyTransNode(i18n.do_translate(parser, token))
@register.tag('blocktrans')
def xblock_translate_block(parser, token):
"""
Proxy implementation of the i18n `blocktrans` tag.
"""
return ProxyTransNode(i18n.do_block_translate(parser, token))
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment