Skip to content

Internationalization

DoCurious uses react-i18next with browser language detection to support multiple languages. The system is configured for English, Spanish, and French.

Setup

The i18n configuration lives in src/i18n/index.ts:

typescript
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import LanguageDetector from 'i18next-browser-languagedetector'

import en from './locales/en.json'
import es from './locales/es.json'
import fr from './locales/fr.json'

i18n
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    resources: {
      en: { translation: en },
      es: { translation: es },
      fr: { translation: fr },
    },
    fallbackLng: 'en',
    debug: import.meta.env.DEV,  // Log missing keys in development
    interpolation: {
      escapeValue: false,  // React already escapes values
    },
    detection: {
      order: ['localStorage', 'navigator', 'htmlTag'],
      caches: ['localStorage'],
      lookupLocalStorage: 'docurious-language',
    },
  })

Detection Order

  1. localStorage -- checks docurious-language key
  2. navigator -- uses browser language setting
  3. htmlTag -- reads lang attribute on <html>

The detected language is cached in localStorage so it persists across sessions.

Supported Languages

typescript
export const supportedLanguages = [
  { code: 'en', name: 'English', flag: '🇺🇸' },
  { code: 'es', name: 'Espanol', flag: '🇪🇸' },
  { code: 'fr', name: 'Francais', flag: '🇫🇷' },
] as const

export type LanguageCode = 'en' | 'es' | 'fr'

Translation File Structure

Translation files are JSON objects in src/i18n/locales/. The English file (en.json) is the primary reference:

json
{
  "common": {
    "loading": "Loading...",
    "error": "Error",
    "save": "Save",
    "cancel": "Cancel",
    "delete": "Delete",
    "edit": "Edit",
    "done": "Done",
    "close": "Close",
    "back": "Back",
    "next": "Next",
    "submit": "Submit",
    "search": "Search",
    "clearAll": "Clear All",
    "showMore": "Show More",
    "showLess": "Show Less",
    "seeAll": "See All",
    "noResults": "No results found",
    "points": "Points"
  },
  "nav": {
    "home": "Home",
    "dashboard": "Dashboard",
    "explore": "Explore",
    "myChallenges": "My Challenges",
    "communities": "Communities",
    "gifts": "Gifts",
    "school": "School",
    "admin": "Admin"
    // ... 30+ navigation keys
  },
  "explore": {
    "title": "Explore",
    "searchPlaceholder": "Search challenges...",
    "bestFit": "Best Fit",
    "featured": "Featured",
    "freeChallenges": "Free Challenges",
    "popular": "Popular",
    "new": "New"
    // ...
  },
  "search": { /* location, type, category, suggestions */ },
  "filters": {
    "title": "Filters",
    "categories": { /* 6 categories */ },
    "formats": { /* hosted, digitallyGuided, kit */ },
    "audiences": { /* kidOriented, family, allAges, adultOriented */ },
    "costs": { /* free, paid */ },
    "skillLevels": { /* beginner through expert */ },
    "durations": { /* halfDay, oneDay, multiDay, ongoing */ },
    "locations": { /* indoors, outdoors, combination */ },
    "participantOptions": { /* solo, pair, smallGroup, largeGroup */ },
    "seasons": { /* anySeason, spring, summer, fall, winter */ },
    "equipmentOptions": { /* noEquipment, basicSupplies, specialized */ }
  },
  "challenge": { /* free, difficulty, duration, startChallenge, etc. */ },
  "language": {
    "title": "Language",
    "select": "Select Language"
  }
}

Current Coverage

Translation coverage is currently focused on core navigation and the explore/search experience:

AreaCoverage
Navigation labelsTranslated
Explore pageTranslated
Search UITranslated
Filter panelTranslated
Challenge cardsPartially translated
Auth pagesNot yet translated
Admin pagesNot yet translated
School pagesNot yet translated
Vendor pagesNot yet translated
Error messagesNot yet translated
Form validationNot yet translated

Estimated overall coverage: ~6% of user-facing strings.

Using Translations in Components

With the useTranslation Hook

tsx
import { useTranslation } from 'react-i18next'

function ExploreHeader() {
  const { t } = useTranslation()

  return (
    <div>
      <h1>{t('explore.title')}</h1>
      <input placeholder={t('explore.searchPlaceholder')} />
    </div>
  )
}

With Interpolation

tsx
const { t } = useTranslation()

// If you add: "greeting": "Hello, {{name}}!"
t('greeting', { name: user.displayName })
// → "Hello, Alex!"

With Plurals

tsx
// If you add: "items_one": "{{count}} item", "items_other": "{{count}} items"
t('items', { count: 5 })
// → "5 items"

How to Add Translation Keys

  1. Add the key to src/i18n/locales/en.json:
json
{
  "mySection": {
    "newKey": "English text"
  }
}
  1. Add translations to es.json and fr.json:
json
{
  "mySection": {
    "newKey": "Texto en espanol"
  }
}
  1. Use in components:
tsx
const { t } = useTranslation()
return <span>{t('mySection.newKey')}</span>

Missing keys fall back to English. In development mode, missing key warnings are logged to the console.

How to Add a New Language

  1. Create a translation file at src/i18n/locales/xx.json (copy en.json as a starting point).

  2. Import and register in src/i18n/index.ts:

typescript
import xx from './locales/xx.json'

// Add to resources:
resources: {
  en: { translation: en },
  es: { translation: es },
  fr: { translation: fr },
  xx: { translation: xx },  // new language
},
  1. Add to the supportedLanguages array:
typescript
export const supportedLanguages = [
  { code: 'en', name: 'English', flag: '🇺🇸' },
  { code: 'es', name: 'Espanol', flag: '🇪🇸' },
  { code: 'fr', name: 'Francais', flag: '🇫🇷' },
  { code: 'xx', name: 'Language Name', flag: '🏳️' },
] as const
  1. Update the LanguageCode type (derived automatically from supportedLanguages).

LanguageSwitcher Component

A LanguageSwitcher component exists in the UI for users to change their language preference. It reads supportedLanguages and calls i18n.changeLanguage(code) on selection. The selected language is automatically persisted to localStorage under the docurious-language key.

Key Dependencies

PackageVersionPurpose
i18next23.11.5Core i18n framework
react-i18next14.1.2React bindings
i18next-browser-languagedetector7.2.1Auto-detect browser language

See Also

  • i18n guide -- internationalization feature overview, supported languages, and content translation strategy

DoCurious Platform Documentation