This guide covers setting up and configuring Unity Localization, including locales, String and Asset Tables, Addressables integration, and CJK font support via Asset Tables.
0. Package Installation Check
Before doing anything else, verify that the Localization package is installed. Many APIs in this skill will fail silently or throw confusing errors if the package isn't present.
- Check by reading the project, not by asking the Package Manager. Look for
com.unity.localizationinPackages/packages-lock.json. That file records what Unity actually resolved, it is plain JSON, and reading it needs no Editor and no async call. (Packages/manifest.jsononly records what was requested, so check the lock file.) - Install if missing:
UnityEditor.PackageManager.Client.Add("com.unity.localization"). - Wait properly.
Client.AddandClient.Listare asynchronous: they return a request that is stillInProgresswhen the call returns, so reading the result in the same statement tells you nothing. Do not busy-wait onIsCompletedeither; that blocks the main thread you are running on. Instead, return after firing the install, then pollpackages-lock.jsonin a later call until the id appears. Installation also triggers a domain reload, so expect the first poll or two to fail; a fresh install typically resolves in a few seconds. - Confirm the types are actually loaded before using them, since the lock file can be written
before the assemblies are ready:
Only proceed once that returnsvar t = System.Type.GetType( "UnityEngine.Localization.Settings.LocalizationSettings, Unity.Localization"); return t != null ? "ready" : "not loaded yet";ready.
1. Localization Settings & Locales
If LocalizationEditorSettings.ActiveLocalizationSettings is null, you must find or create it:
- Find: Use
AssetDatabase.FindAssets("t:LocalizationSettings", new[] { "Assets" }). If found, load the first one and assign it toLocalizationEditorSettings.ActiveLocalizationSettings.- Always pass the search folders. An unscoped
FindAssetssearches the whole project including read-only packages, so it can return an asset from a package and you end up pointing the project at something you cannot edit. This applies to everyFindAssetscall in this skill.
- Always pass the search folders. An unscoped
- Create: If not found, create a new instance and save it to
Assets/Localization/LocalizationSettings.asset. UseScriptableObject.CreateInstance<LocalizationSettings>()followed byAssetDatabase.CreateAsset(). - Activate: Set
LocalizationEditorSettings.ActiveLocalizationSettings = settings. - Locales: Ensure locales (en, fr, de, etc.) exist. Create them if missing and add them to settings using
LocalizationEditorSettings.AddLocale(locale).
2. Modifying Localization Tables
Programmatic changes to String or Asset tables require notification to the Editor. Always create the required asset tables, unless there is already an existing one in the project.
Safe Population Pattern
When populating tables from a dataset, match by Locale.Identifier.Code explicitly. The order of GetLocales() is not guaranteed to match your input data array — assuming it does will cause silent data mismatches that are very hard to debug.
For Asset Tables, use the GUID of the asset: table.GetEntry(sharedId) ?? table.AddEntry(sharedId, guid);.
Refresh & Notification
After any modification (adding keys, updating values), notify the Editor so it can refresh its internal state. Skipping this will leave the Editor showing stale data until the next reimport.
- Call
EditorUtility.SetDirty(collection),EditorUtility.SetDirty(collection.SharedData), on each modifiedTable. - Unity 6+ Notification:
LocalizationEditorSettings.EditorEvents.RaiseCollectionModified(sender, collection); - Always call
AssetDatabase.SaveAssets()at the end.
3. UI Localization and Layout
Namespacing & Conflicts
- Always qualify names: Use
UnityEngine.UI.Image,UnityEngine.UI.VerticalLayoutGroup,UnityEngine.UI.ScrollRect,UnityEngine.UI.Mask,UnityEngine.UI.CanvasScaler,UnityEngine.UI.GraphicRaycaster,UnityEngine.UI.ContentSizeFitter,UnityEngine.UI.LayoutRebuilder, etc. UnityEngine.UIis both a namespace and a class container, so unqualified names produceCS0118(namespace used like a type). Full qualification avoids this entirely.- Single Instance: Always check
GameObject.Find("YourCanvasName")and destroy the old one before creating a new one. - Locale switching: use the package, and keep preview and runtime separate. These are two
different mechanisms, and conflating them is why locale switching often ends up hand-rolled.
- To preview a locale while authoring, use the Localization Scene Controls window
(
Window > Asset Management > Localization Scene Controls). This is Editor-only. It is not a runtime feature, so it is not the answer when the game itself needs a language setting. - To switch locale at runtime, assign
LocalizationSettings.SelectedLocale. That is the supported entry point, and everything bound throughLocalizeStringEventupdates from it. - To pin which locale the game starts in, configure a startup locale selector on the
Localization Settings asset.
SpecificLocaleSelectoris the one that forces a chosen locale; the default chain otherwise picks up the system language. - NEVER hand-roll locale state. A real in-game language menu is fine and expected, as long as
it sets
SelectedLocaleand lets the package propagate the change. What is forbidden is a debug dropdown or menu that tracks its own "current language" variable, swaps strings itself, or reaches around the package, because nothing else in the project will follow it.
- To preview a locale while authoring, use the Localization Scene Controls window
(
Localized String Events (Robust Binding)
-
Check Component Type: Identify if the target is
TextMeshProor legacyUnityEngine.UI.Text. -
Bind Correctly: add the public
UnityEngine.Localization.Components.LocalizeStringEventcomponent and wire it yourself — setStringReferenceto the table entry, then add anOnUpdateStringlistener that assigns the value to the text component (TMP_Text.textfor TextMeshPro,UnityEngine.UI.Text.textfor legacy Text).Do not reflect into
UnityEditor.Localization.Plugins.TMPro.LocalizeComponent_TMProor its UGUI counterpart. Those areinternal(measured on Localization 1.5.12), so reaching them means routing around access control to reach an API Unity makes no stability commitment about — it can change or disappear in any package release.LocalizeStringEventis public and does the same job with the wiring made explicit. -
Layout Rebuild: After setting localized text or populating a list, call
UnityEngine.UI.LayoutRebuilder.ForceRebuildLayoutImmediate(parentTransform)to ensure dimensions update.
4. Asian Language Font Support (CJK)
Avoid TMP Fallback Fonts for CJK locales. Use Asset Table Font Swapping for each specific locale instead — fallbacks are unreliable and hard to debug when glyphs are missing.
Prerequisite: TMP Essential Resources must be imported
Check this before touching any TMP API. In a project that has never imported them,
TMP_Settings.instance is null and TMP calls fail with a bare
NullReferenceException that names nothing useful. TMP_FontAsset.CreateFontAsset is one of them, so
font creation dies on the first line with an error