# 3. Smart Strings, Plurals and Variables Part of [[Localization]]. Previous: [[2. Localizing UI Toolkit and uGUI Text]]. Next: [[4. Asset Tables Fonts Icons and Voice]]. > [!note] Verification status > Smart flag (entry menu > Smart Format, or `IsSmart`), plural syntax `{0:plural:an apple|{} apples}` with CLDR categories, conditional `{global.my-bool:Open|Closed}`, Persistent Variables `{group-name.variable-name}`, `PersistentVariablesSource` via `LocalizationSettings.StringDatabase.SmartFormatter.GetSourceExtension<PersistentVariablesSource>()`, and `using (PersistentVariablesSource.UpdateScope())` all appear on the Localization 1.5 Smart Strings / Plural / Persistent Variables pages (2026-11-10). The namespace `UnityEngine.Localization.SmartFormat.Extensions` for `PersistentVariablesSource` is from memory - if it doesn't resolve, let your IDE add the using. **Code not compiled in Unity.** ## 3.1 Turn Smart on Placeholders only work in entries marked **Smart**: in the Tables window, entry menu (⋮) > **Smart Format**. A non-smart entry prints `{value}` literally - the most common "it doesn't work" report. ## 3.2 Placeholder cookbook | Need | Smart String (en) | Notes | |---|---|---| | Named value | `HP {value}` | Name matches the local variable / argument | | Number format | `Score: {value:N0}` | Culture-aware thousands separator | | Plural | `{count:plural:1 key\|{} keys}` | Uses the *table's* locale CLDR rules; Russian needs 3 forms, Japanese 1 | | Forced plural locale | `{0:plural(en):is 1 item\|are {} items}` | Rarely needed | | Conditional | `The door is {global.door-open:Open\|Closed}.` | bool -> first/second branch | | List | `{0:list:{}\|, \|, and }` | Language-specific separators belong in each table | | Nested translation | `You found {item}.` where `item` is a `LocalizedString` variable | The item name is itself translated | | Global value | `Welcome back, {global.player-name}!` | No script argument needed | Translators edit the whole sentence, including where `{count}` goes. That's the point. ## 3.3 Local variables vs global variables - **Local variables** live on one `LocalizedString` (set in Inspector/UXML or `localizedString.Add("name", variable)` - part 2). - **Global variables** live in a **Variables Group** asset (`Assets > Create > Localization > Variables Group`) assigned to the **Persistent Variables Source** in Localization Settings > String Database > Smart Format > Sources. Group names must be unique and have no spaces (e.g. `global`). Use globals for values many strings share: player name, currency, difficulty, platform-specific button names. ## 3.4 Script: update global variables ```csharp // Assets/Scripts/Localization/GlobalTextVariables.cs using UnityEngine; using UnityEngine.Localization.Settings; using UnityEngine.Localization.SmartFormat.Extensions; using UnityEngine.Localization.SmartFormat.PersistentVariables; /// Pushes game state into the "global" Variables Group so every Smart String using /// {global.player-name} or {global.gold} refreshes automatically. public class GlobalTextVariables : MonoBehaviour { public string groupName = "global"; StringVariable playerName; IntVariable gold; BoolVariable usingGamepad; void Start() { var source = LocalizationSettings.StringDatabase.SmartFormatter .GetSourceExtension<PersistentVariablesSource>(); if (source == null) { Debug.LogError("Add a Persistent Variables Source to the Smart Format sources."); return; } var group = source[groupName]; playerName = group["player-name"] as StringVariable; gold = group["gold"] as IntVariable; usingGamepad = group["using-gamepad"] as BoolVariable; } public void SetPlayerName(string value) { if (playerName != null) playerName.Value = value; } public void SetGold(int value) { if (gold != null) gold.Value = value; } /// Batch several changes into one refresh of every affected string. public void ApplyProfile(string name, int coins, bool gamepad) { using (PersistentVariablesSource.UpdateScope()) { SetPlayerName(name); SetGold(coins); if (usingGamepad != null) usingGamepad.Value = gamepad; } } } ``` **Explanation** - Each variable type (`StringVariable`, `IntVariable`, `BoolVariable`...) notifies listeners when `Value` changes; every LocalizedString that references it re-formats - **Observer** with zero wiring per label. - `UpdateScope()` defers refreshes until the scope ends, so loading a save doesn't re-format the entire UI three times. - `using-gamepad` drives prompts like `Press {global.using-gamepad:(A)|[E]} to open` - hook it to device changes from [[Input System]] (Level Up! context-sensitive prompts). ## 3.5 Exercise Write the English, German and Russian forms of `You have {count} new messages` with plurals. Test 0, 1, 2, 5, 21. Russian uses one/few/many - which your English-only brain will get wrong without the CLDR rules. ## Sources - Smart Strings: https://docs.unity3d.com/Packages/[email protected]/manual/Smart/SmartStrings.html - Plural formatter: https://docs.unity3d.com/Packages/[email protected]/manual/Smart/Plural-Formatter.html - Persistent Variables source: https://docs.unity3d.com/Packages/[email protected]/manual/Smart/Persistent-Variables-Source.html - `LocalizedStringDatabase` (SmartFormatter): https://docs.unity3d.com/Packages/[email protected]/api/UnityEngine.Localization.Settings.LocalizedStringDatabase.html