internationalization - nself-org/nchat GitHub Wiki

Internationalization (i18n) Guide

Last Updated: January 31, 2026 Version: 1.0.0

Overview

nself-chat includes a comprehensive internationalization (i18n) framework that supports multi-language content, RTL (right-to-left) languages, and locale-specific formatting. This guide covers everything you need to know about using and contributing translations.


Table of Contents

  1. Supported Languages
  2. Architecture
  3. Using Translations
  4. Translation Structure
  5. RTL Support
  6. Date and Number Formatting
  7. Contributing Translations
  8. Testing Translations
  9. Best Practices
  10. Troubleshooting

Supported Languages

nself-chat currently supports the following languages:

Language Code Direction Completion Native Name
English en LTR 100% English
Spanish es LTR 100% Espaรฑol
French fr LTR 100% Franรงais
German de LTR 100% Deutsch
Chinese (Simplified) zh LTR 100% ไธญๆ–‡
Arabic ar RTL 100% ุงู„ุนุฑุจูŠุฉ
Japanese ja LTR 95% ๆ—ฅๆœฌ่ชž
Portuguese pt LTR 95% Portuguรชs
Russian ru LTR 95% ะ ัƒััะบะธะน

Planned Languages

  • Hebrew (he) - RTL support
  • Korean (ko)
  • Italian (it)
  • Dutch (nl)
  • Turkish (tr)
  • Hindi (hi)

Architecture

Core Components

The i18n system consists of several key components:

src/
โ”œโ”€โ”€ lib/i18n/
โ”‚   โ”œโ”€โ”€ locales.ts          # Locale configurations
โ”‚   โ”œโ”€โ”€ translator.ts       # Translation engine
โ”‚   โ”œโ”€โ”€ i18n-config.ts      # Configuration
โ”‚   โ”œโ”€โ”€ plurals.ts          # Pluralization rules
โ”‚   โ”œโ”€โ”€ date-formats.ts     # Date/time formatting
โ”‚   โ”œโ”€โ”€ number-formats.ts   # Number formatting
โ”‚   โ”œโ”€โ”€ rtl.ts              # RTL support
โ”‚   โ”œโ”€โ”€ language-detector.ts # Browser detection
โ”‚   โ””โ”€โ”€ index.ts            # Public API
โ”œโ”€โ”€ stores/
โ”‚   โ””โ”€โ”€ locale-store.ts     # Zustand state management
โ”œโ”€โ”€ components/i18n/
โ”‚   โ”œโ”€โ”€ LocaleProvider.tsx  # React context
โ”‚   โ”œโ”€โ”€ LanguageSwitcher.tsx # UI component
โ”‚   โ”œโ”€โ”€ TranslatedText.tsx  # Text component
โ”‚   โ”œโ”€โ”€ FormattedDate.tsx   # Date formatting
โ”‚   โ”œโ”€โ”€ FormattedNumber.tsx # Number formatting
โ”‚   โ””โ”€โ”€ RTLWrapper.tsx      # RTL layout
โ””โ”€โ”€ locales/
    โ”œโ”€โ”€ en/                 # English translations
    โ”‚   โ”œโ”€โ”€ common.json
    โ”‚   โ”œโ”€โ”€ chat.json
    โ”‚   โ”œโ”€โ”€ settings.json
    โ”‚   โ””โ”€โ”€ admin.json
    โ”œโ”€โ”€ es/                 # Spanish translations
    โ”œโ”€โ”€ fr/                 # French translations
    โ”œโ”€โ”€ de/                 # German translations
    โ”œโ”€โ”€ zh/                 # Chinese translations
    โ”œโ”€โ”€ ar/                 # Arabic translations
    โ”œโ”€โ”€ ja/                 # Japanese translations
    โ”œโ”€โ”€ pt/                 # Portuguese translations
    โ””โ”€โ”€ ru/                 # Russian translations

Translation Loading

Translations are loaded dynamically using code-splitting:

// Dynamic import
const translations = await import(`@/locales/${locale}/${namespace}.json`)

This ensures:

  • Small initial bundle size
  • On-demand loading of languages
  • Automatic code-splitting per locale
  • Faster page loads

Using Translations

Basic Translation

import { translate, t } from '@/lib/i18n/translator'

// Using translate function
const text = translate('common:app.name')

