Skip to main content

Translation System

Deltalytix uses next-international for internationalization (i18n). Currently supported languages:
  • ๐Ÿ‡บ๐Ÿ‡ธ English (en) - Default
  • ๐Ÿ‡ซ๐Ÿ‡ท French (fr) - Complete
We welcome contributions to add more languages!

How It Works

Translation Structure

Translations are organized in the /locales directory:
locales/
โ”œโ”€โ”€ client.ts          # Client-side i18n setup
โ”œโ”€โ”€ server.ts          # Server-side i18n setup
โ”œโ”€โ”€ en.ts              # English translations (main file)
โ”œโ”€โ”€ fr.ts              # French translations (main file)
โ”œโ”€โ”€ en/                # English translation modules
โ”‚   โ”œโ”€โ”€ shared.ts
โ”‚   โ”œโ”€โ”€ landing.ts
โ”‚   โ”œโ”€โ”€ pricing.ts
โ”‚   โ”œโ”€โ”€ auth.ts
โ”‚   โ”œโ”€โ”€ dropzone.ts
โ”‚   โ”œโ”€โ”€ mindset.ts
โ”‚   โ”œโ”€โ”€ propfirm.ts
โ”‚   โ”œโ”€โ”€ chat.ts
โ”‚   โ”œโ”€โ”€ teams.ts
โ”‚   โ”œโ”€โ”€ referral.ts
โ”‚   โ”œโ”€โ”€ admin.ts
โ”‚   โ”œโ”€โ”€ faq.ts
โ”‚   โ””โ”€โ”€ terms.ts
โ””โ”€โ”€ fr/                # French translation modules
    โ”œโ”€โ”€ shared.ts
    โ”œโ”€โ”€ landing.ts
    โ””โ”€โ”€ ... (same structure)

Client vs Server

Use useI18n() hook in client components:
'use client'
import { useI18n } from '@/locales/client'

export function TradeCard() {
  const t = useI18n()
  
  return (
    <div>
      <h3>{t('trade-table.instrument')}</h3>
      <p>{t('trade-table.pnl')}</p>
    </div>
  )
}

Adding a New Language

1

Create Language Directory

Create a new directory for your language code:
mkdir locales/es  # For Spanish
mkdir locales/de  # For German
mkdir locales/pt  # For Portuguese
2

Copy Translation Files

