How to Add Translations and i18n Support in Unciv: A Complete Developer Guide
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:
-
Copy an existing translation file (e.g.,
English.properties) and rename it to your target language using exact case sensitivity without spaces. -
Place the file in the translations directory:
assets/jsons/translations/French.properties -
Populate the file with key-value pairs where keys are the original English strings exactly as they appear in the source code:
"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) 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. Add a new enum entry to expose your language to the system:
Swahili("sw-KE", languageName = "Swahili")
Each enum constant accepts four parameters:
- languageTag – A valid BCP-47 tag (e.g.,
"fr-FR","de-DE") passed toLocale.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
truefor incomplete languages that should not appear in the UI.
Once added, LocaleCode.getSupportedLanguages() (lines 113-115 in 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) initializes the selection grid by calling:
languageTables = topTable.addLanguageTables(stage.width - 60f)
The addLanguageTables() extension function (lines 55-87 in 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) reuses the same helper methods. When a player selects a new language, the game persists the choice and reloads translations:
settings.language = chosenLanguage
settings.updateLocaleFromLanguage()
game.translations.tryReadTranslationForCurrentLanguage()
The tryReadTranslationForCurrentLanguage() method (lines 56-60 in 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) 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 for CJK-specific handling).
Verify Your Translation with Tests
Unciv includes automated validation in tests/src/com/unciv/logic/TranslationTests.kt. After adding your translation file, run the test suite to verify all keys are present:
./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:
// 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
.propertiesfile inassets/jsons/translations/using the English strings as keys. - Register the language in
LocaleCode.ktwith a valid BCP-47languageTagand optional display name. - Rebuild to automatically populate the language picker and options menu—no UI code changes required.
- Test using
TranslationTeststo ensure complete key coverage. - Handle special characters via the built-in
DiacriticSupportsystem 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →