# Localization System
> **Relevant source files**
> * [src/io/github/samera2022/mouse_macros/Localizer.java](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/Localizer.java)
> * [src/io/github/samera2022/mouse_macros/listener/GlobalMouseListener.java](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/listener/GlobalMouseListener.java)
> * [src/io/github/samera2022/mouse_macros/util/FileUtil.java](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/util/FileUtil.java)
> * [src/lang/en_us.json](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/lang/en_us.json)
> * [src/lang/zh_cn.json](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/lang/zh_cn.json)
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](Configuration-and-Persistence). For details on how UI components apply translations, see [User Interface Components](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.
```mermaid
flowchart TD
LangFiles["lang/*.json files
en_us.json
zh_cn.json"]
SystemLocale["java.util.Locale
System Language"]
Localizer["Localizer class
- translations Map
- currentLang String
- isDevMode boolean"]
LoadMethod["load(lang) method"]
GetMethod["get(key) method"]
Config["ConfigManager.Config
- lang field
- 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
```
**Sources:** [src/io/github/samera2022/mouse_macros/Localizer.java L1-L98](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/Localizer.java#L1-L98), [src/io/github/samera2022/mouse_macros/manager/ConfigManager.java L14-L121](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/manager/ConfigManager.java#L14-L121)
---
## Translation Storage and Lookup
The `Localizer` class maintains an in-memory `Map` 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` | 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](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/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:
```mermaid
flowchart TD
Start["getAvailableLangs() called"]
CheckMode["isDevMode()?"]
ScanDir["Scan lang/ directory
FileUtil.listFileNames()"]
ExtractDev["Extract filename
without .json extension"]
GetURL["Get resource URL
lang/ directory"]
CheckProtocol["Protocol == 'jar'?"]
OpenJar["Open JAR file
JarFile class"]
EnumEntries["Enumerate entries
filter 'lang/*.json'"]
ExtractJar["Extract filename
without .json extension"]
BuildArray["Build String[] array
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
```
**Sources:** [src/io/github/samera2022/mouse_macros/manager/ConfigManager.java L66-L120](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/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](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/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:
```mermaid
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
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)
```
**Sources:** [src/io/github/samera2022/mouse_macros/Localizer.java L47-L65](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/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:
```mermaid
flowchart TD
Start["get(key) called"]
LookupCurrent["Look up key in
translations map"]
CheckFound["Value found?"]
ReturnValue["Return translation value"]
CheckLang["currentLang
== 'en_us'?"]
LoadEnglish["Load lang/en_us.json"]
LookupEnglish["Look up key in
English translations"]
CheckEnFound["Value found
in English?"]
ReturnEnglish["Return English value"]
ReturnKey["Return key itself
(untranslated)"]
Start --> LookupCurrent
LookupCurrent --> CheckFound
CheckFound --> ReturnValue
CheckFound --> CheckLang
CheckLang --> LoadEnglish
CheckLang --> ReturnKey
LoadEnglish --> LookupEnglish
LookupEnglish --> CheckEnFound
CheckEnFound --> ReturnEnglish
CheckEnFound --> ReturnKey
```
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](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/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:
```mermaid
flowchart TD
GetLocale["java.util.Locale.getDefault()"]
Normalize["Normalize locale string
replace '-' with '_'
toLowerCase()"]
DetectLang["Starts with?"]
SetLang["Set language identifier"]
CheckExists["Check if lang file exists
(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
```
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](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/Localizer.java#L20-L33)
---
## Runtime Language Switching
The system supports changing languages at runtime without restarting the application:
```mermaid
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)
```
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](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/Localizer.java#L35-L41), [README.md L30-L31](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/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](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/manager/ConfigManager.java#L27-L35), [README.md L74-L75](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/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:
```json
{
"key1": "translated text 1",
"key2": "translated text 2",
"button.save": "Save",
"dialog.title": "Settings"
}
```
The system uses Gson to deserialize JSON into `Map`:
```
// From Localizer.load()
translations = new Gson().fromJson(reader, Map.class);
```
**Sources:** [src/io/github/samera2022/mouse_macros/Localizer.java L52-L58](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/Localizer.java#L52-L58), [README.md L30](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/README.md#L30-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 entries = jar.entries();
// Filter entries starting with "lang/" and ending with ".json"
```
**Sources:** [src/io/github/samera2022/mouse_macros/Localizer.java L47-L65](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/Localizer.java#L47-L65), [src/io/github/samera2022/mouse_macros/manager/ConfigManager.java L67-L119](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/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:
```mermaid
flowchart TD
MainFrame["MainFrame
buttons, labels, menus"]
SettingsDialog["SettingsDialog
settings options"]
MacroSettings["MacroSettingsDialog
macro configuration"]
HotkeyDialog["HotkeyDialog
key binding interface"]
AboutDialog["AboutDialog
author information"]
Keys["Translation key strings
'button.record'
'dialog.settings.title'
'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
```
**Sources:** [README.md L30](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/README.md#L30-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](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/src/io/github/samera2022/mouse_macros/Localizer.java#L62-L93), [src/io/github/samera2022/mouse_macros/manager/ConfigManager.java L115-L117](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/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.