jumpserver/jumpserver · error · ValueError
invalid i18n key
Error message
invalid i18n key
What it means
In apps/common/utils/yml.py, a Jinja2 environment registers a 'trans' filter whose safe_trans wrapper only accepts string keys; passing a non-str (int, dict, list, None) raises ValueError('invalid i18n key') before translate() is called. StrictUndefined is also set on the env, so template data must be complete.
Source
Thrown at apps/common/utils/yml.py:42
lang = settings.LANGUAGE_CODE if lang is None else lang
lang = lang.lower().replace('_', '-')
lang_data = i18n.get(key, {})
return lang_data.get(lang, lang_data.get(lang.split('-')[0], key))
def yaml_load_with_i18n(stream, lang=None):
ori_text = stream.read()
data = yaml.safe_load(ori_text)
i18n = data.get("i18n", {})
env = SandboxedEnvironment(
undefined=StrictUndefined,
autoescape=False,
)
def safe_trans(key):
if not isinstance(key, str):
raise ValueError("invalid i18n key")
return translate(key, i18n, lang)
env.filters.clear()
env.globals.clear()
env.filters["trans"] = safe_trans
template = env.from_string(ori_text)
try:
rendered = template.render()
except Exception as e:
rendered = ori_text
result = yaml.safe_load(rendered)
result.pop("i18n", None)
return result
def wrap_ansible_unsafe(value):View on GitHub (pinned to 6ec464fabd)
Solutions
- Coerce to str at the call site: {{ x|string|trans }} or fix the data producer to emit message keys
- Guard in the template: {% if x is string %}{{ x|trans }}{% else %}{{ x }}{% endif %}
- Fix the context data so 'trans' only ever receives translation message keys (strings)
- If non-string passthrough should be allowed, relax safe_trans to return str(key) or the value unchanged instead of raising
Example fix
# before
{{ item.label | trans }} {# item.label is sometimes 42 #}
# after
{{ item.label | string | trans }} Defensive patterns
Strategy: type-guard
Validate before calling
key = context.get('label')
if not isinstance(key, str):
context['label'] = key = str(key) if key is not None else ''
env.render(context) Type guard
def is_trans_keyable(value) -> bool:
return isinstance(value, str) Try / catch
try:
out = env.render(ctx)
except ValueError as e:
if 'invalid i18n key' in str(e):
ctx = {k: (v if not k.endswith('_trans') else str(v)) for k, v in ctx.items()}
out = env.render(ctx)
else:
raise Prevention
- Pipe through |string in templates when data may be non-text: {{ x|string|trans }}
- Keep translation key fields typed as str in data models/validators
- Programmatically render with a small wrapper that pre-validates keys against isinstance(value, str)
When it happens
Trigger: Rendering a Jinja2 template with {{ x | trans }} where x is a number, boolean, or nested object; programmatic calls env.filters['trans'](123); templates whose variable resolves to a translated value that is itself a dict/list.
Common situations: Templates iterating heterogeneous data where some items are numeric IDs passed through the filter; refactors changing a context variable from str to an enum/int; YAML/template data where the key field is optional and sometimes null.
Related errors
- Invalid template, should contains %
- Invalid template, args not match: {} {}
- Invalid Tag length
- has no public key
- has no private key
AI-assisted analysis of jumpserver/jumpserver@6ec464fabd (2026-08-28).
Data as JSON: /api/errors/9fff9212d18b9eec.
Report an issue: GitHub.