Skip to content

Developer Guide

This section covers the data model, configuration reference, caching strategy, and extension points available to developers integrating I18N Manager into an application.

Data Model

I18N Manager persists three related entities. A language defines a locale available in the application. A key is a unique identifier for a translatable string. A translation links a key to a language and holds the translated value.

---
config:
  layout: elk
---
erDiagram
    AJ_I18N_LANGUAGE {
        integer id               PK
        varchar language_key
        varchar language_region
        boolean is_default_language
    }
    AJ_I18N_KEY {
        integer id          PK
        varchar item_key
        varchar description
    }
    AJ_I18N_TRANSLATION {
        integer id           PK
        varchar translation
        integer fk_item_key  FK
        integer fk_language  FK
    }

    AJ_I18N_KEY      ||--o{ AJ_I18N_TRANSLATION : "translated by"
    AJ_I18N_LANGUAGE ||--o{ AJ_I18N_TRANSLATION : "used in"
Hold "Alt" / "Option" to enable pan & zoom

Database Schema

I18N Manager manages three tables. The schema below represents the DDL generated by Hibernate for a standard relational database.

CREATE SEQUENCE aj_i18n_language_seq
    START WITH 1 INCREMENT BY 50;

CREATE SEQUENCE aj_i18n_key_seq
    START WITH 1 INCREMENT BY 50;

CREATE SEQUENCE aj_i18n_translation_seq
    START WITH 1 INCREMENT BY 50;

CREATE TABLE AJ_I18N_LANGUAGE (
    id                   INTEGER      NOT NULL,
    language_key         VARCHAR(255) NOT NULL,
    language_region      VARCHAR(255) NOT NULL,
    is_default_language  BOOLEAN,
    CONSTRAINT pk_aj_i18n_language             PRIMARY KEY (id),
    CONSTRAINT uq_aj_i18n_language_key_region  UNIQUE (language_key, language_region)
);

CREATE TABLE AJ_I18N_KEY (
    id           INTEGER      NOT NULL,
    item_key     VARCHAR(255),
    description  VARCHAR(255),
    CONSTRAINT pk_aj_i18n_key   PRIMARY KEY (id),
    CONSTRAINT uq_aj_i18n_key   UNIQUE (item_key)
);

CREATE TABLE AJ_I18N_TRANSLATION (
    id           INTEGER      NOT NULL,
    translation  VARCHAR(4000),
    fk_item_key  INTEGER      NOT NULL,
    fk_language  INTEGER      NOT NULL,
    CONSTRAINT pk_aj_i18n_translation          PRIMARY KEY (id),
    CONSTRAINT fk_aj_i18n_translation_key      FOREIGN KEY (fk_item_key) REFERENCES AJ_I18N_KEY (id),
    CONSTRAINT fk_aj_i18n_translation_language FOREIGN KEY (fk_language) REFERENCES AJ_I18N_LANGUAGE (id)
);

The language_key column stores an ISO 639-1 language code (e.g., en, es). The language_region column stores an ISO 3166-1 alpha-2 region code (e.g., US, GB) or an empty string for a non-regional language. The unique constraint on (language_key, language_region) prevents duplicate locale definitions. The translation column holds up to 4000 characters, so a translation can contain a short explanatory paragraph (4000 is the largest length portable across databases, matching Oracle's VARCHAR2 limit). The item_key column is limited by the configurable appjars.i18n.key.max-length property (default 255).

Module Overview

I18N Manager is structured as six Maven modules following the AppJars layered architecture:

Module Artifact ID Description
Model appjars-i18n-manager-model DTOs, filter classes, auto-configuration, cache setup
Business API appjars-i18n-manager-business LanguageService, I18nKeyService, TranslationItemService, LocaleService interfaces
Business Impl appjars-i18n-manager-business-impl Service implementations with caching and validation
Data API appjars-i18n-manager-data LanguageDao, I18nKeyDao, TranslationItemDao interfaces
Data Impl appjars-i18n-manager-data-impl JPA entities, DAO implementations, Spring Data repositories
Flow UI appjars-i18n-manager-flow Vaadin views, VaadinI18nProvider, route configuration

A monolithic application includes the three implementation modules (-business-impl, -data-impl, -flow). The API modules (-business, -data) are pulled in transitively.

Spring Auto-Configuration

I18N Manager registers itself through Spring Boot's auto-configuration mechanism. The entry point is I18nManagerAutoConfiguration, declared in:

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

In addition to scanning entities and components, the auto-configuration enables Spring caching (@EnableCaching) and registers a ConcurrentMapCacheManager bean with three named caches:

Cache name Purpose
aj-i18n-manager-languages Language lookups, default language, and locale list
aj-i18n-manager-keys Individual translation key lookups
aj-i18n-manager-translations Translation lookups by key and locale

All service mutations evict the relevant cache entries. Bulk operations evict entire cache regions. The in-memory cache provided by default is suitable for single-node deployments. Distributed cache managers can be substituted by registering a compatible CacheManager bean.

Configuration Properties

The following properties can be set in application.properties to customise the behaviour of I18N Manager:

Property Default Description
com.appjars.i18nmanager.url.views.languages i18n/languages URL path of the language management view
com.appjars.i18nmanager.url.views.translationitems i18n/items URL path of the translation items view
com.appjars.i18nmanager.url.views.translationitemsparams i18n/items/:lang URL path of the translation items view with a pre-selected language parameter
appjars.i18n.key.max-length 255 Maximum character length for translation keys
appjars.i18nmanager.properties.encoding UTF-8 Charset used to decode the bundled messages_<locale>.properties files when scanning for missing keys (e.g. UTF-8, ISO-8859-1)
com.appjars.utils.i8n.supportedLocales en Comma-separated list of locale tags reported as supported by the Vaadin i18n provider
appjars.translations.default-locale en Locale used as the final fallback when no matching translation is found

Translation Lookup and Fallback

When a translation is requested for a given key and locale, I18N Manager applies a three-level fallback hierarchy:

  1. Regional translation — the translation for the exact locale (e.g., en_US).
  2. Non-regional fallback — if no regional translation exists, the translation for the same language without a region (e.g., en with an empty region).
  3. Default language — if neither of the above exists, the translation for the language marked as default.
  4. Empty string — if no translation is found at any level.

This hierarchy is applied at the database query level through a single LEFT JOIN query, making lookups efficient regardless of how many fallback levels are traversed.

Vaadin I18n Provider

I18N Manager provides VaadinI18nProvider, a @Primary Spring bean that replaces the standard Vaadin i18n provider. When the -flow module is on the classpath, all calls to getTranslation() in Vaadin components are automatically routed through the database-backed translation service.

VaadinI18nProvider integrates with the fallback hierarchy described above and supports MessageFormat parameter substitution, so translation strings may contain placeholders such as {0} and {1}.

The set of locales reported to Vaadin as supported is driven by LanguageService.getProvidedLocales(), which returns all languages stored in the database. This means the locale list is dynamic and updates as languages are added or removed through the UI.

Service API

LanguageService

Manages language definitions. Key methods beyond standard CRUD:

// Return the language marked as default
LanguageDto getDefaultLanguage();

// Set a language as the default (clears the previous default)
void setDefaultLanguage(LanguageDto language);

// Find a language by its key and region
Optional<LanguageDto> findByLanguageKeyAndRegion(String languageKey, String languageRegion);

// Find the non-regional entry for a language key (region = "")
Optional<LanguageDto> findNonRegionalLanguage(String languageKey);

// Return all languages as Java Locale objects
List<Locale> getProvidedLocales();

// Export all translations for a language as a Properties object
Properties generateMessagePropertiesForLanguage(LanguageDto language);

I18nKeyService

Manages translation keys. Key methods beyond standard CRUD:

// Find a key by its string value
Optional<I18nKeyDto> findByKey(String key);

// Load keys and translations from a Properties object
void persistAll(Properties properties, LanguageDto language, boolean overwrite);

// Load keys and translations from an InputStream (e.g., a .properties file)
void persistAll(InputStream inputStream, LanguageDto language, boolean overwrite) throws IOException;

// Bulk delete keys and their associated translations
void deleteAll(Collection<I18nKeyDto> keys);

The persistAll methods are the programmatic equivalent of the upload action in the UI. When overwrite is false, existing translations are preserved and only missing keys are added.

TranslationItemService

Manages individual translation values. Key methods beyond standard CRUD:

// Look up a translation for a key and locale, applying the fallback hierarchy
Optional<String> getTranslation(String itemKey, Locale locale);

// Save or delete a translation (an empty value triggers deletion)
void process(TranslationItemDto item);

// Bulk save or delete translations
void processAll(Collection<TranslationItemDto> items);

// Delete all translations associated with a key
void deleteByItemKey(I18nKeyDto key);

// Delete all translations for a language
void deleteByLanguage(Integer langId);

getTranslation() is the method called by VaadinI18nProvider for every UI string lookup. Results are cached in aj-i18n-manager-translations.

LocaleService

Provides utilities for working with Java Locale objects independently of stored data:

// Return the localised display name of a language
String getDisplayLanguage(Locale targetLocale, String languageKey);

// Return all ISO 639-1 language codes
List<String> getAllLocaleKeys();

Programmatic Translation Import

Translations can be loaded programmatically from standard Java .properties files at application startup. This is useful for seeding an empty database with a base translation set:

@Component
public class TranslationSeeder {

    @Autowired private LanguageService languageService;
    @Autowired private I18nKeyService i18nKeyService;

    @EventListener(ContextRefreshedEvent.class)
    public void seed() throws IOException {
        Optional<LanguageDto> english = languageService.findByLanguageKeyAndRegion("en", "");
        if (english.isPresent()) {
            try (InputStream is = getClass().getResourceAsStream("/messages_en.properties")) {
                i18nKeyService.persistAll(is, english.get(), false);
            }
        }
    }
}

Setting overwrite to false ensures that manual edits made through the UI are not reverted on each startup.

Customisation

Assigning a Router Layout

By default, I18N Manager registers its views without a parent layout. To wrap them in the application's main layout, inject the RouteConfigurer bean using its qualifier and call setViewsRouterLayout in a @PostConstruct method:

@Autowired
@Qualifier("I18nManagerRouteConfigurer")
private RouteConfigurer routeConfigurer;

@PostConstruct
public void configure() {
    routeConfigurer.setViewsRouterLayout(MainLayout.class);
}

The @Qualifier is required because multiple AppJars may contribute a bean named RouteConfigurer.

Customising View URLs

The default URL paths can be overridden in application.properties:

com.appjars.i18nmanager.url.views.languages=myapp/languages
com.appjars.i18nmanager.url.views.translationitems=myapp/translations
com.appjars.i18nmanager.url.views.translationitemsparams=myapp/translations/:lang