Skip to content

Commit

Permalink
Provide translation domain, context and target language when doing im…
Browse files Browse the repository at this point in the history
…plicit conversion

Note that 'target_language' is no longer defined as a variable,
but as internal state (and disallowed as a variable name).
  • Loading branch information
malthe committed Nov 15, 2023
1 parent a2ba61e commit 8a555a2
Show file tree
Hide file tree
Showing 13 changed files with 95 additions and 49 deletions.
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ Changes

In next release ...

- Implicit translation now provides the translation context, domain,
and target language to the translation function (if applicable). Previously,
the target language was provided, but this did not respect a change via
`i18n:target`.
(`#369 <https://github.com/malthe/chameleon/issues/369>`_)

- Replace ``pkg_resources`` with newer and faster ``importlib.resources`` and
``importlib.metadata``. Just importing ``pkg_resources`` becomes slower and
Expand Down
73 changes: 49 additions & 24 deletions src/chameleon/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
COMPILER_INTERNALS_OR_DISALLOWED = {
"econtext",
"rcontext",
"target_language",
"str",
"int",
"float",
Expand Down Expand Up @@ -173,7 +174,12 @@ def indent(s):
try:
target = target.__html__
except AttributeError:
__converted = convert(target)
__converted = translate(
target,
domain=__i18n_domain,
context=__i18n_context,
target_language=target_language
)
target = str(target) if target is __converted else __converted
else:
target = target()""")
Expand All @@ -199,22 +205,32 @@ def func(target):
try:
target = target.__html__
except AttributeError:
__converted = convert(target)
__converted = translate(
target,
domain=__i18n_domain,
context=__i18n_context,
target_language=target_language
)
target = str(target) if target is __converted else __converted
else:
target = target()
return target""")
return target"""
)


emit_translate = template(is_func=True,
func_args=('target', 'msgid', 'target_language',
func_args=('target', 'msgid',
'default'),
func_defaults=(None,),
source=r"""
target = translate(msgid, default=default, domain=__i18n_domain,
context=__i18n_context,
target_language=target_language)""")
target = translate(
msgid,
default=default,
domain=__i18n_domain,
context=__i18n_context,
target_language=target_language
)""")


emit_func_convert_and_escape = template(
Expand All @@ -240,7 +256,12 @@ def func(target, quote, quote_entity, default, default_marker):
try:
target = target.__html__
except:
__converted = convert(target)
__converted = translate(
target,
domain=__i18n_domain,
context=__i18n_context,
target_language=target_language
)
target = str(target) if target is __converted \
else __converted
else:
Expand Down Expand Up @@ -408,7 +429,6 @@ def __call__(self, name, engine):
"translate(msgid, domain=__i18n_domain, context=__i18n_context, target_language=target_language)", # noqa: E501 line too long
msgid=target,
mode="eval",
target_language=load("target_language"),
)
else:
if translate:
Expand All @@ -429,7 +449,6 @@ def __call__(self, name, engine):
"translate(msgid, mapping=mapping, domain=__i18n_domain, context=__i18n_context, target_language=target_language)", # noqa: E501 line too long
msgid=ast.Str(
s=formatting_string),
target_language=load("target_language"),
mapping=ast.Dict(
keys=keys,
values=values),
Expand Down Expand Up @@ -926,10 +945,7 @@ def visit_Translate(self, node, target):
else:
msgid = target
return self.translate(node.node, target) + \
emit_translate(
target, msgid, "target_language",
default=target
)
emit_translate(target, msgid, default=target)

def visit_Static(self, node, target):
value = annotated(node)
Expand All @@ -954,7 +970,6 @@ class Compiler:
defaults = {
'translate': Symbol(simple_translate),
'decode': Builtin("str"),
'convert': Builtin("str"),
'on_error_handler': Builtin("str")
}

