Following the last post where I had briefly covered the idea behind multiple languages through Django, I finally setup the app to support for now just two languages - the first being English and the second German as that is the only foreign language I speak to some extent. Other than default settings with Django, it also needs django-modeltranslation which can be found from:
https://github.com/deschler/django-modeltranslation
In the settings.py file, the model translation app will appear before all other apps, as most other apps will be modified because of multi language support.
INSTALLED_APPS = [ 'modeltranslation', # ]
It needs the LocaleMiddleware to be able to handle multi language content. This needs to come after SessionMiddleware as sessions will store data and this will need to be translated as well, but should come before CommonMiddleware. So far I am not using CacheMiddleware, but I will need to enable cache later. In case CacheMiddleware is being used, LocaleMiddleware must come after it.
Finally, comes the language settings. Creating a Django project usually produces LANGUAGE_CODE, USE_I18N and USE_TZ variables. The LANGUAGE_CODE is usually en_US, but I changed it to en to keep things simple.
LANGUAGE_CODE = 'en' LANGUAGES = [ ('en', _('English')), ('de', _('German')), ] TIME_ZONE = 'UTC' USE_I18N = True USE_TZ = True # Where translations will be stored LOCALE_PATHS = [ BASE_DIR / 'locale', ]
After creating the locale directory in the root of the project, we can now start generating translation files. The very first time, it is necessary to specify the exact locales that you want to create translations for:
django-admin makemessages --locale=de
This will create the .po files where translations need to be inserted. To start inserting translations, all the text that needs to be translated needs to wrapped with:
from django.utils.translation import gettext_lazy as _ # _('Course not found from URL')
The normal text 'Course not found from URL' will be wrapped with gettext_lazy from the translation module.
The makemessages command will create a folder for each language in the locale directory and create a django.po file. All that is need to be done is go through the file and insert translations:
msgid "Course not found from URL" msgstr "Translation of Course not found from URL"
To get translations enabled at the database level, a new file translation.py file needs to be created in each app that contains a models.py file. As an example, for the courses app:
from modeltranslation.translator import translator, TranslationOptions from .models import Course class CourseTranslation(TranslationOptions): ''' Translation of Course model ''' fields = ('title', 'subtitle', 'description') translator.register(Course, CourseTranslation)
We need to specify the fields for which translation needs to be enabled, and register these fields along with the model. This will now create additional columns for those fields for each language that is supported - so title becomes title, title_en and title_de. The default title will be the same as the default language title_en.
To ensure that multiple languages can also be edited in Django admin, the admin.py file also needs modification:
from django.contrib import admin from modeltranslation.admin import TranslationAdmin from .models import Course class CourseAdmin(TranslationAdmin): ''' Translated model of Course model for admin ''' pass admin.site.register(Course, CourseAdmin)
I had to delete the database and run the migrations again to ensure that there were no violations. But once that was done, the rest was pretty smooth - the admin panel showed multiple language fields.
What I was surprised about was that not much change needed to made for the API and the hook between the API and the database. To enable multi-language support in the API, the API calls will need to have an Accept-Language header. If this header is absent, the default English is used. To now hook this up with the rest, I needed to create a base view which then is inherited by all other views:
from django.utils import translation from rest_framework.generics import GenericAPIView class BaseAPIView(GenericAPIView): ''' Base API view for handling language content ''' def initial(self, request, *args, **kwargs): super().initial(request, *args, **kwargs) lang = request.META.get('HTTP_ACCEPT_LANGUAGE') if lang is not None: translation.activate(lang)
The initial method is defined in the APIView of Django Rest Framework and
"Performs any actions that need to occur before the handler method gets called. This method is used to enforce permissions and throttling, and perform content negotiation."
The documentation also adds that one would typically not need to override this method. However, enabling language support does seem like a legitimate override of this method. Once the language is enabled with translation.activate(), the bridge is complete. Not only is it possible to retrieve data in different languages, but also to create and update data in different languages using POST and PUT methods.
So far this seems like a very convenient way to enable multi-language support in the app. It would be interesting to see if any issues crop up when many languages are enabled as then the columns of the tables will increase in number.