Skip to content

Localizer

Samera2022 edited this page Jan 30, 2026 · 1 revision

Localizer

Relevant source files

Purpose and Scope

The Localizer utility class provides internationalization (i18n) functionality for the MouseMacros application. It manages loading, caching, and retrieval of localized strings from JSON language files. This system supports seven languages (English, Chinese, Japanese, Korean, Russian, Spanish, and French) and provides automatic system language detection with English fallback.

For information about the language file structure and supported translation keys, see Language Files and Translation Keys. For details on how appearance settings control language selection, see Configuration Files and Settings.

Sources: src/io/github/samera2022/mouse_macros/Localizer.java L1-L98


Architecture Overview

The Localizer operates as a singleton utility with static methods, loading translations at application startup and providing runtime access to localized strings throughout the UI layer.

System Context Diagram

flowchart TD

StaticInit["Localizer static block"]
EnvDetect["Environment Detection"]
SysLangDetect["System Language Detection"]
DevFiles["Development Mode<br>lang/*.json files"]
JarFiles["Production Mode<br>JAR resources"]
LocalizerClass["Localizer"]
TranslationMap["translations Map"]
CurrentLang["currentLang String"]
DevModeFlag["isDevMode boolean"]
RuntimeSwitchFlag["runtimeSwitch boolean"]
LoadMethod["load(String lang)"]
GetMethod["get(String key)"]
IsDevMode["isDevMode()"]
GetCurrentLang["getCurrentLang()"]
SetRuntimeSwitch["setRuntimeSwitch(boolean)"]
ConfigManager["ConfigManager.getAvailableLangs()"]
UIComponents["MainFrame, Dialogs, Listeners"]
SettingsDialog["SettingsDialog<br>language selection"]
PrimaryLookup["Primary Language Lookup"]
EnglishFallback["English Fallback<br>en_us.json"]
KeyReturn["Return key if not found"]

EnvDetect --> DevModeFlag
SysLangDetect --> LoadMethod
DevModeFlag --> DevFiles
DevModeFlag --> JarFiles
DevFiles --> LoadMethod
JarFiles --> LoadMethod
LoadMethod --> TranslationMap
LoadMethod --> CurrentLang
GetMethod --> PrimaryLookup
LocalizerClass --> LoadMethod
LocalizerClass --> GetMethod
LocalizerClass --> IsDevMode
LocalizerClass --> GetCurrentLang
LocalizerClass --> SetRuntimeSwitch
ConfigManager --> IsDevMode
ConfigManager --> JarFiles
UIComponents --> GetMethod
SettingsDialog --> LoadMethod
TranslationMap --> GetMethod
CurrentLang --> GetMethod

subgraph subGraph5 ["Fallback Mechanism"]
    PrimaryLookup
    EnglishFallback
    KeyReturn
    PrimaryLookup --> EnglishFallback
    EnglishFallback --> KeyReturn
end

subgraph subGraph4 ["Integration Points"]
    ConfigManager
    UIComponents
    SettingsDialog
end

subgraph subGraph3 ["API Methods"]
    LoadMethod
    GetMethod
    IsDevMode
    GetCurrentLang
    SetRuntimeSwitch
end

subgraph subGraph2 ["Localizer Core"]
    LocalizerClass
    TranslationMap
    CurrentLang
    DevModeFlag
    RuntimeSwitchFlag
end

subgraph subGraph1 ["Language File Sources"]
    DevFiles
    JarFiles
end

subgraph subGraph0 ["Application Initialization"]
    StaticInit
    EnvDetect
    SysLangDetect
    StaticInit --> EnvDetect
    StaticInit --> SysLangDetect
end
Loading

Sources: src/io/github/samera2022/mouse_macros/Localizer.java L1-L98, src/io/github/samera2022/mouse_macros/manager/ConfigManager.java L79-L132


Environment Detection

The Localizer determines its operational mode (development or production) during static initialization by checking for the presence of language files in the file system.

Development vs Production Mode

