Table of Contents
- Languages
- Overview
- Language Codes
- Lang Object
- Translation Files
- String Templates
- Advanced Features
- Terrarum Sans Bitmap
- Adding Translations
- Missing Translations
- Language Selection
- Caching System
- Name Generation
- Language Properties
- Advanced Usage
- Keyboard Input
- Common Patterns
- Debugging
- Performance Considerations
- Limitations
- See Also
Languages
Terrarum features comprehensive internationalisation (i18n) support with over 20 languages, a custom multilingual font system, and sophisticated text rendering including support for complex scripts.
Overview
The language system provides:
- 20+ supported languages — From English to Japanese to Hindi
- Terrarum Sans Bitmap — Custom multilingual bitmap font
- String templates — Dynamic text formatting
- Pluralisation — Language-specific plural rules
- Postpositions — Korean/Japanese grammatical particles
- Fallback system — Graceful handling of missing translations
- Polyglot support — Integration with Polyglot format
Language Codes
Languages are identified by ISO locale codes:
Supported Languages
| Code | Language |
|---|---|
en |
English |
koKR |
Korean |
jaJP |
Japanese |
zhCN |
Chinese (Simplified) |
de |
German |
frFR |
French |
es |
Spanish |
it |
Italian |
ptBR |
Portuguese (Brazilian) |
ruRU |
Russian |
plPL |
Polish |
nlNL |
Dutch |
daDK |
Danish |
noNB |
Norwegian (Bokmål) |
fiFI |
Finnish |
isIC |
Icelandic |
csCZ |
Czech |
huHU |
Hungarian |
bgBG |
Bulgarian |
elGR |
Greek |
hiIN |
Hindi |
Lang Object
The Lang singleton manages all translations:
Getting Translations
// Basic usage
val text = Lang["STRING_ID"]
// With capitalisation
val capitalised = Lang["string_id", capitalise = true]
// Safe access (returns null if missing)
val text = Lang.getOrNull("STRING_ID")
String ID Format
Translation keys follow a naming convention:
CONTEXT_SPECIFIC_DESCRIPTION
Examples:
MENU_LANGUAGE_THIS // Menu item
BLOCK_STONE_NAME // Block name
ITEM_PICKAXE_DESC // Item description
UI_INVENTORY_TITLE // UI text
Translation Files
File Structure
Translations are organised by language:
assets/locales/
├── en/
│ ├── main.json
│ ├── blocks.json
│ └── items.json
├── koKR/
│ ├── main.json
│ └── ...
└── jaJP/
└── ...
Regular Format
Standard translation files use simple JSON:
{
"MENU_NEW_GAME": "New Game",
"MENU_LOAD_GAME": "Load Game",
"MENU_OPTIONS": "Options",
"BLOCK_STONE_NAME": "Stone",
"ITEM_PICKAXE_NAME": "Pickaxe"
}
Polyglot Format
For Polyglot-compatible files (prefix: Polyglot-100_*.json):
{
"resources": {
"polyglot": { /* metadata */ },
"data": [
{
"n": "STRING_ID",
"s": "Translated Text"
},
{
"n": "ANOTHER_STRING_ID",
"s": "Another Translation"
}
]
}
}
String Templates
Templates allow dynamic text insertion:
Basic Template Syntax
// Translation file:
"WELCOME_MESSAGE": "Welcome, %s!"
// Usage:
val text = String.format(Lang["WELCOME_MESSAGE"], playerName)
// Result: "Welcome, Steve!"
Bind Operator
The >>= operator binds strings to templates:
// Define template
"WALL_NAME_TEMPLATE": "%s Wall"
// Use bind operator in item names
item.originalName = "BLOCK_STONE>>=WALL_NAME_TEMPLATE"
// Result: "Stone Wall"
This is particularly useful for generating variants:
"BLOCK_WOOD_OAK" // "Oak Wood"
"BLOCK_WOOD_OAK>>=WALL_NAME_TEMPLATE" // "Oak Wood Wall"
Advanced Features
Korean Postpositions
Korean requires dynamic postposition selection based on the final character:
// Automatic postposition selection
val name = "사과" // Apple (ends with vowel)
Lang.formatKoreanPostposition(name, "은는") // Returns "는"
val name2 = "돌" // Stone (ends with consonant)
Lang.formatKoreanPostposition(name2, "은는") // Returns "은"
The system supports:
- 은/는 — Topic marker
- 이/가 — Subject marker
- 을/를 — Object marker
- 로/으로 — Direction marker
Pluralisation
Different languages have different plural rules:
English
// Regular plurals: add 's'
"apple" -> "apples"
// Irregular handled specially
"photo", "demo" -> normal 's' plural
French
// Special words with normal 's' plural
"bal", "banal", "fatal", "final"
// Others follow French rules
The pluralisation system is extensible for each language.
Capitalisation
Automatic capitalisation respects language rules:
val text = Lang["menu_item", capitalise = true]
// English: "Menu Item"
// German: "Menü-Element" (noun capitalisation rules)
Terrarum Sans Bitmap
The game uses a custom bitmap font supporting all languages:
Font Features
- Multilingual — Latin, Cyrillic, Greek, CJK, Hangul, Devanagari
- Bitmap-based — Pixel-perfect rendering at any size
- Fallback chains — Graceful degradation for missing glyphs
- SDF rendering — Smooth scaling with signed distance fields
Font Sizes
The font system provides multiple sizes:
- 24pt — UI text
- 32pt — Large text, headings
- 48pt — Very large text
- BigAlphNum — Numbers and basic letters
- TinyAlphNum — Tiny fallback font
See: Terrarum Sans Bitmap Repository
Adding Translations
For Modules
Add language files in your module:
<module>/locales/
├── en/
│ └── mymod.json
└── koKR/
└── mymod.json
Register them with the language loader:
ModMgr.GameLanguageLoader.loadAll(
moduleInfo,
File("<module>/locales/")
)
Translation File Example
{
"MYMOD_ITEM_SPECIAL_SWORD": "Special Sword",
"MYMOD_ITEM_SPECIAL_SWORD_DESC": "A sword with special properties",
"MYMOD_BLOCK_MAGIC_STONE": "Magic Stone",
"MYMOD_UI_CRAFTING_TITLE": "Magical Crafting"
}
Best Practises
- Use prefixes — Namespace your string IDs with module name
- Group by context — Separate files for items, blocks, UI
- Provide fallbacks — Always have English (
en) translations - Keep keys stable — Don't change IDs once released
- Use templates — Avoid duplicating similar strings
- Test all languages — Ensure text fits in UI elements
Missing Translations
The system handles missing translations gracefully:
val text = Lang["NONEXISTENT_KEY"]
// Returns: "$NONEXISTENT_KEY" (with $ prefix)
The $ prefix indicates a missing translation. Check logs for warnings.
Fallback Chain
- Try requested language (e.g.,
koKR) - Fall back to English (
en) - Return
$KEYif not found
Language Selection
Setting Language
App.GAME_LOCALE = "koKR" // Set to Korean
Lang.load(File("./assets/locales/")) // Reload translations
Current Language
val currentLang = App.GAME_LOCALE
Available Languages
val languages: Set<String> = Lang.languageList
Caching System
The Lang object caches decoded strings for performance:
// Cached per locale
private val decodeCache = HashMap<String, HashMap<String, String>>()
Cache is cleared when language changes.
Name Generation
The system includes name generators for various cultures:
Namesets
Namesets are CSV files defining name components:
assets/locales/nameset_scandinavian_m.csv
assets/locales/nameset_scandinavian_f.csv
assets/locales/nameset_russian_m.csv
assets/locales/nameset_russian_f.csv
assets/locales/nameset_dwarven.csv
assets/locales/nameset_exotic_deities.csv
Using Namesets
val name = RandomWordsName.generate("scandinavian_m")
// Returns: "Ragnar", "Bjorn", "Olaf", etc.
Language Properties
Language-specific properties are defined in langprop.csv:
lang,direction,font,features
en,ltr,latin,plural_s
koKR,ltr,hangul,postposition
jaJP,ltr,japanese,
ar,rtl,arabic,rtl_text
Properties include:
- direction — Text direction (ltr/rtl)
- font — Primary font family
- features — Language-specific features
Advanced Usage
Dynamic Text Generation
Generate text with multiple variables:
val template = Lang["CRAFTING_RECIPE_YIELDS"]
// "Crafting %s with %s yields %d %s"
val text = String.format(
template,
item1Name,
item2Name,
quantity,
resultName
)
Formatting Numbers
Use locale-aware number formatting:
val formatter = NumberFormat.getInstance(Locale(App.GAME_LOCALE))
val formatted = formatter.format(12345.67)
// English: "12,345.67"
// German: "12.345,67"
// French: "12 345,67"
Handling Long Text
For UI elements, check text width:
val font = App.fontGame
val width = font.getWidth(translatedText)
if (width > maxWidth) {
// Truncate or wrap text
}
Keyboard Input
See also: Keyboard Layout and IME
The IME system allows input in any language:
- Low Layer — Base keyboard (e.g., QWERTY)
- High Layer — Language-specific input (e.g., Korean Hangul)
Common Patterns
Item Names with Templates
class MyBlock : BlockBase() {
init {
originalName = "BLOCK_MYBLOCK>>=BLOCK_WALL_NAME_TEMPLATE"
// Automatically generates "My Block Wall"
}
}
UI Text with Variables
fun showInventoryCount(count: Int) {
val text = String.format(
Lang["UI_INVENTORY_ITEMS_COUNT"],
count
)
// "Items: 42"
}
Safe Translation Access
val text = Lang.getOrNull("OPTIONAL_STRING_ID") ?: "Default Text"
Debugging
Finding Missing Translations
Search for $ in rendered text:
if (text.startsWith("$")) {
println("Missing translation: ${text.substring(1)}")
}
Logging Untranslated Strings
Enable debug logging:
App.IS_DEVELOPMENT_BUILD = true
Missing translations will be logged to console.
Performance Considerations
- Cache translations — Don't call
Lang[]every frame - Preload common strings — Load frequently-used text at init
- Avoid string concatenation — Use templates instead
- Batch font measurements — Measure multiple strings together
Limitations
- No RTL support yet — Right-to-left languages not fully implemented
- Limited plural forms — Only basic plural rules
- Static at runtime — Language changes require restart
- No gender agreement — Some languages need grammatical gender
See Also
- Glossary — Language-related terminology
- Keyboard Layout and IME — Input methods for complex scripts
- Terrarum Sans Bitmap — Font repository
- Modules — Adding translations to modules