# How Godot's TranslationServer Handles Localization and Plural Rules: A Deep Dive into the i18n Architecture

> Explore Godot's TranslationServer for game localization. Discover how it handles locale strings, comparison, and Unicode CLDR plural rules for global reach.

- Repository: [Godot Engine/godot](https://github.com/godotengine/godot)
- Tags: deep-dive
- Published: 2026-02-26

---

**Godot's TranslationServer centralizes internationalization by parsing and normalizing locale strings through `standardize_locale()`, comparing locale compatibility via the `compare_locales()` scoring algorithm, and resolving Unicode CLDR plural rules from static data tables compiled into [`core/string/locales.h`](https://github.com/godotengine/godot/blob/main/core/string/locales.h).**

The TranslationServer serves as the backbone of internationalization (i18n) in the Godot game engine, managing everything from locale normalization to plural form resolution. Understanding how the TranslationServer handles localization and plural rules is essential for developers building multilingual applications, as it governs how the engine selects languages, matches regional variants, and computes grammatical number forms. This analysis examines the core implementation in [`core/string/translation_server.cpp`](https://github.com/godotengine/godot/blob/main/core/string/translation_server.cpp) and its accompanying data tables.

## Initializing Locale Data at Server Startup

When the TranslationServer constructor runs, it immediately invokes `init_locale_info()` to populate static `HashMap`s with localization metadata derived from CLDR (Common Locale Data Repository) and Unicode standards【/tmp/instagit_ldgju1gx/core/string/translation_server.cpp#L41-L86】. These maps drive every subsequent localization operation:

- `language_map` – Maps ISO-639-1 codes to human-readable language names
- `script_map` – Maps ISO-15924 codes to script names
- `locale_rename_map` – Handles legacy Windows locale identifiers
- `country_name_map` and `country_rename_map` – Manage ISO-3166 country codes and temporary aliases
- `variant_map` – Tracks regional variant tokens
- `plural_rules_map` – Associates locales with CLDR plural expressions
- `num_system_map` – Stores numeric system data including digit symbols and percent signs

### Data Sources in locales.h

All static tables reside in [`core/string/locales.h`](https://github.com/godotengine/godot/blob/main/core/string/locales.h). The `plural_rules` array specifically stores GNU gettext-style plural expressions (e.g., `"nplurals=2; plural=(n != 1);"`) compiled from Unicode CLDR release-47 and GNU gettext data【/tmp/instagit_ldgju1gx/core/string/locales.h#L1824-L1855】.

## Normalizing Locale Strings with standardize_locale()

The `standardize_locale()` method converts arbitrary locale identifiers into canonical form suitable for the engine. It creates a temporary `Locale` object (constructed in [`translation_server.cpp`](https://github.com/godotengine/godot/blob/main/translation_server.cpp)【/tmp/instagit_ldgju1gx/core/string/translation_server.cpp#L69-L96】) and executes the following normalization pipeline:

1. **Replace dashes with underscores** to handle macOS-style locales (`en-US` → `en_US`)
2. **Split components** into language, script, country, and variant segments
3. **Apply rename maps** to convert legacy identifiers using `locale_rename_map` and `country_rename_map`
4. **Validate script codes** against `script_map`, clearing unsupported entries
5. **Add defaults** when enabled: look up preferred scripts from `locale_script_info` and fill default countries when scripts are present but countries are missing

The resulting canonical string (e.g., `"sr_Latn_RS"` or `"en_US"`) is used consistently throughout the engine's translation system.

## Scoring Locale Compatibility with compare_locales()

The engine determines translation fallback priority using `compare_locales()`, which assigns numerical scores based on component matching【/tmp/instagit_ldgju1gx/core/string/translation_server.cpp#L19-L75】:

| Score | Condition |
|-------|-----------|
| **10** | Exact string match |
| **5** | Same language with no additional components |
| **+1 / -1** | Bonus or penalty for matching/mismatching script, country, or variant |
| **0** | Different languages (no compatibility) |

Results are cached in `locale_compare_cache` to optimize repeated lookups during translation resolution.

## Resolving CLDR Plural Rules

### The plural_rules Data Structure

The static `plural_rules` array in [`locales.h`](https://github.com/godotengine/godot/blob/main/locales.h) pairs locale patterns with GNU gettext expressions that define how many plural forms a language uses and the boolean conditions for selecting each form. For example, Russian uses `"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"` to handle its three grammatical forms.

### Lookup Algorithm in get_plural_rules()

The `get_plural_rules()` function implements a hierarchical fallback chain【/tmp/instagit_ldgju1gx/core/string/translation_server.cpp#L5-L22】:

```cpp
String TranslationServer::get_plural_rules(const String &p_locale) const {
    const String *rule = plural_rules_map.getptr(p_locale);
    if (rule) return *rule;

    Locale l = Locale(*this, p_locale, false);
    // Try language+country, then language only
    if (!l.country.is_empty()) {
        rule = plural_rules_map.getptr(l.language + "_" + l.country);
        if (rule) return *rule;
    }
    rule = plural_rules_map.getptr(l.language);
    if (rule) return *rule;

    return String(); // No rule found
}

```

The function first attempts an exact match (e.g., `"fr_CA"`), then falls back to language-country combinations, and finally to language-only codes. This ensures broad coverage while allowing regional overrides when specific plural behavior differs (such as French in Canada versus France).

## Practical Implementation Examples

### Setting a Locale and Retrieving Canonical Names

```gdscript

# Normalize and identify a locale

TranslationServer.set_locale("pt_BR")
var canonical = TranslationServer.get_locale_name(TranslationServer.get_locale())
print(canonical)  # → "Portuguese (Latin), Brazil"

```

This sequence calls `set_locale` → `standardize_locale` → `Locale` constructor, then queries `language_map`, `script_map`, and `country_name_map` to assemble the human-readable identifier.

### Accessing Plural Rules for Grammatical Number Handling

```gdscript
var locale = "ru"
var rule = TranslationServer.get_plural_rules(locale)
print(rule)   

# → "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"

var plural = PluralRules.new(rule)
print(plural.get_plural_form(5))   # → 2 (Russian plural form for "many")

```

The `get_plural_rules` method queries `plural_rules_map` and falls back to language-only entries when specific regional variants lack dedicated rules.

### Comparing Locales for Fallback Selection

```gdscript
var score = TranslationServer.compare_locales("en_US", "en_GB")
print(score)   # → 6 (language matches: 5, script empty: 0, country differs: -1, variant empty: 0)

```

This scoring mechanism enables the engine to select the best available translation when an exact locale match does not exist in the loaded translation resources.

## Summary

- **Data-driven architecture**: The TranslationServer relies on static tables in [`core/string/locales.h`](https://github.com/godotengine/godot/blob/main/core/string/locales.h) compiled from CLDR and Unicode standards rather than external dependencies.
- **Robust normalization**: The `standardize_locale()` method handles legacy identifiers, variant tags, and platform-specific formatting (dashes vs. underscores) to produce canonical locale strings.
- **Hierarchical plural resolution**: `get_plural_rules()` implements a three-tier fallback system (exact → language-country → language-only) to ensure grammatical number rules are always available.
- **Optimized matching**: The `compare_locales()` scoring algorithm caches results to efficiently determine translation fallback priority based on linguistic similarity.
- **GDScript accessibility**: All core functionality is exposed to scripting, allowing runtime locale manipulation and custom plural form calculations via the `PluralRules` class.

## Frequently Asked Questions

### How does the TranslationServer normalize locale strings like "en-US" versus "en_US"?

The `standardize_locale()` method in [`core/string/translation_server.cpp`](https://github.com/godotengine/godot/blob/main/core/string/translation_server.cpp) creates a temporary `Locale` object that replaces dashes with underscores, splits the identifier into components, applies rename maps for legacy identifiers, validates script codes against `script_map`, and optionally adds default scripts and countries from `locale_script_info`.

### What scoring system does compare_locales use to determine locale compatibility?

`compare_locales()` assigns **10** points for exact matches, **5** points for matching languages with no additional components, then adds or subtracts **1** point for each matching or mismatching script, country, or variant. Scores below **0** indicate incompatible languages. The engine uses this score to select the best available translation when exact locale matches are unavailable.

### Where does Godot store plural rule definitions used by the TranslationServer?

Plural rules reside in the static `plural_rules` array inside [`core/string/locales.h`](https://github.com/godotengine/godot/blob/main/core/string/locales.h), indexed by `plural_rules_map` during server initialization. These rules follow GNU gettext syntax (e.g., `"nplurals=2; plural=(n != 1);"`) and are compiled from Unicode CLDR release-47 data paired with GNU gettext's plural-table definitions.

### How can developers access plural rules in GDScript to handle grammatical numbers correctly?

Developers call `TranslationServer.get_plural_rules(locale)` to retrieve the plural expression string, then instantiate a `PluralRules` object with that string. The `get_plural_form(n)` method returns the integer index of the correct grammatical form for the given number, enabling dynamic selection of singular, dual, paucal, or plural translations as required by the target language's grammar rules.