A lightweight Unity localization package for runtime translation, localized UI text, object-based localization contexts, language-file assets, and editor-side localization management.
| Feature | Description |
|---|---|
| Key-value localization | Fetch text by key, with modular files, merged sources, and hierarchical key paths such as game.ui.start. |
| Runtime translation API | L10n handles initialization, language loading, translation, missing keys, runtime overrides, and default-region reads. |
| Structured parameters | L10nParams separates key option segments from dynamic variables and replaces ambiguous legacy params string[] usage. |
| Dynamic values and contexts | L10nContext / ILocalizableContext bind objects to key roots and provide runtime values for placeholders. |
| Expression parsing | Localization text supports {value}, {a + b}, {value:0.0}, and parameterized dynamic values. |
| Inline references | $Key.Path$ / $@Key.Path$ embed other localized strings and can participate in tooltip import behavior. |
| Color tags | §Rtext§, §#ff8800text§, and §<Keyword>text§ convert to TMP/UGUI-compatible <color> tags. |
| UI components | Built-in localizers support TextMeshPro and legacy UnityEngine.UI.Text. |
| Editor tools | .yml / .lang import, key management, CSV import/export, sorting, and missing-key workflows are available in editor code. |
| Extension points | Projects can provide custom formatters, contexts, color resolvers, and missing-key behavior. |
- Unity
2021.3or newer - A Unity project that can load assets from
Assets/Resources - The Unity utility dependencies required by this package
- TextMeshPro, if using the TextMeshPro localizer component
Package manifest:
{
"name": "com.minervagamestudio.localization",
"version": "0.4.0",
"displayName": "Localization",
"unity": "2021.3"
}Add the package to Packages/manifest.json:
{
"dependencies": {
"com.minervagamestudio.localization": "https://github.com/minerva-studio/localization.git"
}
}You can also install it from Unity:
Window > Package Manager > + > Add package from git URL...
Then paste:
https://github.com/minerva-studio/localization.git
Create a L10nDataManager asset from Unity's asset menu:
Create > Localization > Localization Manager
The manager owns:
defaultRegionregionsfilessourcesmissingKeySolutionreferenceImportOptiontooltipImportOptionuseUnderlineResolverwordSpacelistDelimiter
The package loads project settings from:
Assets/Resources/Localization/LocalizationSetting.asset
Open the LocalizationSetting asset and assign the L10nDataManager to its manager field.
At runtime, LocalizationSettings loads this asset from Resources and initializes L10n.
A language file is a LanguageFile asset or an imported .lang / .yml file.
Suggested layout:
Assets/
Resources/
Localization/
LocalizationSetting.asset
Base/
Lang_UI.yml
EN_US/
Lang_UI_EN_US.lang
ZH_CN/
Lang_UI_ZH_CN.lang
Use the exact region strings configured in L10nDataManager.regions.
Example entries:
Game.UI.MainMenu.Start.name: Start
Game.UI.MainMenu.Quit.name: Quit
Game.UI.MainMenu.Quit.msg: Are you sure you want to quit?The system auto-initializes from LocalizationSettings, but a game can explicitly load a region:
using Minerva.Localizations;
L10n.Init();
L10n.Load("EN_US");To initialize from a specific manager:
L10n.InitAndLoad(manager, "EN_US");When a language is loaded, L10n.OnLocalizationLoaded is invoked. Built-in text localizers listen to this event and refresh themselves.
L10nDataManager is the editor and runtime hub for localization data. It is a ScriptableObject created from:
Create > Localization > Localization Manager
It manages supported regions, language files, source files, key collection rebuilding, sorting, import/export behavior, reference import options, tooltip import options, and missing-key behavior.
LocalizationSettings is the project-level settings asset loaded from:
Assets/Resources/Localization/LocalizationSetting.asset
It stores the active L10nDataManager reference used to initialize the localization system at runtime.
L10n is the main runtime facade. It handles:
InitLoadReloadTrTrKeyTrRawTryTrTrDefaultContainsExistWriteOptionOf
After a region is loaded, L10n.OnLocalizationLoaded is raised so UI localizers and other listeners can refresh displayed text.
LanguageFile is the concrete language content asset. It contains a region, a tag, localization entries, and optional child files.
Master files merge their own entries and child-file entries. If duplicate keys exist, the later imported entry overrides the earlier one and a warning is logged.
string text = L10n.Tr("Game.UI.MainMenu.Start.name", L10nParams.Empty);Legacy calls without L10nParams are still supported:
string text = L10n.Tr("Game.UI.MainMenu.Start.name");L10nParams option segments are appended to the base key.
string name = L10n.Tr("Game.Item.HealthPotion", L10nParams.Create("name"));
string desc = L10n.Tr("Game.Item.HealthPotion", L10nParams.Create("desc"));These calls resolve:
Game.Item.HealthPotion.name
Game.Item.HealthPotion.desc
Multiple options are appended in order:
string desc = L10n.Tr(
"Game.Effect.Buff",
L10nParams.Create("Fire", "desc")
);This resolves:
Game.Effect.Buff.Fire.desc
string text = L10n.Tr(
"Game.UI.Timer.info",
L10nParams.Create().With("seconds", 12.5f)
);Language entry:
Game.UI.Timer.info: "Time: {seconds:0.0}s"If a type has a registered localization context, it can be translated directly:
string displayName = L10n.Tr(itemData, L10nParams.Create("name"));
string description = L10n.Tr(itemData, L10nParams.Create("desc"));Internally, this resolves the object to L10nContext.Of(object), builds the localization key, reads raw content, and evaluates localization escape patterns.
Use TrKey when the key is explicit but dynamic values should come from a context:
var context = L10nContext.Of(itemData);
string text = L10n.TrKey("Game.UI.Inventory.Selected.info", context, L10nParams.Empty);Use TrRaw when the raw string is already available but still needs variables, references, or color tags resolved:
var context = L10nContext.Of(itemData);
string text = L10n.TrRaw(
"Price: {price}",
context,
L10nParams.Empty
);Use TrDefault to read from the default localization region:
string fallback = L10n.TrDefault("Game.UI.MainMenu.Start.name", L10nParams.Empty);L10nParams separates key option segments from dynamic variables.
var parameters = L10nParams.Empty;
var another = L10nParams.Create();var nameOption = L10nParams.Create("name");
var nestedOption = L10nParams.Create("Fire", "desc");var parameters = L10nParams
.Create("desc")
.With("level", 2)
.With("damage", 12.5f);Multiple variables can be added at once:
var parameters = L10nParams
.Create("desc")
.With(
("level", 2),
("damage", 12.5f),
("cooldown", 3.0f)
);parameters = parameters.WithOptions("name");
parameters = parameters.AppendOptions("tooltip");
parameters = parameters.PrependOptions("Common");Legacy string parameters are converted with L10nParams.FromStrings(...).
L10n.Tr("Game.Item.HealthPotion", "name", "level=2");Conversion rules:
- strings without
=become options - strings with
=become variables
For example, ["Daily", "level=2", "desc"] becomes:
- Options:
["Daily", "desc"] - Variables:
{ "level": "2" }
Prefer explicit L10nParams in new code:
L10n.Tr(
"Game.Item.HealthPotion",
L10nParams.Create("name").With("level", 2)
);Localization contexts define how objects map to localization keys and dynamic values.
A custom context can implement ILocalizableContext:
using Minerva.Localizations;
public sealed class ItemL10nContext : ILocalizableContext
{
private readonly ItemData item;
public ItemL10nContext(ItemData item)
{
this.item = item;
}
public string BaseKeyString => $"Game.Item.{item.ID}";
public object GetEscapeValue(string escapeKey, L10nParams parameters)
{
return escapeKey switch
{
"level" => item.Level,
"rarity" => item.Rarity,
_ => escapeKey
};
}
}For reusable object mappings, derive from L10nContext and register it:
using Minerva.Localizations;
public sealed class ItemL10nContext : L10nContext
{
private ItemData item;
protected override void Parse(object value)
{
item = (ItemData)value;
BaseKeyString = $"Game.Item.{item.ID}";
BaseValue = item;
}
}Register the context during initialization:
L10nContext.Register<ItemL10nContext, ItemData>();Use allowInheritance: true when the same context should apply to child classes:
L10nContext.Register<ItemL10nContext, ItemData>(allowInheritance: true);L10nContext can resolve dynamic values from the context object:
Game.Item.HealthPotion.desc: "Restores {amount} HP."If itemData.amount exists, {amount} can be resolved from BaseValue.
You can also override GetEscapeValue:
public override object GetEscapeValue(string escapeKey, L10nParams parameters)
{
switch (escapeKey)
{
case "level":
return parameters.GetVariableOrDefault("level", 0);
case "power":
int level = parameters.GetVariableOrDefault("level", 0);
return item.GetPower(level);
default:
return base.GetEscapeValue(escapeKey, parameters);
}
}Global values are useful for input labels, platform names, player names, and other shared values:
L10nContext.GlobalEscapeValue["Key_Confirm"] = static (_, _) => "Space";Language entry:
Game.UI.ConfirmHint.info: "Press {Key_Confirm} to continue."A single context can register temporary resolvers:
var context = L10nContext.Of(itemData);
context.LocalEscapeValue["bonus"] = static (_, _) => "10";LocalEscapeValue stores DynamicValueProvider delegates, and the delegate returns string.
Localization entries are resolved through a small pipeline:
$...$/$@...$key references{...}dynamic values and expressions§...§color tags- nested results, until the maximum recursion depth is reached
The maximum recursion depth is controlled by L10n.MAX_RECURSION.
Game.UI.Player.Level.info: "Level: {level}"Runtime:
L10n.Tr("Game.UI.Player.Level.info", L10nParams.Create().With("level", 5));Dynamic values can be resolved from:
- variables passed through
L10nParams.With(...) - values provided by the active context
- registered global escape-value providers
If a dynamic value cannot be resolved, the output falls back to the variable name or records diagnostics, depending on the translation path.
Dynamic values support a format segment after :.
Game.UI.Timer.info: "Time: {seconds:0.0}s"The format is applied to the final value:
Game.Skill.Duration.info: "Duration: {windup + lifetime:0.0}s"{...} can contain simple expressions:
Game.Skill.Cooldown.info: "Cooldown: {cooldown}s"
Game.Skill.Damage.info: "Damage: {damage:0}"
Game.Skill.Duration.info: "Total duration: {windup + lifetime:0.0}s"Expression values can come from L10nParams, the active context, or global dynamic-value providers.
Dynamic values can receive one-off parameters with <...>:
Game.Skill.Duration.info: "Level 3 duration: {duration<level=3>:0.0}s"When this is parsed:
durationis the escape key<level=3>is converted intoL10nParams- the context can read it with
parameters.GetVariableOrDefault("level", 0)
Example context:
public sealed class SkillL10nContext : L10nContext
{
private SkillData skill;
protected override void Parse(object value)
{
skill = (SkillData)value;
BaseKeyString = $"Game.Skill.{skill.ID}";
BaseValue = skill;
}
public override object GetEscapeValue(string escapeKey, L10nParams parameters)
{
int level = parameters.GetVariableOrDefault("level", skill.Level);
switch (escapeKey)
{
case "duration":
return skill.GetDuration(level);
case "cooldown":
return skill.GetCooldown(level);
default:
return base.GetEscapeValue(escapeKey, parameters);
}
}
}Use $Other.Key$ to embed another localized string:
Game.UI.Inventory.Empty.msg: "No $Game.Item.Generic.name$ found."
Game.Item.Generic.name: "item"Use $@Other.Key$ when the reference should be imported with tooltip behavior according to the manager's reference import settings.
Example:
docs.movement.name: "Movement Control"
docs.quickStart.msg: "See $docs.movement.name$ for details."
docs.tooltip.msg: "See $@docs.movement.name$ for details."With link and underline import enabled, $@docs.movement.name$ can produce a result similar to:
<link=docs.movement.name><u>Movement Control</u></link>Use § tags for color formatting:
Game.UI.Warning.msg: "§RWarning§"
Game.UI.Rarity.Legendary.name: "§#FFD700Legendary§"
Game.UI.Element.Fire.name: "§<Fire>Fire§"Supported color selectors:
- single-letter color codes such as
R,G,B - hex colors such as
#FFD700 - named resolver tags such as
<Fire>
Color tags are converted to TextMeshPro-compatible <color=...> tags.
Single-letter colors:
| Marker | Color |
|---|---|
B / b |
Color.blue |
G / g |
Color.green |
R / r |
Color.red |
C / c |
Color.cyan |
M / m |
Color.magenta |
Y / y |
Color.yellow |
K / k |
Color.black |
W / w |
Color.white |
Named colors are resolved through ColorResolvers.Resolve(name). A project can register a custom resolver:
ColorResolvers.Register(ProjectColorPalette.Resolve);References, variables, and colors can be combined:
Game.UI.Help.msg: "§Y$@docs.movement.name$§"
Game.UI.Power.info: "Power: §R{power:0}§"Use a backslash to escape special syntax characters:
Game.UI.Example.msg: "Use \{braces\}, \$dollars\$, and \§color markers\§ literally."Template:
Game.Skill.Projectile.tip: "Projectile lasts {duration<level=1>:0.0}s. See $@docs.projectile.life.name$ - §Yimportant§."
docs.projectile.life.name: "Projectile Lifetime"With tooltip import enabled, the output can look like:
Projectile lasts 2.3s. See <link=docs.projectile.life.name><u>Projectile Lifetime</u></link> - <color=yellow>important</color>.Attach TextLocalizer to a GameObject with TMP_Text.
using Minerva.Localizations.Components;
public class Example : MonoBehaviour
{
public TextLocalizer localizer;
private void Start()
{
localizer.Load("Game.UI.MainMenu.Start.name");
}
}TextLocalizer requires a TMP_Text component and writes the translated result into textField.text.
Use TextLocalizerLegacyText for legacy UnityEngine.UI.Text.
Derive from TextLocalizerBase when the target text component is custom:
using Minerva.Localizations.Components;
public sealed class MyTextLocalizer : TextLocalizerBase
{
public MyTextComponent target;
public override void SetDisplayText(string text)
{
target.Value = text;
}
}TextLocalizerBase.Load() translates the assigned key with L10n.Tr(...) or L10n.TrKey(...) when a context is provided.
.yml files are useful as source files for key authoring.
Game.UI.Common.Confirm.name: Confirm
Game.UI.Common.Cancel.name: Cancel
Game.UI.Common.Back.name: Back.lang files hold concrete translations for a region.
Game.UI.Common.Confirm.name: Confirm
Game.UI.Common.Cancel.name: Cancel
Game.UI.Common.Back.name: BackThe package imports these files into LanguageFile assets.
LanguageFile assets contain:
tagregionentriesisMasterFilemasterFilechildFileswordSpacelistDelimiter
Master files merge their own entries and child-file entries. If duplicate keys exist, the later imported entry overrides the earlier one and a warning is logged.
Use L10nDataManager.AddKey(...) or AddKeyToFile(...) from editor scripts:
manager.AddKeyToFile(
"Game.UI.MainMenu.Start.name",
fileTag: "UI",
defaultValue: "Start"
);manager.MoveKey(oldKey, newKey);
manager.RemoveKey(key);manager.SortEntries();The manager has context-menu actions for CSV export/import. CSV export creates a table where rows are localization keys and columns are regions.
The manager can rebuild its key collection from all registered files and sources:
manager.RebuildKeyList();This is useful after importing external localization files or changing the registered source files.
The package does not require a specific key shape. A practical convention is:
Game.[Category].[OptionalSubCategory].[Name].[Suffix]
Examples:
Game.UI.MainMenu.Start.name
Game.Item.HealthPotion.name
Game.Item.HealthPotion.desc
Game.UI.Timer.info
Common suffixes:
| Suffix | Use for |
|---|---|
name |
Display names, labels, button/action names |
desc |
Stable descriptions of objects, actions, options, or effects |
msg |
Player-facing message body text |
title |
Window, panel, or section titles |
hint |
Tutorial hints or interaction hints |
info |
Context-specific labels, values, readouts, status text, or stat blocks |
tip |
Tooltip text |
Use the suffix to describe the kind of text, not the UI flow that displays it. For example, confirmation window body text can use .msg:
Game.UI.MainMenu.Quit.msg
The manager controls missing-key behavior through MissingKeySolution:
| Value | Behavior |
|---|---|
RawDisplay |
Display the raw key |
Empty |
Display an empty string |
ForceDisplay |
Display a generated value, usually based on the last part of the key |
Fallback |
Fallback to another language |
L10n.OnKeyMissing is invoked when a key cannot be resolved.
Check that this asset exists:
Assets/Resources/Localization/LocalizationSetting.asset
Then verify that its manager field references a valid L10nDataManager.
Check that:
- the target region is loaded with
L10n.Load(region) - the key exists in the active
LanguageFile - the active
LanguageFileis registered inL10nDataManager.files - the file's
regionmatches the loaded region string exactly
Check that:
- the component has a non-empty key
L10nhas loaded a region- the key exists in the active localization files
- for
TextLocalizer, the GameObject also has aTMP_Textcomponent - the component is enabled
If {value} prints as value, the variable was not found. Pass it through L10nParams.With(...) or provide it from the active context.
Check that the referenced key exists and that the $...$ marker is closed.
Check that the tag is closed:
Game.UI.Example.msg: "§RRed Text§"For nested colors, prefer TextMeshPro color tags instead of nesting § tags.
| API | Purpose |
|---|---|
L10n.Init() |
Initialize from LocalizationSettings |
L10n.Init(manager) |
Initialize from a specific L10nDataManager |
L10n.Load(region) |
Load a region |
L10n.InitAndLoad(manager, region) |
Initialize and load in one call |
L10n.Reload() |
Reload current localization |
L10n.Tr(key, params) |
Translate a key |
L10n.Tr(context, params) |
Translate an object/context |
L10n.TrKey(key, context, params) |
Translate with an explicit key and context |
L10n.TrRaw(raw, context, params) |
Resolve a raw localization string |
L10n.TryTr(...) |
Translate and return diagnostics |
L10n.TrDefault(...) |
Translate from the default region |
L10n.Contains(key) |
Check the current localization file |
L10n.Exist(key) |
Check any localization file |
L10n.Write(key, value) |
Runtime override until reload |
L10n.OptionOf(partialKey) |
Find possible key completions |