Document https://crowdin.com/project/drytrix-timetracker in CONTRIBUTING.md, docs/TRANSLATION_SYSTEM.md, and contributor guides. Update CONTRIBUTING_TRANSLATIONS.md with the public project URL, clearer translator vs maintainer onboarding, and an optional "Further Crowdin integration" section (GitHub app, automation, QA, glossary, notifications). Refresh crowdin.yml header comments (project URL, secrets pointers), normalize preserve_hierarchy to a boolean, and keep the nb→no languages_mapping quoted for YAML 1.1 compatibility.
7.6 KiB
Translation System Documentation
Overview
TimeTracker includes a comprehensive internationalization (i18n) system powered by Flask-Babel. Enabled locales are defined in app/config.py (LANGUAGES), for example:
- English (en) - Default
- Dutch (nl - Nederlands)
- German (de - Deutsch)
- French (fr - Français)
- Italian (it - Italiano)
- Finnish (fi - Suomi)
- Spanish (es), Norwegian (no), Arabic (ar), Hebrew (he), and others as configured
User Experience
Language Switcher
The language switcher is located in the top navigation bar, positioned between the command palette button and the user profile menu. It features:
- 🌐 Globe icon for easy recognition
- Current language label (on larger screens)
- Dropdown menu with all available languages
- Visual indicator (checkmark) for the currently selected language
- Smooth hover transitions and animations
Language Selection
Users can change the interface language in two ways:
- Via Navigation Bar: Click the globe icon and select a language from the dropdown
- Direct URL: Visit
/i18n/set-language?lang=<code>(e.g.,?lang=defor German)
Language preference is persisted:
- For authenticated users: Saved to user profile in database
- For guests: Stored in session
Technical Details
Translation Files
Translation files are located in translations/ directory (see app/config.py for the full list of enabled locales; additional .po files may exist for locales not yet wired in config).
translations/
├── en/LC_MESSAGES/messages.po # English
├── nl/LC_MESSAGES/messages.po # Dutch
├── de/LC_MESSAGES/messages.po # German
├── fr/LC_MESSAGES/messages.po # French
├── it/LC_MESSAGES/messages.po # Italian
├── fi/LC_MESSAGES/messages.po # Finnish
├── es/LC_MESSAGES/messages.po # Spanish
└── ... # Other locales as configured
Configuration
Language configuration is defined in app/config.py:
LANGUAGES = {
'en': 'English',
'nl': 'Nederlands',
'de': 'Deutsch',
'fr': 'Français',
'it': 'Italiano',
'fi': 'Suomi',
}
BABEL_DEFAULT_LOCALE = 'en'
Locale Selection Priority
The system determines the user's language in the following order:
- User preference from database (for authenticated users)
- Session override (via set-language route)
- Browser Accept-Language header (best match)
- Default locale (en)
See app/__init__.py for the locale selector implementation.
In Templates
Use the _() function to mark strings for translation:
<h1>{{ _('Welcome to TimeTracker') }}</h1>
<button>{{ _('Start Timer') }}</button>
For strings with variables, use named parameters:
<p>{{ _('%(app)s is a web-based time tracking application', app='TimeTracker') }}</p>
In Python Code
Import and use the translation function:
from flask_babel import _
message = _('Timer started successfully')
flash(_('Project created'), 'success')
Translation Compilation
Translation files (.po) are automatically compiled to binary files (.mo) when the application starts. The compilation is handled by app/utils/i18n.py which:
- Checks if
.mofiles exist and are up-to-date - Compiles
.poto.mousing Babel's message tools - Runs automatically during application initialization
Adding a New Language
To add a new language:
-
Add to configuration in
app/config.py:LANGUAGES = { # ... existing languages ... 'es': 'Español', # Add Spanish } -
Create translation directory:
mkdir -p translations/es/LC_MESSAGES -
Initialize translation file:
pybabel init -i messages.pot -d translations -l es -
Translate the strings in
translations/es/LC_MESSAGES/messages.po -
Restart the application - translations will compile automatically
Updating Translations
When you add new translatable strings to the application:
-
Extract messages:
pybabel extract -F babel.cfg -o messages.pot . -
Update all translation files:
pybabel update -i messages.pot -d translations -
Translate new strings in each
.pofile -
Restart application - changes will be compiled automatically
Translation File Format
Translation files use the PO (Portable Object) format:
# Comment
msgid "Original English text"
msgstr "Translated text"
# With context
msgid "Dashboard"
msgstr "Tableau de bord" # French
# Plurals
msgid "1 hour"
msgid_plural "%d hours"
msgstr[0] "1 heure"
msgstr[1] "%d heures"
Best Practices
-
Keep strings short and contextual
- Good:
_('Save') - Avoid:
_('Click this button to save your changes to the database')
- Good:
-
Use sentence case
- Good:
_('Start timer') - Avoid:
_('START TIMER')
- Good:
-
Avoid concatenation
- Good:
_('Welcome back, %(name)s', name=user.name) - Avoid:
_('Welcome back,') + ' ' + user.name
- Good:
-
Provide context in comments
# Translators: This is the button to start the time tracking timer _('Start Timer') -
Test in multiple languages to ensure UI layout works correctly
Troubleshooting
Language not changing
- Check browser console for JavaScript errors
- Verify the language code exists in
LANGUAGESconfig - Clear browser cache and cookies
- Check that
.mofiles exist intranslations/<lang>/LC_MESSAGES/
Translations not showing
- Ensure strings are wrapped in
_()function - Check that
.mofiles are compiled (restart application) - Verify translation exists in the
.pofile - Check for syntax errors in
.pofile
Compilation errors
If translations fail to compile:
- Check
.pofile syntax (must be valid) - Ensure
msgidandmsgstrare properly quoted - Look for encoding issues (files must be UTF-8)
Styling
Language switcher styling is defined in app/static/base.css:
- Smooth hover transitions
- Consistent with application design system
- Responsive design (icon-only on small screens)
- Follows light/dark theme
Accessibility
The language switcher includes:
- Proper ARIA labels and attributes
- Keyboard navigation support
- Clear visual indication of current language
- Tooltip with current language name
- Semantic HTML structure
Performance
- Translations are compiled at startup (one-time operation)
- Compiled
.mofiles are cached in memory - No runtime performance impact
- Minimal bundle size increase per language (~50-100KB)
Future Enhancements
Potential improvements:
- Add more languages (Spanish, Portuguese, Japanese, etc.)
- Right-to-left (RTL) language support (Arabic, Hebrew)
- User-contributed translations via CONTRIBUTING_TRANSLATIONS.md (issues, spreadsheet, Crowdin — Drytrix TimeTracker, or Weblate)
- Automatic language detection improvement
- Translation coverage reporting
Support
For questions or issues with translations:
- Check this documentation
- Contributors without Git: see CONTRIBUTING_TRANSLATIONS.md (issue template, spreadsheet option, maintainer workflow, and Crowdin — Drytrix TimeTracker using root
crowdin.ymland the Crowdin sync GitHub Action) - Review
app/__init__.pylocale selector - Inspect browser network requests to
/i18n/set-language - Check application logs for translation compilation errors
Last Updated: 2026-04-15 Flask-Babel Version: 4.0.0 Babel Version: 2.14.0