Localization
Key-to-text lookup, runtime language switching, and UI text that updates live when the player changes language.
Ship your game in multiple languages without hardcoding UI text.
| Interface | ILocalizationService |
| Off switch | NullLocalization |
| Assembly | CommonGameSystem.Localization (optional — requires Unity's built-in uGUI package) |
| Startup | Registered automatically at boot; you load the translation tables |
What it does
Instead of writing "Start" directly into a menu, you write a key — a stable identifier such as "menu.start". The Localization service resolves that key to the text of the current language at runtime. When the player switches language in your settings menu, every on-screen LocalizedText component updates instantly — no scene reload, no restart.
Missing translations never crash the game. A lookup that fails in the current language falls back to the fallback language (English by default), and if that also fails, the key itself is returned. Your UI always shows something.
Translations live in LocalizationTableAsset ScriptableObjects — plain data assets you fill in the Inspector or generate from a spreadsheet. Languages are identified by standard BCP-47 language tags, the same short codes browsers and operating systems use: "en", "ko", "ja", "zh-CN", and so on.
Because the module lives in its own optional assembly, removing uGUI from your project simply excludes the assembly — the rest of the framework still compiles and boots.
Quick start
Resolve the service in a startup script and load your tables once:
using CommonGameSystem.Core;
using UnityEngine;
[DefaultExecutionOrder(100)] // Run after the framework has started.
public class GameStartup : MonoBehaviour
{
[SerializeField] private LocalizationTableAsset[] tables; // assign in the Inspector
private ILocalizationService _loc;
private void Awake()
{
_loc = ServiceLocator.Resolve<ILocalizationService>();
_loc.LoadTables(tables);
// Optional: start in the player's OS language.
_loc.SetLocale(LocaleId.FromSystemLanguage(Application.systemLanguage));
}
}
After that, any script can fetch text with Get:
string title = _loc.Get("menu.title");
API reference
Lookup
string Get(string key)— Resolves a key: current language → fallback language → the key itself. Never throws.string Get(string key, params object[] args)— Same lookup, then formats the result withstring.Formatusing the invariant culture. A bad format string returns the unformatted text instead of throwing.bool TryGet(string key, out string value)— Lookup with no side effects. Returnsfalseif the key is missing (valueis set to the key). Logs nothing — useful for probing keys without warning spam.
Language switching
void SetLocale(string localeId)— Switches the active language. Publishes aLocaleChangedevent on the Event Bus and saves the choice through the Configuration service, so it persists across sessions.string CurrentLocale { get; }— The active locale id.IReadOnlyList<string> AvailableLocales { get; }— The locale ids found in the loaded tables. Drive your language-picker UI from this list.string GetLocaleDisplayName(string localeId)— The language's name in its own language (for example,"한국어"for Korean), for a language-picker UI. Falls back to the id if not set.
Setup
void LoadTables(IReadOnlyList<LocalizationTableAsset> tables)— Loads the translation tables and activates the saved (or default) language. Call it once at startup.
Static helper
LocaleId.FromSystemLanguage(SystemLanguage)— MapsApplication.systemLanguageto a BCP-47 tag (for example,SystemLanguage.Korean→"ko"). Use it to auto-detect the player's OS language on first launch.
The LocalizedText component
For static UI text, skip the code entirely: attach a LocalizedText component to any uGUI Text or TextMeshPro component and set its key in the Inspector. The component fetches the text on enable and re-fetches it whenever the language changes.
For dynamic text with values in it, use SetArgs:
using CommonGameSystem.Core;
using UnityEngine;
public class ScoreDisplay : MonoBehaviour
{
private LocalizedText _locText;
private void Start()
{
_locText = GetComponent<LocalizedText>();
}
public void SetScore(int newScore)
{
// Table entry: "score.current" = "Score: {0}" → displays "Score: 42"
_locText.SetArgs(newScore);
}
}
Example: a language button
using CommonGameSystem.Core;
using UnityEngine;
public class LanguageMenu : MonoBehaviour
{
private ILocalizationService _loc;
private void Start()
{
_loc = ServiceLocator.Resolve<ILocalizationService>();
}
// Wire this to a UI button, passing "en", "ko", "ja", ...
public void OnLanguageButtonClicked(string localeId)
{
_loc.SetLocale(localeId); // Every LocalizedText on screen updates live.
}
}
The choice is saved automatically — the next session starts in the language the player picked.
Turning it off
ServiceLocator.Replace<ILocalizationService>(new NullLocalization());
Get(key) returns the key unchanged, SetLocale and LoadTables are silent no-ops, and nothing is logged. Use this to strip localization entirely in lightweight builds or tests.
Common pitfalls
- Main thread only. Calling
GetorSetLocalefrom a worker thread throwsInvalidOperationException. All MonoBehaviour callbacks are safe. - Cache the service. Resolving in
Update()repeats a dictionary lookup every frame. Cache the reference inAwake()orStart()instead. - Locale ids must match exactly.
"EN"is not"en", andApplication.systemLanguage.ToString()yields"Korean", not"ko". UseLocaleId.FromSystemLanguage()for auto-detection, and lowercase BCP-47 tags in your tables. LocalizedTextre-subscribes on every enable. On a pooled UI prefab (damage numbers, toasts), each pool cycle subscribes to and unsubscribes fromLocaleChanged. That is correct behavior — reactivation picks up the current language — but watch high-frequency pools. Prefer updating dynamic text withSetArgs.- Shutdown order. Services shut down in the reverse of their start order, so the Localization service can be disposed before the Event Bus. If a
LocaleChangedevent arrives after that,LocalizedTextswallows it silently — the text does not update, and nothing crashes. - Custom types need
link.xmlon IL2CPP. If you serialize your own settings types that carry localization keys, make sure your project'slink.xmlpreserves them. The shipped framework assemblies already preserve their own types.
Related pages
- Event Bus — the
LocaleChangedevent - Configuration — where the language choice is saved
- UI Framework — panels and screens that host your localized text