// Using shorthand
const text = t('common:app.name')

// With default namespace (assumes 'common')
const text = t('app.name')

With Interpolation

// Simple interpolation
const text = t('time.ago', {
  values: { time: '5 minutes' },
})
// Output: "5 minutes ago"

// Multiple values
const text = t('validation.minLength', {
  values: { min: 8 },
})
// Output: "Must be at least 8 characters"

With Pluralization

// Pluralization (automatic based on count)
const text = t('time.minutes', {
  count: 1,
})
// Output: "1 minute"

const text = t('time.minutes', {
  count: 5,
})
// Output: "5 minutes"

Using React Hooks

import { useTranslation } from '@/hooks/use-translation';

function MyComponent() {
  const { t, locale, setLocale } = useTranslation();

  return (
    <div>
      <h1>{t('app.welcome')}</h1>
      <p>Current locale: {locale}</p>
    </div>
  );
}

Using Components

import { TranslatedText } from '@/components/i18n/TranslatedText';
import { FormattedDate } from '@/components/i18n/FormattedDate';
import { FormattedNumber } from '@/components/i18n/FormattedNumber';

function MyComponent() {
  return (
    <div>
      {/* Simple translation */}
      <TranslatedText i18nKey="app.name" />

      {/* With interpolation */}
      <TranslatedText
        i18nKey="time.ago"
        values={{ time: '5 minutes' }}
      />

      {/* Date formatting */}
      <FormattedDate date={new Date()} format="long" />

      {/* Number formatting */}
      <FormattedNumber value={1234.56} style="currency" currency="USD" />
    </div>
  );
}

Translation Structure

Namespaces

Translations are organized into namespaces:

common.json - Common UI elements

{
  "app": {
    "name": "nChat",
    "tagline": "Team Communication Platform",
    "loading": "Loading...",
    "error": "An error occurred"
  },
  "navigation": { ... },
  "time": { ... },
  "validation": { ... },
  "errors": { ... },
  "status": { ... },
  "notifications": { ... },
  "confirmations": { ... },
  "empty": { ... },
  "accessibility": { ... },
  "language": { ... }
}

chat.json - Chat-specific strings

{
  "messages": { ... },
  "channels": { ... },
  "threads": { ... },
  "directMessages": { ... },
  "mentions": { ... },
  "files": { ... },
  "search": { ... },
  "presence": { ... },
  "notifications": { ... },
  "members": { ... },
  "reactions": { ... },
  "formatting": { ... }
}

settings.json - Settings UI

{
  "settings": { ... },
  "profile": { ... },
  "account": { ... },
  "appearance": { ... },
  "notifications": { ... },
  "privacy": { ... },
  "language": { ... },
  "accessibility": { ... },
  "advanced": { ... },
  "about": { ... }
}

admin.json - Admin dashboard

{
  "admin": { ... },
  "dashboard": { ... },
  "users": { ... },
  "roles": { ... },
  "channels": { ... },
  "moderation": { ... },
  "analytics": { ... },
  "settings": { ... },
  "integrations": { ... },
  "logs": { ... },
  "setup": { ... }
}

Key Naming Conventions

  1. Use dot notation: section.subsection.key
  2. Be descriptive: channels.createPublic not channels.cp
  3. Group related keys: All button text under buttons.*
  4. Plural forms: Use _one and _other suffixes
    {
      "messages_one": "{{count}} message",
      "messages_other": "{{count}} messages"
    }

Interpolation Syntax

Use {{variable}} for variable interpolation:

{
  "welcome": "Welcome, {{name}}!",
  "itemsSelected": "{{count}} items selected",
  "validation": {
    "minLength": "Must be at least {{min}} characters"
  }
}

RTL Support

Supported RTL Languages

  • Arabic (ar)
  • Hebrew (he) - planned

Automatic RTL Detection

The i18n system automatically detects RTL languages and applies appropriate styles:

// Automatic detection and application
import { getDirection, applyDocumentDirection } from '@/lib/i18n/rtl'

const direction = getDirection('ar') // 'rtl'
applyDocumentDirection('ar') // Applies dir="rtl" to <html>

RTL Component Wrapper

import { RTLWrapper } from '@/components/i18n/RTLWrapper';

