Internationalization & Translation¶
Complete guide to adding new languages, translating content, and managing multilingual support in SATHI.
Overview¶
SATHI supports comprehensive internationalization (i18n) through two complementary systems:
Django Parler: For translating database content (questionnaires, items, responses)
Django gettext: For translating the user interface (templates, messages, labels)
This dual approach allows:
Healthcare workers to create questionnaires in multiple languages
Patients to view the interface in their preferred language
Administrators to manage translations through both UI and files
Architecture¶
Translation Layers¶
- Database Content (Parler)
Questionnaire names and descriptions
Item text and instructions
Likert/Range scale labels
Construct scale names
Diagnosis and treatment names
- User Interface (gettext)
Navigation menus
Form labels and buttons
Error messages
Help text and instructions
Static page content
Language Configuration¶
Supported Languages¶
Languages are configured in chaviprom/settings.py:
# Load from environment variable
LANGUAGES_STRING = env('LANGUAGES', default='en:English,hi:Hindi,bn:Bengali')
# Parse into Django format
LANGUAGES = [
tuple(lang.split(':'))
for lang in LANGUAGES_STRING.split(',')
]
# Example result:
# LANGUAGES = [
# ('en', 'English'),
# ('hi', 'Hindi'),
# ('bn', 'Bengali'),
# ]
Environment Variable Format:
# In .env file
LANGUAGES=en:English,hi:Hindi,bn:Bengali,ta:Tamil,te:Telugu
Default Language¶
LANGUAGE_CODE = 'en' # Default language
PARLER_LANGUAGES = {
None: (
{'code': 'en'},
{'code': 'hi'},
{'code': 'bn'},
),
'default': {
'fallback': 'en',
'hide_untranslated': False,
}
}
Adding a New Language¶
Step 1: Update Environment Configuration¶
Add the new language to your .env file:
# Before
LANGUAGES=en:English,hi:Hindi,bn:Bengali
# After (adding Tamil)
LANGUAGES=en:English,hi:Hindi,bn:Bengali,ta:Tamil
Language Code Reference:
en- Englishhi- Hindibn- Bengalita- Tamilte- Telugumr- Marathigu- Gujaratikn- Kannadaml- Malayalampa- Punjabior- Odiaas- Assamese
Step 2: Update Parler Configuration¶
Edit chaviprom/settings.py to add the language code to PARLER_LANGUAGES:
PARLER_LANGUAGES = {
None: (
{'code': 'en'},
{'code': 'hi'},
{'code': 'bn'},
{'code': 'ta'}, # New language
),
'default': {
'fallback': 'en',
'hide_untranslated': False,
}
}
Step 3: Create Translation Files¶
Prerequisites:
gettextmust be installed on your systemLinux:
sudo apt-get install gettextmacOS:
brew install gettextWindows: Download from GNU gettext
Generate Message Files:
A Makefile is provided at the project root to ensure message extraction is scoped
correctly. It only scans the chaviprom, patientapp, providerapp, and
templates directories, ignoring everything else (docs, static files, third-party
code, etc.).
# Create/update .po file for a single language (Tamil example)
make messages LANG=ta
# For multiple languages, run the command once per language
make messages LANG=ta
make messages LANG=te
make messages LANG=mr
If you prefer to run django-admin directly, you must pass the ignore flags
manually to avoid scanning directories that contain raw template examples (e.g.
docs/build/) which will cause SyntaxError during extraction:
django-admin makemessages -l ta \
--ignore="docs/*" \
--ignore="venv/*" \
--ignore="node_modules/*" \
--ignore="static/*" \
--ignore="staticfiles/*" \
--ignore="promapp/*" \
--ignore="media/*" \
--ignore="item_media/*" \
--ignore="logs/*" \
--ignore=".git/*" \
--ignore=".github/*"
Note
The promapp directory is deliberately excluded because its translatable strings
are managed through Django Parler (database-level translations), not gettext.
What This Does:
Scans Python and template files in
chaviprom/,patientapp/,providerapp/, andtemplates/Creates
locale/ta/LC_MESSAGES/django.poExtracts strings marked with
{% trans %}andgettext()Ignores docs, static assets, virtual environment, and node_modules
Step 4: Translate Interface Strings¶
Edit the generated .po file at locale/ta/LC_MESSAGES/django.po:
# Example translations
msgid "Welcome"
msgstr "வரவேற்பு"
msgid "Patient Portal"
msgstr "நோயாளி போர்ட்டல்"
msgid "Complete Questionnaire"
msgstr "கேள்வித்தாளை முடிக்கவும்"
msgid "Submit"
msgstr "சமர்ப்பிக்கவும்"
Translation Tools:
Poedit: GUI editor for .po files (poedit.net)
Lokalize: KDE translation tool
Manual editing: Any text editor works
Step 5: Compile Translations¶
After editing .po files, compile them to binary format:
# Compile all languages
python manage.py compilemessages -i "venv" -i "node_modules"
# Compile specific language
python manage.py compilemessages -l ta -i "venv" -i "node_modules"
Alternatively, use the Makefile:
make compilemessages LANG=ta
Output:
Creates
locale/ta/LC_MESSAGES/django.mo(binary file)This file is used by Django at runtime
Must recompile after every
.pofile change
Step 6: Restart Server¶
# Development
python manage.py runserver
# Production (Supervisor)
sudo supervisorctl restart chaviprom
Database Content Translation¶
Translating Through Admin Interface¶
SATHI uses Django Parler for database content translation. All translatable models have language tabs in the admin interface.
Translating a Questionnaire:
Navigate to Admin → Questionnaires → Select Questionnaire
Click the language tabs at the top (English | Hindi | Bengali | Tamil)
Switch to target language tab
Enter translated content: - Name - Description - Instructions
Click Save
Translating Items:
Navigate to Admin → Items → Select Item
Switch to target language tab
Translate: - Item name (question text) - Item instructions - Media descriptions
Click Save
Translating Likert Scales:
Navigate to Admin → Likert Scales → Select Scale
Switch to target language tab
Translate scale name
Navigate to Likert Scale Response Elements
Translate each response option text
Click Save
Translating Through Django Shell¶
For bulk translations or programmatic updates:
from promapp.models import Questionnaire
# Get questionnaire
q = Questionnaire.objects.get(id=1)
# Set English translation
q.set_current_language('en')
q.name = "Patient Health Questionnaire"
q.description = "Assess patient health status"
q.save()
# Set Hindi translation
q.set_current_language('hi')
q.name = "रोगी स्वास्थ्य प्रश्नावली"
q.description = "रोगी स्वास्थ्य स्थिति का आकलन करें"
q.save()
# Set Tamil translation
q.set_current_language('ta')
q.name = "நோயாளி சுகாதார கேள்வித்தாள்"
q.description = "நோயாளி சுகாதார நிலையை மதிப்பிடவும்"
q.save()
Checking Available Translations¶
from promapp.models import Item
item = Item.objects.get(id=1)
# Get all available translations
translations = item.get_available_languages()
# Returns: ['en', 'hi', 'bn']
# Check if translation exists
if item.has_translation('ta'):
item.set_current_language('ta')
print(item.name)
User Interface Translation¶
Template Translation¶
Use {% trans %} and {% blocktrans %} template tags:
Simple Translation:
{% load i18n %}
<h1>{% trans "Welcome to SATHI" %}</h1>
<p>{% trans "Complete your questionnaires" %}</p>
<button>{% trans "Submit" %}</button>
Translation with Variables:
{% load i18n %}
{% blocktrans with name=patient.name %}
Welcome, {{ name }}!
{% endblocktrans %}
{% blocktrans count counter=items|length %}
You have {{ counter }} questionnaire to complete.
{% plural %}
You have {{ counter }} questionnaires to complete.
{% endblocktrans %}
Translation with Context:
{% load i18n %}
{# Context helps translators understand usage #}
{% trans "Date" context "questionnaire submission date" %}
{% trans "Date" context "patient birth date" %}
Python Code Translation¶
Use gettext() and related functions:
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy
from django.utils.translation import ngettext
# Simple translation
message = _("Questionnaire submitted successfully")
# Lazy translation (for class-level definitions)
class MyForm(forms.Form):
name = forms.CharField(
label=gettext_lazy("Patient Name")
)
# Plural forms
def get_message(count):
return ngettext(
"%(count)d item completed",
"%(count)d items completed",
count
) % {'count': count}
Model Translation¶
For model field help text and verbose names:
from django.db import models
from django.utils.translation import gettext_lazy as _
class Patient(models.Model):
name = models.CharField(
max_length=200,
verbose_name=_("Patient Name"),
help_text=_("Enter the patient's full name")
)
class Meta:
verbose_name = _("Patient")
verbose_name_plural = _("Patients")
Language Switching¶
URL-Based Language Switching¶
SATHI uses i18n_patterns for language prefixes in URLs:
# chaviprom/urls.py
from django.conf.urls.i18n import i18n_patterns
urlpatterns = i18n_patterns(
path('patients/', include('patientapp.urls')),
path('questionnaires/', include('promapp.urls')),
)
URL Examples:
/en/patients/- English/hi/patients/- Hindi/ta/patients/- Tamil
Middleware Configuration¶
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware', # Language detection
'django.middleware.common.CommonMiddleware',
# ... other middleware
]
Language Selector Widget¶
The navbar includes a language dropdown:
{% load i18n %}
<form action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<select name="language" onchange="this.form.submit()">
{% get_available_languages as languages %}
{% for lang_code, lang_name in languages %}
<option value="{{ lang_code }}"
{% if lang_code == LANGUAGE_CODE %}selected{% endif %}>
{{ lang_name }}
</option>
{% endfor %}
</select>
</form>
Session-Based Language Preference¶
Language preference is stored in the user’s session:
# In views
from django.utils.translation import activate
def set_user_language(request, language_code):
activate(language_code)
request.session[LANGUAGE_SESSION_KEY] = language_code
return redirect(request.META.get('HTTP_REFERER', '/'))
Translation Workflow¶
Development Workflow¶
Mark Strings for Translation
# In Python from django.utils.translation import gettext as _ message = _("Hello, world!")
{# In templates #} {% trans "Hello, world!" %}
Extract Messages (use the Makefile to ensure correct scoping)
make messages LANG=ta
Translate Strings
Edit
locale/ta/LC_MESSAGES/django.poCompile Messages
make compilemessages LANG=ta
Test Translations
Switch language in UI
Verify all strings are translated
Check for encoding issues
Commit Changes
git add locale/ git commit -m "Add Tamil translations"
Production Deployment¶
Pull Latest Translations
git pull origin main
Compile Messages
python manage.py compilemessages -i "venv" -i "node_modules"
Collect Static Files (if needed)
python manage.py collectstatic --noinput
Restart Application
sudo supervisorctl restart chaviprom
Makefile Reference¶
A Makefile at the project root provides two targets for managing translations.
It ensures that makemessages only extracts strings from the application modules
that use gettext (chaviprom/, patientapp/, providerapp/) and the shared
templates/ directory.
# Usage
make messages LANG=bn # Extract messages for Bengali
make compilemessages LANG=bn # Compile .po → .mo for Bengali
Scoped Directories (included):
chaviprom/— project settings, views, formspatientapp/— patient-facing appproviderapp/— healthcare provider apptemplates/— shared Django templates
Ignored Directories:
docs/— contains raw template examples that break the parserpromapp/— uses Django Parler for database-level translations, not gettextvenv/,node_modules/— third-party codestatic/,staticfiles/— compiled assetsmedia/,item_media/,logs/— runtime data
Warning
Running django-admin makemessages without the ignore flags will fail with a
SyntaxError because docs/build/ contains .rst.txt files with raw Django
template examples that the gettext parser cannot handle.
Translation File Structure¶
Directory Layout¶
chaviprom/
├── locale/
│ ├── en/
│ │ └── LC_MESSAGES/
│ │ ├── django.po
│ │ └── django.mo
│ ├── hi/
│ │ └── LC_MESSAGES/
│ │ ├── django.po
│ │ └── django.mo
│ ├── bn/
│ │ └── LC_MESSAGES/
│ │ ├── django.po
│ │ └── django.mo
│ └── ta/
│ └── LC_MESSAGES/
│ ├── django.po
│ └── django.mo
PO File Format¶
# Translation metadata
msgid ""
msgstr ""
"Project-Id-Version: SATHI 1.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-04-22 19:00+0530\n"
"Language: ta\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
# Simple translation
#: templates/navbar.html:15
msgid "Home"
msgstr "முகப்பு"
# Translation with context
#: promapp/views.py:45
msgctxt "questionnaire status"
msgid "Completed"
msgstr "முடிந்தது"
# Plural forms
#: promapp/views.py:67
msgid "%(count)d questionnaire"
msgid_plural "%(count)d questionnaires"
msgstr[0] "%(count)d கேள்வித்தாள்"
msgstr[1] "%(count)d கேள்வித்தாள்கள்"
Best Practices¶
Translation Guidelines¶
Use Descriptive Context
# Good _("Date", context="submission date") _("Date", context="birth date") # Avoid _("Date")
Avoid String Concatenation
# Bad message = _("Welcome") + " " + user.name # Good message = _("Welcome, %(name)s") % {'name': user.name}
Use Lazy Translation for Class Definitions
from django.utils.translation import gettext_lazy as _ class MyModel(models.Model): name = models.CharField(verbose_name=_("Name"))
Keep Translations in Sync
# Update all language files when adding new strings make messages LANG=bn make messages LANG=hi make messages LANG=ta
Test All Languages
Switch to each language
Verify UI renders correctly
Check for text overflow
Validate special characters
Font Configuration¶
Different languages may require different fonts:
# settings.py
LANGUAGE_FONTS = {
'en': 'Arial, sans-serif',
'hi': 'Noto Sans Devanagari, sans-serif',
'bn': 'Noto Sans Bengali, sans-serif',
'ta': 'Noto Sans Tamil, sans-serif',
}
In templates:
{% load i18n %}
<style>
body {
font-family: {{ LANGUAGE_FONTS|get:LANGUAGE_CODE }};
}
</style>
RTL Language Support¶
For right-to-left languages (Arabic, Urdu):
# settings.py
LANGUAGES_BIDI = ['ar', 'ur', 'he']
{% load i18n %}
<html dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">
Troubleshooting¶
Common Issues¶
Translations Not Appearing
Check
.mofile exists:ls locale/ta/LC_MESSAGES/django.moRecompile messages:
make compilemessages LANG=ta
Restart server:
python manage.py runserver
Encoding Errors
Ensure .po files use UTF-8:
"Content-Type: text/plain; charset=UTF-8\n"
Missing Translations
Update message files:
make messages LANG=ta
Fuzzy Translations
Remove #, fuzzy markers from .po files after reviewing translations.
Database Translations Not Showing¶
Check Parler Configuration
# Verify language is in PARLER_LANGUAGES PARLER_LANGUAGES = { None: ( {'code': 'en'}, {'code': 'ta'}, # Must be here ), }
Verify Translation Exists
item = Item.objects.get(id=1) print(item.get_available_languages())
Check Fallback Settings
PARLER_LANGUAGES = { 'default': { 'fallback': 'en', 'hide_untranslated': False, # Show English if translation missing } }
Testing Translations¶
Manual Testing¶
Switch Language in UI - Use language selector in navbar - Verify URL changes (
/en/→/ta/)Check All Pages - Navigation menus - Forms and labels - Error messages - Help text
Test Database Content - Create questionnaire in English - Add Tamil translation - Switch language and verify
Automated Testing¶
from django.test import TestCase
from django.utils.translation import activate
class TranslationTests(TestCase):
def test_tamil_translation(self):
activate('ta')
response = self.client.get('/patients/')
self.assertContains(response, 'நோயாளர்கள்')
def test_questionnaire_translation(self):
from promapp.models import Questionnaire
q = Questionnaire.objects.create()
q.set_current_language('en')
q.name = "Health Survey"
q.save()
q.set_current_language('ta')
q.name = "சுகாதார கணக்கெடுப்பு"
q.save()
q.set_current_language('ta')
self.assertEqual(q.name, "சுகாதார கணக்கெடுப்பு")
Resources¶
External Resources¶
Translation Services¶
For professional translations:
Google Translate API (for initial drafts)
Professional translation services
Community translators
Native speakers for review
Language Codes Reference¶
ISO 639-1 codes for Indian languages:
as- Assamese (অসমীয়া)bn- Bengali (বাংলা)gu- Gujarati (ગુજરાતી)hi- Hindi (हिन्दी)kn- Kannada (ಕನ್ನಡ)ml- Malayalam (മലയാളം)mr- Marathi (मराठी)or- Odia (ଓଡ଼ିଆ)pa- Punjabi (ਪੰਜਾਬੀ)ta- Tamil (தமிழ்)te- Telugu (తెలుగు)