# Internationalization (i18n)

URL: /kida/docs/usage/i18n/
Section: usage
Description: Translate templates with trans blocks, pluralization, and gettext integration

---

> For a complete page index, fetch /kida/llms.txt.

Kida has built-in support for translatable templates. Mark strings with
`{% trans %}`, wire up a gettext backend, and Kida handles escaping,
pluralization, and variable interpolation.

## Quick start

```jinja2
{% trans %}Hello, world!{% endtrans %}
```

Without translations installed, strings pass through unchanged. Install a
gettext translations object to translate them:

```python
import gettext

translations = gettext.translation("myapp", "locales", languages=["fr"])

env = Environment(loader=loader)
env.install_translations(translations)
```

Now `{% trans %}Hello, world!{% endtrans %}` renders `"Bonjour, monde !"` (or
whatever the French `.po` file says).

## Syntax

### Simple translation

```jinja2
{% trans %}Hello, world!{% endtrans %}
```

Message ID: `"Hello, world!"`

### With variables

Declare variable bindings in the `{% trans %}` tag. Reference them as
`{{ name }}` in the body:

```jinja2
{% trans name=user.name %}Hello, {{ name }}!{% endtrans %}
```

Message ID: `"Hello, %(name)s!"`

The translated string uses Python `%`-formatting. Variable values are
HTML-escaped automatically (see [Escaping](#escaping) below).

### Pluralization

Use `{% plural %}` to provide singular and plural forms. A `count` variable
is required -- it determines which form `ngettext` selects:

```jinja2
{% trans count=items | length %}
  One item found.
{% plural %}
  {{ count }} items found.
{% endtrans %}
```

Singular ID: `"One item found."`
Plural ID: `"%(count)s items found."`

When `count` is 1, the singular form renders. Otherwise, the plural form
renders with `count` interpolated.

### Multiple variables

Separate bindings with commas:

```jinja2
{% trans name=user.name, count=user.posts | length %}
  {{ name }} has one post.
{% plural %}
  {{ name }} has {{ count }} posts.
{% endtrans %}
```

### Closing tags

Both `{% endtrans %}` and `{% end %}` work:

```jinja2
{% trans %}Hello{% endtrans %}
{% trans %}Hello{% end %}
```

## Shorthand functions

For inline translations, use the `_` and `_n` globals:

```jinja2
{{ _("Hello") }}
{{ _n("%(count)s item", "%(count)s items", count) }}
```

These are regular functions available in any expression context -- output tags,
filter arguments, default values, etc.:

```jinja2
{% def greeting(msg=_("Hello")) %}
  <h1>{{ msg }}</h1>
{% end %}
```

## Whitespace handling

Whitespace inside `{% trans %}` bodies is normalized. Leading and trailing
whitespace is stripped, and internal whitespace is collapsed to single spaces:

```jinja2
{% trans %}
  Hello,
  world!
{% endtrans %}
```

Message ID: `"Hello, world!"` (not `"\n  Hello,\n  world!\n"`)

This keeps message IDs clean regardless of template indentation.

## Escaping

Translation and escaping interact carefully:

| Scenario | Behavior |
|----------|----------|
| No variables | Translated string is escaped via the autoescape function |
| With variables | String is wrapped in `Markup`, variable values are auto-escaped |
| `Markup` values | Not double-escaped -- `Markup` instances pass through |

### Variable escaping

```jinja2
{% trans name=user_input %}Hello, {{ name }}!{% endtrans %}
```

If `user_input` is `"<script>alert(1)</script>"`, the output is:

```
Hello, &lt;script&gt;alert(1)&lt;/script&gt;!
```

The translated string itself is trusted (translators produce safe content).
Variable values are escaped on interpolation via `Markup.__mod__`.

### Pre-escaped values

If a variable is already `Markup`, it is not double-escaped:

```python
from kida.utils.html import Markup

template.render(safe_html=Markup("<b>bold</b>"))
```

```jinja2
{% trans name=safe_html %}Hello, {{ name }}!{% endtrans %}
{# Hello, <b>bold</b>! #}
```

## Body restrictions

Inside `{% trans %}` bodies, only simple `{{ name }}` references are allowed.
Filters, attribute access, and method calls are **not permitted** in the body:

```jinja2
{# Good -- bind the expression in the tag #}
{% trans name=user.name | title %}Hello, {{ name }}!{% endtrans %}

{# Bad -- complex expression in body #}
{% trans %}Hello, {{ user.name | title }}!{% endtrans %}
```

This constraint ensures message IDs are predictable and extractable. Bind
complex expressions in the `{% trans %}` tag, then reference the bound name
in the body.

## Configuration

### install_translations

Pass a standard library `gettext.GNUTranslations` object (or any object with
`gettext()` and `ngettext()` methods):

```python
import gettext

translations = gettext.translation("myapp", "locales", languages=["fr"])
env.install_translations(translations)
```

### install_gettext_callables

Pass the translation functions directly:

```python
env.install_gettext_callables(
    gettext=my_gettext_func,
    ngettext=my_ngettext_func,
)
```

### Default behavior

When no translations are installed, Kida uses identity functions:

- `_("Hello")` returns `"Hello"`
- `_n("item", "items", 1)` returns `"item"`
- `_n("item", "items", 5)` returns `"items"`

Templates render untranslated strings. No configuration needed for
single-language projects.

## Patterns

### Per-request language switching

In web applications, install translations per request:

```python
def get_translations(locale: str):
    return gettext.translation("myapp", "locales", languages=[locale])

@app.before_request
def set_locale():
    locale = request.accept_languages.best_match(["en", "fr", "de"])
    app.jinja_env.install_translations(get_translations(locale))
```

### Translation in components

`{% trans %}` works inside `{% def %}` components:

```jinja2
{% def nav_link(href, active=false) %}
<a href="{{ href }}" {% if active %}class="active"{% end %}>
  {% slot %}{% end %}
</a>
{% end %}

{% call nav_link("/settings") %}
  {% trans %}Settings{% endtrans %}
{% end %}
```

### Pluralized counts with formatting

```jinja2
{% trans count=notifications | length %}
  You have one new notification.
{% plural %}
  You have {{ count }} new notifications.
{% endtrans %}
```

### Static content with no variables

For simple strings, `{% trans %}` requires no bindings:

```jinja2
<footer>
  {% trans %}Copyright 2026. All rights reserved.{% endtrans %}
</footer>
```