function MyComponent() {
  return (
    <RTLWrapper>
      <div>Content automatically adapts to RTL</div>
    </RTLWrapper>
  );
}

CSS Considerations

Use logical properties for RTL compatibility:

/* โŒ Avoid */
.element {
  margin-left: 10px;
  text-align: left;
}

/* โœ… Use logical properties */
.element {
  margin-inline-start: 10px;
  text-align: start;
}

/* โœ… Or use Tailwind RTL utilities */
.element {
  @apply ms-2.5; /* margin-start */
  @apply text-start;
}

Date and Number Formatting

Date Formatting

import { formatDate, formatRelativeTime } from '@/lib/i18n/date-formats'

// Format date
const formatted = formatDate(new Date(), 'long', 'en')
// Output: "January 31, 2026"

// Relative time
const relative = formatRelativeTime(new Date(), 'en')
// Output: "just now"

Date Format Options

  • short: "1/31/26"
  • medium: "Jan 31, 2026"
  • long: "January 31, 2026"
  • full: "Friday, January 31, 2026"
  • time: "3:45 PM"
  • datetime: "Jan 31, 2026, 3:45 PM"

Number Formatting

import { formatNumber, formatCurrency } from '@/lib/i18n/number-formats'

// Format number
const num = formatNumber(1234.56, 'en')
// Output: "1,234.56"

// Format currency
const price = formatCurrency(99.99, 'USD', 'en')
// Output: "$99.99"

// Format percentage
const percent = formatNumber(0.85, 'en', { style: 'percent' })
// Output: "85%"

Contributing Translations

We welcome translation contributions from the community! Here's how to contribute:

Quick Start

  1. Fork the repository

    git clone https://github.com/yourusername/nself-chat.git
    cd nself-chat
  2. Choose your language

    • Check /src/locales/ for existing languages
    • Copy en/ folder as template if starting new language
  3. Translate the files

    • Edit JSON files in /src/locales/[your-lang]/
    • Keep the keys the same, translate only the values
    • Maintain interpolation variables like {{name}}
  4. Update locale configuration

    Edit /src/lib/i18n/locales.ts:

    export const SUPPORTED_LOCALES = {
      // ... existing locales
      it: {
        code: 'it',
        name: 'Italiano',
        englishName: 'Italian',
        script: 'Latn',
        direction: 'ltr',
        bcp47: 'it-IT',
        flag: '๐Ÿ‡ฎ๐Ÿ‡น',
        dateFnsLocale: 'it',
        numberLocale: 'it-IT',
        pluralRule: 'other',
        isComplete: false,
        completionPercent: 0,
      },
    }
  5. Test your translations

    pnpm dev
    # Navigate to settings and change language
  6. Submit a pull request

    git checkout -b add-italian-translation
    git add src/locales/it/
    git commit -m "Add Italian translation"
    git push origin add-italian-translation

Translation Guidelines

1. Maintain Context

  • Understand the UI context where text appears
  • Ask for screenshots if unclear
  • Check similar apps in your language for terminology

2. Preserve Formatting

  • Keep interpolation variables: {{name}}, {{count}}
  • Maintain HTML entities: &nbsp;, &mdash;
  • Don't translate technical terms like "URL", "API", "OAuth"

3. Handle Plurals Correctly

English uses _one and _other:

{
  "messages_one": "{{count}} message",
  "messages_other": "{{count}} messages"
}

Some languages need more forms:

  • Arabic: _zero, _one, _two, _few, _many, _other
  • Polish: _one, _few, _many, _other
  • Russian: _one, _few, _many, _other

4. Use Appropriate Formality

  • Match the app's tone (professional but friendly)
  • Use informal "you" where appropriate (tu vs. vous)
  • Be consistent throughout

5. Test Edge Cases

  • Long words (German compounds)
  • Short words (Chinese characters)
  • RTL text (Arabic/Hebrew)
  • Special characters

Validation Checklist

Before submitting:

  • All JSON files are valid (no syntax errors)
  • All keys from English version are present
  • Interpolation variables are unchanged
  • Plural forms follow locale's plural rules
  • No machine translation without review
  • Tested in UI (if possible)
  • Proper character encoding (UTF-8)
  • Consistent terminology across files

