# 4. Asset Tables, Fonts, Icons and Voice
Part of [[Localization]]. Previous: [[3. Smart Strings Plurals and Variables]]. Next: [[5. Language Menu Saving and Runtime Scripting]].
> [!note] Verification status
> Asset binding types for UI Toolkit (`LocalizedTexture`, `LocalizedSprite`, `LocalizedFont`, `LocalizedTmpFont`, `LocalizedAudioClip`, ...) and the `style.backgroundImage` binding example come from the 1.5 UI Toolkit page; `LocalizedAsset<T>.AssetChanged` and `LoadAssetAsync()` from the API pages; `LocalizedTextCoreFontAsset` was added in 1.5.12 (changelog). Whether a `LocalizedTextCoreFontAsset` UI Toolkit binding is available in UI Builder for 6000.5 is **not verified**. **Code not compiled in Unity.**
## 4.1 Asset Table Collections
Same idea as string tables, but values are asset references: sprites, textures, audio clips, fonts, prefabs, materials.
1. Tables window > **New Table Collection** > **Asset Table Collection** named `UIAssets`.
2. Add entries: `font.body`, `icon.flag`, `vo.intro.line01`.
3. Drag the asset for each locale into its column. Empty cells fall back per your fallback settings.
## 4.2 Fonts
Fonts are the first thing that breaks: an English font has no Japanese glyphs (you see empty boxes).
- One font **per script family**, not per language: Latin/Cyrillic/Greek font, a CJK font (Noto Sans CJK / JP), Arabic/Hebrew if you need RTL.
- Bind the font per locale: in UI Toolkit, bind the label's font style to a localized font asset (UI Builder > Add binding on the font field). For TMP, use the package's localize-asset event component for TMP fonts (`LocalizedTmpFont`; look under *Add Component > Localization* - exact component name not verified for 1.5).
- Or use **fallback font assets** in TextCore/TMP so the base font falls back to CJK glyphs - fewer bindings, bigger memory. Pick one strategy per project.
- **Right-to-left** (Arabic, Hebrew) needs shaping and RTL layout support from your text system - check your UI stack before promising RTL.
## 4.3 Icons over words (Level Up! Level 8)
Rogers' HUD advice - simple symbols, minimal text - is also the cheapest localization. Use icons for health, ammo, map, quest marker; use words only where meaning needs them. When an icon *is* culture-specific (mailbox, hand gestures, flags, colors with meaning), localize the **icon**:
```xml
<!-- UXML: localized background image for a culture-specific icon -->
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:l="UnityEngine.Localization">
<ui:VisualElement name="mail-icon" class="hud-icon">
<Bindings>
<l:LocalizedTexture property="style.backgroundImage" table="UIAssets" entry="icon.mail" />
</Bindings>
</ui:VisualElement>
</ui:UXML>
```
## 4.4 Script: localized voice line with subtitle
A voice line = a localized `AudioClip` + a localized subtitle string with the same key stem. Ties into cutscenes (Level 17) and dialogue ([[0.Dialogue System]]).
```csharp
// Assets/Scripts/Localization/LocalizedVoiceLine.cs
using System.Collections;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.ResourceManagement.AsyncOperations;
[RequireComponent(typeof(AudioSource))]
public class LocalizedVoiceLine : MonoBehaviour
{
public LocalizedAudioClip clip = new LocalizedAudioClip { TableReference = "Voice", TableEntryReference = "vo.intro.line01" };
public LocalizedString subtitle = new LocalizedString("Dialogue", "vo.intro.line01");
public System.Action<string> onSubtitle; // hook your subtitle UI here
AudioSource source;
void Awake() => source = GetComponent<AudioSource>();
public void Play() => StartCoroutine(PlayRoutine());
IEnumerator PlayRoutine()
{
AsyncOperationHandle<AudioClip> clipOp = clip.LoadAssetAsync();
AsyncOperationHandle<string> textOp = subtitle.GetLocalizedStringAsync();
yield return clipOp;
yield return textOp;
if (textOp.Status == AsyncOperationStatus.Succeeded) onSubtitle?.Invoke(textOp.Result);
if (clipOp.Status == AsyncOperationStatus.Succeeded && clipOp.Result != null)
{
source.clip = clipOp.Result;
source.Play();
yield return new WaitWhile(() => source.isPlaying);
}
else
{
// No VO for this locale: keep the subtitle on screen for a reading-time estimate.
float seconds = textOp.Status == AsyncOperationStatus.Succeeded ? 1.5f + textOp.Result.Length * 0.05f : 2f;
yield return new WaitForSeconds(seconds);
}
onSubtitle?.Invoke(string.Empty);
}
}
```
**Explanation**
- `LoadAssetAsync()` and `GetLocalizedStringAsync()` return Addressables handles; yielding them in a coroutine waits for completion.
- The fallback path matters: many games ship VO in 2-3 languages and subtitles in 12. Reading time scales with string length (a crude but honest estimate; CJK reads faster per character - tune per locale).
- Route the AudioSource through the Dialogue mixer group so ducking works ([[Audio Mixers and Character Audio]]).
## 4.5 Exercise
Localize the HUD so that German needs **zero** extra UI space: convert every label you can into an icon, keep only the quest title as text. Compare with Level 8's clean-screen advice in [[Level Up! (Review)]].
## Sources
- Asset Tables: https://docs.unity3d.com/Packages/
[email protected]/manual/AssetTables.html
- UI Toolkit bindings (asset types, style.backgroundImage): https://docs.unity3d.com/Packages/
[email protected]/manual/UIToolkit.html
- `LocalizedAudioClip` API: https://docs.unity3d.com/Packages/
[email protected]/api/UnityEngine.Localization.LocalizedAudioClip.html
- `LocalizedAsset<T>` API (AssetChanged, LoadAssetAsync): https://docs.unity3d.com/Packages/
[email protected]/api/UnityEngine.Localization.LocalizedAsset-1.html
- Changelog (1.5.12 LocalizedTextCoreFontAsset, dropdowns): https://docs.unity3d.com/Packages/
[email protected]/changelog/CHANGELOG.html