Simple Yet Powerful: A Custom Translation System for Flask Apps
Technical documentation for the translation system in sparQ.
How I built a lightweight, effective translation system without Babel
Author: Asim · February 12, 2025
Overview
While building sparQ, I needed a translation system that was both simple to maintain and powerful enough to handle dynamic content. The popular choice would have been Flask-Babel, but I wanted something more lightweight and straightforward. Here’s how I implemented a custom translation system that’s both elegant and effective.
The Problem with Traditional Solutions
Flask-Babel is great, but it comes with overhead:
- Complex setup and configuration
- Compilation of translation files
- Separate processes for extracting strings
- Heavy dependency footprint
For a modular application like sparQ, where each module might need its own translations, this felt unnecessarily complex.
A Simpler Approach
Instead, I implemented a JSON-based translation system that:
- Uses simple key-value pairs in JSON files
- Loads translations dynamically at runtime
- Supports module-specific translations
- Handles fallbacks gracefully
Here’s the core of the system:
def translate(key, **kwargs):
"""Translate a key with optional format arguments"""
lang = g.get('lang', 'en')
# Get translation from cache
translations = _get_translations(lang)
# Get the translated string, fallback to key
text = translations.get(key, key)
# Format with any provided arguments
if kwargs:
text = text.format(**kwargs)
return text
Module-Specific Translations
Each module has its own translation files:
modules/
├── core/
│ └── lang/
│ ├── en.json
│ └── es.json
└── people/
└── lang/
├── en.json
└── es.json
This structure means:
- Modules are self-contained
- Translations are close to the code they support
- Easy to maintain and update
- No need for compilation steps
Dynamic Loading and Caching
The system loads translations dynamically but efficiently:
def preload_translations():
"""Preload all translation files into memory"""
for lang in SUPPORTED_LANGUAGES:
_load_translations(lang)
Translations are cached in memory but can be reloaded on demand - perfect for development.
Using the System
In templates, it’s as simple as:
<h1>{{ _('Welcome') }}</h1>
<p>{{ _('Hello {name}', name=user.first_name) }}</p>
In Python code:
from system.i18n.translation import _
def greet_user(user):
return _('Hello {name}', name=user.first_name)
The Power of Simplicity
This approach has several advantages:
- No Compilation - Edit JSON files and refresh
- Module Scoping - Each module manages its own translations
- Easy Maintenance - Simple key-value pairs in JSON
- Dynamic Updates - Changes take effect immediately
- Lightweight - No extra dependencies
Real-World Usage
In sparQ, this system handles everything from simple labels to complex dynamic content. For example, our chat module uses it for:
- UI elements
- System messages
- Dynamic notifications
- Error messages
All with perfect language switching on the fly.
Performance Considerations
The JSON-based approach might seem less efficient than compiled translations, but in practice:
- Initial load is negligible (few KB of JSON)
- In-memory caching makes lookups fast
- No runtime compilation needed
- Scales well with application size
Future Improvements
While the current system serves our needs well, I’m considering some enhancements:
- Plural forms support
- RTL language handling
- Translation management UI
- Export/import functionality
Conclusion
Sometimes the simplest solution is the best one. By avoiding the complexity of traditional translation systems, we’ve built something that’s:
- Easy to understand
- Simple to maintain
- Powerful enough for real-world use
- Perfect for modular applications
This approach has worked so well that I can’t imagine going back to a more complex system. It’s a reminder that we don’t always need heavy frameworks to solve problems effectively.
The next time you need translations in a Flask app, consider whether a simple JSON-based system might serve your needs better than a full-featured but complex solution.