Expand Down Expand Up @@ -1191,8 +1206,9 @@ def visit_Macro(self, node):
param("rcontext"),
param("__i18n_domain"),
param("__i18n_context"),
param("target_language"),
],
defaults=[load("None"), load("None")],
defaults=[load("None"), load("None"), load("None")],
),
body=body
)
Expand All @@ -1202,13 +1218,22 @@ def visit_Macro(self, node):
def visit_Text(self, node):
yield EmitText(node.value)

# TODO Refactor!

def visit_Domain(self, node):
backup = "__previous_i18n_domain_%s" % mangle(id(node))
return template("BACKUP = __i18n_domain", BACKUP=backup) + \
template("__i18n_domain = NAME", NAME=ast.Str(s=node.name)) + \
self.visit(node.node) + \
template("__i18n_domain = BACKUP", BACKUP=backup)

def visit_Target(self, node):
backup = "__previous_i18n_target_%s" % mangle(id(node))
return template("BACKUP = target_language", BACKUP=backup) + \
self._engine(node.expression, store("target_language")) + \
self.visit(node.node) + \
template("target_language = BACKUP", BACKUP=backup)

def visit_TxContext(self, node):
backup = "__previous_i18n_context_%s" % mangle(id(node))
return template("BACKUP = __i18n_context", BACKUP=backup) + \
Expand Down Expand Up @@ -1253,9 +1278,7 @@ def visit_Content(self, node):
body = self._engine(node.expression, store(name))

if node.translate:
body += emit_translate(
name, name, load_econtext("target_language")
)
body += emit_translate(name, name)

if node.char_escape:
body += template(
Expand Down Expand Up @@ -1441,8 +1464,7 @@ def visit_Translate(self, node):
"msgid, mapping=mapping, default=default, domain=__i18n_domain, context=__i18n_context, target_language=target_language))", # noqa: E501 line too long
msgid=msgid,
default=default,
mapping=mapping,
target_language=load_econtext("target_language"))
mapping=mapping)

# pop away translation block reference
self._translations.pop()
Expand Down Expand Up @@ -1603,7 +1625,8 @@ def visit_UseInternalMacro(self, node):
render = "render_%s" % mangle(node.name)
token_reset = template("__token = None")
return token_reset + template(
"f(__stream, econtext.copy(), rcontext, __i18n_domain)",
"f(__stream, econtext.copy(), rcontext, "
"__i18n_domain, __i18n_context, target_language)",
f=render) + \
template("econtext.update(rcontext)")

Expand Down Expand Up @@ -1690,10 +1713,12 @@ def visit_UseExternalMacro(self, node):
param("rcontext"),
param("__i18n_domain"),
param("__i18n_context"),
param("target_language"),
],
defaults=[
load("__i18n_domain"),
load("__i18n_context")],
load("__i18n_context"),
load("target_language"),],
),
body=body or [
ast.Pass()],
Expand Down Expand Up @@ -1730,7 +1755,7 @@ def visit_UseExternalMacro(self, node):
) +
template(
"__m(__stream, econtext.copy(), "
"rcontext, __i18n_domain)"
"rcontext, __i18n_domain, __i18n_context, target_language)"
) +
template("econtext.update(rcontext)")
)
Expand Down
6 changes: 6 additions & 0 deletions src/chameleon/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,12 @@ class Domain(Node):
_fields = "name", "node"


class Target(Node):
"""Update translation target."""

_fields = "expression", "node"


class TxContext(Node):
"""Update translation context."""

