-
Notifications
You must be signed in to change notification settings - Fork 86
i18n Translation Guide
- Overview
- Directory Structure
- Translation Keys Naming Convention
- How to Add Translations
- Quality Assurance Tips
- Creating Namespaces
- Using Translations in Components
- Best Practices
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.
- 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
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
- Use UPPERCASE_UNDERSCORE format
- Be descriptive - Key name should indicate its purpose
- Avoid abbreviations unless commonly understood
- Group related keys using prefixes when appropriate
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
}
Language names:
{
"English": "English",
"Hindi": "हिंदी",
"Telugu": "తెలుగు"
}
Keys with spaces(legacy compatibility - avoid in new code):
{
"DEFAULT DASHBOARD VIEW": "Default Dashboard View"
}
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]
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"
}
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/
- ✅ 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.
Namespaces help organize translations by feature/page. Create a new namespace when you have 20+ translation keys for a specific feature.
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>
);
}
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>
);
}
function MyComponent() {
const { t } = useTranslation(); // Uses 'common' namespace by default
return <button>{t('SUBMIT')}</button>;
}
function MyComponent() {
const { t: tCommon } = useTranslation('common');
const { t: tProfile } = useTranslation('profile');
return (
<div>
<button>{tCommon('SAVE')}</button>
<h1>{tProfile('YOUR_PROFILE')}</h1>
</div>
);
}
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>
Bad Approach:
<button>Save</button>
<h1>User Profile</h1>
Good Approach
<button>{t('SAVE')}</button>
<h1>{t('USER_PROFILE')}</h1>
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>;
Bad Approach:
{
"SAVE_BUTTON": "Save",
"cancelBtn": "Cancel",
"Submit": "Submit"
}
Good Approach:
{
"SAVE": "Save",
"CANCEL": "Cancel",
"SUBMIT": "Submit"
}
Bad Approach:
<p>{t('HELLO')} {userName}!</p>
Good Approach:
{ "GREETING": "Hello, {{name}}!" }
<p>{t('GREETING', { name: userName })}</p>
- 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
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": "సేవ్ చేయండి" }