Skip to content

Translation & Internationalization

Bifolk supports English and German with full Django i18n support.

Target Audience: Software developers


Supported Languages

Language Code Status
English en Primary (source language)
German de Formal "Sie" form

Coverage target: 100%


Language Selection

Bifolk uses a two-layer language detection system:

1. User Profile Language (highest priority) → UserLanguageMiddleware
2. Language Cookie                          → LocaleMiddleware (Django)
3. Browser Accept-Language Header           → LocaleMiddleware (Django)
4. LANGUAGE_CODE setting (default: 'en')    → Fallback

UserLanguageMiddleware

Custom middleware (bifolk/middleware.py) that reads the language from user.profile.language and activates it for authenticated users. Overrides browser detection.

Configuration

# bifolk/settings.py
LANGUAGE_CODE = 'en'

LANGUAGES = [
    ('en', 'English'),
    ('de', 'Deutsch'),
]

LOCALE_PATHS = [
    BASE_DIR / 'locale',
]

Translation Workflow

1. Mark strings → _("text") or {% trans "text" %}
2. Extract      → python manage.py makemessages -l de --ignore=venv
3. Translate    → Edit locale/de/LC_MESSAGES/django.po
4. Compile      → python manage.py compilemessages -l de
5. Verify       → python scripts/translation/check_translations.py

Marking Strings for Translation

In Python Code

from django.utils.translation import gettext_lazy as _

# Model fields (module-level) — use gettext_lazy
class Hive(models.Model):
    name = models.CharField(max_length=100, verbose_name=_("Hive Name"))

# Choices — use gettext_lazy
HEALTH_CHOICES = [
    ('excellent', _('Excellent')),
    ('good', _('Good')),
]

# Messages in views (function-level) — gettext_lazy works here too
from django.contrib import messages
messages.success(request, _("Hive created successfully"))

Note

Use gettext_lazy (aliased as _) everywhere. It works at both module-level (models, forms) and function-level (views). Use plain gettext only when you specifically need immediate translation.

In Templates

{% load i18n %}

{# Simple string #}
<h1>{% trans "Hive Management" %}</h1>

{# With variables #}
{% blocktrans %}Welcome to {{ site_name }}{% endblocktrans %}

{# Pluralization #}
{% blocktrans count counter=hives.count %}
You have {{ counter }} hive.
{% plural %}
You have {{ counter }} hives.
{% endblocktrans %}

{# Context for ambiguous words #}
<button>{% trans "Save" context "button" %}</button>

In Forms

from django.utils.translation import gettext_lazy as _

class HiveForm(forms.ModelForm):
    class Meta:
        model = Hive
        fields = ['name', 'location']
        labels = {
            'name': _('Hive Name'),
            'location': _('Location'),
        }

Translation Files

File Location

app/locale/
└── de/
    └── LC_MESSAGES/
        ├── django.po      # Source translations (edit this)
        └── django.mo      # Compiled binary (auto-generated)

.po File Format

# Translator comment
#: hives/models.py:28
msgid "Hive Type"
msgstr "Bienenstocktyp"

# Context-specific translation
msgctxt "button"
msgid "Save"
msgstr "Speichern"

# Pluralization
msgid "%(count)s hive"
msgid_plural "%(count)s hives"
msgstr[0] "%(count)s Bienenstock"
msgstr[1] "%(count)s Bienenstöcke"

Commands Reference

Extract Messages

# Extract for German
cd app && python manage.py makemessages -l de --ignore=venv

# Extract for all languages
cd app && python manage.py makemessages -a --ignore=venv

Compile Messages

cd app && python manage.py compilemessages -l de

Check Coverage

python scripts/translation/check_translations.py

Audit Translations

python scripts/translation/audit_translations.py

Detect Hardcoded Strings

python scripts/translation/detect_hardcoded_strings.py

See scripts/translation/README.md for full script documentation.


Multi-Language Model Fields

For dynamic, user-configurable choices, Bifolk uses explicit language columns:

class ConfigurableChoice(models.Model):
    value = models.CharField(max_length=100)    # Internal value
    label_en = models.CharField(max_length=200)  # English label
    label_de = models.CharField(max_length=200)  # German label

    def get_label(self, language='en'):
        if language == 'de' and self.label_de:
            return self.label_de
        return self.label_en

Usage in views:

label = choice.get_label(request.LANGUAGE_CODE)

Adding a New Language

  1. Add to LANGUAGES in bifolk/settings.py
  2. Add to Profile.LANGUAGE_CHOICES in users/models.py
  3. Create migration: python manage.py makemigrations users
  4. Extract messages: python manage.py makemessages -l <code> --ignore=venv
  5. Translate the .po file
  6. Compile: python manage.py compilemessages -l <code>

German Translation Guidelines

  • Use formal German ("Sie" form, not "du")
  • Maintain consistent terminology:
English German
Hive Bienenstock
Queen Koenigin
Inspection Kontrolle
Harvest Ernte
Batch Charge
Bucket Eimer
Jar Glas

Troubleshooting

Problem Solution
Translations not appearing Run compilemessages and restart
Fuzzy translations ignored Remove #, fuzzy from .po file, recompile
Wrong language showing Check user profile language, clear browser cookies
makemessages missing strings Verify _() or {% trans %} syntax, check --ignore patterns