Usage / Keybinds / BLOC Format / FAQ / How It Works
Deep dive into the architecture and implementation details of PicoShot Localization.
┌────────────────────────────────────────────────────────────┐
│ Unity Application │
├────────────────────────────────────────────────────────────┤
│ UI Layer │ Code Layer │
│ ┌──────────────────────┐ │ ┌──────────────────────┐ │
│ │ LocalizationText │ │ │ LocalizationManager │ │
│ │ Component │◄──┼──┤ │ │
│ └──────────────────────┘ │ └──────────┬───────────┘ │
│ │ │ │ │
│ ▼ │ ▼ │
│ ┌──────────────────────┐ │ ┌──────────────────────┐ │
│ │ TMP_Text / Dropdown │ │ │ LanguageDefinitions │ │
│ │ Text / TextMesh │ │ │ (Metadata) │ │
│ └──────────────────────┘ │ └──────────────────────┘ │
└─────────────────────────────┴──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Data Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ LocaleData │ │ BLOC Files │ │ LocalizationConfig │ │
│ │ (Runtime) │ │ (.bloc) │ │ (ScriptableObject) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
private static void AutoInitialize()The system automatically initializes when your game starts, before the first scene loads.
Initialize()
└─► ScanAvailableLanguages()
└─► Scan Locales/ folder for .bloc files
└─► Verify hashes (if anti-tamper enabled)
└─► Filter by selected languages (if protection enabled)
└─► Extract all translation keys from default language
SetLanguage(DetectSystemLanguage())
└─► Load current language data
└─► Load fallback language data
└─► Fire OnLanguageChanged event
| Format | Size | Load Time | Memory |
|---|---|---|---|
| JSON | 100% | Slow (parse) | High |
| BLOC | 30-50% | Fast (binary) | Low |
BLOC uses:
- String Pool: Deduplicates repeated text
- Integer IDs: References strings by index
- Direct Access: No parsing needed
- Optional Compression: Deflate for smaller files
┌─────────────────────────────────────────┐
│ Header (24 bytes) │
│ - Magic: "BLOC" │
│ - Version: 1 │
│ - Flags: Compression │
│ - Language Code: "en" │
│ - Entry Count │
│ - String Count │
│ - String Pool Offset │
├─────────────────────────────────────────┤
│ Entry Table │
│ - Key ID (4 bytes) │
│ - Value ID (4 bytes) │
│ OR Array Header (4 bytes) │
│ + Item IDs (4 bytes each) │
├─────────────────────────────────────────┤
│ String Pool (UTF-8) │
│ - Variable-length strings │
│ - Length-prefixed │
├─────────────────────────────────────────┤
│ Footer (4 bytes) │
│ - CRC32 Checksum │
└─────────────────────────────────────────┘
See BLOC_FORMAT.md for complete specification.
string text = LocalizationManager.GetText("greeting");GetText("greeting")
└─► Check _currentLanguageData dictionary (O(1))
│ └─► Found? Return value
│
└─► Check _fallbackLanguageData (if different)
│ └─► Found? Return value + fire OnMissingTranslation
│
└─► Return key as fallback + fire OnMissingTranslation
string text = LocalizationManager.GetText("welcome", "Player");GetText("welcome", "Player")
└─► Get raw text: "Welcome, {0}!"
└─► string.Format("Welcome, {0}!", "Player")
└─► If RTL: Apply RtlTextHandler.Fix()
└─► Return: "Welcome, Player!"
string[] options = LocalizationManager.GetArray("menu_items");GetArray("menu_items")
└─► Check _arrayCache first (performance)
│ └─► Found? Return cached array
│
└─► Lookup in _currentLanguageData
└─► Convert List<string> to string[]
└─► If RTL: Fix each element
└─► Cache in _arrayCache
└─► Return array
bool isRtl = LanguageDefinitions.IsRightToLeft("ar");
// Checks if language code is in RightToLeftLanguages HashSetArabic Text Input
└─► TashkeelHandler (optional)
│ └─► Remove diacritical marks (if ShowTashkeel = false)
│ └─► Store positions for restoration
│
└─► ArabicLetterConverter
│ └─► Convert general forms to isolated forms
│
└─► ArabicGlyphConnector
│ └─► Determine letter positions in words
│ └─► Apply contextual forms (initial, medial, final)
│ └─► Handle Lam-Alef ligature
│
└─► TashkeelHandler (restore)
│ └─► Insert diacritical marks back
│
└─► Text Reversal
└─► Reverse character order for display
Arabic letters change shape based on position in a word:
- Isolated: ب (standalone)
- Beginning: بـ (at start)
- Middle: ـبـ (in middle)
- End: ـب (at end)
The RTL processor handles this automatically.
LocalizationTextComponent
└─► OnEnable()
│ └─► Subscribe to OnLanguageChanged
│
└─► OnLanguageChanged event fires
│ └─► UpdateText() called automatically
│
└─► UpdateText()
└─► Get translation from LocalizationManager
└─► Apply format parameters
└─► Apply text processors
└─► Update TMP_Text.text (or other component)
Processors allow runtime text transformation:
// Add processor
component.AddTextProcessor(text => $"<color=green>{text}</color>");
// Chain multiple processors
component.AddTextProcessor(text => text.ToUpper());
component.AddTextProcessor(text => $"[ {text} ]");
// Result: "[ <COLOR=GREEN>HELLO</COLOR> ]"Build Time:
└─► LocalesBuildProcessor runs
└─► Calculate SHA256 hash of each .bloc file
└─► Store hashes in LocalizationConfig
Runtime:
└─► ScanAvailableLanguages()
└─► For each file:
└─► Calculate actual hash
└─► Compare with stored hash
└─► Mismatch? Log error & skip file
Runtime:
└─► Check file name against SelectedLanguages list
└─► Not in list? Skip file
└─► This prevents loading unwanted languages
| Data | Cache Duration | Purpose |
|---|---|---|
| Current Language | Until changed | Active translations |
| Fallback Language | Until changed | Missing key fallback |
| Array Results | Until language changed | Avoid repeated conversions |
| Available Languages | Until RefreshAvailableLanguages() called | File list |
| All Keys | Until RefreshAvailableLanguages() called | Key enumeration |
- Individual
GetText()results (fast enough to calculate) - Processed text results (processors may change)
- Format parameter results (parameters change)
LocalizationManager.Dispose()
└─► Clear all events
└─► Clear language data dictionaries
└─► Clear cache
└─► Reset initialization flagCalled automatically on Application.quitting.
┌─────────────────────────────────────────┐
│ Language Editor │
├─────────────────────────────────────────┤
│ [Languages] [Keys] [Components] [...] │ ← Tab Navigation
├─────────────────────────────────────────┤
│ │
│ Left Panel │ Right Panel │
│ ┌──────────────┐ │ ┌──────────────┐ │
│ │ Keys List │ │ │ Key Details │ │
│ │ (resizable) │ │ │ - Translation│ │
│ │ │ │ │ - Hint │ │
│ │ │ │ │ - Actions │ │
│ └──────────────┘ │ └──────────────┘ │
│ │
└─────────────────────────────────────────┘
public class LocalesBuildProcessor : IPreprocessBuildWithReport, IPostprocessBuildWithReport- Preprocess: Validate files, check hashes
- Postprocess: Copy locales to build output
- Use
GetArray()for dropdowns (cached) - Use format parameters instead of string concatenation
- Subscribe to
OnLanguageChangedfor UI updates - Call
Dispose()when done (for testing)
- Call
GetText()every frame inUpdate()(cache the result) - Load all languages at once (only current + fallback needed)
- Use extremely long translation keys (affects memory)