{"record":{"id":"9fff9212d18b9eec","repo":"jumpserver/jumpserver","slug":"invalid-i18n-key","errorCode":null,"errorMessage":"invalid i18n key","messagePattern":"invalid i18n key","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"apps/common/utils/yml.py","lineNumber":42,"sourceCode":"    lang = settings.LANGUAGE_CODE if lang is None else lang\n    lang = lang.lower().replace('_', '-')\n    lang_data = i18n.get(key, {})\n    return lang_data.get(lang, lang_data.get(lang.split('-')[0], key))\n\n\ndef yaml_load_with_i18n(stream, lang=None):\n    ori_text = stream.read()\n    data = yaml.safe_load(ori_text)\n    i18n = data.get(\"i18n\", {})\n\n    env = SandboxedEnvironment(\n        undefined=StrictUndefined,\n        autoescape=False,\n    )\n\n    def safe_trans(key):\n        if not isinstance(key, str):\n            raise ValueError(\"invalid i18n key\")\n        return translate(key, i18n, lang)\n\n    env.filters.clear()\n    env.globals.clear()\n    env.filters[\"trans\"] = safe_trans\n\n    template = env.from_string(ori_text)\n    try:\n        rendered = template.render()\n    except Exception as e:\n        rendered = ori_text\n\n    result = yaml.safe_load(rendered)\n    result.pop(\"i18n\", None)\n    return result\n\n\ndef wrap_ansible_unsafe(value):","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/jumpserver/jumpserver/blob/6ec464fabd61b95912d539455a3a5f15f5c59fe0/apps/common/utils/yml.py#L24-L60","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\n{{ item.label | trans }}   {# item.label is sometimes 42 #}\n# after\n{{ item.label | string | trans }}","handlingStrategy":"type-guard","validationCode":"key = context.get('label')\nif not isinstance(key, str):\n    context['label'] = key = str(key) if key is not None else ''\nenv.render(context)","typeGuard":"def is_trans_keyable(value) -> bool:\n    return isinstance(value, str)","tryCatchPattern":"try:\n    out = env.render(ctx)\nexcept ValueError as e:\n    if 'invalid i18n key' in str(e):\n        ctx = {k: (v if not k.endswith('_trans') else str(v)) for k, v in ctx.items()}\n        out = env.render(ctx)\n    else:\n        raise","preventionTips":["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)"],"tags":["python","jinja2","i18n","type-validation","template"],"backgroundTag":"template-filter-type-mismatch","analyzedSha":"6ec464fabd61b95912d539455a3a5f15f5c59fe0","analyzedAt":"2026-08-28T11:33:00.925Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}