Expand Down
8 changes: 7 additions & 1 deletion src/chameleon/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,14 @@ def render(self, **__kw):
rcontext = {}
self.cook_check()
stream = self.output_stream_factory()
target_language = __kw.get("target_language")
try:
self._render(stream, econtext, rcontext)
self._render(
stream,
econtext,
rcontext,
target_language=target_language
)
except RecursionError:
raise
except BaseException:
Expand Down
3 changes: 3 additions & 0 deletions src/chameleon/tests/inputs/079-implicit-i18n.pt
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@
<br />
bar.
</div>
<div tal:define="Message import:chameleon.tests.test_templates.Message" i18n:target="string:fr">
${Message()}
</div>
</body>
</html>
1 change: 1 addition & 0 deletions src/chameleon/tests/inputs/124-translation-target.pt
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<body>
<h1 i18n:translate="" i18n:target="string:fr">Hello world!</h1>
<p i18n:translate="" i18n:target="default">It's a big world.</p>
<p>${target_language}</p>
</body>
</html>
3 changes: 3 additions & 0 deletions src/chameleon/tests/outputs/079-en.pt
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@
<br />
bar. ('bar.' translation into 'en')
</div>
<div>
Message ('message' translation into 'fr')
</div>
</body>
</html>
3 changes: 3 additions & 0 deletions src/chameleon/tests/outputs/079.pt
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@
<br />
bar.
</div>
<div>
Message ('message' translation into 'fr')
</div>
</body>
</html>
3 changes: 2 additions & 1 deletion src/chameleon/tests/outputs/124-en.pt
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<body>
<h1>Hello world! ('Hello world!' translation into 'fr')</h1>
<p>It's a big world. ('It's a big world.' translation into 'en')</p>
<p>en</p>
</body>
</html>
</html>
3 changes: 2 additions & 1 deletion src/chameleon/tests/outputs/124.pt
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<body>
<h1>Hello world! ('Hello world!' translation into 'fr')</h1>
<p>It's a big world.</p>
<p></p>
</body>
</html>
</html>
2 changes: 1 addition & 1 deletion src/chameleon/tests/test_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ class TestException(Exception):
def __init__(self, *args, **kwargs):
calls.append((args, kwargs))

def _render(stream, econtext, rcontext):
def _render(stream, econtext, rcontext, **kw):
exc = TestException('foo', bar='baz')
rcontext['__error__'] = ('expression', 1, 42, 'test.pt', exc),
raise exc
Expand Down
28 changes: 12 additions & 16 deletions src/chameleon/zpt/program.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,22 +395,6 @@ def visit_element(self, start, end, children):
if defines is None:
raise ParseError("Invalid define syntax.", clause)

# i18n:target
try:
target_language = ns[I18N, 'target']
except KeyError:
pass
else:
# The name "default" is an alias for the target language
# variable. We simply replace it.
target_language = target_language.replace(
'default', 'target_language'
)

defines.append(
('local', ("target_language", ), target_language)
)

assignments = [
nodes.Assignment(
names, nodes.Value(expr), context == "local")
Expand Down Expand Up @@ -509,6 +493,17 @@ def CASE(node):
else:
CONTEXT = partial(nodes.TxContext, clause)

# i18n:target
try:
clause = ns[I18N, 'target']
except KeyError:
TARGET = skip
else:
TARGET = lambda node: nodes.Define( # noqa: E731 do not assign a lambda expression, use a def
[nodes.Alias(["default"], "target_language")],
nodes.Target(clause, node)
)

# i18n:name
try:
clause = ns[I18N, 'name']
Expand All @@ -532,6 +527,7 @@ def CASE(node):
SWITCH,
DOMAIN,
CONTEXT,
TARGET,
)

# metal:fill-slot
Expand Down
6 changes: 1 addition & 5 deletions src/chameleon/zpt/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,15 +314,11 @@ def decode(inst, encoding=encoding):
else:
decode = bytes.decode

target_language = _kw.get('target_language')

setdefault = _kw.setdefault
setdefault("__translate", translate)
setdefault("__convert",
partial(translate, target_language=target_language))
setdefault("__decode", decode)
setdefault("target_language", None)
setdefault("__on_error_handler", self.on_error_handler)
setdefault("target_language", None)

# Make sure we have a repeat dictionary
if 'repeat' not in _kw:
Expand Down

0 comments on commit 8a555a2

Please sign in to comment.