# How to Add Translations and i18n Support in Unciv: A Complete Developer Guide

> Add translations and i18n support to Unciv. Follow our developer guide to create property files, register locales, and rebuild for automatic language support. Enhance your game today.

- Repository: [Yair Morgenstern/Unciv](https://github.com/yairm210/Unciv)
- Tags: how-to-guide
- Published: 2026-06-18

---

**To add translations and i18n support in Unciv, create a `<Language>.properties` file in `assets/jsons/translations/`, register the language in the `LocaleCode` enum with a valid BCP-47 tag, and rebuild the project—the new language automatically appears in the language picker and options menu without additional UI code.**

Unciv is an open-source, moddable remake of Civilization V built with Kotlin and LibGDX. The game handles internationalization through Java `.properties` files loaded by the `Translations` class, while the `LocaleCode` enum provides the mapping between internal language names and Java `Locale` objects. Below is the exact workflow used in the yairm210/Unciv repository to integrate new languages.

## Create a New Translation File

All user-facing text is stored in language-specific `.properties` files under `assets/jsons/translations/`. To add a new language:

1. Copy an existing translation file (e.g., `English.properties`) and rename it to your target language using exact case sensitivity without spaces.
2. Place the file in the translations directory:
   ```

   assets/jsons/translations/French.properties
   ```

3. Populate the file with key-value pairs where keys are the original English strings exactly as they appear in the source code:
   ```properties
   "Construction = Construction"
   "Gold = Gold"
   ```

   Keys containing placeholders like `[amount]` must retain the brackets in both key and value.

The `Translations.tryReadTranslationForLanguage()` method (lines 41-45 in [`core/src/com/unciv/models/translations/Translations.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/models/translations/Translations.kt)) invokes `TranslationFileReader.read()` to parse these files into `TranslationEntry` objects stored in memory.

## Register the Language in LocaleCode

Unciv maps language names to Java `Locale` instances through the `LocaleCode` enum defined in [`core/src/com/unciv/models/metadata/LocaleCode.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/models/metadata/LocaleCode.kt). Add a new enum entry to expose your language to the system:

```kotlin
Swahili("sw-KE", languageName = "Swahili")

```

Each enum constant accepts four parameters:

- **languageTag** – A valid BCP-47 tag (e.g., `"fr-FR"`, `"de-DE"`) passed to `Locale.forLanguageTag()`.
- **fastlaneFolder** (optional) – Directory name used for app store metadata; defaults to `locale().language`.
- **languageName** (optional) – The string stored in `GameSettings.language`; defaults to the enum name if omitted.
- **unused** (optional) – Boolean flag set to `true` for incomplete languages that should not appear in the UI.

Once added, `LocaleCode.getSupportedLanguages()` (lines 113-115 in [`LocaleCode.kt`](https://github.com/yairm210/Unciv/blob/main/LocaleCode.kt)) automatically includes your new entry in the list of available languages returned to the UI layer.

## Expose the Language in the UI

No manual UI registration is required. Unciv’s interface dynamically builds language selection tables using the supported languages list.

### Language Picker Screen

The `LanguagePickerScreen` class (lines 69-73 in [`core/src/com/unciv/ui/screens/LanguagePickerScreen.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/ui/screens/LanguagePickerScreen.kt)) initializes the selection grid by calling:

```kotlin
languageTables = topTable.addLanguageTables(stage.width - 60f)

```

The `addLanguageTables()` extension function (lines 55-87 in [`core/src/com/unciv/ui/components/widgets/LanguageTable.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/ui/components/widgets/LanguageTable.kt)) iterates over `LocaleCode.getSupportedLanguages()` to generate rows displaying the flag and translation completion percentage.

### Options Menu Integration

The `LanguageTab` class (lines 12-38 in [`core/src/com/unciv/ui/popups/options/LanguageTab.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/ui/popups/options/LanguageTab.kt)) reuses the same helper methods. When a player selects a new language, the game persists the choice and reloads translations:

```kotlin
settings.language = chosenLanguage
settings.updateLocaleFromLanguage()
game.translations.tryReadTranslationForCurrentLanguage()

```

The `tryReadTranslationForCurrentLanguage()` method (lines 56-60 in [`Translations.kt`](https://github.com/yairm210/Unciv/blob/main/Translations.kt)) clears diacritic caches and reloads the appropriate `.properties` file from the assets folder.

## Handle Special Characters and Diacritics

If your target language uses glyphs not supported by the default font, Unciv provides the `DiacriticSupport` class. During initialization, `Translations.createTranslations()` instantiates `DiacriticSupport` (lines 40-42 in [`Translations.kt`](https://github.com/yairm210/Unciv/blob/main/Translations.kt)) to remap missing characters to fallback alphabets. For most Latin-based languages, no additional configuration is necessary beyond ensuring the font asset includes the required glyphs (see [`DesktopFont.kt`](https://github.com/yairm210/Unciv/blob/main/DesktopFont.kt) for CJK-specific handling).

## Verify Your Translation with Tests

Unciv includes automated validation in [`tests/src/com/unciv/logic/TranslationTests.kt`](https://github.com/yairm210/Unciv/blob/main/tests/src/com/unciv/logic/TranslationTests.kt). After adding your translation file, run the test suite to verify all keys are present:

```bash
./gradlew :tests:test --tests com.unciv.logic.TranslationTests

```

Passing this test confirms the language is fully integrated and contains no missing translation keys.

## Complete Example: Adding Swahili Support

The following steps illustrate the complete workflow for adding Swahili:

```kotlin
// Step 1: Create assets/jsons/translations/Swahili.properties
// (Copy English.properties and translate the right-hand values)

// Step 2: Add to core/src/com/unciv/models/metadata/LocaleCode.kt
enum class LocaleCode(
    val languageTag: String,
    private val fastlaneFolder: String? = null,
    private val languageName: String? = null,
    val unused: Boolean = false
) {
    English("en-US"),
    French("fr-FR"),
    Swahili("sw-KE", languageName = "Swahili")
}

// Step 3: Rebuild the project
// The language appears automatically in LanguagePickerScreen and LanguageTab

```

After rebuilding, launch the game and navigate to **Options → Language** to select **Swahili**. The UI will immediately render text from `Swahili.properties`.

## Summary

- **Create** a `.properties` file in `assets/jsons/translations/` using the English strings as keys.
- **Register** the language in [`LocaleCode.kt`](https://github.com/yairm210/Unciv/blob/main/LocaleCode.kt) with a valid BCP-47 `languageTag` and optional display name.
- **Rebuild** to automatically populate the language picker and options menu—no UI code changes required.
- **Test** using `TranslationTests` to ensure complete key coverage.
- **Handle** special characters via the built-in `DiacriticSupport` system if necessary.

## Frequently Asked Questions

### Where does Unciv store translation files?

Unciv stores all translation files in `assets/jsons/translations/` as standard Java `.properties` files. Each file uses the original English UI strings as keys and the translated text as values, parsed by `TranslationFileReader` at runtime.

### Do I need to modify UI code to add a new language?

No. The `LanguageTable` widget and `LanguagePickerScreen` automatically query `LocaleCode.getSupportedLanguages()` to build the selection interface. Adding an entry to the `LocaleCode` enum is sufficient for the language to appear in both the initial picker and the in-game options menu.

### What is the purpose of the `unused` flag in LocaleCode?

The `unused` property marks languages that are incomplete or not yet ready for public use. When set to `true`, the language is excluded from `getSupportedLanguages()`, hiding it from the UI while allowing translators to test their work via manual configuration or development builds.

### How does Unciv handle missing translation keys?

At runtime, Unciv falls back to the English text if a key is missing from the selected language file. The `TranslationTests` suite validates that every language file contains all required keys, preventing incomplete translations from passing CI checks.