Skip to main content

Internationalization and Localization

Internationalization (i18n) and localization (l10n) in this codebase are primarily managed through specialized template nodes that interface with Django's translation and formatting utilities. These nodes allow for dynamic string translation, pluralization, and locale-aware formatting of dates and numbers.

String Translation

Translation is handled by two primary nodes in django/templatetags/i18n.py: TranslateNode for simple literals and BlockTranslateNode for complex blocks containing variables or plural forms.

Simple Translations with TranslateNode

The TranslateNode implements the {% translate %} (or {% trans %}) tag. It resolves a filter expression and translates the resulting string using the active language.

Key features of TranslateNode:

  • Disambiguation: The context option (mapping to pgettext) allows translators to distinguish between identical strings with different meanings.
  • No-op Marking: The noop option marks a string for extraction by makemessages but skips the actual translation at runtime.
  • Context Storage: The as option stores the translated result in a context variable instead of rendering it immediately.

In django/templatetags/i18n.py, the render method handles the restoration of percent signs, which are doubled during the parsing of template text to prevent them from being interpreted as format flags:

def render(self, context):
# ... resolution logic ...
value = render_value_in_context(output, context)
# Restore percent signs. Percent signs in template text are doubled
# so they are not interpreted as string format flags.
is_safe = isinstance(value, SafeData)
value = value.replace("%%", "%")
value = mark_safe(value) if is_safe else value
# ...

Complex Translations with BlockTranslateNode

The BlockTranslateNode implements the {% blocktranslate %} (or {% blocktrans %}) tag. It is designed for strings that require variable interpolation or pluralization.

Variable Interpolation

Variables are defined using the with keyword. The node captures these in extra_context and resolves them during rendering. Internally, render_token_list converts template variables into named placeholders (e.g., %(var)s) for the translation engine.

Pluralization

Pluralization is triggered by the count argument and the {% plural %} tag within the block. The node uses translation.ngettext (or npgettext if a context is provided) to select the correct plural form based on the resolved counter value.

Example from django/contrib/admin/templates/admin/actions.html:

{% blocktranslate count counter=selection_count %}
1 of {{ selection_count }} selected
{% plural %}
{{ selection_count }} of {{ selection_count }} selected
{% endblocktranslate %}

The render method ensures the counter is a valid number before proceeding:

if self.plural and self.countervar and self.counter:
count = self.counter.resolve(context)
if not isinstance(count, (Decimal, float, int)):
raise TemplateSyntaxError(
"%r argument to %r tag must be a number."
% (self.countervar, self.tag_name)
)

Locale-Aware Formatting

Localization controls how data types like dates, times, and numbers are rendered according to the user's locale.

Controlling Localization with LocalizeNode

The LocalizeNode in django/templatetags/l10n.py implements the {% localize %} tag. It allows developers to explicitly enable or disable localization for a specific block of template code by overriding the use_l10n attribute in the template context.

class LocalizeNode(Node):
def render(self, context):
old_setting = context.use_l10n
context.use_l10n = self.use_l10n
output = self.nodelist.render(context)
context.use_l10n = old_setting
return output

This is frequently used to ensure that JavaScript variables or machine-readable data (like CSS coordinates) use a period as a decimal separator regardless of the user's locale:

{% localize off %}
var price = {{ product.price }};
{% endlocalize %}

Timezone Management

Timezone support allows datetimes to be displayed in a specific timezone, regardless of the global or user-specific default.

Timezone Overrides with TimezoneNode

The TimezoneNode in django/templatetags/tz.py implements the {% timezone %} tag. It uses the timezone.override context manager to change the active timezone for the duration of the block's rendering.

class TimezoneNode(Node):
def render(self, context):
with timezone.override(self.tz.resolve(context)):
output = self.nodelist.render(context)
return output

This ensures that any datetime objects rendered within the block (e.g., via {{ my_date }}) are automatically converted to the specified timezone.

Implementation Details and Constraints

  • Whitespace Management: BlockTranslateNode supports a trimmed option which uses translation.trim_whitespace to remove newlines and extra spaces from the captured content, making the strings cleaner for translators.
  • Safe Data: Both TranslateNode and BlockTranslateNode respect the "safe" status of strings. If the input is marked safe, the translated output is also marked safe after internal processing (like percent sign restoration).
  • Nesting Restrictions: BlockTranslateNode does not allow other block-level tags inside it, with the exception of the {% plural %} tag. Attempting to nest other tags will result in a TemplateSyntaxError.
  • Percent Escaping: In BlockTranslateNode, literal percent signs in the template text are escaped to %% during the token rendering phase (render_token_list) to ensure they are not treated as format specifiers by the Python string formatting used during translation interpolation.