Skip to content

Internationalization and Localization

Samera2022 edited this page Jan 30, 2026 · 1 revision

Localization System

Relevant source files

The Localization System provides internationalization (i18n) support for MouseMacros, enabling the application to display user-facing text in multiple languages. The system loads translations from JSON files, automatically detects the system language, supports runtime language switching, and provides a fallback mechanism to ensure all text keys are resolved.

For information about configuring language settings and dark mode preferences, see Configuration System. For details on how UI components apply translations, see User Interface Components.


System Architecture

The localization system is centered around the Localizer class, which manages translation files, handles language detection, and provides translation lookup with fallback logic. The system integrates with ConfigManager to persist user language preferences and supports both development and production deployment modes.

flowchart TD

LangFiles["lang/*.json files<br>en_us.json<br>zh_cn.json"]
SystemLocale["java.util.Locale<br>System Language"]
Localizer["Localizer class<br>- translations Map<br>- currentLang String<br>- isDevMode boolean"]
LoadMethod["load(lang) method"]
GetMethod["get(key) method"]
Config["ConfigManager.Config<br>- lang field<br>- followSystemSettings"]
GetLangs["getAvailableLangs() method"]
MainFrame["MainFrame"]
Dialogs["Settings/Macro/Hotkey Dialogs"]
Labels["JLabel/JButton components"]

SystemLocale --> Localizer
LangFiles --> LoadMethod
Config --> Localizer
GetLangs --> LangFiles
GetMethod --> MainFrame
GetMethod --> Dialogs
GetMethod --> Labels

subgraph subGraph3 ["UI Components"]
    MainFrame
    Dialogs
    Labels
end

subgraph subGraph2 ["Configuration Integration"]
    Config
    GetLangs
end

subgraph subGraph1 ["Core Localization"]
    Localizer
    LoadMethod
    GetMethod
    LoadMethod --> Localizer
    Localizer --> GetMethod
end

subgraph subGraph0 ["Translation Sources"]
    LangFiles
    SystemLocale
end
Loading

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


Translation Storage and Lookup

The Localizer class maintains an in-memory Map<String, String> named translations that holds all key-value pairs for the currently loaded language. This map is populated during language loading and queried during translation retrieval.

Key Data Structures

Field Type Purpose
translations Map<String, String> Stores translation key-value pairs for current language
currentLang String Identifier of currently loaded language (e.g., "en_us", "zh_cn")
runtimeSwitch boolean Flag indicating if runtime language switching is enabled
isDevMode boolean Determines if running in development environment

Key Methods

Method Return Type Purpose
load(String lang) void Loads translation file for specified language into translations map
get(String key) String Retrieves translation for key, with fallback to English if missing
getCurrentLang() String Returns identifier of currently loaded language
isDevMode() boolean Returns true if running from source directory (not JAR)
setRuntimeSwitch(boolean) void Enables/disables runtime language switching capability

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


Language File Discovery

The system discovers available languages by scanning the lang/ directory for JSON files. The ConfigManager.getAvailableLangs() method handles discovery differently based on deployment mode:

flowchart TD

Start["getAvailableLangs() called"]
CheckMode["isDevMode()?"]
ScanDir["Scan lang/ directory<br>FileUtil.listFileNames()"]
ExtractDev["Extract filename<br>without .json extension"]
GetURL["Get resource URL<br>lang/ directory"]
CheckProtocol["Protocol == 'jar'?"]
OpenJar["Open JAR file<br>JarFile class"]
EnumEntries["Enumerate entries<br>filter 'lang/*.json'"]
ExtractJar["Extract filename<br>without .json extension"]
BuildArray["Build String[] array<br>of language identifiers"]
Return["Return language list"]

Start --> CheckMode
CheckMode --> ScanDir
CheckMode --> GetURL
ExtractDev --> BuildArray
ExtractJar --> BuildArray
BuildArray --> Return

subgraph subGraph1 ["Production Mode (JAR)"]
    GetURL
    CheckProtocol
    OpenJar
    EnumEntries
    ExtractJar
    GetURL --> CheckProtocol
    CheckProtocol --> OpenJar
    OpenJar --> EnumEntries
    EnumEntries --> ExtractJar
end

subgraph subGraph0 ["Development Mode"]
    ScanDir
    ExtractDev
    ScanDir --> ExtractDev
end
Loading

Sources: src/io/github/samera2022/mouse_macros/manager/ConfigManager.java L66-L120

Development vs Production Mode Detection

The system determines its deployment mode by checking for the existence of lang/zh_cn.json in the file system:

// From Localizer static initializer
File devLangFile = new File("lang/zh_cn.json");
isDevMode = devLangFile.exists();
  • Development Mode: Language files loaded directly from lang/ directory using FileReader
  • Production Mode: Language files loaded from JAR resources using ClassLoader.getResourceAsStream()

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


Translation Loading Flow

When a language is loaded via Localizer.load(String lang), the system reads the corresponding JSON file and deserializes it into the translations map:

