A powerful and flexible translation (i18n) library for Java applications including console, Swing, and JavaFX applications. CubisLang supports loading translations from local JSON files and remote CDN sources with caching, encryption, and many advanced features.
β¨ Key Features:
- π Multi-locale support with fallback mechanism
- π Load translations from local JSON files
- π Fetch translations from remote CDN/URLs
- πΎ Smart caching system with configurable duration
- π Support for encrypted translation files
- π Cache revalidation with version query parameters
- π Multiple formatting options (positional, keyword-based)
- π’ Pluralization support
- π― Context-aware translations
- π§ Event listeners for loading and error handling
- π Debug mode for development
- β‘ Thread-safe and optimized for performance
- π Combined locales - Display multiple languages in one result
- π Async locale preloading - Non-blocking background loading for faster access
- π Missing keys extraction - Identify and extract untranslated keys across locales
- π Write missing keys to file - Async batch writing of missing keys directly to locale files
- π§Ή Resource cleanup - AutoCloseable with proper shutdown
β¨ Auto-Translation Features:
- π€ Auto-translation support with Google Translate (free API)
- π Extensible adapter system for custom translation services
- π¨ Built-in caching - Dramatically improves performance
- β‘ Batch translation - Translate multiple texts in one request
- π§ Translation file generator - Automatically create translation files
dependencies {
implementation 'com.cubetiqs:cubis-langx:1.0.0'
}<dependency>
<groupId>com.cubetiqs</groupId>
<artifactId>cubis-langx</artifactId>
<version>1.0.0</version>
</dependency>Create JSON files for each locale in your resources folder:
resources/lang/en.json:
{
"greeting": "Hello!",
"welcome_user": "Welcome, {{0}}!",
"item_count": "You have {{count}} items.",
"formatted_message": "Hello {{username}}, today is {{date}}.",
"ui": {
"button_save": "Save",
"button_cancel": "Cancel"
}
}resources/lang/km.json:
{
"greeting": "αα½ααααΈ!",
"welcome_user": "ααΌαααααΆαααα, {{0}}!",
"item_count": "α’αααααΆα {{count}} ααΆαα»."
}import com.cubetiqs.cubislang.CubisLang;
import com.cubetiqs.cubislang.CubisLangOptions;
public class MyApp {
public static void main(String[] args) {
// Option 1: Manual resource management
CubisLang lang = new CubisLang(
CubisLangOptions.builder()
.setDefaultLocale("en")
.setResourcePath("./resources/lang/")
.setFallbackLocale("en")
.setDebugMode(true)
.build()
);
try {
// Get translation
String greeting = lang.get("greeting");
System.out.println(greeting); // Output: Hello!
} finally {
// Clean up resources when done
lang.shutdown();
}
// Option 2: Try-with-resources (recommended)
try (CubisLang lang2 = new CubisLang(
CubisLangOptions.builder()
.setDefaultLocale("en")
.setResourcePath("./resources/lang/")
.build()
)) {
String greeting = lang2.get("greeting");
System.out.println(greeting);
} // Automatically calls shutdown()
}
}For better performance, preload locales asynchronously on startup:
CubisLang lang = new CubisLang(
CubisLangOptions.builder()
.setDefaultLocale("en")
.setResourcePath("./resources/lang/")
.setPreloadLocales(Arrays.asList("km", "zh", "fr"))
.build()
);
// Constructor returns immediately (non-blocking)
// Locales load in the background for faster access laterBenefits:
- β‘ Non-blocking - Constructor returns immediately
- π Faster switching - Preloaded locales are instantly available
- π― Optimized startup - Load what you need in the background
- πΎ Smart caching - Skips already-loaded locales
// Simple translation
String greeting = lang.get("greeting");
// With positional arguments
String welcome = lang.get("welcome_user", "John");
// Output: Welcome, John!String itemCount1 = lang.getPlural("item_count", 1);
// Output: You have 1 item.
String itemCount5 = lang.getPlural("item_count", 5);
// Output: You have 5 items.String saveButton = lang.getWithContext("button_save", "ui");
// Looks for "ui.button_save" in translation filesMap<String, String> keywords = new HashMap<>();
keywords.put("username", "Alice");
keywords.put("date", "2024-06-01");
String message = lang.getWithKeywords("formatted_message", keywords);
// Output: Hello Alice, today is 2024-06-01.lang.setLocale("km"); // Switch to Khmer
String greeting = lang.get("greeting");
// Output: αα½ααααΈ!
lang.setLocale("en"); // Switch back to EnglishCubisLang implements AutoCloseable for proper resource cleanup:
// Option 1: Try-with-resources (recommended)
try (CubisLang lang = new CubisLang(
CubisLangOptions.builder()
.setDefaultLocale("en")
.setResourcePath("./resources/lang/")
.build()
)) {
String greeting = lang.get("greeting");
// Resources automatically cleaned up
}
// Option 2: Manual cleanup
CubisLang lang = new CubisLang(...);
try {
String greeting = lang.get("greeting");
} finally {
lang.shutdown(); // or lang.close()
}What gets cleaned up:
- π HTTP client connections and thread pools
- πΎ Translation caches
- π§Ή Background preloader threads
When to call shutdown:
- β Application exit
- β Servlet context destroyed
- β Spring bean destruction
- β When CubisLang is no longer needed
Display translations from multiple languages in a single result - perfect for multilingual labels, tourist signs, or language learning apps:
CubisLang lang = new CubisLang(
CubisLangOptions.builder()
.setDefaultLocale("en")
.setResourcePath("./resources/lang/")
.setCombineLocales(Arrays.asList("en", "km", "zh"))
.setCombineSeparator(" / ")
.build()
);
String greeting = lang.get("greeting");
// Output: Hello / αα½ααααΈ / δ½ ε₯½
// Custom separator
.setCombineSeparator(" | ") // Output: Hello | αα½ααααΈ | δ½ ε₯½
// Works with formatting too
String welcome = lang.get("welcome", "John");
// Output: Welcome John / ααΌαααααΆαααα John / ζ¬’θΏ JohnSmart handling of missing translations:
- If a translation exists in only some locales, only those are shown
- If no translations are found in any locale, returns the key
- Example: If "farewell" exists in English and Chinese but not Khmer:
"Goodbye / εθ§"
Use cases:
- π·οΈ Multilingual product labels (international packaging)
- πΊοΈ Tourist information signs (show multiple languages)
- π Language learning apps (display side-by-side translations)
- π International business cards and documents
Automatically translate missing keys using Google Translate's free API:
import com.cubetiqs.cubislang.GoogleTranslateAdapter;
CubisLang lang = new CubisLang(
CubisLangOptions.builder()
.setDefaultLocale("km") // Khmer as primary
.setFallbackLocale("en") // English as fallback
.setResourcePath("./resources/lang/")
.setAutoTranslateEnabled(true)
.setTranslationAdapter(new GoogleTranslateAdapter(10)) // 10 second timeout
.build()
);
// If "farewell" doesn't exist in Khmer, it will auto-translate from English
String farewell = lang.get("farewell");
// Output: ααΆα αΎα! (automatically translated from "Goodbye!")How it works:
- Looks for the key in the current locale (km)
- If not found, looks in the fallback locale (en)
- If found in fallback, returns the fallback value directly (no translation)
- If auto-translation is enabled and the key exists in fallback, you get the fallback text
- Auto-translation is most useful when you want on-demand translation without pre-creating translation files
Note: This uses Google Translate's unofficial/free API. For production use with high volume, consider using the official Google Cloud Translation API or other translation services.
Create your own translation adapter for any translation service:
import com.cubetiqs.cubislang.TranslationAdapter;
public class MyCustomAdapter implements TranslationAdapter {
@Override
public String translate(String text, String sourceLocale, String targetLocale) {
// Call your translation API (Microsoft Translator, DeepL, AWS Translate, etc.)
// For example, using Microsoft Translator:
return callMicrosoftTranslator(text, sourceLocale, targetLocale);
}
@Override
public boolean isAvailable() {
return true; // Check if your service is available
}
}
// Use your custom adapter
CubisLang lang = new CubisLang(
CubisLangOptions.builder()
.setAutoTranslateEnabled(true)
.setTranslationAdapter(new MyCustomAdapter())
.build()
);See CustomTranslationAdapter.java for a complete example.
Load translations from a CDN or remote URL:
CubisLang lang = new CubisLang(
CubisLangOptions.builder()
.setDefaultLocale("en")
.setRemoteTranslationEnabled(true)
.setRemoteTranslationUrl("https://cdn.example.com/translations/")
.setCacheRemoteTranslations(true)
.setCacheDurationHours(24)
.setCachePath("./cache/lang/")
.build()
);The library will fetch translations from:
https://cdn.example.com/translations/en.jsonhttps://cdn.example.com/translations/km.json- etc.
For sensitive translations, enable encryption:
CubisLang lang = new CubisLang(
CubisLangOptions.builder()
.setDefaultLocale("en")
.setRemoteTranslationEnabled(true)
.setRemoteTranslationUrl("https://secure.example.com/translations/")
.setEncryptionEnabled(true)
.setDecryptionKey("your-secret-key-16ch")
.build()
);Monitor translation loading:
CubisLang lang = new CubisLang(
CubisLangOptions.builder()
.setDefaultLocale("en")
.setOnTranslationLoadedListener(locale -> {
System.out.println("Loaded: " + locale);
})
.setOnTranslationErrorListener((locale, error) -> {
System.err.println("Error loading " + locale + ": " + error);
})
.setMissingTranslationHandler((locale, key) -> {
System.out.println("Missing key '" + key + "' in " + locale);
})
.build()
);| Option | Type | Default | Description |
|---|---|---|---|
defaultLocale |
String | "en" | Default locale to use |
resourcePath |
String | "./resources/lang/" | Path to local translation files |
fallbackLocale |
String | "en" | Fallback locale when translation is missing |
remoteTranslationEnabled |
boolean | false | Enable remote translation fetching |
remoteTranslationUrl |
String | null | Base URL for remote translations |
encryptionEnabled |
boolean | false | Enable decryption for remote files |
decryptionKey |
String | null | Decryption key (AES) |
cacheRemoteTranslations |
boolean | true | Cache remote translations locally |
cacheDurationHours |
int | 24 | Cache validity duration |
cachePath |
String | "./resources/cache/lang/" | Cache storage path |
debugMode |
boolean | false | Enable debug logging |
autoTranslateEnabled |
boolean | false | Enable auto-translation for missing keys |
translationAdapter |
Object | null | Translation adapter (e.g., GoogleTranslateAdapter) |
{
"key": "value",
"greeting": "Hello!",
"goodbye": "Goodbye!"
}{
"ui": {
"button_save": "Save",
"button_cancel": "Cancel"
},
"error": {
"not_found": "Not found",
"invalid": "Invalid input"
}
}{
"welcome": "Welcome, {{0}}!",
"message": "Hello {{username}}, you have {{count}} messages."
}Identify and extract missing translation keys - useful for maintaining translations across multiple locales:
import com.google.gson.JsonObject;
import java.util.Map;
import java.util.Set;
try (CubisLang lang = new CubisLang(
CubisLangOptions.builder()
.setDefaultLocale("en")
.setResourcePath("./resources/lang/")
.build()
)) {
// Load the locales you want to compare
lang.setLocale("en");
lang.setLocale("fr");
// Find which keys are missing in French
Set<String> missingKeys = lang.findMissingKeys("en", "fr");
System.out.println("Missing keys: " + missingKeys);
// Output: [farewell, thanks, ui.button.cancel]
// Get missing keys with their English values (as a template)
Map<String, String> missingWithValues = lang.extractMissingKeysWithValues("en", "fr");
System.out.println("Missing translations:");
missingWithValues.forEach((key, value) ->
System.out.println(key + " = " + value + " [NEEDS TRANSLATION]")
);
// Extract as JSON preserving nested structure
JsonObject missingJson = lang.extractMissingKeysAsJson("en", "fr");
System.out.println(missingJson);
// Can be saved to a file for translators:
// Files.write(Paths.get("missing_fr.json"),
// new Gson().toJson(missingJson).getBytes());
}Practical use cases:
- π Generate translation task lists for translators
- π Audit which locales need updates
- π Create template files with missing keys
- β CI/CD checks to ensure all locales are complete
Automatically track and write missing translation keys directly to locale files with async batch processing - perfect for development and continuous translation workflows:
try (CubisLang lang = new CubisLang(
CubisLangOptions.builder()
.setDefaultLocale("en")
.setResourcePath("./resources/lang/")
.setWriteMissingKeysToFile(true) // Enable async batch writing
.setMissingKeysBatchSize(100) // Flush when 100 keys collected
.setMissingKeysFlushIntervalSeconds(30) // Or every 30 seconds
.build()
)) {
// As you use translations, missing keys are automatically tracked
String greeting = lang.get("missing_greeting");
String farewell = lang.get("missing_farewell");
// Keys are batched and written asynchronously to en.json
// The file is updated with missing keys added as empty values:
// {
// "hello": "Hello",
// "world": "World",
// "missing_greeting": "",
// "missing_farewell": ""
// }
// Manually trigger flush if needed
lang.flushMissingKeys();
// Switch locale
lang.setLocale("km");
lang.get("some_key"); // Missing keys written to km.json
} // Automatic final flush on closeBenefits:
- π Non-blocking - Runs in background thread, doesn't slow down your app
- π¦ Batch processing - Collects keys and writes in batches for efficiency
- π Automatic flush - Writes on shutdown or when batch size reached
- π Direct to locale files - Missing keys added to
en.json,km.json, etc. - π― No duplicates - Each key recorded only once per locale
- β¨ Pretty printed JSON - Maintains readable format in locale files
Perfect for:
- π¨ Development environments - Automatically collect missing keys as you code
- π Continuous translation - Translators can fill in empty values in real locale files
- π Translation progress tracking - See which keys need translation
- π Production monitoring - Track missing keys in live applications
CubisLang lang = new CubisLang(
CubisLangOptions.builder()
.setDefaultLocale("en")
.setResourcePath("./resources/lang/")
.build()
);
System.out.println(lang.get("app_title"));JButton saveButton = new JButton(lang.getWithContext("button_save", "ui"));
JLabel welcomeLabel = new JLabel(lang.get("welcome_user", username));Button saveButton = new Button(lang.getWithContext("button_save", "ui"));
Label welcomeLabel = new Label(lang.get("welcome_user", username));- Use fallback locale: Always set a fallback locale (usually English)
- Cache remote translations: Enable caching to reduce network requests
- Handle missing translations: Implement a missing translation handler
- Use nested keys: Organize translations with nested structures for better management
- Debug mode in development: Enable debug mode during development to catch issues early
- Version your remote translations: Add version query parameters to CDN URLs for cache busting
CubisLang is thread-safe and can be used in multi-threaded applications. The translation cache uses ConcurrentHashMap to ensure safe concurrent access.
- Translations are loaded on-demand and cached in memory
- Remote translations are cached locally to minimize network requests
- JSON parsing is optimized using Gson
- Thread-safe concurrent access to translation cache
- Gson: JSON parsing
- OkHttp: HTTP client for remote fetching
- Mustache.java: Template rendering for keyword formatting
- SLF4J: Logging abstraction
MIT License - see LICENSE file for details
Contributions are welcome! Please feel free to submit a Pull Request.
For issues, questions, or contributions, please visit:
- GitHub: https://github.com/cubetiq/cubis-langx-java
- Issues: https://github.com/cubetiq/cubis-langx-java/issues
Made with β€οΈ by Cubis