Overview
The AdonisJS Starter Kit includes comprehensive internationalization support using AdonisJS i18n . The system supports multiple languages with automatic locale detection based on browser preferences, custom headers, or cookies.
Supported Languages
The starter kit comes pre-configured with:
English (en) - Default
French (fr)
Portuguese (pt)
Configuration
i18n Configuration
import app from '@adonisjs/core/services/app'
import { defineConfig , formatters , loaders } from '@adonisjs/i18n'
const i18nConfig = defineConfig ({
defaultLocale: 'en' ,
supportedLocales: [ 'en' , 'fr' , 'pt' ],
formatter: formatters . icu (),
loaders: [
loaders . fs ({
location: app . makePath ( 'app/users/resources/lang' ),
}),
loaders . fs ({
location: app . makePath ( 'app/common/resources/lang' ),
}),
loaders . fs ({
location: app . makePath ( 'app/auth/resources/lang' ),
}),
],
})
export default i18nConfig
Modular Structure : Translation files are organized by feature module (users, auth, common) for better maintainability.
Locale Detection
The DetectUserLocaleMiddleware automatically detects the user’s preferred language:
app/core/middleware/detect_user_locale_middleware.ts
import { I18n } from '@adonisjs/i18n'
import i18nManager from '@adonisjs/i18n/services/main'
import type { NextFn } from '@adonisjs/core/types/http'
import { type HttpContext } from '@adonisjs/core/http'
export default class DetectUserLocaleMiddleware {
protected getRequestLocale ( ctx : HttpContext ) {
const supportedLocales = i18nManager . supportedLocales ()
// 1. Check custom header
const customHeaderLocale = ctx . request . header ( 'X-User-Language' )
if ( customHeaderLocale && supportedLocales . includes ( customHeaderLocale )) {
return customHeaderLocale
}
// 2. Check cookie
const cookieLocale = ctx . request . cookie ( 'user-locale' )
if ( cookieLocale && supportedLocales . includes ( cookieLocale )) {
return cookieLocale
}
// 3. Fallback to Accept-Language header
const userLanguages = ctx . request . languages ()
return i18nManager . getSupportedLocaleFor ( userLanguages ) ?? i18nManager . defaultLocale
}
async handle ( ctx : HttpContext , next : NextFn ) {
const language = this . getRequestLocale ( ctx )
// Update cookie if necessary
if ( ! ctx . request . cookie ( 'user-locale' ) || ctx . request . cookie ( 'user-locale' ) !== language ) {
ctx . response . cookie ( 'user-locale' , language , {
httpOnly: true ,
path: '/' ,
maxAge: 60 * 60 * 24 * 30 , // 30 days
sameSite: true ,
})
}
// Set i18n instance
ctx . i18n = i18nManager . locale ( language || i18nManager . defaultLocale )
ctx . containerResolver . bindValue ( I18n , ctx . i18n )
// Share with templates
if ( 'view' in ctx ) {
ctx . view . share ({ i18n: ctx . i18n })
}
return next ()
}
}
declare module '@adonisjs/core/http' {
export interface HttpContext {
i18n : I18n
}
}
Detection Priority
Custom Header
Check X-User-Language header for explicit language preference.
Cookie
Check user-locale cookie for saved preference.
Accept-Language
Parse browser’s Accept-Language header to find best match.
Default Locale
Fall back to configured default locale (English).
Switching Languages
Switch Locale Middleware
Users can switch languages via a dedicated route:
app/common/middlewares/switch_locale_middleware.ts
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
export default class SwitchLocaleMiddleware {
async handle ( ctx : HttpContext , next : NextFn ) {
ctx . response . cookie ( 'user-locale' , ctx . params . locale , {
httpOnly: true ,
path: '/' ,
maxAge: 60 * 60 * 24 * 30 , // 30 days
sameSite: true ,
})
ctx . response . redirect (). back ()
return await next ()
}
}
Switch Locale Route
import router from '@adonisjs/core/services/router'
import { middleware } from '#start/kernel'
router . get ( '/switch/:locale' , () => {})
. use ( middleware . switchLocale ())
After switching, users are redirected back to the previous page with the new locale applied.
Translation Files
File Structure
app/
├── auth/
│ └── resources/
│ └── lang/
│ ├── en/
│ │ ├── auth.json
│ │ └── errors.json
│ ├── fr/
│ │ ├── auth.json
│ │ └── errors.json
│ └── pt/
│ ├── auth.json
│ └── errors.json
├── users/
│ └── resources/
│ └── lang/
│ ├── en/
│ │ └── users.json
│ ├── fr/
│ │ └── users.json
│ └── pt/
│ └── users.json
└── common/
└── resources/
└── lang/
└── ...
Example Translation File
app/auth/resources/lang/en/auth.json
{
"signin" : {
"title" : "Sign in to your account" ,
"description" : "Enter your email below to access your account" ,
"form" : {
"email" : {
"label" : "Email" ,
"placeholder" : "Enter your email"
},
"password" : {
"label" : "Password" ,
"placeholder" : "Enter your password"
}
},
"actions" : {
"forgot_password" : "Forgot your password?" ,
"submit" : "Sign In" ,
"google" : "Sign in with Google"
}
},
"registration" : {
"title" : "Create your account" ,
"form" : {
"full_name" : {
"label" : "Full Name" ,
"placeholder" : "Your full name"
}
}
}
}
Using Translations
In Controllers
Access translations via the i18n service:
export default class SignInController {
async handle ({ i18n , session } : HttpContext ) {
session . flashErrors ({
E_TOO_MANY_REQUESTS: i18n . t ( 'errors.E_TOO_MANY_REQUESTS' ),
})
}
}
In Validators
Configure validators to use i18n for validation messages:
import vine from '@vinejs/vine'
import i18nManager from '@adonisjs/i18n/services/main'
const i18n = i18nManager . locale ( 'fr' )
vine . messagesProvider = i18n . createMessagesProvider ()
export const signUpValidator = vine . compile (
vine . object ({
fullName: vine . string (). trim (). minLength ( 3 ). maxLength ( 255 ),
email: vine . string (). email (). toLowerCase (). trim (),
password: vine . string (). minLength ( 1 ). confirmed (),
})
)
Validation Messages Provider
The middleware automatically sets up validation messages:
RequestValidator . messagesProvider = ( ctx ) => {
return ctx . i18n . createMessagesProvider ()
}
The starter kit uses ICU message format for advanced translations:
Variables
{
"welcome" : "Welcome {name}!"
}
i18n . t ( 'welcome' , { name: 'John' })
// Output: "Welcome John!"
Pluralization
{
"items" : "{count, plural, =0 {no items} one {# item} other {# items}}"
}
i18n . t ( 'items' , { count: 0 }) // "no items"
i18n . t ( 'items' , { count: 1 }) // "1 item"
i18n . t ( 'items' , { count: 5 }) // "5 items"
{
"greeting" : "{gender, select, male {Hello Mr. {name}} female {Hello Ms. {name}} other {Hello {name}}}"
}
Frontend Integration
Language Selector Component
import { usePage } from '@inertiajs/react'
export function LanguageSwitcher () {
const { i18n } = usePage (). props
return (
< select
value = { i18n . locale }
onChange = { ( e ) => {
window . location . href = `/switch/ ${ e . target . value } `
} }
>
< option value = "en" > English </ option >
< option value = "fr" > Français </ option >
< option value = "pt" > Português </ option >
</ select >
)
}
Using Translations in React
Create a translation hook:
app/common/ui/hooks/use_translation.ts
import { usePage } from '@inertiajs/react'
export function useTranslation () {
const { i18n } = usePage (). props
const t = ( key : string , data ?: Record < string , any >) => {
return i18n . t ( key , data )
}
return { t , locale: i18n . locale }
}
Use in components:
import { useTranslation } from '#common/ui/hooks/use_translation'
export function SignInForm () {
const { t } = useTranslation ()
return (
< div >
< h1 > { t ( 'auth.signin.title' ) } </ h1 >
< p > { t ( 'auth.signin.description' ) } </ p >
</ div >
)
}
Email Translations
Emails also support internationalization:
{
"emails" : {
"reset_password" : {
"subject" : "Reset your password" ,
"title" : "Oops!" ,
"subtitle" : "It seems that you've forgotten your password." ,
"action_btn" : "Reset Password"
}
}
}
Best Practices
Consistent Keys Use consistent, hierarchical key names (e.g., module.component.field.label).
Default Values Always provide English translations as fallback values.
Context Include context in key names to avoid ambiguity (button.submit vs form.submit).
Plurals Use ICU plural format for count-based messages.
Adding New Languages
Update Config
Add the locale code to supportedLocales in config/i18n.ts.
Create Directories
Create new language directories in each module’s resources/lang/ folder.
Translate Content
Copy English JSON files and translate all values.
Test
Test translations by switching locale via cookie or header.
Fully Internationalized : The entire application supports multiple languages with automatic detection and easy switching.