sequenceDiagram
  participant Caller
  participant Localizer
  participant File System / JAR
  participant Gson
  participant translations Map

  Caller->>Localizer: load("zh_cn")
  loop [Development Mode]
    Localizer->>File System / JAR: new FileReader("lang/zh_cn.json")
    File System / JAR-->>Localizer: FileReader instance
    Localizer->>File System / JAR: getResourceAsStream("lang/zh_cn.json")
    File System / JAR-->>Localizer: InputStream
    Localizer->>Localizer: new InputStreamReader(stream, UTF-8)
    Localizer->>Gson: fromJson(reader, Map.class)
    Gson-->>Localizer: Map<String, String>
    Localizer->>translations Map: Replace entire map
    Localizer->>Localizer: currentLang = "zh_cn"
    File System / JAR-->>Localizer: Exception
    Localizer->>translations Map: Set to empty HashMap
  end
  Localizer-->>Caller: void (load complete)
Loading

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


Translation Retrieval and Fallback Mechanism

The Localizer.get(String key) method implements a two-tier fallback strategy to ensure all translation keys are resolved:

flowchart TD

Start["get(key) called"]
LookupCurrent["Look up key in<br>translations map"]
CheckFound["Value found?"]
ReturnValue["Return translation value"]
CheckLang["currentLang<br>== 'en_us'?"]
LoadEnglish["Load lang/en_us.json"]
LookupEnglish["Look up key in<br>English translations"]
CheckEnFound["Value found<br>in English?"]
ReturnEnglish["Return English value"]
ReturnKey["Return key itself<br>(untranslated)"]

Start --> LookupCurrent
LookupCurrent --> CheckFound
CheckFound --> ReturnValue
CheckFound --> CheckLang
CheckLang --> LoadEnglish
CheckLang --> ReturnKey
LoadEnglish --> LookupEnglish
LookupEnglish --> CheckEnFound
CheckEnFound --> ReturnEnglish
CheckEnFound --> ReturnKey
Loading

This fallback mechanism ensures that:

  1. If the key exists in the current language, use it
  2. If not, and current language is not English, try loading English and check there
  3. If still not found, return the key itself (acts as placeholder)

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


Automatic Language Detection

On application startup, the Localizer static initializer automatically detects the system's default locale and loads the appropriate language file:

flowchart TD

GetLocale["java.util.Locale.getDefault()"]
Normalize["Normalize locale string<br>replace '-' with '_'<br>toLowerCase()"]
DetectLang["Starts with?"]
SetLang["Set language identifier"]
CheckExists["Check if lang file exists<br>(dev mode only)"]
DefaultToEN["Default to 'en_us'"]
LoadLang["load(currentLang)"]

subgraph subGraph0 ["Static Initialization"]
    GetLocale
    Normalize
    DetectLang
    SetLang
    CheckExists
    DefaultToEN
    LoadLang
    GetLocale --> Normalize
    Normalize --> DetectLang
    DetectLang --> SetLang
    DetectLang --> SetLang
    SetLang --> CheckExists
    CheckExists --> DefaultToEN
    CheckExists --> LoadLang
    DefaultToEN --> LoadLang
end
Loading

The detection logic:

  • Retrieves system locale via java.util.Locale.getDefault().toString()
  • Normalizes format: replaces hyphens with underscores, converts to lowercase
  • Maps locale prefixes to language identifiers: * zh*zh_cn * en*en_us
  • Falls back to en_us if language file doesn't exist (dev mode) or for unrecognized locales

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


Runtime Language Switching

The system supports changing languages at runtime without restarting the application:

sequenceDiagram
  participant User
  participant SettingsDialog
  participant Localizer
  participant ConfigManager
  participant UI Components

  User->>SettingsDialog: Select new language
  SettingsDialog->>ConfigManager: config.lang = newLang
  SettingsDialog->>ConfigManager: saveConfig()
  SettingsDialog->>Localizer: setRuntimeSwitch(true)
  SettingsDialog->>Localizer: load(newLang)
  note over Localizer: translations map updated
  SettingsDialog->>UI Components: Trigger UI refresh
  loop [For each UI component]
    UI Components->>Localizer: get(translationKey)
    Localizer-->>UI Components: Translated text
    UI Components->>UI Components: Update component text
  end
  SettingsDialog->>Localizer: setRuntimeSwitch(false)
Loading

The runtimeSwitch flag allows the system to track when a language change is in progress, enabling UI components to respond appropriately to the change.

Sources: src/io/github/samera2022/mouse_macros/Localizer.java L35-L41, README.md L30-L31


Configuration System Integration

The localization system integrates tightly with ConfigManager to persist user language preferences:

Configuration Fields

Field in Config Type Default Purpose
followSystemSettings boolean true If true, sync language with OS locale; if false, use lang field
lang String "zh_cn" User-selected language identifier

Configuration Flow

When followSystemSettings is enabled:

  1. Application startup checks ConfigManager.config.followSystemSettings
  2. If true, system language is detected and loaded automatically (overriding config.lang)
  3. If false, config.lang value is loaded explicitly

