Internationalization (i18n) Conventions
Internationalization (i18n)
Section titled āInternationalization (i18n)āThis document outlines the internationalization (i18n) conventions for the Hatchgrid project. All contributors are expected to follow these guidelines to ensure that the application can be easily translated into different languages.
Table of Contents
Section titled āTable of ContentsāIntroduction
Section titled āIntroductionāInternationalization (i18n) is the process of designing and developing an application in a way that it can be easily adapted to different languages and regions without any engineering changes.
Backend Configuration
Section titled āBackend ConfigurationāThe Spring Boot backend is configured for internationalization in application.yml
:
spring: messages: basename: i18n/messages encoding: UTF-8 fallback-to-system-locale: false web: locale: en locale-resolver: accept_header
Configuration Details:
basename
: Points to message bundle files insrc/main/resources/i18n/
encoding
: UTF-8 encoding for proper character supportfallback-to-system-locale
: Disabled to ensure consistent behaviorlocale
: Default locale set to English (en
)locale-resolver
: Uses HTTPAccept-Language
header for locale detection
Frontend Configuration
Section titled āFrontend ConfigurationāThe Vue.js frontend uses vue-i18n for internationalization. See Frontend Translation Integration for detailed configuration.
Locale Resolution
Section titled āLocale ResolutionāThe locale of the user is determined by the Accept-Language
header of the HTTP request. The default locale is en
(English).
Resource Bundles
Section titled āResource BundlesāWe use resource bundles to store the translated strings. The resource bundles are located in the src/main/resources/i18n
directory.
Current Resource Bundles:
messages.properties
: The default resource bundle (English)messages_es.properties
: The Spanish resource bundle
Naming Convention:
Resource bundles follow the pattern messages_{locale}.properties
:
messages.properties
: Default (English)messages_es.properties
: Spanishmessages_de.properties
: German (future)messages_fr.properties
: French (future)
Message Categories:
The message bundles are organized into logical categories:
- Application: General app information (
app.*
) - Common: Shared messages and validations (
common.*
) - Authentication: Login/logout messages (
auth.*
) - User: User management messages (
user.*
) - Newsletter: Newsletter-specific messages (
newsletter.*
) - Workspace: Workspace management (
workspace.*
) - Email: Email-related messages (
email.*
) - Error: Error messages (
error.*
) - Health: Health check messages (
health.*
)
Message Keys
Section titled āMessage KeysāThe message keys should be descriptive and should follow a consistent naming convention.
Good:
user.profile.title
user.profile.firstName
user.profile.lastName
Bad:
title
firstName
lastName
Pluralization
Section titled āPluralizationāWe use the java.text.ChoiceFormat
class to handle pluralization.
double[] limits = {0,1,2};String[] parts = {"There are no files.","There is one file.","There are {2} files."};ChoiceFormat form = new ChoiceFormat(limits, parts);
Date and Time Formatting
Section titled āDate and Time FormattingāWe use the java.text.DateFormat
class to format dates and times.
DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, locale);String formattedDate = df.format(new Date());
Number Formatting
Section titled āNumber FormattingāWe use the java.text.NumberFormat
class to format numbers.
NumberFormat nf = NumberFormat.getNumberInstance(locale);String formattedNumber = nf.format(123456.789);
Currency Formatting
Section titled āCurrency FormattingāWe use the java.text.NumberFormat
class to format currencies.
NumberFormat cf = NumberFormat.getCurrencyInstance(locale);String formattedCurrency = cf.format(123456.789);
Usage in Backend Code
Section titled āUsage in Backend CodeāUsing MessageSource
Section titled āUsing MessageSourceāInject MessageSource
into your Spring components to access localized messages:
@Serviceclass UserService( private val messageSource: MessageSource) {
fun createUser(user: User, locale: Locale): String { // Business logic here...
return messageSource.getMessage( "user.created", null, locale ) }
fun validateEmail(email: String, locale: Locale): String? { return if (!isValidEmail(email)) { messageSource.getMessage( "common.validation.email", null, locale ) } else null }}
In Controllers
Section titled āIn Controllersā@RestControllerclass UserController( private val userService: UserService, private val messageSource: MessageSource) {
@PostMapping("/users") fun createUser( @RequestBody user: User, @RequestHeader("Accept-Language") acceptLanguage: String? ): ResponseEntity<ApiResponse> { val locale = parseLocale(acceptLanguage) ?: Locale.ENGLISH
val result = userService.createUser(user, locale) val message = messageSource.getMessage("user.created", null, locale)
return ResponseEntity.ok(ApiResponse(message = message)) }}
Locale Resolution
Section titled āLocale ResolutionāThe application automatically resolves the userās locale from the Accept-Language
header. You can also manually parse and use specific locales as needed.
Testing
Section titled āTestingā- All new features should be tested to ensure that they are properly internationalized.
- The tests should cover all the supported locales.
- Test message resolution with different locales:
@Testfun `should return localized message for Spanish locale`() { val locale = Locale.forLanguageTag("es") val message = messageSource.getMessage("user.created", null, locale)
assertThat(message).isEqualTo("Usuario creado exitosamente")}