Internationalization Overview
SaaS4Builders is fully internationalized. The boilerplate ships with four locales — English, French, Spanish, and Italian — complete with translations for every user-facing string. The locale list is entirely yours to customize: you can add new languages, remove ones you don't need, or replace the defaults altogether. The i18n system spans both the backend and frontend, using two independent but complementary translation layers.
Architecture at a Glance
The translation system is split into two layers, each using the conventions native to its stack:
| Layer | Technology | Storage | Scope |
|---|---|---|---|
| Backend | Laravel lang files + Astrotomic Translatable | PHP arrays in backend/lang/ + database translation tables | API messages, emails, PDF exports, model data (product names, plan descriptions) |
| Frontend | Nuxt i18n module (@nuxtjs/i18n) | JSON files in frontend/i18n/locales/ | UI labels, navigation, form validation messages, error messages |
These two layers are independent — the backend does not serve translations to the frontend. Each stack loads its own translations at its own level, which means:
- Backend translations are used for API error messages, email content, invoice PDFs, and any server-rendered text
- Frontend translations are used for everything the user sees in the browser
- Catalog data (product names, plan descriptions, feature names) lives in the database with per-locale rows, managed by the Astrotomic Translatable package
Included Locales
The boilerplate ships with complete translations for four locales:
| Code | Language | BCP 47 Tag | Direction |
|---|---|---|---|
en | English | en-US | LTR |
fr | Français | fr-FR | LTR |
es | Español | es-ES | LTR |
it | Italiano | it-IT | LTR |
Both stacks use the same locale codes (en, fr, es, it). The BCP 47 tags (e.g., en-US) are used on the frontend for locale-aware formatting of numbers, currencies, and dates via the browser's Intl API.
Translation coverage per included locale:
| Layer | Files | Approximate Keys per Locale |
|---|---|---|
| Backend | 16 PHP files per locale (64 total) | ~320 |
| Frontend | 1 JSON file per locale (4 total) | ~1,700 |
Backend Locale Detection
The backend determines the active locale through the SetLocaleFromHeader middleware, which runs on every API request:
public function handle(Request $request, Closure $next): Response
{
// Priority 1: Query parameter ?lang=xx
$langParam = $request->query('lang');
if ($langParam !== null && is_string($langParam)) {
$supportedLocales = config('app.supported_locales', ['en']);
$language = $this->extractLanguageCode($langParam);
if (in_array($language, $supportedLocales, true)) {
App::setLocale($language);
return $next($request);
}
}
// Priority 2: Accept-Language header
$locale = $this->parseAcceptLanguage($request->header('Accept-Language'));
App::setLocale($locale);
return $next($request);
}
Resolution priority:
- Query parameter (
?lang=fr) — explicit override, highest priority - Accept-Language header — respects RFC quality values (e.g.,
fr;q=0.9, en;q=0.8) - Fallback — defaults to
enif no supported locale is found
The active locale list is configured via environment variable — add or remove codes here to control which languages your application accepts:
APP_SUPPORTED_LOCALES=en,fr,es,it
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
Frontend Locale Detection
The frontend uses the @nuxtjs/i18n module with a URL prefix strategy — the locale appears in the URL path:
/en/dashboard → English
/fr/dashboard → French
/es/dashboard → Spanish
/it/dashboard → Italian
i18n: {
defaultLocale: process.env.NUXT_PUBLIC_LOCALE || 'en',
locales,
strategy: 'prefix',
detectBrowserLanguage: {
useCookie: true,
cookieKey: 'i18n_redirected',
redirectOn: 'root',
},
},
On first visit, the browser's preferred language is detected and stored in the i18n_redirected cookie. Subsequent visits use the cookie value to redirect from the root URL to the appropriate locale prefix. Users can switch locales at any time through the language selector in the UI.
The locale definitions live in a dedicated configuration file:
export const localeCodes = ['en', 'fr', 'es', 'it'] as const
export type LocaleCode = (typeof localeCodes)[number]
export const locales: LocaleObject[] = [
{ code: 'en', name: 'English', language: 'en-US', dir: 'ltr', file: 'en.json' },
{ code: 'fr', name: 'Français', language: 'fr-FR', dir: 'ltr', file: 'fr.json' },
{ code: 'es', name: 'Español', language: 'es-ES', dir: 'ltr', file: 'es.json' },
{ code: 'it', name: 'Italiano', language: 'it-IT', dir: 'ltr', file: 'it.json' },
]
The language field maps each short code to its BCP 47 tag, which the Intl API uses for locale-aware formatting of currencies, numbers, and dates.
What Gets Translated
Translation content falls into three categories, each handled differently:
System Messages (Backend PHP Files)
API error messages, email subjects and bodies, invoice PDF labels, notification text, and validation messages. These live in PHP array files under backend/lang/{locale}/, organized by domain.
'invitation_sent' => 'Invitation sent successfully.',
'seat_limit_reached' => 'Seat limit reached: :current/:max seats used.',
UI Strings (Frontend JSON Files)
Navigation labels, button text, form labels, page titles, and client-side error messages. These live in JSON files under frontend/i18n/locales/.
{
"navigation": {
"sidebar": {
"dashboard": "Dashboard",
"billing": "Billing",
"team": "Team"
}
}
}
Catalog Data (Database Translations)
Product names, plan descriptions, and feature names — content that is managed by platform administrators and may change at runtime. These are stored in dedicated translation tables in the database, managed by the astrotomic/laravel-translatable package.
| Model | Translatable Fields |
|---|---|
| Product | name, description |
| Plan | name, description |
| Feature | name, description |
What's Next
- Backend Translations — Laravel lang file structure, model translations with Astrotomic Translatable, and how to add new translation keys
- Frontend i18n — Nuxt i18n module setup, JSON locale files, Zod validation integration, and currency formatting
Nuxt Studio Integration
Visual content editing with Nuxt Studio, secured by a custom Sanctum-based auth bridge that reuses your existing platform admin permissions.
Backend Translations
Laravel translation files, model translations with Astrotomic Translatable, validation of translatable input, and how to add new translation keys or locales.