Copy all files from the en/ directory to your new language directory:
cp -r locales/en/* locales/es/
3

Create Main Language File

Create a main file (e.g., es.ts) that imports all modules:
locales/es.ts
import shared from "./es/shared"
import landing from "./es/landing"
import pricing from "./es/pricing"
import auth from "./es/auth"
import dropzone from "./es/dropzone"
import mindset from "./es/mindset"
import propfirm from "./es/propfirm"
import chat from "./es/chat"
import terms from "./es/terms"
import embed from "./es/embed"
import teams from "./es/teams"
import referral from "./es/referral"
import admin from "./es/admin"
import faq from "./es/faq"

export default {
  ...shared,
  ...landing,
  ...pricing,
  ...auth,
  ...dropzone,
  ...mindset,
  ...propfirm,
  ...chat,
  ...terms,
  ...embed,
  ...teams,
  ...referral,
  ...admin,
  ...faq,
  // Add footer and other translations...
}
4

Update Client Configuration

Add your language to locales/client.ts:
locales/client.ts
"use client"
import { createI18nClient } from 'next-international/client'

export const {
  useI18n,
  useScopedI18n,
  I18nProviderClient,
  useChangeLocale,
  useCurrentLocale
} = createI18nClient({
  en: () => import('./en'),
  fr: () => import('./fr'),
  es: () => import('./es'), // Add your language
})
5

Update Server Configuration

Add your language to locales/server.ts:
locales/server.ts
import { createI18nServer } from 'next-international/server'

export const {
  getI18n,
  getScopedI18n,
  getCurrentLocale,
  getStaticParams,
} = createI18nServer({
  en: () => import('./en'),
  fr: () => import('./fr'),
  es: () => import('./es'), // Add your language
})
6

Translate the Content

Now translate all strings in your language files. Start with the most important files:
  1. shared.ts - Common UI elements
  2. landing.ts - Landing page
  3. auth.ts - Authentication
  4. Continue with other modules

Translation Guidelines

Translation Keys

Translation keys use dot notation for organization:
// Good - organized and descriptive
"dashboard.myAccount": "My Account"
"dashboard.settings": "Settings"
"trade-table.instrument": "Instrument"
"trade-table.pnl": "PnL"

// Bad - flat and unclear
"myaccount": "My Account"
"settings1": "Settings"

Variable Interpolation

Use curly braces for variables:
// In translation file
{
  "calendar.trades": "trades",
  "calendar.trade": "trade",
  "footer.copyright": "ยฉ {year} Deltalytix. All rights reserved.",
  "import.successDescription": "{numberOfTradesAdded} trades have been imported."
}

// In component
const t = useI18n()
const year = new Date().getFullYear()

return (
  <footer>
    <p>{t('footer.copyright', { year })}</p>
    <p>{t('import.successDescription', {
      numberOfTradesAdded: 127
    })}</p>
  </footer>
)

Pluralization

// For conditional plurals
const tradeCount = trades.length
const label = tradeCount === 1 
  ? t('calendar.trade') 
  : t('calendar.trades')

// Display: "1 trade" or "5 trades"

Context-Specific Translations

Organize translations by feature or page:
locales/en/shared.ts
export default {
  shared: {
    loading: 'Loading your shared trades...',
    notFound: 'Share not found',
    expired: 'Share expired',
    error: 'Error',
    sharedOn: 'Shared on',
    dateRange: 'Date range',
  }
} as const
locales/fr/shared.ts
export default {
  shared: {
    loading: 'Chargement de vos trades partagรฉs...',
    notFound: 'Partage non trouvรฉ',
    expired: 'Partage expirรฉ',
    error: 'Erreur',
    sharedOn: 'Partagรฉ le',
    dateRange: 'Pรฉriode',
  }
} as const

Translation Examples

Basic Usage

import { useI18n } from '@/locales/client'

export function DashboardHeader() {
  const t = useI18n()
  
  return (
    <header>
      <h1>{t('dashboard.myAccount')}</h1>
      <nav>
        <a href="#">{t('dashboard.profile')}</a>
        <a href="#">{t('dashboard.settings')}</a>
        <button>{t('dashboard.logOut')}</button>
      </nav>
    </header>
  )
}

With Variables

import { useI18n } from '@/locales/client'

export function AccountCard({ account }: { account: Account }) {
  const t = useI18n()
  
  return (
    <div>
      <h3>{t('propFirm.configurator.title', {
        accountNumber: account.accountNumber
      })}</h3>
      <p>{t('filters.balanceLabel', {
        balance: formatCurrency(account.balance)
      })}</p>
    </div>
  )
}

Scoped Translations

import { useScopedI18n } from '@/locales/client'

export function TradeTable() {
  // Get translations scoped to 'trade-table'
  const t = useScopedI18n('trade-table')
  
  return (
    <table>
      <thead>
        <tr>
          <th>{t('instrument')}</th>
          <th>{t('pnl')}</th>
          <th>{t('quantity')}</th>
        </tr>
      </thead>
    </table>
  )
}

Changing Locale

import { useChangeLocale, useCurrentLocale } from '@/locales/client'

export function LanguageSelector() {
  const changeLocale = useChangeLocale()
  const currentLocale = useCurrentLocale()
  
  return (
    <select
      value={currentLocale}
      onChange={(e) => changeLocale(e.target.value)}
    >
      <option value="en">English</option>
      <option value="fr">Franรงais</option>
      <option value="es">Espaรฑol</option>
    </select>
  )
}

Translation Workflow

Step-by-Step Process

1

Identify Untranslated Text

Look for hardcoded strings in components:
// โŒ Bad - hardcoded
<button>Delete Trade</button>

// โœ… Good - translated
<button>{t('trade-table.delete')}</button>
2

Add to English File

Add the translation key to the appropriate English file:
locales/en.ts
{
  "trade-table.delete": "Delete Trade"
}
3

Add to All Other Languages

Add the same key to all other language files:
locales/fr.ts
{
  "trade-table.delete": "Supprimer le trade"
}
4

Use in Component

Import and use the translation:
const t = useI18n()
return <button>{t('trade-table.delete')}</button>
5

Test All Languages

Switch between languages to verify:
  • Translation appears correctly
  • No missing keys
  • Formatting is appropriate
  • Variables work as expected

Common Patterns

Nested Objects

export default {
  "calendar.weekdays": {
    "sun": "Sun",
    "mon": "Mon",
    "tue": "Tue",
    "wed": "Wed",
    "thu": "Thu",
    "fri": "Fri",
    "sat": "Sat"
  }
}

// Usage
const day = t('calendar.weekdays.mon') // "Mon"

Complex Translations

// For objects with multiple properties
"authentication.termsAndPrivacy": {
  prefix: "By clicking continue, you agree to our",
  terms: "Terms of Service",
  and: "and",
  privacy: "Privacy Policy"
}

// Usage in component
const terms = t('authentication.termsAndPrivacy')

return (
  <p>
    {terms.prefix} <a>{terms.terms}</a> {terms.and} <a>{terms.privacy}</a>
  </p>
)

Conditional Text

// Translation file
{
  "widgets.tags.deleteConfirmTitle": "Delete Tag",
  "widgets.tags.deleteConfirmDescription": "Are you sure you want to delete the tag '{tag}'? This action cannot be undone."
}

// Component
const t = useI18n()
const tagName = "Mistake"

return (
  <AlertDialog>
    <AlertDialogTitle>
      {t('widgets.tags.deleteConfirmTitle')}
    </AlertDialogTitle>
    <AlertDialogDescription>
      {t('widgets.tags.deleteConfirmDescription', { tag: tagName })}
    </AlertDialogDescription>
  </AlertDialog>
)

Best Practices

Be Consistent

Use consistent terminology across all translations. Create a glossary for key terms.

Context Matters

Provide context for translators. Some words have different meanings in different contexts.

Keep it Natural

Translate meaning, not just words. Make it sound natural in the target language.

Test Thoroughly

Test translations in the actual UI. Some languages need more space than English.

Translation Checklist

  • All new text has translation keys
  • Keys are descriptive and organized
  • Variables are properly interpolated
  • Translations added to all language files
  • Tested in UI with all languages
  • No hardcoded strings remain
  • Pluralization handled correctly
  • Context provided for ambiguous terms

Formatting and Locale

Dates and Times

Deltalytix uses date-fns for date formatting:
import { format } from 'date-fns'
import { enUS, fr } from 'date-fns/locale'
import { useCurrentLocale } from '@/locales/client'

const currentLocale = useCurrentLocale()
const dateLocale = currentLocale === 'fr' ? fr : enUS

const formatted = format(date, 'PPP', { locale: dateLocale })
// EN: January 1, 2026
// FR: 1 janvier 2026

Numbers and Currency

import { useCurrentLocale } from '@/locales/client'

const locale = useCurrentLocale()

// Currency formatting
const formatted = new Intl.NumberFormat(locale, {
  style: 'currency',
  currency: 'USD'
}).format(1234.56)
// EN: $1,234.56
// FR: 1 234,56 $US

// Number formatting
const number = new Intl.NumberFormat(locale).format(1234567)
// EN: 1,234,567
// FR: 1 234 567

Contributing Translations

Submitting Your Translation

1

Create a Branch

git checkout -b translation/spanish
2

Add Translation Files

Create all necessary translation files for your language.
3

Test Thoroughly

Test all pages and features with your translation.
4

Submit Pull Request

Create a PR with:
  • Clear title: โ€œAdd Spanish translation (es)โ€
  • Description of whatโ€™s translated
  • Screenshots showing the translation in use

PR Checklist for Translations

  • All translation files created
  • Client and server configs updated
  • No English text remains in files
  • Tested in development environment
  • Screenshots included in PR
  • No missing translation keys
  • Formatting appropriate for language
Need help? Ask in our Discord #translations channel!

Resources

Build docs developers (and LLMs) love