Skip to content

i18n Translation Guide

Dheeraj Reddy edited this page Nov 18, 2025 · 2 revisions

Table of Contents

  1. Overview
  2. Directory Structure
  3. Translation Keys Naming Convention
  4. How to Add Translations
  5. Quality Assurance Tips
  6. Creating Namespaces
  7. Using Translations in Components
  8. Best Practices

Overview

The Saayam webapp uses react-i18next for internationalization (i18n). All text content, including buttons, labels, messages, and UI elements, must use translation keys—even for English.

Key Principles:

  • Every piece of text must use a translation key (no hardcoded strings)
  • Namespace organization - Group related translations into separate JSON files.
  • 10 Languages supported as of now: English (en), Hindi (hi), Telugu (te), Bengali (bn), German (de), Spanish (es), French (fr), Portuguese (pt), Russian (ru), Mandarin Chinese (zh)
  • UPPERCASE_UNDERSCORE naming convention for all keys

Directory Structure

src/common/i18n/
├── locales/
│   ├── en/
│   │   ├── common.json      # Common UI elements (buttons, navigation, etc.)
│   │   ├── auth.json        # Authentication related
│   │   ├── categories.json  # Help categories
│   │   ├── availability.json # Availability page
│   │   ├── identity.json    # Identity document page
│   │   ├── profile.json     # Profile pages
│   │   └── ...
│   ├── hi/
│   │   ├── common.json      # Hindi translations
│   │   ├── auth.json
│   │   └── ...
│   ├── te/                # Telugu translations
│   ├── bn/                # Bengali translations
│   ├── de/                # German translations
│   ├── es/                # Spanish translations
│   ├── fr/                # French translations
│   ├── pt/                # Portuguese translations
│   ├── ru/                # Russian translations
│   └── zh/                # Mandarin Chinese translations
├── i18n.js                # i18next configuration
├── languagesData.js       # Supported languages list
└── utils.js               # i18n utility functions

Translation Keys Naming Convention

Rules:

  1. Use UPPERCASE_UNDERSCORE format
  2. Be descriptive - Key name should indicate its purpose
  3. Avoid abbreviations unless commonly understood
  4. Group related keys using prefixes when appropriate

Examples:

Good Approach:

{
  "FIRST_NAME": "First Name",
  "LAST_NAME": "Last Name",
  "SAVE": "Save",
  "CANCEL": "Cancel",
  "PROFILE_UPDATE_SUCCESS": "Profile updated successfully!",
  "PASSWORD_REQUIREMENTS_ERROR": "Password must be at least 8 characters long"
}

Bad Approach:

{
  "firstName": "First Name",           // camelCase - incorrect
  "first_name": "First Name",          // lowercase - incorrect
  "First Name": "First Name",          // spaces - incorrect
  "FN": "First Name",                  // abbreviation - unclear
  "PROFILE_UPDATED": "Profile updated successfully!"  // inconsistent tense
}

Special Cases:

Language names:

{
  "English": "English",
  "Hindi": "हिंदी",
  "Telugu": "తెలుగు"
}

Keys with spaces(legacy compatibility - avoid in new code):

{
  "DEFAULT DASHBOARD VIEW": "Default Dashboard View"
}

How to Add Translations

Step 1: Identify the Namespace

