or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

annotation-config.mdaot.mdcaching.mdcontext-lifecycle.mdevents.mdformatting.mdi18n.mdindex.mdjmx.mdresilience.mdscheduling.mdstereotypes.mdvalidation.md
tile.json

i18n.mddocs/

Internationalization (I18n)

MessageSource Configuration

@Configuration
public class I18nConfig {

    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource ms = new ResourceBundleMessageSource();
        ms.setBasenames("messages", "errors");
        ms.setDefaultEncoding("UTF-8");
        ms.setFallbackToSystemLocale(false);
        ms.setCacheSeconds(3600);  // Cache for 1 hour
        return ms;
    }

    // Reloadable version for development
    @Bean
    public MessageSource reloadableMessageSource() {
        ReloadableResourceBundleMessageSource ms = new ReloadableResourceBundleMessageSource();
        ms.setBasenames("classpath:messages", "file:/etc/app/messages");
        ms.setDefaultEncoding("UTF-8");
        ms.setCacheSeconds(60);  // Reload every minute
        return ms;
    }
}

Property Files

# messages.properties (default)
greeting.message=Welcome
user.created=User {0} created successfully
order.total=Order total: {0,number,currency}
event.date=Event on {0,date,long}

# messages_fr.properties
greeting.message=Bienvenue
user.created=Utilisateur {0} créé avec succès
order.total=Total de la commande: {0,number,currency}
event.date=Événement le {0,date,long}

# messages_de.properties
greeting.message=Willkommen
user.created=Benutzer {0} erfolgreich erstellt
order.total=Bestellsumme: {0,number,currency}
event.date=Ereignis am {0,date,long}

Using MessageSource

@Service
public class UserService {

    @Autowired
    private MessageSource messageSource;

    public String createUser(String username, Locale locale) {
        // Simple message
        String msg = messageSource.getMessage("user.created",
            new Object[]{username}, locale);

        // With default
        String msg2 = messageSource.getMessage("user.welcome",
            new Object[]{username}, "Welcome!", locale);

        // MessageSourceResolvable
        MessageSourceResolvable resolvable = new DefaultMessageSourceResolvable(
            new String[]{"user.created", "default.message"},
            new Object[]{username},
            "User created"
        );
        String msg3 = messageSource.getMessage(resolvable, locale);

        return msg;
    }
}

MessageSourceAccessor

@Service
public class NotificationService implements MessageSourceAware {

    private MessageSourceAccessor messages;

    @Override
    public void setMessageSource(MessageSource messageSource) {
        this.messages = new MessageSourceAccessor(messageSource, Locale.ENGLISH);
    }

    public void sendNotification(String username) {
        // Simplified access without Locale parameter
        String greeting = messages.getMessage("greeting.message");
        String userMsg = messages.getMessage("user.created", new Object[]{username});

        // With default message
        String custom = messages.getMessage("custom.key",
            new Object[]{username}, "Default message");
    }
}

LocaleContextHolder

@Component
public class LocaleService {

    public void processWithLocale() {
        // Get current locale
        Locale locale = LocaleContextHolder.getLocale();

        // Set locale for current thread
        LocaleContextHolder.setLocale(Locale.FRENCH);

        try {
            // Operations use French locale
            doSomething();
        } finally {
            // Reset locale
            LocaleContextHolder.resetLocaleContext();
        }

        // With timezone
        LocaleContextHolder.setLocaleContext(
            new TimeZoneAwareLocaleContext() {
                @Override
                public Locale getLocale() {
                    return Locale.GERMAN;
                }

                @Override
                public TimeZone getTimeZone() {
                    return TimeZone.getTimeZone("Europe/Berlin");
                }
            }
        );
    }
}

Hierarchical MessageSource

@Configuration
public class HierarchicalConfig {

    @Bean
    public MessageSource parentMessageSource() {
        ResourceBundleMessageSource parent = new ResourceBundleMessageSource();
        parent.setBasenames("common-messages");
        parent.setDefaultEncoding("UTF-8");
        return parent;
    }

    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource child = new ResourceBundleMessageSource();
        child.setBasenames("app-messages");
        child.setDefaultEncoding("UTF-8");
        child.setParentMessageSource(parentMessageSource());
        return child;
    }
}

Parameterized Messages

@Service
public class FormattingService {

    @Autowired
    private MessageSource messageSource;

    public void formattedMessages(Locale locale) {
        // Number formatting
        String total = messageSource.getMessage("order.total",
            new Object[]{new BigDecimal("99.99")}, locale);
        // Result: "Order total: $99.99" (US) or "Total: 99,99 €" (FR)

        // Date formatting
        String event = messageSource.getMessage("event.date",
            new Object[]{LocalDate.now()}, locale);
        // Result: "Event on January 15, 2024" (US) or "Événement le 15 janvier 2024" (FR)

        // Choice formatting
        // messages.properties: files.count={0,choice,0#no files|1#one file|1<{0,number,integer} files}
        String filesMsg = messageSource.getMessage("files.count",
            new Object[]{5}, locale);
        // Result: "5 files"
    }
}

Message Interpolation (Bean Validation)

@Configuration
public class ValidationI18nConfig {

    @Bean
    public LocalValidatorFactoryBean validator(MessageSource messageSource) {
        LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
        validator.setValidationMessageSource(messageSource);
        return validator;
    }
}

// ValidationMessages.properties
javax.validation.constraints.NotNull.message=Field {0} must not be null
javax.validation.constraints.Size.message=Field {0} must be between {min} and {max} characters

public class User {
    @NotNull
    @Size(min = 2, max = 50)
    private String username;
}