# 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