Review Process

  1. Automated checks:

    • JSON syntax validation
    • Key completeness check
    • Interpolation variable check
  2. Community review:

    • Native speakers review translations
    • Maintainers check for consistency
    • UI testing with new translations
  3. Approval and merge:

    • At least one native speaker approval
    • Maintainer approval
    • Merge to main branch
    • Deploy in next release

Testing Translations

Manual Testing

  1. Change language in UI:

    • Go to Settings โ†’ Language
    • Select your language
    • Navigate through all pages
  2. Check for issues:

    • Text overflow/truncation
    • Misaligned elements
    • Missing translations (showing keys)
    • Broken layouts (especially RTL)

Automated Testing

# Run translation tests
pnpm test src/lib/i18n/__tests__/

# Specific locale test
pnpm test src/lib/i18n/__tests__/locales.test.ts

# Test translator
pnpm test src/lib/i18n/__tests__/translator.test.ts

Validation Script

# Validate all translation files
node scripts/validate-translations.js

# Check specific language
node scripts/validate-translations.js --locale=es

# Find missing keys
node scripts/validate-translations.js --find-missing

Best Practices

For Developers

  1. Always use translation keys

    // โŒ Don't hardcode strings
    <button>Save</button>
    
    // โœ… Use translation keys
    <button>{t('app.save')}</button>
  2. Provide context in keys

    // โŒ Vague
    t('submit')
    
    // โœ… Descriptive
    t('settings.account.submit')
  3. Use interpolation for dynamic content

    // โŒ Don't concatenate
    const text = userName + ' sent a message'
    
    // โœ… Use interpolation
    const text = t('messages.userSent', { values: { user: userName } })
  4. Handle plurals properly

    // โŒ Manual plural logic
    const text = count === 1 ? '1 message' : `${count} messages`
    
    // โœ… Use plural keys
    const text = t('messages.count', { count })

For Translators

  1. Translate meaning, not words

    • Adapt idioms to your language
    • Use natural phrasing
    • Consider cultural context
  2. Maintain consistency

    • Use same terms for same concepts
    • Keep tone consistent
    • Follow your language's style guide
  3. Test in context

    • See how translations look in UI
    • Check text length and wrapping
    • Verify with native speakers
  4. Document ambiguities

    • Add comments for unclear terms
    • Provide alternatives for review
    • Ask questions in pull requests

Troubleshooting

Common Issues

1. Translations not loading

Problem: Changing language doesn't update UI

Solutions:

// Check if namespace is loaded
const { loadNamespace } = useLocaleStore()
await loadNamespace('chat')

// Force reload
window.location.reload()

2. Missing translation keys

Problem: Seeing keys like common:app.name instead of translated text

Solutions:

  • Check if key exists in translation file
  • Verify namespace is loaded
  • Check for typos in key name
  • Ensure fallback locale (en) has the key

3. RTL layout issues

Problem: RTL languages display incorrectly

Solutions:

  • Use logical CSS properties (margin-inline-start vs margin-left)
  • Wrap components in <RTLWrapper>
  • Check dir attribute on <html>
  • Use Tailwind RTL utilities

4. Plural forms not working

Problem: Always showing same plural form

Solutions:

// Ensure count is passed
t('messages.count', { count: 5 }); // โœ…

t('messages.count', { values: { count: 5 } }); // โŒ

// Check plural keys exist
{
  "messages.count_one": "{{count}} message",
  "messages.count_other": "{{count}} messages"
}

5. Date/number formatting incorrect

Problem: Dates or numbers not formatted for locale

Solutions:

// Use locale-aware formatting
import { formatDate } from '@/lib/i18n/date-formats';
const formatted = formatDate(date, 'long', locale);

// Or use components
<FormattedDate date={date} format="long" />
<FormattedNumber value={1234.56} />

Debug Mode

Enable i18n debug mode:

// In i18n-config.ts
export const i18nConfig = {
  debug: true, // Enable logging
  // ...
}

This will log:

  • Missing translations
  • Fallback usage
  • Namespace loading
  • Translation lookups

Resources

Documentation

External Resources

Community


License

All translations are part of the nself-chat project and follow the same license (MIT).

Contributors retain copyright of their translations but grant nself-chat the right to use, modify, and distribute them under the project license.


Need Help?

Want to contribute? See Contributing.md for more information!

โš ๏ธ **GitHub.com Fallback** โš ๏ธ