# 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