Mode Detection Method Language File Source Use Case
Development Checks if lang/zh_cn.json file exists Reads from lang/ directory in project IDE/development environment
Production File check fails Reads from JAR resources lang/*.json Packaged application

Environment Detection Logic

flowchart TD

Start["Static Initialization"]
CheckFile["Check File:<br>new File('lang/zh_cn.json').exists()"]
SetDevMode["isDevMode = true"]
SetProdMode["isDevMode = false"]
DetectSysLang["Detect System Language<br>Locale.getDefault()"]
NormalizeLang["Normalize language code<br>replace '-' with '_'"]
CheckLangFile["Language file<br>exists?"]
UseSysLang["Use system language"]
FallbackEnglish["Fallback to en_us"]
LoadLang["load(currentLang)"]

Start --> CheckFile
CheckFile --> SetDevMode
CheckFile --> SetProdMode
SetDevMode --> DetectSysLang
SetProdMode --> DetectSysLang
DetectSysLang --> NormalizeLang
NormalizeLang --> CheckLangFile
CheckLangFile --> UseSysLang
CheckLangFile --> FallbackEnglish
UseSysLang --> LoadLang
FallbackEnglish --> LoadLang
Loading

Code Implementation:

Sources: src/io/github/samera2022/mouse_macros/Localizer.java L15-L33


Language Loading Process

The load(String lang) method is the core mechanism for loading translation files into memory. It handles both development and production environments differently.

Loading Flow

sequenceDiagram
  participant Caller
  participant load(lang)
  participant isDevMode check
  participant File System
  participant ClassLoader
  participant Gson Parser
  participant translations Map

  Caller->>load(lang): load("zh_cn")
  load(lang)->>isDevMode check: Check isDevMode
  loop [Development Mode]
    isDevMode check->>File System: new File("lang/zh_cn.json")
    File System->>File System: Check existence
    File System-->>load(lang): FileReader
    load(lang)->>Gson Parser: fromJson(reader, Map.class)
    isDevMode check->>ClassLoader: getResourceAsStream("lang/zh_cn.json")
    ClassLoader-->>load(lang): InputStream
    load(lang)->>Gson Parser: fromJson(InputStreamReader, Map.class)
  end
  Gson Parser-->>load(lang): Map<String, String>
  load(lang)->>translations Map: Update translations
  load(lang)->>load(lang): currentLang = "zh_cn"
  load(lang)-->>Caller: Success
  note over load(lang): On exception: translations = empty HashMap
Loading

Path Resolution

Environment File Path Access Method
Development /lang/{lang}.json or lang/{lang}.json new FileReader(file)
Production lang/{lang}.json (JAR resource) ClassLoader.getResourceAsStream(path)

Code Implementation:

Sources: src/io/github/samera2022/mouse_macros/Localizer.java L47-L65


Translation Retrieval and Fallback Mechanism

The get(String key) method implements a two-tier fallback strategy to ensure that UI text is always available, even when translations are incomplete.

Retrieval Logic Flow

flowchart TD

GetCall["get(key)"]
PrimaryLookup["translations.get(key)"]
CheckValue["value != null?"]
ReturnValue["Return value"]
CheckCurrent["currentLang == 'en_us'?"]
LoadEnglish["Load en_us.json"]
EnglishLookup["enMap.get(key)"]
CheckEnValue["enValue != null?"]
ReturnEnValue["Return enValue"]
ReturnKey["Return key<br>(untranslated)"]

GetCall --> PrimaryLookup
PrimaryLookup --> CheckValue
CheckValue --> ReturnValue
CheckValue --> CheckCurrent
CheckCurrent --> ReturnKey
CheckCurrent --> LoadEnglish
LoadEnglish --> EnglishLookup
EnglishLookup --> CheckEnValue
CheckEnValue --> ReturnEnValue
CheckEnValue --> ReturnKey
Loading

Fallback Behavior Table

Scenario Primary Lookup English Fallback Final Result
Key exists in current language ✓ Found Not attempted Localized string
Key missing, current is English ✗ Not found Not applicable Key string
Key missing, current is not English ✗ Not found Attempted English string or key
All lookups fail ✗ Not found ✗ Not found Key string

Code Implementation:

Sources: src/io/github/samera2022/mouse_macros/Localizer.java L67-L93


Runtime Language Switching

The Localizer supports runtime language switching through a control flag and manual reload mechanism.

Runtime Switch Mechanism

flowchart TD

RuntimeSwitch["runtimeSwitch boolean"]
SetMethod["setRuntimeSwitch(boolean)"]
IsMethod["isRuntimeSwitch()"]
UserAction["User selects new language"]
ConfigUpdate["ConfigManager updates config.lang"]
LoadCall["Localizer.load(newLang)"]
MapUpdate["translations Map updated"]
CurrentLangUpdate["currentLang updated"]
ComponentUtil["ComponentUtil triggers resize"]
FrameAdjust["Frame adjustments based on<br>readjustFrameMode"]
TextRefresh["UI components re-fetch strings"]

MapUpdate --> ComponentUtil

subgraph subGraph2 ["UI Update"]
    ComponentUtil
    FrameAdjust
    TextRefresh
    ComponentUtil --> FrameAdjust
    FrameAdjust --> TextRefresh
end

subgraph subGraph1 ["Language Change Process"]
    UserAction
    ConfigUpdate
    LoadCall
    MapUpdate
    CurrentLangUpdate
    UserAction --> ConfigUpdate
    ConfigUpdate --> LoadCall
    LoadCall --> MapUpdate
    LoadCall --> CurrentLangUpdate
end

subgraph subGraph0 ["Control Flag"]
    RuntimeSwitch
    SetMethod
    IsMethod
    SetMethod --> RuntimeSwitch
    RuntimeSwitch --> IsMethod
end
Loading

Runtime Switch API

Method Purpose Usage
setRuntimeSwitch(boolean enable) Enable/disable runtime switching flag Called by settings dialog before language change
isRuntimeSwitch() Check if runtime switch is active Used by ComponentUtil to determine resize behavior
load(String lang) Reload translations for new language Called after config update
getCurrentLang() Get active language code Used to check current state

Code Implementation:

Sources: src/io/github/samera2022/mouse_macros/Localizer.java L35-L41, src/io/github/samera2022/mouse_macros/Localizer.java L95-L97


Integration with Configuration System

The ConfigManager works with Localizer to discover available languages and manage language selection preferences.

Available Languages Detection

The ConfigManager.getAvailableLangs() method enumerates language files using the same environment detection logic as Localizer.

flowchart TD

GetLangs["getAvailableLangs()"]
CheckMode["isDevMode()?"]
ListFiles["FileUtil.listFileNames('lang')"]
StripExt1["Strip .json extension"]
GetResURL["getResource('lang/')"]
CheckProtocol["protocol == 'jar'?"]
ParsePath["Parse JAR path"]
OpenJar["new JarFile(jarPath)"]
EnumEntries["Enumerate entries"]
FilterLang["Filter 'lang/*.json'"]
ExtractName["Extract filename"]
ListDir["List directory files"]
FilterJson["Filter .json files"]
StripExt2["Strip extension"]
BuildArray["Build String[] array"]
ReturnLangs["Return available languages"]

GetLangs --> CheckMode
CheckMode --> ListFiles
CheckMode --> GetResURL
StripExt1 --> BuildArray
ExtractName --> BuildArray
StripExt2 --> BuildArray
BuildArray --> ReturnLangs

subgraph subGraph3 ["Production Mode"]
    GetResURL
    CheckProtocol
    GetResURL --> CheckProtocol
    CheckProtocol --> ParsePath
    CheckProtocol --> ListDir

subgraph subGraph2 ["File Protocol"]
    ListDir
    FilterJson
    StripExt2
    ListDir --> FilterJson
    FilterJson --> StripExt2
end

subgraph subGraph1 ["JAR Processing"]
    ParsePath
    OpenJar
    EnumEntries
    FilterLang
    ExtractName
    ParsePath --> OpenJar
    OpenJar --> EnumEntries
    EnumEntries --> FilterLang
    FilterLang --> ExtractName
end
end

subgraph subGraph0 ["Development Mode"]
    ListFiles
    StripExt1
    ListFiles --> StripExt1
end
Loading

Language Discovery Table

Environment Discovery Method File Pattern
Development FileUtil.listFileNames("lang") lang/*.json in filesystem
Production (JAR) JAR entry enumeration Entries matching lang/*.json
Production (File) Directory listing .json files in lang/ directory

Code Implementation:

Sources: src/io/github/samera2022/mouse_macros/manager/ConfigManager.java L79-L132, src/io/github/samera2022/mouse_macros/Localizer.java L43-L45


Usage Examples Throughout the Codebase

The Localizer.get(key) method is called extensively throughout the UI layer to retrieve localized strings.

Common Usage Patterns

flowchart TD

MainFrame["MainFrame"]
Dialogs["Various Dialogs"]
Listeners["Event Listeners"]
GetButton["Localizer.get('button.record')"]
GetLog["Localizer.get('log.recording_mouse_pressed')"]
GetTooltip["Localizer.get('tooltip.enable_quick_mode')"]
GetLabel["Localizer.get('label.language')"]
UIKeys["UI Element Labels"]
LogKeys["Log Messages"]
TooltipKeys["Tooltip Text"]
DialogKeys["Dialog Messages"]

MainFrame --> GetButton
MainFrame --> GetLabel
Dialogs --> GetTooltip
Listeners --> GetLog
GetButton --> UIKeys
GetLog --> LogKeys
GetTooltip --> TooltipKeys
GetLabel --> DialogKeys

subgraph subGraph2 ["Translation Keys"]
    UIKeys
    LogKeys
    TooltipKeys
    DialogKeys
end

subgraph subGraph1 ["Localizer Usage"]
    GetButton
    GetLog
    GetTooltip
    GetLabel
end

subgraph subGraph0 ["UI Components"]
    MainFrame
    Dialogs
    Listeners
end
Loading

Example Call Sites

Component Key Example Purpose
GlobalMouseListener "recording_key_pressed" Log recording events
GlobalMouseListener "log.mouse_left" Button name display
GlobalMouseListener "log.recording_scroll_msg1" Scroll event logging
MainFrame Button labels, menu items UI text
Dialogs Dialog titles, labels Settings interface

Code Examples:

Sources: src/io/github/samera2022/mouse_macros/listener/GlobalMouseListener.java L9, src/io/github/samera2022/mouse_macros/listener/GlobalMouseListener.java L47-L48, src/io/github/samera2022/mouse_macros/listener/GlobalMouseListener.java L73, src/io/github/samera2022/mouse_macros/listener/GlobalMouseListener.java L87, src/io/github/samera2022/mouse_macros/listener/GlobalMouseListener.java L125


API Reference

Static Fields

Field Type Purpose Access
translations Map<String, String> Stores current language key-value pairs Private
currentLang String Currently loaded language code Private
runtimeSwitch boolean Flag for runtime language switching Private
isDevMode boolean Environment detection flag Private

Public Methods

Method Signature Return Type Description
load(String lang) void Loads translations for specified language
get(String key) String Retrieves translation for key with fallback
getCurrentLang() String Returns current language code
isDevMode() boolean Returns true if in development mode
setRuntimeSwitch(boolean enable) void Sets runtime switching flag
isRuntimeSwitch() boolean Returns runtime switching flag state

Method Details

load(String lang)

Loads a language file and populates the translations map.

  • Parameters: * lang - Language code (e.g., "en_us", "zh_cn")
  • Behavior: * In dev mode: reads from lang/{lang}.json file * In production: reads from JAR resource lang/{lang}.json * Updates currentLang on success * Sets translations to empty map on error
  • Thread Safety: Not thread-safe; typically called during initialization or from UI thread

get(String key)

Retrieves a localized string for the given key.

  • Parameters: * key - Translation key (e.g., "button.record")
  • Returns: * Localized string if found in current language * English translation if current language missing key * Key itself if no translation found
  • Thread Safety: Thread-safe for reading (map is replaced atomically on load)

isDevMode()

Checks if running in development environment.

  • Returns: true if lang/zh_cn.json file exists, false otherwise
  • Usage: Primarily by ConfigManager for language discovery
  • Initialized: During static initialization

Sources: src/io/github/samera2022/mouse_macros/Localizer.java L1-L98


Design Considerations

Static Initialization

The Localizer uses static initialization to ensure translations are available before any UI components are created. The static block:

  1. Detects environment (dev vs production)
  2. Auto-detects system language via Locale.getDefault()
  3. Normalizes language codes (replacing - with _)
  4. Checks for language file existence
  5. Loads the appropriate language file

This ensures that even early initialization code can safely call Localizer.get().

Fallback Strategy

The two-tier fallback (current language → English → key) ensures:

  • Graceful degradation: Missing translations don't break UI
  • Developer visibility: Untranslated keys appear as-is for easy identification
  • English baseline: English (en_us) serves as complete reference

Environment Abstraction

The dual-mode (dev/production) design allows:

  • Easy development: Work with editable JSON files in IDE
  • Proper packaging: Read from JAR resources in production
  • Consistent API: Same method calls work in both modes

Sources: src/io/github/samera2022/mouse_macros/Localizer.java L15-L33, src/io/github/samera2022/mouse_macros/Localizer.java L47-L65, src/io/github/samera2022/mouse_macros/Localizer.java L67-L93

Clone this wiki locally