Skip to main content

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:

English

en (Default)

Arabic

ar - العربية

Danish

da - Dansk

German

de - Deutsch

Spanish

es - Español

French

fr - Français

Italian

it - Italiano

Japanese

ja - 日本語

Korean

ko - 한국어

Polish

pl - Polski

Portuguese (BR)

pt - Português

Russian

ru - Русский

Swedish

sv - Svenska

Turkish

tr - Türkçe

Ukrainian

uk - Українська

Chinese (Simplified)

zh-Hans - 中文 (简体)

Chinese (Traditional)

zh-Hant - 中文 (繁體)

Configuration

The i18n configuration is defined in nuxt.config.ts:
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:
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:
i18n/locales/en.json
{
  "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:
en.json
{
  "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:
en.json
{
  "common": {
    "tournament_count": "No tournaments | {n} tournament | {n} tournaments"
  }
}
<template>
  <p>{{ $t('common.tournament_count', { n: count }, count) }}</p>
</template>

Adding New Translations

1

Add keys to English (en.json)

Start by adding your keys to the English translation file, which serves as the source of truth:
i18n/locales/en.json
{
  "pages": {
    "new_feature": {
      "title": "New Feature",
      "description": "Description of the new feature",
      "action": "Get Started"
    }
  }
}
2

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>
3

Add to other language files

Add the corresponding translations to other locale files (or leave for translators):
i18n/locales/es_ES.json
{
  "pages": {
    "new_feature": {
      "title": "Nueva Característica",
      "description": "Descripción de la nueva característica",
      "action": "Empezar"
    }
  }
}
4

Verify translations

Run the translation checker to ensure all keys are properly defined:
yarn check-translations

Translation Validation Script

The project includes a comprehensive translation validation script at scripts/check-translations.js.

Running the Checker

yarn check-translations

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"
    }
  }
}

Form Labels and Validation

{
  "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"
    }
  }
}

Action Buttons

{
  "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:
i18n

Contributing Guide

Learn how to contribute translations

Code Style Guide

Understand the codebase conventions

Nuxt i18n Docs

Official Nuxt i18n documentation

Build docs developers (and LLMs) love