Commit ba54084a by Jillian Vogel

Adds templatetags.i18n, loaded when rendering django template

Supports `trans` and `blocktrans` tags by using the _i18n_service added to
XBlock context (if found) to provide the current language's translations
during template processing.

Bumps version to 1.1.0
parent c868900f
......@@ -35,7 +35,7 @@ def package_data(pkg, root_list):
setup(
name='xblock-utils',
version='1.0.5',
version='1.1.0',
description='Various utilities for XBlocks',
packages=[
'xblockutils',
......@@ -43,5 +43,5 @@ setup(
install_requires=[
'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 @@
#
import unittest
import gettext
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
......@@ -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é Ⱡσяєм #
"""
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"
expected_filled_js_template = u"""\
......@@ -99,6 +123,25 @@ 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):
def test_load_unicode(self):
s = ResourceLoader(__name__).load_unicode("data/simple_django_template.txt")
......@@ -113,6 +156,17 @@ class TestResourceLoader(unittest.TestCase):
s = loader.render_django_template("data/simple_django_template.txt", example_context)
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):
loader = ResourceLoader(__name__)
s = loader.render_mako_template("data/simple_mako_template.txt", example_context)
......
......@@ -8,7 +8,7 @@ envlist = py{27}-django{18,111}
deps =
-rrequirements.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 =
pip install -r {envdir}/src/xblock-sdk/requirements/test.txt
pip install -r {envdir}/src/xblock-sdk/requirements/base.txt
......@@ -31,5 +31,5 @@ deps =
commands =
{[base]commands}
pip install Django>=1.11,<2.0
pep8 xblockutils --max-line-length=120
pycodestyle xblockutils --max-line-length=120
pylint xblockutils
......@@ -28,7 +28,8 @@ import warnings
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.lookup import TemplateLookup as MakoTemplateLookup
......@@ -46,14 +47,38 @@ class ResourceLoader(object):
resource_content = pkg_resources.resource_string(self.module_name, resource_path)
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['_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 = Template(template_str)
return template.render(Context(context))
template = Template(template_str, engine=engine)
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):
"""
......
"""
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