Sources: src/io/github/samera2022/mouse_macros/manager/ConfigManager.java L27-L35, README.md L74-L75


Language File Format

Language files are JSON documents stored in the lang/ directory with the naming convention {language_identifier}.json:

File Naming Convention

File Language Description
en_us.json English (US) English translations, serves as fallback
zh_cn.json Simplified Chinese Chinese translations

JSON Structure

Each language file is a flat JSON object mapping translation keys to translated strings:

{
  "key1": "translated text 1",
  "key2": "translated text 2",
  "button.save": "Save",
  "dialog.title": "Settings"
}

The system uses Gson to deserialize JSON into Map<String, String>:

// From Localizer.load()
translations = new Gson().fromJson(reader, Map.class);

Sources: src/io/github/samera2022/mouse_macros/Localizer.java L52-L58, README.md L30


Deployment Mode Handling

The localization system adapts its file loading strategy based on whether the application is running from source code or a packaged JAR:

Development Mode

Detection: new File("lang/zh_cn.json").exists() returns true

File Loading:

File file = new File("lang/" + lang + ".json");
translations = new Gson().fromJson(new FileReader(file), Map.class);

Language Discovery:

String[] files = FileUtil.listFileNames("lang");
// Process file names to extract language identifiers

Production Mode (JAR)

Detection: new File("lang/zh_cn.json").exists() returns false

File Loading:

InputStream in = Localizer.class.getClassLoader()
    .getResourceAsStream("lang/" + lang + ".json");
InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8);
translations = new Gson().fromJson(reader, Map.class);

Language Discovery:

URL dirURL = ConfigManager.class.getClassLoader().getResource("lang/");
JarFile jar = new JarFile(jarPath);
Enumeration<JarEntry> entries = jar.entries();
// Filter entries starting with "lang/" and ending with ".json"

Sources: src/io/github/samera2022/mouse_macros/Localizer.java L47-L65, src/io/github/samera2022/mouse_macros/manager/ConfigManager.java L67-L119


Integration with UI Components

All user-facing UI components retrieve display text through the Localizer.get() method. This ensures consistent internationalization across the entire application:

flowchart TD

MainFrame["MainFrame<br>buttons, labels, menus"]
SettingsDialog["SettingsDialog<br>settings options"]
MacroSettings["MacroSettingsDialog<br>macro configuration"]
HotkeyDialog["HotkeyDialog<br>key binding interface"]
AboutDialog["AboutDialog<br>author information"]
Keys["Translation key strings<br>'button.record'<br>'dialog.settings.title'<br>'label.language'"]
GetMethod["Localizer.get(key)"]
TransMap["translations Map"]

MainFrame --> GetMethod
SettingsDialog --> GetMethod
MacroSettings --> GetMethod
HotkeyDialog --> GetMethod
AboutDialog --> GetMethod
GetMethod --> MainFrame
GetMethod --> SettingsDialog
GetMethod --> MacroSettings
GetMethod --> HotkeyDialog
GetMethod --> AboutDialog
Keys --> MainFrame
Keys --> SettingsDialog

subgraph subGraph2 ["Localization Layer"]
    GetMethod
    TransMap
    GetMethod --> TransMap
    TransMap --> GetMethod
end

subgraph subGraph1 ["Translation Keys"]
    Keys
end

subgraph subGraph0 ["UI Components"]
    MainFrame
    SettingsDialog
    MacroSettings
    HotkeyDialog
    AboutDialog
end
Loading

Sources: README.md L30

Diagram 4 from high-level architecture


Error Handling and Graceful Degradation

The localization system implements several safety mechanisms to handle missing translations or file loading failures:

Translation Key Fallback

  1. Primary Lookup: Check translations map for the requested key
  2. English Fallback: If not found and current language is not English, load en_us.json and check again
  3. Key Return: If still not found, return the key itself (acts as visible placeholder for missing translations)

File Loading Failure

When a language file fails to load (corrupted JSON, missing file, etc.):

catch (Exception e) {
    translations = new HashMap<>();
}

The translations map is set to an empty HashMap, causing all get() calls to fall through to the English fallback or key return logic.

Language Discovery Failure

If ConfigManager.getAvailableLangs() encounters an error while scanning language files:

catch (Exception e) {
    e.printStackTrace();
}
// Returns empty String[] array
return langs.toArray(new String[0]);

Sources: src/io/github/samera2022/mouse_macros/Localizer.java L62-L93, src/io/github/samera2022/mouse_macros/manager/ConfigManager.java L115-L117


Summary

The Localization System provides comprehensive internationalization support through:

  • JSON-based translations stored in lang/*.json files
  • Automatic language detection from system locale on startup
  • Runtime language switching without application restart
  • Two-tier fallback mechanism (current language → English → key itself)
  • Dual-mode file loading supporting both development and JAR deployment
  • Tight integration with ConfigManager for persistence and UI components for display
  • Graceful degradation when translations or files are missing

The system's architecture ensures that all user-facing text can be translated, with robust fallback mechanisms to prevent display of raw translation keys or application crashes due to missing language resources.

Clone this wiki locally