Quick answer: The most common cause is accessing the table before the Localization system has finished its asynchronous initialization. The LocalizationSettings.InitializationOperation must complete before any table lookups will return valid data.
Here is how to fix Unity localization table not loading. You set up the Unity Localization package, created a String Table Collection, added entries for every locale, and wired up a LocalizedString reference in your UI — but at runtime the text is blank. The table lookup returns null or an empty string, and no error appears in the console. This is one of the most frustrating localization bugs because everything looks correct in the editor. The problem is almost always related to async initialization timing, missing preload configuration, or a locale that was never selected.
The Symptom
You have a StringTableCollection with entries for multiple locales. In the editor, previewing the table works fine and shows the correct translations. But when you enter Play mode or build the game, your UI text components show empty strings, placeholder keys, or throw NullReferenceException errors when you try to access the localized values from code.
The issue is particularly confusing because it can be intermittent. Sometimes the strings load correctly, especially if the scene takes a moment to set up before accessing the table. Other times, on a fast machine or in a build where the first frame runs immediately, every localized string is empty. This intermittency is the hallmark of an async timing bug.
You might also notice that the problem only affects certain tables. If you have multiple String Table Collections, some may load while others return null. This points to a preload configuration issue rather than a global initialization failure.
What Causes This
1. Accessing the table before initialization completes. The Unity Localization package initializes asynchronously. When the game starts, the system must load locale metadata, determine the selected locale, and fetch the relevant table data. If your Start() or Awake() method queries a string table before this process finishes, the table reference resolves to null. The LocalizationSettings.InitializationOperation may still be in progress, and the table data simply does not exist in memory yet.
2. String Table Collection not set to preload. By default, string tables are loaded on demand when they are first accessed. If your code requests a table entry on the very first frame, the on-demand load has not had time to complete. Enabling preload on the collection forces the Localization system to load the table during its initialization phase, so the data is ready before any gameplay code runs.
3. No locale selected. The Localization system needs a selected locale to know which column of the table to read. If LocalizationSettings.SelectedLocale is null — which can happen if no Locale assets exist in the project, if the Locale Generator was not used, or if the project-specific locale was removed — every table lookup returns null because there is no locale to resolve entries against.
4. Incorrect TableReference or TableEntryReference. The TableReference uses the table collection name, not the asset file name. If you renamed the collection in the editor but your code still references the old name, the lookup fails silently and returns an empty string.
The Fix
Step 1: Wait for localization initialization before accessing tables. The most reliable approach is to await the initialization operation before performing any table lookups. You can do this with a coroutine or an async method.
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.Settings;
using UnityEngine.Localization.Tables;
using System.Collections;
public class LocalizedUIText : MonoBehaviour
{
[SerializeField] private string tableName = "UI_Strings";
[SerializeField] private string entryKey = "welcome_message";
private IEnumerator Start()
{
// Wait for the Localization system to finish initializing
yield return LocalizationSettings.InitializationOperation;
// Now it is safe to access string tables
var tableOp = LocalizationSettings.StringDatabase
.GetTableAsync(tableName);
yield return tableOp;
StringTable table = tableOp.Result;
if (table != null)
{
var entry = table.GetEntry(entryKey);
if (entry != null)
{
Debug.Log($"Localized value: {entry.LocalizedValue}");
}
}
}
}
The key insight is that Start() as a coroutine allows you to yield return the initialization operation. This pauses your method until the Localization system is fully ready. If you prefer async/await, you can use the Task-based pattern instead.
using UnityEngine;
using UnityEngine.Localization.Settings;
using System.Threading.Tasks;
public class AsyncLocalizedLoader : MonoBehaviour
{
private async void Start()
{
// Await initialization using the Task property
await LocalizationSettings.InitializationOperation.Task;
var selectedLocale = LocalizationSettings.SelectedLocale;
Debug.Log($"Locale ready: {selectedLocale.Identifier.Code}");
// Safe to read from tables now
var localizedValue = await LocalizationSettings.StringDatabase
.GetLocalizedStringAsync("UI_Strings", "welcome_message").Task;
Debug.Log($"Localized string: {localizedValue}");
}
}
Step 2: Enable preloading on your String Table Collections. Open Edit > Project Settings > Localization. In the String Table Collections list, select each collection and check the Preload toggle. This instructs the Localization system to load the table data during its initialization phase. Without preloading, the first call to GetTableAsync triggers a fresh load, which adds latency and can return null if you do not await it.
// You can also verify preload status at runtime
using UnityEngine;
using UnityEngine.Localization.Settings;
public class PreloadDiagnostic : MonoBehaviour
{
private IEnumerator Start()
{
yield return LocalizationSettings.InitializationOperation;
var tables = LocalizationSettings.StringDatabase
.GetAllTables();
yield return tables;
foreach (var table in tables.Result)
{
Debug.Log($"Table loaded: {table.TableCollectionName}, " +
$"entries: {table.Count}");
}
}
}
If a table shows zero entries after initialization, it was either not preloaded or contains no entries for the selected locale.
Step 3: Verify the selected locale and table references. A null locale is the silent killer of localization lookups. The system will not throw an exception — it will simply return nothing.
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.Settings;
using System.Collections.Generic;
public class LocaleDiagnostic : MonoBehaviour
{
private IEnumerator Start()
{
yield return LocalizationSettings.InitializationOperation;
Locale selected = LocalizationSettings.SelectedLocale;
if (selected == null)
{
Debug.LogError("No locale selected! Table lookups will return null.");
// Attempt to fall back to the first available locale
List<Locale> available = LocalizationSettings.AvailableLocales.Locales;
if (available.Count > 0)
{
LocalizationSettings.SelectedLocale = available[0];
Debug.Log($"Fell back to locale: {available[0].Identifier.Code}");
}
else
{
Debug.LogError("No locales available. Add Locale assets via " +
"Edit > Project Settings > Localization > Locale Generator.");
}
}
else
{
Debug.Log($"Selected locale: {selected.Identifier.Code}");
}
}
}
Also verify that your TableReference matches the collection name exactly. Open the String Table Collection asset in the inspector and note the Table Collection Name field. This is what you pass to GetTableAsync or GetLocalizedStringAsync, not the file name of the asset.
Why This Works
The Localization package is designed around asynchronous operations because loading locale data from disk or Addressables can take a variable amount of time. The initialization operation loads the locale list, determines the player's preferred locale based on system language, and preloads any tables that are configured for it. By awaiting this operation, you guarantee that the entire pipeline is warm before you ask it for data.
Preloading changes when tables enter memory. Without preloading, a table is fetched on first access, which means your first GetTableAsync call triggers a disk read. If you yield on that call, you get the data — but if you try to use it synchronously or in the same frame, you get null. Preloading moves that disk read into the initialization phase, so by the time your code runs after awaiting initialization, the table is already resident.
The locale check is essential because the Localization system uses the selected locale as a key to look up the correct column in each table. Without a selected locale, the system does not know which translation to return. The fallback pattern shown above ensures that even if the player's system language does not match any of your locales, the game still has a usable default.
"The Localization package is deceptively quiet when things go wrong. No exceptions, no warnings — just empty strings. The initialization await is the single most important line you can add."
Common Variations
If you are using Addressables with your Localization tables, the async timing becomes even more critical. Addressable-based tables may require network fetches or additional bundle loads, which increases the time between initialization start and completion. Always await the initialization operation regardless of your asset pipeline.
If you switch locales at runtime using LocalizationSettings.SelectedLocale = newLocale, the tables for the new locale may need to be loaded. Listen for the LocalizationSettings.SelectedLocaleChanged event and re-fetch your strings after it fires. Do not assume that changing the locale instantly updates all cached values.
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.Settings;
public class LocaleSwitcher : MonoBehaviour
{
private void OnEnable()
{
LocalizationSettings.SelectedLocaleChanged += OnLocaleChanged;
}
private void OnDisable()
{
LocalizationSettings.SelectedLocaleChanged -= OnLocaleChanged;
}
private void OnLocaleChanged(Locale newLocale)
{
Debug.Log($"Locale changed to: {newLocale.Identifier.Code}");
// Re-fetch all displayed strings here
RefreshAllLocalizedUI();
}
private void RefreshAllLocalizedUI()
{
// Your logic to update text components
}
}
Related Issues
If your localization tables load but specific entries return the key name instead of the translated string, the entry likely does not exist for the selected locale. Check the String Table Collection and ensure every locale column has a value for that key. If you are dealing with font rendering issues for CJK locales, that is a separate problem related to font asset coverage and TMP font atlases, not table loading.
Await initialization. Enable preload. Check the locale. In that order.