Overview
5Stack supports 17 languages out of the box, making it accessible to a global community. The platform uses @nuxtjs/i18n for internationalization with JSON-based translation files.
Supported Languages
The platform currently supports:
Portuguese (BR) pt - Português
Chinese (Simplified) zh-Hans - 中文 (简体)
Chinese (Traditional) zh-Hant - 中文 (繁體)
Configuration
The i18n configuration is defined in nuxt.config.ts:
i18n : {
strategy : "no_prefix" ,
bundle : {
optimizeTranslationDirective : false ,
},
detectBrowserLanguage : {
useCookie : true ,
cookieKey : "i18n_redirected" ,
redirectOn : "root" ,
fallbackLocale : "en" ,
},
locales : [
{ code: "en" , name: "English" , file: "en.json" , flag: "🇬🇧" },
{ code: "ar" , name: "العربية" , file: "ar_SA.json" , flag: "🇸🇦" },
{ code: "de" , name: "Deutsch" , file: "de_DE.json" , flag: "🇩🇪" },
// ... more locales
],
lazy : true ,
defaultLocale : "en" ,
}
Additional configuration in i18n/i18n.config.ts:
export default defineI18nConfig (() => {
return {
fallbackWarn: false ,
fallbackLocale: "en" ,
}
} )
Translation File Structure
Translation files are located in i18n/locales/ and follow a nested structure:
{
"pagination" : {
"items_per_page" : "per page"
},
"pages" : {
"tournaments" : {
"title" : "Upcoming Tournaments" ,
"create" : "Create Tournament" ,
"open_for_registration" : "Open for Registration" ,
"tabs" : {
"finished" : "Finished" ,
"live" : "Live" ,
"upcoming" : "Upcoming"
}
},
"teams" : {
"title" : "Teams" ,
"description" : "Manage teams and rosters" ,
"create" : "Create Team"
}
},
"common" : {
"submit" : "Submit" ,
"cancel" : "Cancel" ,
"save" : "Save" ,
"delete" : "Delete"
}
}
Key Naming Conventions
Translation keys follow a hierarchical structure:
Top-level categories : pages, common, layouts, components
Page-specific keys : pages.tournaments.title, pages.teams.create
Reusable keys : common.submit, common.loading
Use snake_case : items_per_page, open_for_registration
Keys should be descriptive and follow the pattern: category.subcategory.key_name
Using Translations in Components
Basic Usage
Use the $t() function in templates:
< template >
< div >
< h1 > {{ $t ( 'pages.tournaments.title' ) }} </ h1 >
< button > {{ $t ( 'pages.tournaments.create' ) }} </ button >
</ div >
</ template >
With Composition API
Access translations in script setup:
< script setup lang = "ts" >
const { t } = useI18n ()
const pageTitle = t ( 'pages.tournaments.title' )
const createLabel = computed (() => t ( 'pages.tournaments.create' ))
</ script >
< template >
< div >
< h1 > {{ pageTitle }} </ h1 >
< button > {{ createLabel }} </ button >
</ div >
</ template >
Interpolation
Pass dynamic values to translations:
{
"common" : {
"greeting" : "Hello, {name}!" ,
"item_count" : "Showing {count} items"
}
}
< template >
< p > {{ $t ( 'common.greeting' , { name: userName }) }} </ p >
< p > {{ $t ( 'common.item_count' , { count: items . length }) }} </ p >
</ template >
Pluralization
Handle plural forms:
{
"common" : {
"tournament_count" : "No tournaments | {n} tournament | {n} tournaments"
}
}
< template >
< p > {{ $t ( 'common.tournament_count' , { n: count }, count ) }} </ p >
</ template >
Adding New Translations
Add keys to English (en.json)
Start by adding your keys to the English translation file, which serves as the source of truth: {
"pages" : {
"new_feature" : {
"title" : "New Feature" ,
"description" : "Description of the new feature" ,
"action" : "Get Started"
}
}
}
Use translations in your code
Reference the new keys in your components: < template >
< div >
< h1 > {{ $t ( 'pages.new_feature.title' ) }} </ h1 >
< p > {{ $t ( 'pages.new_feature.description' ) }} </ p >
< button > {{ $t ( 'pages.new_feature.action' ) }} </ button >
</ div >
</ template >
Add to other language files
Add the corresponding translations to other locale files (or leave for translators): {
"pages" : {
"new_feature" : {
"title" : "Nueva Característica" ,
"description" : "Descripción de la nueva característica" ,
"action" : "Empezar"
}
}
}
Verify translations
Run the translation checker to ensure all keys are properly defined:
Translation Validation Script
The project includes a comprehensive translation validation script at scripts/check-translations.js.
Running the Checker
What It Checks
The script performs several validation tasks:
Finds all $t("...") and t("...") calls in Vue, JS, and TS files: // Detected patterns
$t ( "pages.tournaments.title" )
t ( 'common.submit' )
i18n . t ( `settings.api_keys` )
Tracks dynamic template keys and validates them: // Dynamic key with variable
$t ( `pages. ${ pageName } .title` )
// The prefix "pages." is tracked and all matching keys are marked as used
Identifies string literals that look like translation keys: // These are detected based on pattern matching
const key = "pages.tournaments.title"
const translationKey = 'common.submit'
Reports keys used in code but not found in translation files: Missing Translations:
- pages.new_page.title
Used in:
pages/new-page.vue
Lists translation keys that exist but aren’t referenced anywhere: Unused Translations:
- pages.old_feature.description
Script Implementation
Key features of the validation script:
scripts/check-translations.js
// Extract translation keys from file content
function extractTranslationKeys ( content , keyPrefixPattern , dynamicPrefixes ) {
const keys = new Set ()
// Match $t("...") / t("...") patterns
const directPattern = / \b (?: \$ t | t ) \s * \( \s * ( [ '"` ] )( [ ^ '"` ] + ) \1 (?: \s * [ ,) ] ) / g
const directMatches = Array . from ( content . matchAll ( directPattern ))
directMatches . forEach (( match ) => {
const key = match [ 2 ]
// Handle dynamic keys like $t(`foo.${bar}`)
if ( key . includes ( "${" )) {
const prefix = key . split ( "${" )[ 0 ]
if ( prefix && dynamicPrefixes ) {
dynamicPrefixes . add ( prefix )
}
return
}
keys . add ( key )
})
return [ ... keys ]
}
Example Output
=== Translation Check Results ===
Checking en translations:
Missing Translations:
- pages.dashboard.welcome
Used in:
pages/dashboard.vue
Unused Translations:
- pages.old_page.subtitle
Summary:
Total available translations: 1247
Total used translations: 1246
Missing translations: 1
Unused translations: 1
Best Practices
Never Hardcode Text Always use translation keys for user-facing text, even for English-only content
Use Descriptive Keys Make keys self-documenting: pages.tournaments.create not btn1
Organize by Context Group related translations under the same parent key
Keep English Updated English (en.json) is the source of truth - update it first
Validate Regularly Run yarn check-translations before committing changes
Avoid Duplication Use common.* keys for frequently repeated text
Common Patterns
Page Titles and Descriptions
{
"pages" : {
"tournaments" : {
"title" : "Tournaments" ,
"description" : "Browse and manage CS2 tournaments"
}
}
}
{
"forms" : {
"tournament" : {
"name_label" : "Tournament Name" ,
"name_placeholder" : "Enter tournament name" ,
"name_required" : "Tournament name is required" ,
"max_teams_label" : "Maximum Teams"
}
}
}
Status Messages
{
"messages" : {
"success" : {
"tournament_created" : "Tournament created successfully" ,
"settings_saved" : "Settings saved"
},
"error" : {
"generic" : "An error occurred. Please try again." ,
"not_found" : "The requested resource was not found"
}
}
}
{
"common" : {
"actions" : {
"create" : "Create" ,
"edit" : "Edit" ,
"delete" : "Delete" ,
"save" : "Save" ,
"cancel" : "Cancel" ,
"confirm" : "Confirm"
}
}
}
Dynamic Language Switching
Users can switch languages at runtime:
< script setup lang = "ts" >
const { locale , locales } = useI18n ()
const availableLocales = computed (() => {
return locales . value . filter ( l => l . code !== locale . value )
})
const switchLanguage = ( code : string ) => {
locale . value = code
}
</ script >
< template >
< select v-model = " locale " >
< option v-for = " loc in locales " : key = " loc . code " : value = " loc . code " >
{{ loc . flag }} {{ loc . name }}
</ option >
</ select >
</ template >
Handling Missing Translations
When a translation key is missing, the system falls back to English. Always check the browser console for missing translation warnings during development.
The fallback behavior is configured in i18n.config.ts:
fallbackWarn : false , // Disable warnings in production
fallbackLocale : "en" , // Use English as fallback
Translation File Maintenance
File Locations
All translation files are in i18n/locales/:
i18n/
└── locales/
├── en.json # English (source)
├── ar_SA.json # Arabic
├── de_DE.json # German
├── es_ES.json # Spanish
├── fr_FR.json # French
└── ... # Other languages
Prettier Ignore
Translation files are excluded from Prettier formatting to preserve manual formatting and ordering.
From .prettierignore:
Contributing Guide Learn how to contribute translations
Code Style Guide Understand the codebase conventions
Nuxt i18n Docs Official Nuxt i18n documentation