Localization System
The Popcorning Guinea Pig engine provides comprehensive localization support through the ILocalizationService, enabling games to support multiple languages and cultures.
Overview
The localization system allows you to:
- Support multiple languages and regions
- Store translations in JSON or XML files
- Use parameterized text with placeholders
- Handle pluralization rules
- Support right-to-left (RTL) languages
- Automatically detect system language
- Fallback to default language for missing translations
ILocalizationService Interface
public interface ILocalizationService
{
void LoadLanguage(string languageCode);
string GetText(string key, params object[] parameters);
string GetText(string key, string defaultText, params object[] parameters);
bool HasText(string key);
string CurrentLanguage { get; }
string[] AvailableLanguages { get; }
CultureInfo CurrentCulture { get; }
void SetLanguage(string languageCode);
}
Setting Up Localization
1. Create Language Files
Create JSON files for each supported language in your assets directory:
assets/localization/
├── en.json # English (default)
├── es.json # Spanish
├── fr.json # French
├── de.json # German
├── ja.json # Japanese
└── zh.json # Chinese
2. English Language File (en.json)
{
"ui": {
"mainMenu": {
"title": "Popcorning Guinea Pig",
"playButton": "Play Game",
"settingsButton": "Settings",
"exitButton": "Exit Game"
},
"settings": {
"title": "Settings",
"audio": "Audio",
"graphics": "Graphics",
"language": "Language",
"back": "Back"
},
"gameplay": {
"score": "Score: {0}",
"level": "Level {0}",
"lives": "Lives: {0}",
"gameOver": "Game Over",
"wellDone": "Well Done!",
"timeRemaining": "{0} seconds remaining"
}
},
"messages": {
"welcome": "Welcome to Popcorning Guinea Pig!",
"playerJoined": "{0} has joined the game",
"playerLeft": "{0} has left the game",
"itemCollected": "You collected {0} {1}",
"achievements": {
"firstWin": "Congratulations on your first victory!",
"speedRunner": "You completed the level in record time!",
"collector": "You found all the hidden items!"
}
},
"plurals": {
"itemCount": {
"zero": "No items",
"one": "1 item",
"other": "{0} items"
},
"playerCount": {
"zero": "No players",
"one": "1 player",
"other": "{0} players"
}
}
}
3. Spanish Language File (es.json)
{
"ui": {
"mainMenu": {
"title": "Conejillo de Indias Saltarín",
"playButton": "Jugar",
"settingsButton": "Configuración",
"exitButton": "Salir"
},
"settings": {
"title": "Configuración",
"audio": "Audio",
"graphics": "Gráficos",
"language": "Idioma",
"back": "Atrás"
},
"gameplay": {
"score": "Puntuación: {0}",
"level": "Nivel {0}",
"lives": "Vidas: {0}",
"gameOver": "Fin del Juego",
"wellDone": "¡Bien Hecho!",
"timeRemaining": "{0} segundos restantes"
}
},
"messages": {
"welcome": "¡Bienvenido a Conejillo de Indias Saltarín!",
"playerJoined": "{0} se ha unido al juego",
"playerLeft": "{0} ha abandonado el juego",
"itemCollected": "Has recogido {0} {1}",
"achievements": {
"firstWin": "¡Felicitaciones por tu primera victoria!",
"speedRunner": "¡Completaste el nivel en tiempo récord!",
"collector": "¡Encontraste todos los objetos ocultos!"
}
}
}
4. Initialize Localization Service
public class MyGame : Game
{
private readonly ILocalizationService _localization;
public MyGame()
{
_localization = GetService<ILocalizationService>();
// Load default language or system language
var systemLanguage = CultureInfo.CurrentCulture.TwoLetterISOLanguageName;
_localization.LoadLanguage(systemLanguage);
// Fallback to English if system language not supported
if (!_localization.AvailableLanguages.Contains(systemLanguage))
{
_localization.LoadLanguage("en");
}
}
}
Basic Usage
Getting Localized Text
// Simple text lookup
string title = _localization.GetText("ui.mainMenu.title");
string playButton = _localization.GetText("ui.mainMenu.playButton");
// Text with parameters
string score = _localization.GetText("ui.gameplay.score", playerScore);
string level = _localization.GetText("ui.gameplay.level", currentLevel);
// Text with default fallback
string customText = _localization.GetText("custom.key", "Default Text");
Using Localized Text in UI
public class MainMenuScene : Scene
{
private readonly ILocalizationService _localization;
public MainMenuScene()
{
_localization = GetService<ILocalizationService>();
CreateUI();
}
private void CreateUI()
{
// Title label
var titleLabel = new Label
{
Text = _localization.GetText("ui.mainMenu.title"),
Position = new Vector2(400, 100)
};
// Play button
var playButton = new Button
{
Text = _localization.GetText("ui.mainMenu.playButton"),
Position = new Vector2(400, 200),
OnClick = StartGame
};
// Settings button
var settingsButton = new Button
{
Text = _localization.GetText("ui.mainMenu.settingsButton"),
Position = new Vector2(400, 250),
OnClick = ShowSettings
};
AddElement(titleLabel);
AddElement(playButton);
AddElement(settingsButton);
}
}
Advanced Features
Language Selection UI
Create a language selection dropdown:
public class LanguageSelector : UIElement
{
private readonly ILocalizationService _localization;
private readonly Dropdown _languageDropdown;
public LanguageSelector()
{
_localization = GetService<ILocalizationService>();
_languageDropdown = new Dropdown
{
Items = GetLanguageDisplayNames(),
SelectedIndex = GetCurrentLanguageIndex(),
OnSelectionChanged = OnLanguageChanged
};
AddChild(_languageDropdown);
}
private string[] GetLanguageDisplayNames()
{
var languages = _localization.AvailableLanguages;
var displayNames = new string[languages.Length];
for (int i = 0; i < languages.Length; i++)
{
displayNames[i] = GetLanguageDisplayName(languages[i]);
}
return displayNames;
}
private string GetLanguageDisplayName(string languageCode)
{
return languageCode switch
{
"en" => "English",
"es" => "Español",
"fr" => "Français",
"de" => "Deutsch",
"ja" => "日本語",
"zh" => "中文",
_ => languageCode.ToUpper()
};
}
private void OnLanguageChanged(int selectedIndex)
{
var languages = _localization.AvailableLanguages;
if (selectedIndex >= 0 && selectedIndex < languages.Length)
{
_localization.SetLanguage(languages[selectedIndex]);
RefreshUI();
}
}
private void RefreshUI()
{
// Notify UI elements to refresh their text
GameEvents.LocalizationChanged?.Invoke();
}
}
Auto-Refreshing UI Elements
Create UI elements that automatically update when language changes:
public class LocalizedLabel : Label
{
private readonly string _textKey;
private readonly object[] _parameters;
private readonly ILocalizationService _localization;
public LocalizedLabel(string textKey, params object[] parameters)
{
_textKey = textKey;
_parameters = parameters;
_localization = GetService<ILocalizationService>();
UpdateText();
// Subscribe to language changes
GameEvents.LocalizationChanged += UpdateText;
}
private void UpdateText()
{
Text = _localization.GetText(_textKey, _parameters);
}
public void UpdateParameters(params object[] newParameters)
{
_parameters = newParameters;
UpdateText();
}
protected override void Dispose()
{
GameEvents.LocalizationChanged -= UpdateText;
base.Dispose();
}
}
// Usage
var scoreLabel = new LocalizedLabel("ui.gameplay.score", 0);
var levelLabel = new LocalizedLabel("ui.gameplay.level", 1);
// Update parameters when values change
scoreLabel.UpdateParameters(newScore);
levelLabel.UpdateParameters(newLevel);
Pluralization Support
Handle pluralization rules for different languages:
public static class PluralRules
{
public static string GetPluralForm(ILocalizationService localization, string key, int count)
{
var currentLang = localization.CurrentLanguage;
// Determine plural form based on language rules
string pluralKey = currentLang switch
{
"en" => count == 0 ? "zero" : (count == 1 ? "one" : "other"),
"es" => count == 0 ? "zero" : (count == 1 ? "one" : "other"),
"fr" => count <= 1 ? "one" : "other",
"ru" => GetRussianPluralForm(count),
_ => count == 1 ? "one" : "other"
};
var fullKey = $"{key}.{pluralKey}";
return localization.HasText(fullKey)
? localization.GetText(fullKey, count)
: localization.GetText($"{key}.other", count);
}
private static string GetRussianPluralForm(int count)
{
if (count % 10 == 1 && count % 100 != 11) return "one";
if (count % 10 >= 2 && count % 10 <= 4 && (count % 100 < 10 || count % 100 >= 20)) return "few";
return "other";
}
}
// Usage
string itemText = PluralRules.GetPluralForm(_localization, "plurals.itemCount", itemCount);
Date and Number Formatting
Format numbers and dates according to current culture:
public static class LocalizedFormatting
{
public static string FormatNumber(ILocalizationService localization, double number)
{
return number.ToString("N0", localization.CurrentCulture);
}
public static string FormatCurrency(ILocalizationService localization, decimal amount)
{
return amount.ToString("C", localization.CurrentCulture);
}
public static string FormatDate(ILocalizationService localization, DateTime date)
{
return date.ToString("d", localization.CurrentCulture);
}
public static string FormatTime(ILocalizationService localization, DateTime time)
{
return time.ToString("t", localization.CurrentCulture);
}
}
// Usage
string score = LocalizedFormatting.FormatNumber(_localization, playerScore);
string price = LocalizedFormatting.FormatCurrency(_localization, itemPrice);
string date = LocalizedFormatting.FormatDate(_localization, DateTime.Now);
Text Direction Support
Handle right-to-left (RTL) languages:
public static class TextDirectionHelper
{
private static readonly HashSet<string> RtlLanguages = new()
{
"ar", "he", "fa", "ur"
};
public static bool IsRightToLeft(string languageCode)
{
return RtlLanguages.Contains(languageCode);
}
public static void ApplyTextDirection(UIElement element, string languageCode)
{
if (IsRightToLeft(languageCode))
{
element.TextAlignment = TextAlignment.Right;
element.FlowDirection = FlowDirection.RightToLeft;
}
else
{
element.TextAlignment = TextAlignment.Left;
element.FlowDirection = FlowDirection.LeftToRight;
}
}
}
Best Practices
Organization
- Hierarchical keys: Use dot notation for nested organization
- Consistent naming: Follow a standard naming convention across all files
- Context grouping: Group related text by UI section or feature
Translation Management
- Key validation: Ensure all languages have the same keys
- Parameter consistency: Use consistent parameter ordering across languages
- Translation notes: Add comments for translators when context is important
Performance
- Preload languages: Load language files at startup, not on-demand
- Cache lookups: Cache frequently accessed translations
- Lazy loading: Only load active language, not all languages
User Experience
- Language persistence: Save user's language preference
- Immediate updates: Refresh UI immediately when language changes
- Fallback handling: Always provide fallback text for missing keys
Example: Complete Localization Setup
public class GameLocalizationManager
{
private readonly ILocalizationService _localization;
private readonly ISettingsService _settings;
public event Action LanguageChanged;
public GameLocalizationManager(ILocalizationService localization, ISettingsService settings)
{
_localization = localization;
_settings = settings;
Initialize();
}
private void Initialize()
{
// Load saved language preference or detect system language
var savedLanguage = _settings.GetSetting<string>("Language");
if (!string.IsNullOrEmpty(savedLanguage))
{
_localization.SetLanguage(savedLanguage);
}
else
{
DetectAndSetSystemLanguage();
}
}
private void DetectAndSetSystemLanguage()
{
var systemLang = CultureInfo.CurrentCulture.TwoLetterISOLanguageName;
var availableLanguages = _localization.AvailableLanguages;
if (availableLanguages.Contains(systemLang))
{
_localization.SetLanguage(systemLang);
}
else
{
_localization.SetLanguage("en"); // Fallback to English
}
}
public void ChangeLanguage(string languageCode)
{
if (_localization.AvailableLanguages.Contains(languageCode))
{
_localization.SetLanguage(languageCode);
_settings.SetSetting("Language", languageCode);
_settings.SaveSettings();
LanguageChanged?.Invoke();
}
}
public string GetLocalizedText(string key, params object[] parameters)
{
return _localization.GetText(key, parameters);
}
public string GetLocalizedTextWithFallback(string key, string fallback, params object[] parameters)
{
return _localization.HasText(key)
? _localization.GetText(key, parameters)
: fallback;
}
}
// Usage in game
public class MyGame : Game
{
private GameLocalizationManager _localizationManager;
public MyGame()
{
var localization = GetService<ILocalizationService>();
var settings = GetService<ISettingsService>();
_localizationManager = new GameLocalizationManager(localization, settings);
_localizationManager.LanguageChanged += OnLanguageChanged;
}
private void OnLanguageChanged()
{
// Refresh all UI elements
RefreshAllScenes();
}
}
The localization system enables your Popcorning Guinea Pig games to reach a global audience with proper language and cultural support.