# 5. Language Menu, Saving and Runtime Scripting
Part of [[Localization]]. Previous: [[4. Asset Tables Fonts Icons and Voice]]. Next: [[6. Localizing Game Data Dialogue and Pseudo-Localization]].
> [!note] Verification status
> `LocalizationSettings.InitializationOperation`, `SelectedLocale`, `SelectedLocaleChanged`, `AvailableLocales`, `StringDatabase.GetLocalizedStringAsync(TableReference, TableEntryReference, ...)`, `Locale.LocaleName`, `PlayerPrefLocaleSelector` (stores the code with `PlayerPrefs.SetString`) from the 1.5 API/manual pages (2026-11-10). `LocaleIdentifier.CultureInfo` and `DropdownField.choices/index` are from memory of the APIs. **Code not compiled in Unity.**
## 5.1 Initialization
The system loads locales and preloaded tables asynchronously. Anything that reads `SelectedLocale` or strings at startup should wait for `LocalizationSettings.InitializationOperation`. Bindings (part 2) and `StringChanged` handle this for you; code that calls `GetLocalizedString()` synchronously doesn't (and `WaitForCompletion` isn't supported on WebGL).
## 5.2 Persisting the player's choice
Order the **Startup Locale Selectors**: Command Line -> **Player Pref** -> System -> Specific. The Player Pref selector stores the chosen locale code with `PlayerPrefs.SetString`, so a player who picked German stays in German even if their OS is English. Put **System** after it so first launch follows the device language. (Command line first lets QA force a language: `-language=ja`.)
If your save system already owns settings ([[High Scores, Saves and Player Stats]], [[PlayerPrefs Advanced]]), store the locale code there too and drop the Player Pref selector - one source of truth.
## 5.3 Script: language dropdown (UI Toolkit)
UXML: a `DropdownField` named `language` in your settings screen.
```csharp
// Assets/Scripts/Localization/LanguageMenu.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.Settings;
using UnityEngine.UIElements;
[RequireComponent(typeof(UIDocument))]
public class LanguageMenu : MonoBehaviour
{
public string dropdownName = "language";
DropdownField dropdown;
List<Locale> locales;
bool suppress;
IEnumerator Start()
{
yield return LocalizationSettings.InitializationOperation;
dropdown = GetComponent<UIDocument>().rootVisualElement.Q<DropdownField>(dropdownName);
if (dropdown == null) { Debug.LogWarning("Language dropdown not found"); yield break; }
locales = LocalizationSettings.AvailableLocales.Locales;
var names = new List<string>(locales.Count);
foreach (Locale l in locales) names.Add(NativeName(l));
dropdown.choices = names;
Sync(LocalizationSettings.SelectedLocale);
dropdown.RegisterValueChangedCallback(OnDropdownChanged);
LocalizationSettings.SelectedLocaleChanged += Sync;
}
void OnDestroy() => LocalizationSettings.SelectedLocaleChanged -= Sync;
// Show each language in its own language: "Deutsch", "日本語" - players find their language
// even when the UI is in one they can't read. Needs a font that covers every script (part 4).
static string NativeName(Locale l)
{
var culture = l.Identifier.CultureInfo;
return culture != null ? culture.NativeName : l.LocaleName;
}
void OnDropdownChanged(ChangeEvent<string> evt)
{
if (suppress) return;
int i = dropdown.index;
if (i >= 0 && i < locales.Count) LocalizationSettings.SelectedLocale = locales[i];
}
void Sync(Locale selected)
{
if (dropdown == null || locales == null) return;
suppress = true;
dropdown.index = locales.IndexOf(selected);
suppress = false;
}
}
```
**Explanation**
- `yield return` on the initialization handle is safe in a coroutine and avoids blocking the main thread.
- The `suppress` flag prevents a feedback loop: changing the locale raises `SelectedLocaleChanged`, which sets the dropdown, which would otherwise raise a change event.
- Since 1.5.12 the package can also localize **dropdown choices** themselves (bind UI Toolkit dropdown choices to a `LocalizedStringList`/`LocalizedStringGroup`) - useful for option menus like "Low/Medium/High". The language list should *not* be localized; native names are the convention.
## 5.4 Script: strings from code
For logs, notifications, or values you compose at runtime:
```csharp
// Assets/Scripts/Localization/Loc.cs
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Localization.Settings;
using UnityEngine.ResourceManagement.AsyncOperations;
/// Tiny facade over LocalizationSettings (a Service Locator in practice).
public static class Loc
{
/// Async: preferred. Calls back on completion.
public static IEnumerator Get(string table, string entry, Action<string> done, params object[] args)
{
AsyncOperationHandle<string> op = LocalizationSettings.StringDatabase.GetLocalizedStringAsync(table, entry, args);
yield return op;
done?.Invoke(op.Status == AsyncOperationStatus.Succeeded ? op.Result :
quot;#{table}/{entry}#");
}
/// Sync: blocks until loaded (not supported on WebGL). OK after initialization with preloaded tables.
public static string GetNow(string table, string entry, params object[] args) =>
LocalizationSettings.StringDatabase.GetLocalizedString(table, entry, args);
}
```
Usage: `StartCoroutine(Loc.Get("UI", "toast.saved", s => toast.Show(s)));`
Returning `#table/entry#` on failure makes missing strings obvious in playtests instead of silently blank.
## 5.5 Exercise
Add the language dropdown to the settings screen, pick German, quit, relaunch: still German? Then launch with `-language=ja` from a shortcut: Japanese wins (command line selector first).
## Sources
- `LocalizationSettings` API: https://docs.unity3d.com/Packages/[email protected]/api/UnityEngine.Localization.Settings.LocalizationSettings.html
- Startup Locale Selectors (Player Pref, Command Line, System): https://docs.unity3d.com/Packages/[email protected]/manual/LocaleSelector.html
- `PlayerPrefLocaleSelector` API: https://docs.unity3d.com/Packages/[email protected]/api/UnityEngine.Localization.Settings.PlayerPrefLocaleSelector.html
- `Locale` API: https://docs.unity3d.com/Packages/[email protected]/api/UnityEngine.Localization.Locale.html
- Scripting overview: https://docs.unity3d.com/Packages/[email protected]/manual/Scripting.html
- `LocalizedStringDatabase` API: https://docs.unity3d.com/Packages/[email protected]/api/UnityEngine.Localization.Settings.LocalizedStringDatabase.html