Determine which namespace your translations belong to:

  • common - Shared UI elements (buttons, navigation, common messages)
  • auth - Authentication and authorization
  • profile - User profile pages
  • availability - Availability scheduling
  • identity - Identity verification
  • categories - Help request categories
  • Create a new namespace if none fit (see [Creating Namespaces]

Step 2: Add Keys to English (en) File

Start with the English translation file: File path: src/common/i18n/locales/en/profile.json

{
  "FIRST_NAME": "First Name",
  "LAST_NAME": "Last Name",
  "EMAIL": "Email",
  "PHONE_NUMBER": "Phone Number",
  "SAVE": "Save",
  "CANCEL": "Cancel"
}

Step 3: Add Translations to All 9 Other Languages

Copy the structure to all language files and translate the values (keep keys identical):

File Path: src/common/i18n/locales/hi/profile.json

{
  "FIRST_NAME": "पहला नाम",
  "LAST_NAME": "उपनाम",
  "EMAIL": "ईमेल",
  "PHONE_NUMBER": "फ़ोन नंबर",
  "SAVE": "सहेजें",
  "CANCEL": "रद्द करें"
}

File Path: src/common/i18n/locales/te/profile.json

{
  "FIRST_NAME": "మొదటి పేరు",
  "LAST_NAME": "చివరి పేరు",
  "EMAIL": "ఇమెయిల్",
  "PHONE_NUMBER": "ఫోన్ నంబర్",
  "SAVE": "సేవ్ చేయండి",
  "CANCEL": "రద్దు చేయండి"
}

Repeat for : bn/, de/, es/, fr/, pt/, ru/, zh/

Quality Assurance Tips:

  • Always review AI translations - AI can make mistakes with context.
  • Test in the UI - Verify translations fit in the UI layout.
  • Get native speaker review - If possible, have a native speaker verify.
  • Check for consistency - Ensure similar terms are translated consistently.
  • Sync text modifications – If you modify visible text in the UI, you must update the corresponding key in the translation files immediately.

Creating Namespaces

Namespaces help organize translations by feature/page. Create a new namespace when you have 20+ translation keys for a specific feature.

Step-By-Step Guide:

1. Create Translation Files

Create the JSON file in all 10 language folders:

# Create new namespace files
touch src/common/i18n/locales/en/donate.json
touch src/common/i18n/locales/hi/donate.json
touch src/common/i18n/locales/te/donate.json
touch src/common/i18n/locales/bn/donate.json
touch src/common/i18n/locales/de/donate.json
touch src/common/i18n/locales/es/donate.json
touch src/common/i18n/locales/fr/donate.json
touch src/common/i18n/locales/pt/donate.json
touch src/common/i18n/locales/ru/donate.json
touch src/common/i18n/locales/zh/donate.json

2. Add Translation Keys

File Path: src/common/i18n/locales/en/donate.json

{
  "PAGE_TITLE": "Support Saayam",
  "DONATE_NOW": "Donate Now",
  "ONE_TIME": "One-time Donation",
  "MONTHLY": "Monthly Donation",
  "AMOUNT": "Amount",
  "PAYMENT_METHOD": "Payment Method",
  "SUBMIT": "Submit Donation",
  "THANK_YOU": "Thank you for your support!"
}

Translate to all other languages.

3. Update i18n.js Configuration

File Path: src/common/i18n/i18n.js Add imports at the top:

import enDonate from "./locales/en/donate.json";
import hiDonate from "./locales/hi/donate.json";
import teDonate from "./locales/te/donate.json";
import bnDonate from "./locales/bn/donate.json";
import deDonate from "./locales/de/donate.json";
import esDonate from "./locales/es/donate.json";
import frDonate from "./locales/fr/donate.json";
import ptDonate from "./locales/pt/donate.json";
import ruDonate from "./locales/ru/donate.json";
import zhDonate from "./locales/zh/donate.json";

Add namespace to the ns array:

ns: ["common", "auth", "categories", "availability", "identity", "profile", "donate"],

Add to each language's resources:

resources: {
  en: {
    common: enCommon,
    auth: enAuth,
    // ... other namespaces
    donate: enDonate,  // ← Add this
  },
  hi: {
    common: hiCommon,
    auth: hiAuth,
    // ... other namespaces
    donate: hiDonate,  // ← Add this
  },
  // ... repeat for all 10 languages
}

4. Use in Components

import { useTranslation } from 'react-i18next';

function DonatePage() {
  const { t } = useTranslation('donate');  // ← Specify namespace
  
  return (
    <div>
      <h1>{t('PAGE_TITLE')}</h1>
      <button>{t('DONATE_NOW')}</button>
      <label>{t('AMOUNT')}</label>
    </div>
  );
}

Using Translations in Components

Basic Usage

import { useTranslation } from 'react-i18next';

function MyComponent() {
  const { t } = useTranslation('profile');  // Specify namespace
  
  return (
    <div>
      <h1>{t('PAGE_TITLE')}</h1>
      <button>{t('SAVE')}</button>
      <p>{t('DESCRIPTION')}</p>
    </div>
  );
}

With Default Namespace(common)

function MyComponent() {
  const { t } = useTranslation();  // Uses 'common' namespace by default
  
  return <button>{t('SUBMIT')}</button>;
}

Multiple Namespaces

function MyComponent() {
  const { t: tCommon } = useTranslation('common');
  const { t: tProfile } = useTranslation('profile');
  
  return (
    <div>
      <button>{tCommon('SAVE')}</button>
      <h1>{tProfile('YOUR_PROFILE')}</h1>
    </div>
  );
}

With Variables

Translation file:

{
  "WELCOME_MESSAGE": "Welcome, {{name}}!",
  "ITEMS_COUNT": "You have {{count}} items"
}

Component:

<p>{t('WELCOME_MESSAGE', { name: 'John' })}</p>
<p>{t('ITEMS_COUNT', { count: 5 })}</p>

With Fallback:

<button>{t('SAVE') || 'Save'}</button>

Best Practices

1. Never Hardcode Text

Bad Approach:

<button>Save</button>
<h1>User Profile</h1>

Good Approach

<button>{t('SAVE')}</button>
<h1>{t('USER_PROFILE')}</h1>

2. Use Appropriate Namespaces

Bad Approach:

// Using common namespace for profile-specific text
const { t } = useTranslation();
return <h1>{t('YOUR_PROFILE_PAGE_TITLE')}</h1>;

Good Approach:

// Using profile namespace for profile-specific text
const { t } = useTranslation('profile');
return <h1>{t('PAGE_TITLE')}</h1>;

3. Be Consistent with Key Names

Bad Approach:

{
  "SAVE_BUTTON": "Save",
  "cancelBtn": "Cancel",
  "Submit": "Submit"
}

Good Approach:

{
  "SAVE": "Save",
  "CANCEL": "Cancel",
  "SUBMIT": "Submit"
}

4. Avoid Concatenation

Bad Approach:

<p>{t('HELLO')} {userName}!</p>

Good Approach:

{ "GREETING": "Hello, {{name}}!" }
<p>{t('GREETING', { name: userName })}</p>

5. Test All Languages

  • Build the project after adding translations: npm run build
  • Manually test each language in the UI
  • Verify text fits in UI components
  • Check for missing translations

6. Keep keys in Sync

All 10 language files must have identical keys. Only values differ.

Bad Approach:

// en/profile.json
{ "SAVE": "Save" }

// hi/profile.json
{ "SAVE_BUTTON": "सहेजें" }  // ← Different key!

Good Approach:

// en/profile.json
{ "SAVE": "Save" }

// hi/profile.json  
{ "SAVE": "सहेजें" }

// te/profile.json
{ "SAVE": "సేవ్ చేయండి" }

Clone this wiki locally