Commit d0311171 authored by Florent Guillaume's avatar Florent Guillaume

I18n interpolation now tries to deal with the case where there is a mix

of Unicode and non-ascii string that are incompatible (because the
encoding of the latter is unknown) by substituting a representation of
the non-ascii string.

I18n interpolation doesn't fail anymore if a i18n:name is not provided,
the ${string} in the translation is just left as is.

Collector #696: tal:replace of a non-string (a number for examlpe)
associated with a i18n:name failed to be interpolated properly.

Improved tests for i18n and added tests for interpolate().
parent 02338f11
...@@ -35,6 +35,18 @@ Zope Changes ...@@ -35,6 +35,18 @@ Zope Changes
Bugs Fixed Bugs Fixed
- I18n interpolation now tries to deal with the case where there is
a mix of Unicode and non-ascii string that are incompatible
(because the encoding of the latter is unknown) by substituting a
representation of the non-ascii string.
- I18n interpolation doesn't fail anymore if a i18n:name is not
provided, the ${string} in the translation is just left as is.
- Collector #696: tal:replace of a non-string (a number for
examlpe) associated with a i18n:name failed to be interpolated
properly.
- Collector #771: ZCatalog failed to index DTML Document if the name - Collector #771: ZCatalog failed to index DTML Document if the name
of a catalog metadata was identical with the name of an acquired of a catalog metadata was identical with the name of an acquired
object. object.
......
...@@ -7,6 +7,9 @@ ...@@ -7,6 +7,9 @@
<span tal:replace="string:Lomax" i18n:name="name" /> was born in <span tal:replace="string:Lomax" i18n:name="name" /> was born in
<span tal:replace="string:Antarctica" i18n:name="country" />. <span tal:replace="string:Antarctica" i18n:name="country" />.
</p> </p>
<p i18n:translate="hmm">
I'm <span tal:replace="python:25" i18n:name="age">Age</span>
</p>
</head> </head>
</body> </body>
</html> </html>
<html> <html>
<body> <body>
<head> <head>
<p>[foo](bar)</p> <p>[foo](bar/{})</p>
<a href="foo" alt="[default](alttext)">link</a> <a href="foo" alt="[default](alttext/{})">link</a>
<p>[dom](${name} was born in ${country}.)</p> <p>[dom](${name} was born in ${country}./{'country':'Antarctica','name':'Lomax'})</p>
<p>[default](hmm/{'age':25})</p>
</head> </head>
</body> </body>
</html> </html>
...@@ -28,8 +28,14 @@ class Folder(util.Base): ...@@ -28,8 +28,14 @@ class Folder(util.Base):
pass pass
class TestTranslationService: class TestTranslationService:
def translate(self, domain, msgid, *args, **kw): def translate(self, domain, msgid, mapping=None, *args, **kw):
return "[%s](%s)" % (domain, msgid) maps = []
if mapping is not None:
# Get a deterministic, sorted representation of dicts.
for k, v in mapping.items():
maps.append('%s:%s' % (`k`, `v`))
maps.sort()
return "[%s](%s/{%s})" % (domain, msgid, ','.join(maps))
class UnitTestSecurityPolicy: class UnitTestSecurityPolicy:
......
...@@ -240,7 +240,7 @@ class DummyDomain: ...@@ -240,7 +240,7 @@ class DummyDomain:
# substrings. Then upcase everything but the placeholders, then glue # substrings. Then upcase everything but the placeholders, then glue
# things back together. # things back together.
def repl(m, mapping=mapping): def repl(m, mapping=mapping):
return mapping[m.group(m.lastindex).lower()] return ustr(mapping[m.group(m.lastindex).lower()])
cre = re.compile(r'\$(?:([_A-Z]\w*)|\{([_A-Z]\w*)\})') cre = re.compile(r'\$(?:([_A-Z]\w*)|\{([_A-Z]\w*)\})')
return cre.sub(repl, msgid.upper()) return cre.sub(repl, msgid.upper())
......
...@@ -52,7 +52,11 @@ _interp_regex = re.compile(r'(?<!\$)(\$(?:%(n)s|{%(n)s}))' %({'n': NAME_RE})) ...@@ -52,7 +52,11 @@ _interp_regex = re.compile(r'(?<!\$)(\$(?:%(n)s|{%(n)s}))' %({'n': NAME_RE}))
_get_var_regex = re.compile(r'%(n)s' %({'n': NAME_RE})) _get_var_regex = re.compile(r'%(n)s' %({'n': NAME_RE}))
def interpolate(text, mapping): def interpolate(text, mapping):
"""Interpolate ${keyword} substitutions.""" """Interpolate ${keyword} substitutions.
This is called when no translation is provided by the translation
service.
"""
if not mapping: if not mapping:
return text return text
# Find all the spots we want to substitute. # Find all the spots we want to substitute.
...@@ -60,7 +64,17 @@ def interpolate(text, mapping): ...@@ -60,7 +64,17 @@ def interpolate(text, mapping):
# Now substitute with the variables in mapping. # Now substitute with the variables in mapping.
for string in to_replace: for string in to_replace:
var = _get_var_regex.findall(string)[0] var = _get_var_regex.findall(string)[0]
text = text.replace(string, mapping.get(var)) if mapping.has_key(var):
# Call ustr because we may have an integer for instance.
subst = ustr(mapping[var])
try:
text = text.replace(string, subst)
except UnicodeError:
# subst contains high-bit chars...
# As we have no way of knowing the correct encoding,
# substitue something instead of raising an exception.
subst = `subst`[1:-1]
text = text.replace(string, subst)
return text return text
......
...@@ -11,6 +11,7 @@ from StringIO import StringIO ...@@ -11,6 +11,7 @@ from StringIO import StringIO
from TAL.TALDefs import METALError from TAL.TALDefs import METALError
from TAL.HTMLTALParser import HTMLTALParser from TAL.HTMLTALParser import HTMLTALParser
from TAL.TALInterpreter import TALInterpreter from TAL.TALInterpreter import TALInterpreter
from TAL.TALInterpreter import interpolate
from TAL.DummyEngine import DummyEngine from TAL.DummyEngine import DummyEngine
...@@ -74,6 +75,15 @@ class OutputPresentationTestCase(TestCaseBase): ...@@ -74,6 +75,15 @@ class OutputPresentationTestCase(TestCaseBase):
EXPECTED = u"""dj-vu""" "\n" EXPECTED = u"""dj-vu""" "\n"
self.compare(INPUT, EXPECTED) self.compare(INPUT, EXPECTED)
def check_i18n_replace_number(self):
INPUT = """
<p i18n:translate="foo ${bar}">
<span tal:replace="python:123" i18n:name="bar">para</span>
</p>"""
EXPECTED = u"""
<p>FOO 123</p>""" "\n"
self.compare(INPUT, EXPECTED)
def check_entities(self): def check_entities(self):
INPUT = ('<img tal:define="foo nothing" ' INPUT = ('<img tal:define="foo nothing" '
'alt="&a; &#1; &#x0a; &a &#45 &; &#0a; <>" />') 'alt="&a; &#1; &#x0a; &a &#45 &; &#0a; <>" />')
...@@ -88,10 +98,55 @@ class OutputPresentationTestCase(TestCaseBase): ...@@ -88,10 +98,55 @@ class OutputPresentationTestCase(TestCaseBase):
interp() interp()
self.assertEqual(sio.getvalue(), EXPECTED) self.assertEqual(sio.getvalue(), EXPECTED)
class InterpolateTestCase(TestCaseBase):
def check_syntax_ok(self):
text = "foo ${bar_0MAN} $baz_zz bee"
mapping = {'bar_0MAN': 'fish', 'baz_zz': 'moo'}
expected = "foo fish moo bee"
self.assertEqual(interpolate(text, mapping), expected)
def check_syntax_bad(self):
text = "foo $_bar_man} $ ${baz bee"
mapping = {'_bar_man': 'fish', 'baz': 'moo'}
expected = text
self.assertEqual(interpolate(text, mapping), expected)
def check_missing(self):
text = "foo ${bar} ${baz}"
mapping = {'bar': 'fish'}
expected = "foo fish ${baz}"
self.assertEqual(interpolate(text, mapping), expected)
def check_redundant(self):
text = "foo ${bar}"
mapping = {'bar': 'fish', 'baz': 'moo'}
expected = "foo fish"
self.assertEqual(interpolate(text, mapping), expected)
def check_numeric(self):
text = "foo ${bar}"
mapping = {'bar': 123}
expected = "foo 123"
self.assertEqual(interpolate(text, mapping), expected)
def check_unicode(self):
text = u"foo ${bar}"
mapping = {u'bar': u'baz'}
expected = u"foo baz"
self.assertEqual(interpolate(text, mapping), expected)
def check_unicode_mixed_unknown_encoding(self):
# This test assumes that sys.getdefaultencoding is ascii...
text = u"foo ${bar}"
mapping = {u'bar': 'd\xe9j\xe0'}
expected = u"foo d\\xe9j\\xe0"
self.assertEqual(interpolate(text, mapping), expected)
def test_suite(): def test_suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(MacroErrorsTestCase, "check_")) suite.addTest(unittest.makeSuite(MacroErrorsTestCase, "check_"))
suite.addTest(unittest.makeSuite(OutputPresentationTestCase, "check_")) suite.addTest(unittest.makeSuite(OutputPresentationTestCase, "check_"))
suite.addTest(unittest.makeSuite(InterpolateTestCase, "check_"))
return suite return suite
......
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