django-modeltranslation

repository·master·Indexed 23 days ago

https://github.com/deschler/django-modeltranslation

A Django library that adds multi-language support to existing models using a registration-based approach. It stores translations in the same database table and provides integration for the Django Admin via TranslationAdmin, TabbedTranslationAdmin, and specialized inline classes. The package includes management commands like update_translation_fields and sync_translation_fields, as well as an extended loaddata command with auto-population support.

Tokens
11.6K
Snippets
33
Records
61
Agent score
78%

What's inside django-modeltranslation

  1. Overview of django-modeltranslation

    master

    django-modeltranslation is a Django application designed to translate the dynamic content of existing models into an arbitrary number of languages.

    Key characteristics:

    • Non-intrusive: It uses a registration approach (similar to Django's admin app) that allows you to add translations without modifying the original model classes. This means you can use the same app in different projects regardless of whether they require translations.
    • Performance: Translation fields are stored in the same database table as the original fields, avoiding expensive SQL joins.
    • Compatibility: It supports inherited models (both abstract and multi-table inheritance) and handles more than just text fields.
    • Integration: It is fully integrated into the Django admin backend and supports features like flexible fallbacks and auto-population.
  2. Accessing translated fields outside of views

    master

    The django-modeltranslation mechanism relies on get_language() to determine which language value to return. Inside a Django view or template, the language is automatically set based on the request.

    Outside of a view (e.g., in management commands, background tasks, or general Python code), Django's language discovery machinery is not active. To ensure reliability, django-modeltranslation provides a wrapper function modeltranslation.utils.get_language which guarantees the returned language is listed in your LANGUAGES setting.

    When writing unit tests that require specific languages outside of a view, use django.utils.translation.trans_real to manually activate and deactivate the desired language.

  3. Warning: Do not rely on the original field value

    master

    When a field is registered for translation, the original field (e.g., title) is considered undetermined.

    While accessing the original field is guaranteed to work on the associated translation field of the current language (for both read and write operations), you should not attempt to rely on the underlying value stored in instance.__dict__['original_field_name']. Relying on the original field can lead to unpredictable side effects.

  4. Inherit fields in TranslationOptions

    master

    You can use class inheritance to share translatable fields across multiple models, which is particularly useful when working with abstract base classes.

    • Abstract Base Classes: A subclass of TranslationOptions will inherit all fields from its parents. For example, if BaseOptions has fields = ('title', 'text'), then ChildOptions(BaseOptions) with fields = ('image',) will result in fields == ('title', 'text', 'image').
    • Non-Abstract Base Classes: If the base class is not abstract (i.e., it is already registered to a model), inheriting from it will not merge fields for the child model. Instead, you should define separate TranslationOptions classes for each model to avoid errors and ensure fields are added correctly to each specific model.
    from modeltranslation.translator import translator, TranslationOptions
    from news.models import News, NewsWithImage
    
    class AbstractNewsTranslationOptions(TranslationOptions):
        fields = ('title', 'text',)
    
    class NewsWithImageTranslationOptions(AbstractNewsTranslationOptions):
        fields = ('image',)
    
    translator.register(News, NewsTranslationOptions)
    translator.register(NewsWithImage, NewsWithImageTranslationOptions)
    
    # NewsWithImageTranslationOptions.fields will be ('title', 'text', 'image')
  5. How translated and translation fields behave

    master

    When a model is registered with modeltranslation, the original fields (e.g., title) become 'translated fields' that act as proxies for the language-specific fields (e.g., title_en, title_de).

    Access Rules:

    1. Reading: Accessing the original field returns the value for the current active language (determined by Django's get_language()).
    2. Writing: Assigning a value to the original field updates the corresponding language-specific field for the current language.
    3. Conflict Resolution: If both the original field and the current language translation field are updated simultaneously (only possible during create() or in the model constructor), the current language translation field takes precedence.

    Example Model Structure: If the default language is de, a registered News model will automatically have these fields:

    • title: The original/translated field.
    • title_de: The default translation field.
    • title_en: The English translation field.
  6. Configure empty values for translation fields

    master

    By default, all translation fields added to a model definition are nullable (null=True), regardless of the original field's nullability.

    Because Django's default CharField formfield often saves empty values as empty strings ('') instead of None, django-modeltranslation patches these fields. The default behavior is:

    • If the original field is not nullable: empty values are saved as ''.
    • If the original field is nullable: empty values are saved as None.

    You can override this behavior using the empty_values attribute in your TranslationOptions class. This is critical for fields with unique=True constraints, where multiple empty strings would cause a database error, but multiple None values would be allowed.

    Valid values for empty_values entries:

    • None: Saves None for empty inputs.
    • '': Saves an empty string '' for empty inputs.
    • 'both': Uses a special widget with a None-checkbox to allow storing both empty strings and None values.
    class CategoryTranslationOptions(TranslationOptions):
        fields = ('name', 'slug')
        # Use None for slug to avoid unique constraint errors with empty strings
        empty_values = {'slug': None}
  7. How translation fields are created in the database

    master

    When a model is registered for translation, modeltranslation automatically adds translation fields to the model class. These fields are not explicitly declared in your models.py but are injected at runtime.

    Field Naming Convention: The new fields are named by taking the original field name and appending the language identifier (e.g., title becomes title_de for German and title_en for English, based on your settings.LANGUAGES).

    Database Schema Example: If you have a model with title and text, the resulting SQL table will include:

    • title (original)
    • title_de (German translation)
    • title_en (English translation)
    • text (original)
    • text_de (German translation)
    • text_en (English translation)

    Important Note on Field Requirements: All added translation fields are automatically set to blank=True and null=True, regardless of whether the original field was required. This makes translations optional by default.

  8. Enable Tabbed Translation Fields in Django Admin

    master

    To separate translation fields into UI tabs using jQuery UI, you can use the specialized admin classes provided by modeltranslation. This is easier than manually configuring the Media class with specific JS/CSS files.

    Option 1: Using Django's built-in jQuery

    Use TabbedTranslationAdmin (an alias for TabbedDjangoJqueryTranslationAdmin). This is recommended if you want to stick to the jQuery library shipped with Django.

    from modeltranslation.admin import TabbedTranslationAdmin
    
    class NewsAdmin(TabbedTranslationAdmin):
        pass

    Option 2: Using an external/newer jQuery version

    If you need a specific version of jQuery (e.g., 1.9.1), you must manually configure the Media class in your TranslationAdmin subclass:

    from modeltranslation.admin import TranslationAdmin
    
    class NewsAdmin(TranslationAdmin):
        class Media:
            js = (
                '//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js',
                '//ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/jquery-ui.min.js',
                'modeltranslation/js/tabbed_translation_fields.js',
            )
            css = {
                'screen': ('modeltranslation/css/tabbed_translation_fields.css',),
            }
  9. Set up django-modeltranslation in your project

    master

    Follow these steps to integrate django-modeltranslation into your Django project:

    1. Add 'modeltranslation' to your INSTALLED_APPS in settings.py.
    2. Set USE_I18N = True in settings.py.
    3. Configure your LANGUAGES in settings.py.
    4. Create a translation.py file in your app directory and register TranslationOptions for every model you want to translate.
    5. Run database migrations using python manage.py makemigrations and python manage.py migrate (only required if the models being translated haven't been synced to the database before).
  10. Use TranslationModelForm to hide translation fields in frontend forms

    master

    When using ModelForms for multilanguage models, you might want to present only the original fields to the user on the frontend, rather than all language-specific translation fields.

    Use TranslationModelForm to automatically strip out all translation fields. The resulting form will only contain the original fields (e.g., title, text). When the form is saved, django-modeltranslation will automatically set the provided values on the correct language-specific attributes based on the user's current language.

    Note: Do not define these forms in the same file as your models.

    from news.models import News
    from modeltranslation.forms import TranslationModelForm
    
    class MyForm(TranslationModelForm):
        class Meta:
            model = News