The MU Online client now includes a comprehensive translation system that supports internationalization (i18n) across different components of the application. The system is designed with conditional compilation to ensure that only necessary translation domains are included in each build configuration.
The system organizes translations into three separate domains:
-
Game Domain (
i18n::Domain::Game)- Availability: Always compiled (both Debug and Release builds)
- Purpose: In-game text, UI labels, messages, dialogs, tooltips
- Use Case: Player-facing content that needs localization in production
- Example Keys:
"button_login","error_connection_failed","tooltip_inventory"
-
Editor Domain (
i18n::Domain::Editor)- Availability: Debug/Editor builds only (
#ifdef _EDITOR) - Purpose: MU Editor UI, tool labels, editor-specific messages
- Use Case: Development tools and in-game editor interface
- Example Keys:
"editor_title","log_exported_s6e3","button_save_item"
- Availability: Debug/Editor builds only (
-
Metadata Domain (
i18n::Domain::Metadata)- Availability: Debug/Editor builds only (
#ifdef _EDITOR) - Purpose: Item field names, attribute labels, technical metadata
- Use Case: Human-readable labels for data structures in the editor
- Example Keys:
"field_Durability","field_Level","field_TwoHand"
- Availability: Debug/Editor builds only (
The translation system uses preprocessor directives to exclude editor/metadata functionality from release builds:
// In i18n.h
enum class Domain {
#ifdef _EDITOR
Editor, // Only available in debug/editor builds
Metadata, // Only available in debug/editor builds
#endif
Game // Always available (both debug and release)
};Benefits:
- Reduced Binary Size: Release builds don't include editor/metadata translation maps
- Zero Overhead: No runtime cost for unused features in production
- Clear Separation: Compiler enforces proper usage at build time
The following structure shows paths relative to the project root directory (where .sln file is located):
MuMain/ # Project root directory
├── src/
│ ├── Translation/
│ │ ├── i18n.h # Translation system header (domain-aware)
│ │ └── i18n.cpp # Translation system implementation
│ ├── GameData/
│ │ └── ItemData/
│ │ └── ItemFieldMetadata.h # Uses metadata translations (#ifdef _EDITOR)
│ ├── MuEditor/
│ │ └── Core/
│ │ └── MuEditorCore.cpp # Loads Editor and Metadata translations (#ifdef _EDITOR)
│ └── bin/
│ └── Translations/
│ ├── en/
│ │ ├── game.json # Game translations (always loaded)
│ │ ├── editor.json # Editor translations (debug only)
│ │ └── metadata.json # Metadata translations (debug only)
│ └── [other locales]/ # de, es, id, pl, pt, ru, tl, uk
Translation files use simple JSON format with key-value pairs:
{
"key_name": "Translated text",
"another_key": "Text with {0} and {1} placeholders",
"multiline_key": "Line 1\nLine 2\nLine 3"
}Supported Features:
- Simple key-value mappings
- Escape sequences:
\n(newline),\t(tab),\"(quote) - Placeholder formatting:
{0},{1}, etc. for dynamic content
// Available in both Debug and Release builds
#include "Translation/i18n.h"
// Simple translation
const char* text = i18n::TranslateGame("button_login", "Login");
// Or use macro
const char* text2 = GAME_TEXT("button_login");
// Check if translation exists
if (i18n::Translator::GetInstance().HasTranslation(i18n::Domain::Game, "key")) {
// ...
}// Only available when _EDITOR is defined
#ifdef _EDITOR
#include "Translation/i18n.h"
// Simple translation
const char* label = i18n::TranslateEditor("editor_title", "MU Item Editor");
// Or use macro
const char* label2 = EDITOR_TEXT("button_export");
// Format with placeholders
std::string msg = i18n::FormatEditor("log_exported_s6e3", {filename});
// Result: "Exported to {filename} (S6E3 format)"
#endif// Only available when _EDITOR is defined
#ifdef _EDITOR
#include "Translation/i18n.h"
// Translate field names
const char* fieldName = i18n::TranslateMetadata("field_Durability", "Durability");
// Or use macro with fallback
const char* fieldName2 = META_TEXT("field_Level", "Level");
// Check and translate
std::string key = "field_" + std::string(fieldName);
if (i18n::HasTranslation(i18n::Domain::Metadata, key.c_str())) {
return i18n::TranslateMetadata(key.c_str(), fieldName);
}
#endif// Typically done during initialization (MuEditorCore.cpp)
i18n::Translator& translator = i18n::Translator::GetInstance();
// Game translations - always loaded
// Path is relative to the executable location (src/bin/)
bool gameLoaded = translator.LoadTranslations(i18n::Domain::Game,
L"Translations/en/game.json");
#ifdef _EDITOR
// Editor and metadata translations - only in debug builds
bool editorLoaded = translator.LoadTranslations(i18n::Domain::Editor,
L"Translations/en/editor.json");
bool metadataLoaded = translator.LoadTranslations(i18n::Domain::Metadata,
L"Translations/en/metadata.json");
#endif| Macro | Domain | Availability | Usage |
|---|---|---|---|
GAME_TEXT(key) |
Game | Always | GAME_TEXT("button_login") |
EDITOR_TEXT(key) |
Editor | Debug only | EDITOR_TEXT("editor_title") |
META_TEXT(key, fallback) |
Metadata | Debug only | META_TEXT("field_Level", "Level") |
- Preprocessor:
_EDITORis defined - Translation Domains: All three (Game, Editor, Metadata)
- Binary Size: Larger due to full translation support
- Use Case: Development, testing, in-game editor
- Preprocessor:
_EDITORis NOT defined - Translation Domains: Game only
- Binary Size: Smaller, optimized for production
- Use Case: Public client distribution
Translation files are located in the src/bin/Translations/ directory (relative to project root).
src/bin/Translations/en/game.json (absolute path from project root):
{
"new_button": "Click Me",
"new_message": "Welcome, {0}!"
}src/bin/Translations/en/editor.json (Debug only):
{
"new_tool": "Advanced Tool",
"log_action": "Action completed: {0}"
}src/bin/Translations/en/metadata.json (Debug only):
{
"field_NewAttribute": "New Attribute"
}// Game translation (always available)
const char* buttonText = GAME_TEXT("new_button");
std::string msg = i18n::Translator::GetInstance().Format(
i18n::Domain::Game, "new_message", {playerName});
#ifdef _EDITOR
// Editor translation (debug only)
const char* toolName = EDITOR_TEXT("new_tool");
std::string log = i18n::FormatEditor("log_action", {"export"});
// Metadata translation (debug only)
const char* attrName = META_TEXT("field_NewAttribute", "NewAttribute");
#endifThe Translator class uses the singleton pattern to ensure a single global instance:
i18n::Translator& translator = i18n::Translator::GetInstance();Internally, the system maintains separate std::map<std::string, std::string> for each domain:
// In i18n.h
class Translator {
private:
#ifdef _EDITOR
std::map<std::string, std::string> m_editorTranslations; // Debug only
std::map<std::string, std::string> m_metadataTranslations; // Debug only
#endif
std::map<std::string, std::string> m_gameTranslations; // Always available
};All translation functions support fallback values:
const char* Translate(Domain domain, const char* key, const char* fallback = nullptr);- If key is found: returns translated text
- If key is not found and fallback is provided: returns fallback
- If key is not found and no fallback: returns the key itself
- Translation Lookup: O(log n) using
std::map::find() - Memory Overhead:
- Release: Only game translations (~minimal overhead)
- Debug: All three domains (~moderate overhead for development)
- Load Time: JSON files are parsed once during initialization
- Runtime Cost: Zero-cost abstraction in release builds (inline functions)