Django i18n (internationalization) is a comprehensive framework for building multilingual web applications using Python and the Django web framework. Django's i18n system leverages gettext, the industry-standard translation tool, to manage message catalogs in .po (Portable Object) files that translators can edit using specialized tools like Poedit or web-based platforms. The framework provides the makemessages command to extract translatable strings from Python code and templates, and compilemessages to convert .po files into optimized .mo (Machine Object) binary files used at runtime. Django's i18n system extends beyond simple string translation to include URL pattern localization, timezone-aware date formatting, currency and number formatting, and pluralization rules that respect language-specific grammar. The django.utils.translation module offers gettext() and gettext_lazy() functions for runtime translation lookup, while template tags like {% trans %} and {% blocktrans %} enable declarative translation in templates. Middleware components automatically detect user language preferences from browser headers, session data, or cookies. For database content, third-party packages like django-modeltranslation and django-parler enable translatable model fields. This guide covers Django i18n from initial configuration through production deployment, including JavaScript internationalization, testing strategies, and integration with modern translation management systems.
Django i18n Architecture
Django's internationalization system operates through a multi-layer architecture that separates translatable content from application logic while maintaining performance through compiled message catalogs.
At the foundation, Django uses GNU gettext as its translation engine. When you mark strings as translatable using gettext() or template tags, Django records these strings and their context. The makemessages management command scans your codebase to extract all translatable strings into .po files—human-readable text files organized by language code.
The translation workflow follows this pattern:
- Development: Mark strings as translatable using
gettext(),_(), or template tags - Extraction: Run
makemessagesto create/update .po files - Translation: Translators edit .po files with translations
- Compilation: Run
compilemessagesto create optimized .mo binary files - Runtime: Django loads .mo files and serves translations based on user locale
Django's LocaleMiddleware determines the active language by checking (in order):
- Language prefix in URL (if using
i18n_patterns) - Session key
LANGUAGE_SESSION_KEY - Cookie named
django_language Accept-LanguageHTTP header- Global
LANGUAGE_CODEsetting
The framework caches translations aggressively for performance. Translation functions like gettext_lazy() return proxy objects that only perform lookup when rendered, enabling translations in module-level code.
Initial Django i18n Setup
Enable internationalization in your Django project settings:
Python1# settings.py 2 3from django.utils.translation import gettext_lazy as _ 4 5# Enable i18n and l10n (localization) 6USE_I18N = True 7USE_L10N = True 8USE_TZ = True # Timezone support 9 10# Default language 11LANGUAGE_CODE = 'en-us' 12 13# Supported languages 14LANGUAGES = [ 15 ('en', _('English')), 16 ('es', _('Spanish')), 17 ('fr', _('French')), 18 ('de', _('German')), 19] 20 21# Translation file paths 22LOCALE_PATHS = [ 23 BASE_DIR / 'locale', 24] 25 26# Middleware configuration (order matters) 27MIDDLEWARE = [ 28 'django.middleware.security.SecurityMiddleware', 29 'django.contrib.sessions.middleware.SessionMiddleware', 30 'django.middleware.locale.LocaleMiddleware', # After SessionMiddleware 31 'django.middleware.common.CommonMiddleware', 32 # ... other middleware 33]
Create the locale directory structure:
Terminalmkdir -p locale/{es,fr,de}/LC_MESSAGES
The directory structure will look like:
myproject/
├── locale/
│ ├── es/
│ │ └── LC_MESSAGES/
│ │ ├── django.po
│ │ └── django.mo
│ ├── fr/
│ │ └── LC_MESSAGES/
│ └── de/
│ └── LC_MESSAGES/
├── myapp/
│ ├── locale/ # App-specific translations
│ │ └── es/
│ │ └── LC_MESSAGES/
│ ├── views.py
│ └── models.py
└── manage.py
Marking Strings for Translation
Django provides multiple functions for marking translatable strings depending on the context.
In Python Code
Python1from django.utils.translation import gettext as _ 2from django.utils.translation import gettext_lazy 3from django.utils.translation import ngettext 4 5def my_view(request): 6 # Simple translation (evaluated immediately) 7 message = _('Welcome to our site') 8 9 # Lazy translation (evaluated when rendered) 10 # Use for module-level code, model fields, form fields 11 help_text = gettext_lazy('Enter your email address') 12 13 # Plural handling 14 count = request.GET.get('count', 0) 15 items_text = ngettext( 16 'You have %(count)d item', 17 'You have %(count)d items', 18 count 19 ) % {'count': count} 20 21 return render(request, 'template.html', { 22 'message': message, 23 'help_text': help_text, 24 'items_text': items_text, 25 })
In Models
Always use gettext_lazy() in model definitions:
Python1from django.db import models 2from django.utils.translation import gettext_lazy as _ 3 4class Product(models.Model): 5 name = models.CharField( 6 max_length=200, 7 verbose_name=_('Product Name'), 8 help_text=_('Enter the product name') 9 ) 10 description = models.TextField( 11 verbose_name=_('Description'), 12 blank=True 13 ) 14 price = models.DecimalField( 15 max_digits=10, 16 decimal_places=2, 17 verbose_name=_('Price') 18 ) 19 20 class Meta: 21 verbose_name = _('Product') 22 verbose_name_plural = _('Products') 23 24 def __str__(self): 25 return self.name
In Forms
Python1from django import forms 2from django.utils.translation import gettext_lazy as _ 3 4class ContactForm(forms.Form): 5 name = forms.CharField( 6 label=_('Your Name'), 7 max_length=100, 8 help_text=_('Enter your full name') 9 ) 10 email = forms.EmailField( 11 label=_('Email Address'), 12 help_text=_('We will never share your email') 13 ) 14 message = forms.CharField( 15 label=_('Message'), 16 widget=forms.Textarea, 17 help_text=_('Enter your message here') 18 ) 19 20 def clean_message(self): 21 message = self.cleaned_data['message'] 22 if len(message) < 10: 23 raise forms.ValidationError( 24 _('Message must be at least 10 characters long') 25 ) 26 return message
With Context
Provide context when the same English word has different translations:
Python1from django.utils.translation import pgettext 2 3# "May" the month vs "may" the verb 4month = pgettext('month name', 'May') 5permission = pgettext('verb', 'may') 6 7# In .po file, these become: 8# msgctxt "month name" 9# msgid "May" 10# msgstr "Mayo" 11# 12# msgctxt "verb" 13# msgid "may" 14# msgstr "poder"
Template Translation Tags
Django templates use special tags for translation.
Basic Translation
DJANGO1{% load i18n %} 2 3<!DOCTYPE html> 4<html> 5<head> 6 <title>{% trans "Welcome to Our Site" %}</title> 7</head> 8<body> 9 <h1>{% trans "Hello, World!" %}</h1> 10 11 {# Translation with variable substitution #} 12 <p>{% blocktrans with name=user.first_name %} 13 Hello, {{ name }}! 14 {% endblocktrans %}</p> 15 16 {# Pluralization in templates #} 17 <p>{% blocktrans count counter=items|length %} 18 You have {{ counter }} item in your cart. 19 {% plural %} 20 You have {{ counter }} items in your cart. 21 {% endblocktrans %}</p> 22 23 {# Context-aware translation #} 24 <p>{% trans "May" context "month name" %}</p> 25</body> 26</html>
Language Selection
Create a language switcher:
DJANGO1{% load i18n %} 2 3<form action="{% url 'set_language' %}" method="post"> 4 {% csrf_token %} 5 <input name="next" type="hidden" value="{{ redirect_to }}" /> 6 <select name="language" onchange="this.form.submit()"> 7 {% get_current_language as CURRENT_LANGUAGE %} 8 {% get_available_languages as AVAILABLE_LANGUAGES %} 9 {% for lang_code, lang_name in AVAILABLE_LANGUAGES %} 10 <option value="{{ lang_code }}" 11 {% if lang_code == CURRENT_LANGUAGE %}selected{% endif %}> 12 {{ lang_name }} 13 </option> 14 {% endfor %} 15 </select> 16</form>
Configure URL pattern in urls.py:
Python1from django.conf.urls.i18n import i18n_patterns 2from django.urls import path, include 3 4urlpatterns = [ 5 path('i18n/', include('django.conf.urls.i18n')), 6]
Translation Variables
DJANGO1{% load i18n %} 2 3{# Get current language #} 4{% get_current_language as LANGUAGE_CODE %} 5<p>Current language: {{ LANGUAGE_CODE }}</p> 6 7{# Get available languages #} 8{% get_available_languages as LANGUAGES %} 9<ul> 10 {% for lang_code, lang_name in LANGUAGES %} 11 <li>{{ lang_code }}: {{ lang_name }}</li> 12 {% endfor %} 13</ul> 14 15{# Get language info #} 16{% get_language_info for LANGUAGE_CODE as lang_info %} 17<p> 18 Language: {{ lang_info.name_local }} 19 ({{ lang_info.code }}) 20 {% if lang_info.bidi %}[RTL]{% endif %} 21</p>
makemessages and compilemessages
Extract and compile translations using Django management commands.
Extracting Messages
Run makemessages to scan code for translatable strings:
Terminal1# Create/update .po files for all languages 2python manage.py makemessages -a 3 4# Create/update for specific language 5python manage.py makemessages -l es 6python manage.py makemessages -l fr 7 8# Include JavaScript files 9python manage.py makemessages -d djangojs -l es 10 11# Ignore virtual environment 12python manage.py makemessages -l es --ignore=venv/* 13 14# Update existing translations without fuzzy matching 15python manage.py makemessages -l es --no-obsolete
This creates files like locale/es/LC_MESSAGES/django.po:
# locale/es/LC_MESSAGES/django.po
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: views.py:15
msgid "Welcome to our site"
msgstr "Bienvenido a nuestro sitio"
#: models.py:10
msgid "Product Name"
msgstr "Nombre del Producto"
#: templates/home.html:5
msgid "Hello, World!"
msgstr "¡Hola, Mundo!"
#: templates/cart.html:12
#, python-format
msgid "You have %(count)d item in your cart."
msgid_plural "You have %(count)d items in your cart."
msgstr[0] "Tienes %(count)d artículo en tu carrito."
msgstr[1] "Tienes %(count)d artículos en tu carrito."
Compiling Messages
Convert .po files to optimized .mo binary files:
Terminal1# Compile all languages 2python manage.py compilemessages 3 4# Compile specific language 5python manage.py compilemessages -l es 6 7# Compile and check for errors 8python manage.py compilemessages --use-fuzzy
Always compile after editing .po files. Django only reads .mo files at runtime.
Automation Script
Create a translation workflow script:
Terminal1#!/bin/bash 2# scripts/update_translations.sh 3 4set -e 5 6echo "Extracting messages..." 7python manage.py makemessages -a --ignore=venv/* 8python manage.py makemessages -d djangojs -a --ignore=venv/* --ignore=node_modules/* 9 10echo "Compiling messages..." 11python manage.py compilemessages 12 13echo "Translation files updated successfully!"
URL Internationalization
Translate URL patterns using i18n_patterns.
Basic URL i18n
Python1# urls.py 2from django.conf.urls.i18n import i18n_patterns 3from django.urls import path 4from . import views 5 6urlpatterns = i18n_patterns( 7 path('about/', views.about, name='about'), 8 path('contact/', views.contact, name='contact'), 9 path('products/', views.product_list, name='product_list'), 10 path('products/<slug:slug>/', views.product_detail, name='product_detail'), 11)
With this configuration, URLs become:
/en/about/(English)/es/acerca-de/(Spanish, if translated)/fr/a-propos/(French, if translated)
Translating URL Patterns
Create translated URL patterns:
Python1from django.urls import path 2from django.conf.urls.i18n import i18n_patterns 3from django.utils.translation import gettext_lazy as _ 4from . import views 5 6urlpatterns = i18n_patterns( 7 path(_('about/'), views.about, name='about'), 8 path(_('contact/'), views.contact, name='contact'), 9 path(_('products/'), views.product_list, name='product_list'), 10 path(_('products/<slug:slug>/'), views.product_detail, name='product_detail'), 11)
Add translations to .po file:
msgid "about/"
msgstr "acerca-de/"
msgid "contact/"
msgstr "contacto/"
msgid "products/"
msgstr "productos/"
msgid "products/<slug:slug>/"
msgstr "productos/<slug:slug>/"
Reverse URL Resolution
Use reverse() to generate translated URLs:
Python1from django.urls import reverse 2from django.utils.translation import activate 3 4# Activate Spanish 5activate('es') 6 7# Generate translated URL 8url = reverse('product_list') # Returns '/es/productos/' 9 10# In templates 11{% url 'product_list' %} # Automatically uses current language
Language Prefix Configuration
Control language prefix behavior:
Python1# settings.py 2 3# Prefix default language URLs (e.g., /en/about/) 4# If False, default language has no prefix (e.g., /about/) 5PREFIX_DEFAULT_LANGUAGE = False
JavaScript Internationalization
Django provides i18n support for frontend JavaScript code.
JavaScript Catalog View
Include the JavaScript catalog in your template:
DJANGO<script src="{% url 'javascript-catalog' %}"></script> <script src="{% static 'js/app.js' %}"></script>
Configure the catalog URL:
Python1# urls.py 2from django.views.i18n import JavaScriptCatalog 3 4urlpatterns = [ 5 path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'), 6]
Using Translations in JavaScript
JavaScript1// static/js/app.js 2 3// Simple translation 4const message = gettext('Welcome to our site'); 5console.log(message); 6 7// String interpolation 8const greeting = interpolate( 9 gettext('Hello, %s!'), 10 [userName] 11); 12 13// Plural handling 14const itemsText = ngettext( 15 'You have %s item', 16 'You have %s items', 17 itemCount 18); 19const formatted = interpolate(itemsText, [itemCount]); 20 21// Get current language 22const currentLang = get_format('LANGUAGE_CODE');
Extracting JavaScript Translations
Create a separate catalog for JavaScript:
Terminal1# Extract JavaScript translations 2python manage.py makemessages -d djangojs -l es 3 4# This creates locale/es/LC_MESSAGES/djangojs.po
Configure which packages to include:
Python1# urls.py 2from django.views.i18n import JavaScriptCatalog 3 4urlpatterns = [ 5 path('jsi18n/', 6 JavaScriptCatalog.as_view(packages=['myapp']), 7 name='javascript-catalog' 8 ), 9]
Modern JavaScript i18n
For modern JavaScript frameworks, export Django translations to JSON:
Python1# myapp/views.py 2from django.http import JsonResponse 3from django.utils.translation import get_language, gettext as _ 4 5def translations_json(request): 6 """Export translations as JSON for frontend frameworks.""" 7 translations = { 8 'welcome_message': _('Welcome to our site'), 9 'login_button': _('Log In'), 10 'signup_button': _('Sign Up'), 11 'error_required': _('This field is required'), 12 # Add all needed translations 13 } 14 15 return JsonResponse({ 16 'language': get_language(), 17 'translations': translations 18 })
Use in React/Vue:
JavaScript1// Fetch translations on app init 2fetch('/api/translations/') 3 .then(res => res.json()) 4 .then(data => { 5 window.translations = data.translations; 6 window.currentLanguage = data.language; 7 }); 8 9// Usage 10const t = (key) => window.translations[key] || key; 11console.log(t('welcome_message'));
Model Translation
Translate database content using third-party packages.
Using django-modeltranslation
Install and configure:
Terminalpip install django-modeltranslation
Python1# settings.py 2 3INSTALLED_APPS = [ 4 'modeltranslation', # Before django.contrib.admin 5 'django.contrib.admin', 6 # ... other apps 7] 8 9LANGUAGES = [ 10 ('en', 'English'), 11 ('es', 'Spanish'), 12 ('fr', 'French'), 13]
Define translation fields:
Python1# myapp/translation.py 2from modeltranslation.translator import register, TranslationOptions 3from .models import Product 4 5@register(Product) 6class ProductTranslationOptions(TranslationOptions): 7 fields = ('name', 'description')
This creates additional fields in the database:
name_en,name_es,name_frdescription_en,description_es,description_fr
Usage:
Python1from django.utils.translation import activate 2from myapp.models import Product 3 4# Create product 5product = Product.objects.create( 6 name_en='Laptop', 7 name_es='Portátil', 8 name_fr='Ordinateur portable', 9 description_en='High-performance laptop', 10 description_es='Portátil de alto rendimiento', 11 description_fr='Ordinateur portable haute performance' 12) 13 14# Access translated fields 15activate('es') 16print(product.name) # Returns 'Portátil' 17 18activate('fr') 19print(product.name) # Returns 'Ordinateur portable'
Run migration after configuring:
Terminal1python manage.py makemigrations 2python manage.py migrate 3 4# Sync existing data 5python manage.py update_translation_fields
Using django-parler
Alternative approach with separate translation tables:
Terminalpip install django-parler
Python1# models.py 2from django.db import models 3from parler.models import TranslatableModel, TranslatedFields 4 5class Product(TranslatableModel): 6 sku = models.CharField(max_length=50, unique=True) 7 price = models.DecimalField(max_digits=10, decimal_places=2) 8 9 translations = TranslatedFields( 10 name=models.CharField(max_length=200), 11 description=models.TextField() 12 ) 13 14 def __str__(self): 15 return self.name
Usage:
Python1from django.utils.translation import activate 2 3# Create with translations 4product = Product.objects.create(sku='LAP001', price=999.99) 5product.set_current_language('en') 6product.name = 'Laptop' 7product.description = 'High-performance laptop' 8product.save() 9 10product.set_current_language('es') 11product.name = 'Portátil' 12product.description = 'Portátil de alto rendimiento' 13product.save() 14 15# Query translated fields 16activate('es') 17products = Product.objects.translated('es')
IntlPull CLI Integration
Integrate Django with IntlPull for collaborative translation management.
Installation
Terminalpip install intlpull
Export to IntlPull
Terminal1# Export Django .po files to IntlPull 2intlpull import \ 3 --project-id your-project-id \ 4 --format po \ 5 --file locale/es/LC_MESSAGES/django.po \ 6 --language es 7 8# Bulk import all languages 9for lang in es fr de; do 10 intlpull import \ 11 --project-id your-project-id \ 12 --format po \ 13 --file locale/$lang/LC_MESSAGES/django.po \ 14 --language $lang 15done
Import from IntlPull
Terminal1# Download updated translations 2intlpull export \ 3 --project-id your-project-id \ 4 --format po \ 5 --language es \ 6 --output locale/es/LC_MESSAGES/django.po 7 8# Compile after import 9python manage.py compilemessages -l es
CI/CD Integration
Automate translation sync in GitHub Actions:
YAML1# .github/workflows/translations.yml 2name: Sync Translations 3 4on: 5 schedule: 6 - cron: '0 0 * * *' # Daily at midnight 7 workflow_dispatch: 8 9jobs: 10 sync: 11 runs-on: ubuntu-latest 12 steps: 13 - uses: actions/checkout@v3 14 15 - name: Set up Python 16 uses: actions/setup-python@v4 17 with: 18 python-version: '3.11' 19 20 - name: Install dependencies 21 run: | 22 pip install django intlpull 23 24 - name: Extract messages 25 run: python manage.py makemessages -a 26 27 - name: Upload to IntlPull 28 env: 29 INTLPULL_API_KEY: ${{ secrets.INTLPULL_API_KEY }} 30 run: | 31 for lang in es fr de; do 32 intlpull import \ 33 --project-id ${{ secrets.INTLPULL_PROJECT_ID }} \ 34 --format po \ 35 --file locale/$lang/LC_MESSAGES/django.po \ 36 --language $lang 37 done 38 39 - name: Download translations 40 env: 41 INTLPULL_API_KEY: ${{ secrets.INTLPULL_API_KEY }} 42 run: | 43 for lang in es fr de; do 44 intlpull export \ 45 --project-id ${{ secrets.INTLPULL_PROJECT_ID }} \ 46 --format po \ 47 --language $lang \ 48 --output locale/$lang/LC_MESSAGES/django.po 49 done 50 51 - name: Compile messages 52 run: python manage.py compilemessages 53 54 - name: Create Pull Request 55 uses: peter-evans/create-pull-request@v5 56 with: 57 commit-message: 'chore: update translations' 58 title: 'Update translations from IntlPull' 59 branch: translations-update
Testing i18n
Ensure translations work correctly across all supported languages.
Unit Testing
Python1# tests.py 2from django.test import TestCase 3from django.utils.translation import activate, gettext as _ 4 5class TranslationTests(TestCase): 6 7 def test_welcome_message_spanish(self): 8 activate('es') 9 message = _('Welcome to our site') 10 self.assertEqual(message, 'Bienvenido a nuestro sitio') 11 12 def test_welcome_message_french(self): 13 activate('fr') 14 message = _('Welcome to our site') 15 self.assertEqual(message, 'Bienvenue sur notre site') 16 17 def test_pluralization(self): 18 from django.utils.translation import ngettext 19 20 activate('es') 21 22 # Singular 23 text = ngettext( 24 'You have %(count)d item', 25 'You have %(count)d items', 26 1 27 ) % {'count': 1} 28 self.assertIn('artículo', text) 29 30 # Plural 31 text = ngettext( 32 'You have %(count)d item', 33 'You have %(count)d items', 34 5 35 ) % {'count': 5} 36 self.assertIn('artículos', text)
View Testing
Python1from django.test import TestCase, Client 2from django.urls import reverse 3 4class LocalizedViewTests(TestCase): 5 6 def test_home_page_spanish(self): 7 response = self.client.get('/es/', follow=True) 8 self.assertEqual(response.status_code, 200) 9 self.assertContains(response, 'Bienvenido') 10 11 def test_language_switcher(self): 12 # Start in English 13 response = self.client.get('/') 14 self.assertContains(response, 'Welcome') 15 16 # Switch to Spanish 17 response = self.client.post( 18 reverse('set_language'), 19 {'language': 'es', 'next': '/'} 20 ) 21 self.assertEqual(response.status_code, 302) 22 23 # Verify Spanish content 24 response = self.client.get('/') 25 self.assertContains(response, 'Bienvenido')
Translation Coverage
Check for missing translations:
Python1# scripts/check_translations.py 2import os 3import polib 4 5def check_translation_coverage(lang_code): 6 po_path = f'locale/{lang_code}/LC_MESSAGES/django.po' 7 if not os.path.exists(po_path): 8 print(f"Missing .po file for {lang_code}") 9 return 10 11 po = polib.pofile(po_path) 12 total = len(po) 13 translated = len(po.translated_entries()) 14 untranslated = len(po.untranslated_entries()) 15 fuzzy = len(po.fuzzy_entries()) 16 17 coverage = (translated / total * 100) if total > 0 else 0 18 19 print(f"\n{lang_code} Translation Coverage:") 20 print(f" Total entries: {total}") 21 print(f" Translated: {translated}") 22 print(f" Untranslated: {untranslated}") 23 print(f" Fuzzy: {fuzzy}") 24 print(f" Coverage: {coverage:.1f}%") 25 26 if untranslated > 0: 27 print(f"\n Missing translations:") 28 for entry in po.untranslated_entries()[:10]: 29 print(f" - {entry.msgid}") 30 31if __name__ == '__main__': 32 for lang in ['es', 'fr', 'de']: 33 check_translation_coverage(lang)
Best Practices
1. Use Lazy Translation for Module-Level Code
Python1# ❌ Bad - evaluated at module import 2from django.utils.translation import gettext as _ 3ERROR_MESSAGE = _('An error occurred') 4 5# ✅ Good - evaluated when accessed 6from django.utils.translation import gettext_lazy as _ 7ERROR_MESSAGE = _('An error occurred')
2. Provide Context for Translators
Python1# ❌ Bad - no context 2label = _('Name') 3 4# ✅ Good - clear context 5label = _('Product Name') 6 7# ✅ Better - with pgettext 8label = pgettext('product field', 'Name')
3. Never Concatenate Translated Strings
Python1# ❌ Bad - word order varies by language 2message = _('Hello') + ', ' + user.name + '!' 3 4# ✅ Good - use format strings 5message = _('Hello, %(name)s!') % {'name': user.name}
4. Keep .po Files in Version Control
Commit .po files but consider excluding .mo files:
# .gitignore
*.mo
Compile .mo files during deployment.
5. Use Format Strings Consistently
Python1# ✅ Named placeholders (preferred) 2_('Welcome, %(username)s!') % {'username': user.name} 3 4# ✅ Positional placeholders (okay) 5_('Welcome, %s!') % user.name 6 7# ❌ Mixed styles (confusing) 8_('Welcome, %(name)s! You have %d messages') % {'name': user.name, ...}
6. Organize Translations by App
Use app-specific locale directories:
myproject/
├── myapp/
│ ├── locale/
│ │ ├── es/
│ │ └── fr/
│ └── views.py
└── settings.py
This keeps translations modular and reusable.
Production Deployment
Pre-Deployment Checklist
- All .po files translated and reviewed
-
compilemessagesrun successfully - .mo files included in deployment
- Language middleware configured
- URL patterns tested in all languages
- JavaScript catalog generated
- Translation coverage > 95% for launch languages
- RTL languages tested (if applicable)
Deployment Script
Terminal1#!/bin/bash 2# scripts/deploy.sh 3 4set -e 5 6echo "Compiling translations..." 7python manage.py compilemessages 8 9echo "Collecting static files..." 10python manage.py collectstatic --noinput 11 12echo "Running migrations..." 13python manage.py migrate 14 15echo "Deployment complete!"
Performance Optimization
Enable translation caching:
Python1# settings.py 2 3CACHES = { 4 'default': { 5 'BACKEND': 'django.core.cache.backends.redis.RedisCache', 6 'LOCATION': 'redis://127.0.0.1:6379/1', 7 } 8} 9 10# Cache translations 11LOCALE_PATHS = [BASE_DIR / 'locale']
Frequently Asked Questions
Q: Should I use gettext() or gettext_lazy()?
A: Use gettext_lazy() for module-level code, model fields, and form fields. Use gettext() inside functions and views where immediate evaluation is acceptable. Lazy translation prevents issues with language detection at import time.
Q: How do I handle user-generated content translation?
A: Use django-modeltranslation or django-parler for database content. For user comments/posts, consider separate translation tables or integration with translation services via API rather than storing all translations upfront.
Q: Can I change language without URL prefix?
A: Yes, use session or cookie-based language detection without i18n_patterns. Set language via the set_language view and store preference in session. This keeps URLs language-neutral.
Q: How do I translate emails?
A: Use gettext() in email template context, activate the user's preferred language before rendering, and create separate email templates per language if HTML structure differs significantly.
Q: What's the difference between .po and .mo files?
A: .po files are human-readable text format edited by translators. .mo files are binary format compiled from .po files for runtime efficiency. Always edit .po files, never .mo files directly.
Q: How do I handle date and number formatting?
A: Django's USE_L10N = True enables automatic formatting based on locale. Use template filters like {{ value|date }} and {{ value|floatformat }} which respect the current language's formatting rules.
Q: Can I mix static and dynamic translations?
A: Yes, combine gettext for UI strings with model translation for database content. Use different .po files (django.po for code, djangojs.po for JavaScript) to organize translations logically.
Q: How do I test RTL languages?
A: Add RTL languages (Arabic, Hebrew) to LANGUAGES, activate them in tests, and check template rendering with {% if LANGUAGE_BIDI %} conditionals. Use browser dev tools to verify RTL layout.
IntlPull streamlines Django localization workflows by providing collaborative translation management, automated .po file sync, and version control for translations. Whether building a new Django application or internationalizing an existing project, proper i18n setup ensures your application serves global audiences effectively. Start with Django's built-in i18n framework, integrate IntlPull for translation collaboration, and follow best practices for maintainable multilingual applications.
