-
Notifications
You must be signed in to change notification settings - Fork 0
Internationalization and Localization
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.
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
Sources: src/io/github/samera2022/mouse_macros/Localizer.java L1-L98, src/io/github/samera2022/mouse_macros/manager/ConfigManager.java L14-L121
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.
| 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 |
| 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
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
Sources: src/io/github/samera2022/mouse_macros/manager/ConfigManager.java L66-L120
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 usingFileReader -
Production Mode: Language files loaded from JAR resources using
ClassLoader.getResourceAsStream()
Sources: src/io/github/samera2022/mouse_macros/Localizer.java L15-L18
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)
Sources: src/io/github/samera2022/mouse_macros/Localizer.java L47-L65
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
This fallback mechanism ensures that:
- If the key exists in the current language, use it
- If not, and current language is not English, try loading English and check there
- If still not found, return the key itself (acts as placeholder)
Sources: src/io/github/samera2022/mouse_macros/Localizer.java L67-L93
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
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_usif language file doesn't exist (dev mode) or for unrecognized locales
Sources: src/io/github/samera2022/mouse_macros/Localizer.java L20-L33
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)
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
The localization system integrates tightly with ConfigManager to persist user language preferences:
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 |
When followSystemSettings is enabled:
- Application startup checks
ConfigManager.config.followSystemSettings - If true, system language is detected and loaded automatically (overriding
config.lang) - If false,
config.langvalue is loaded explicitly
Sources: src/io/github/samera2022/mouse_macros/manager/ConfigManager.java L27-L35, README.md L74-L75
Language files are JSON documents stored in the lang/ directory with the naming convention {language_identifier}.json:
| File | Language | Description |
|---|---|---|
en_us.json |
English (US) | English translations, serves as fallback |
zh_cn.json |
Simplified Chinese | Chinese translations |
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
The localization system adapts its file loading strategy based on whether the application is running from source code or a packaged JAR:
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
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
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
Sources: README.md L30
Diagram 4 from high-level architecture
The localization system implements several safety mechanisms to handle missing translations or file loading failures:
-
Primary Lookup: Check
translationsmap for the requested key -
English Fallback: If not found and current language is not English, load
en_us.jsonand check again - Key Return: If still not found, return the key itself (acts as visible placeholder for missing translations)
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.
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
The Localization System provides comprehensive internationalization support through:
-
JSON-based translations stored in
lang/*.jsonfiles - 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.