# How Music Assistant Translation and Internationalization Works: Build-Time Compilation and Runtime Resolution

> Explore how Music Assistant handles translation and internationalization. Learn about its build-time compilation and runtime resolution for localized strings.

- Repository: [Music Assistant/server](https://github.com/music-assistant/server)
- Tags: internals
- Published: 2026-06-16

---

**Music Assistant implements a hierarchical translation system that compiles distributed [`strings.json`](https://github.com/music-assistant/server/blob/main/strings.json) files into a flat English source catalog at build time, then uses a lazy-loading `TranslationController` at runtime to resolve localized strings with fallback chains and parameter interpolation.**

The **Music Assistant translation and internationalization system** manages all user-facing text across the open-source server codebase. Unlike monolithic translation files, this architecture distributes source strings alongside their respective providers and controllers, then aggregates them into optimized runtime bundles. Understanding this design reveals how the server supports multiple languages while maintaining type safety and performance through deterministic key resolution.

## Build-Time Translation Catalog Generation

The foundation of the system lies in **authoring [`strings.json`](https://github.com/music-assistant/server/blob/main/strings.json) files** scattered throughout the codebase. These files reside at the package root ([`music_assistant/strings.json`](https://github.com/music-assistant/server/blob/main/music_assistant/strings.json)), within each provider (`music_assistant/providers/*/strings.json`), and within each controller (`music_assistant/controllers/*/strings.json`).

### Source File Discovery and Aggregation

The script **[`scripts/build_translations.py`](https://github.com/music-assistant/server/blob/main/scripts/build_translations.py)** orchestrates the build process. The `_collect_source_files()` function walks the `providers/` and `controllers/` directories (lines 51-70), generating *(prefix, path)* pairs that map file locations to key namespaces like `provider.spotify.` or `controller.metadata.`.

### Flattening Nested Structures

Once discovered, the `_flatten_into()` method recursively processes nested dictionaries into dotted keys (lines 84-92). For example, a nested structure becomes `provider.spotify.config_entries.api_key.label`. This flattened catalog is written deterministically (sorted alphabetically) to **[`music_assistant/translations/en.json`](https://github.com/music-assistant/server/blob/main/music_assistant/translations/en.json)** (lines 94-100), which serves as the **source language** (`SOURCE_LANGUAGE = "en"`). This file is committed to the repository and uploaded to Lokalise for community translation.

## Runtime Translation Management

At server startup, the **`TranslationController`** (a subclass of `CoreController` located in [`controllers/translations/__init__.py`](https://github.com/music-assistant/server/blob/main/controllers/translations/__init__.py), lines 40-45) initializes the runtime translation environment.

### The TranslationController Architecture

The controller immediately loads the English source file into `self._source` during initialization (lines 73-76). This eager loading ensures that the fallback language is always available in memory, even if localized bundles are not yet parsed.

### Lazy Locale Loading Strategy

Localized translations follow a **lazy loading pattern**. The `_discover_locale_files()` method scans the `translations/` directory once at startup to catalog available `<lang>.json` files (such as [`nl.json`](https://github.com/music-assistant/server/blob/main/nl.json) or [`de.json`](https://github.com/music-assistant/server/blob/main/de.json)), storing them in `self._locale_files` (lines 93-100). However, actual parsing occurs only when first requested via `ensure_locale_loaded()` (lines 12-30), which uses per-locale `asyncio.Lock` to prevent duplicate loads and race conditions.

### Hierarchical Key Resolution Algorithm

The `get_translation()` method implements a sophisticated fallback chain (lines 82-100):

1. **Owner Prefix Construction**: When an `owner` parameter (e.g., `provider.spotify`) is provided, `_owner_prefix()` constructs the appropriate key prefix (lines 13-18).

2. **Candidate Key Generation**: The `_candidate_keys()` function produces an ordered list of keys to attempt, handling fully-qualified keys to common rewrites (`provider.*` → `common.*`), relative keys with owner-specific variations, and `.name` suffix stripping (lines 20-30).

3. **Locale Normalization**: The `_locale_candidates()` method normalizes hyphens and underscores while adding base language fallbacks, converting `de_DE` into `["de_DE", "de"]` (lines 62-66).

4. **Lookup and Formatting**: The `_lookup()` method checks loaded locale bundles first, then the English source (lines 71-79). Upon finding a match, `_format()` safely interpolates positional placeholders like `{0}` and `{1}` using supplied parameters, leaving the template unchanged if formatting fails (lines 69-77).

## Integration Points and Usage

The translation system integrates deeply into Music Assistant's data models through the **`TRANSLATION_RESOLVER`** global context variable from `music_assistant_models.translations`.

### Configuration Entries and Provider Manifests

When serializing configuration data, `ConfigEntry.to_dict()` uses the active resolver to inject translated `label` and `category_label` values according to the test implementations in [`tests/core/test_translations.py`](https://github.com/music-assistant/server/blob/main/tests/core/test_translations.py) (lines 47-64). Similarly, `ProviderManifest.to_dict()` automatically resolves translation keys like `provider.<domain>.manifest.name` and `provider.<domain>.manifest.description` into localized strings (lines 15-30).

### Error Messages and Background Tasks

`ErrorResultMessage` stores raw `translation_key` values and arguments. When serialized via `to_dict()`, it returns localized `details` strings while stripping the translation machinery from the output (lines 83-104). Background task names utilize translation keys such as `common.background_task.sync_tracks`, with provider names inserted via parameter interpolation (lines 56-66).

## Practical Implementation Examples

```python

# Initialize the controller (normally handled by server startup)

from music_assistant.controllers.translations import TranslationController
ctrl = TranslationController(mass)  # mass is the running Music Assistant instance

await ctrl.setup(None)  # Discovers locales and loads en.json

# Resolve a localized string with parameters

title = ctrl.get_translation(
    "provider.spotify.media.liked",  # Fully-qualified key

    locale="nl",                     # Dutch locale

    params=["My Playlist"]           # Fills {0} placeholder

)

# Returns: "Nummers die je leuk vindt My Playlist"

# Pre-load a locale for a connecting client

await ctrl.ensure_locale_loaded("de")  # Loads de.json if present

# Use the resolver in a request context

from music_assistant_models.translations import TRANSLATION_RESOLVER
from functools import partial

with partial(ctrl.get_translation, locale="nl") as resolver:
    token = TRANSLATION_RESOLVER.set(resolver)
    # ConfigEntry.to_dict() calls within this block return Dutch labels

    TRANSLATION_RESOLVER.reset(token)

# Reverse lookup for search fallback

canonical = await ctrl.reverse_lookup_media_names("Klassiek")

# Returns: {"Classical"}

```

## Summary

- **Distributed authoring**: Source strings live in [`strings.json`](https://github.com/music-assistant/server/blob/main/strings.json) files co-located with providers and controllers, then compile into [`music_assistant/translations/en.json`](https://github.com/music-assistant/server/blob/main/music_assistant/translations/en.json) via [`scripts/build_translations.py`](https://github.com/music-assistant/server/blob/main/scripts/build_translations.py).
- **Lazy runtime loading**: The `TranslationController` eagerly loads English but defers other locales until `ensure_locale_loaded()` is called, using `asyncio.Lock` for thread safety.
- **Hierarchical resolution**: The `get_translation()` method implements multi-layer fallback through owner prefixes, common rewrites, and base language normalization.
- **Context-aware integration**: The `TRANSLATION_RESOLVER` global enables transparent localization of configuration entries, provider manifests, and error messages throughout the request lifecycle.

## Frequently Asked Questions

### How does Music Assistant handle missing translations for specific locales?

When a key is missing in the requested locale, the system falls back through a chain of candidates: first the specific locale (e.g., `de_DE`), then the base language (`de`), then the English source (`en`). The `_lookup()` method in [`controllers/translations/__init__.py`](https://github.com/music-assistant/server/blob/main/controllers/translations/__init__.py) checks each candidate in sequence until finding a match, ensuring that users always see English text rather than raw keys when translations are incomplete.

### What is the purpose of the `reverse_lookup_media_names` method?

The `reverse_lookup_media_names()` function enables search functionality across localized content. When a user searches using a translated term (e.g., "Klassiek" for Classical), this method maps the localized string back to its canonical English name. This allows the media library search to function correctly regardless of the user's interface language, as internal media metadata remains in English.

### How are translation keys structured in Music Assistant?

Keys follow a dotted namespace convention where the prefix indicates ownership. Providers use `provider.<domain>.<key>`, controllers use `controller.<name>.<key>`, and common strings use `common.<key>`. During build time, these hierarchical structures flatten into dot-notation keys like `provider.spotify.config_entries.api_key.label`, while the `_candidate_keys()` runtime function handles lookups for both fully-qualified and relative key formats.

### Can providers define their own translation strings without modifying core files?

Yes. Providers maintain their own [`strings.json`](https://github.com/music-assistant/server/blob/main/strings.json) files within their respective directories (`music_assistant/providers/<provider_name>/strings.json`). The build script automatically discovers these files via `_collect_source_files()` and prefixes their keys appropriately (e.g., `provider.spotify.*`). This modular approach allows third-party providers to ship complete localization support without touching the core Music Assistant codebase.