DEV Community

Cover image for Leveraging Headers for Dynamic Localization in Django
Steve Yonkeu
Steve Yonkeu

Posted on Edited on

Leveraging Headers for Dynamic Localization in Django

Ahhhh it's been long I passed here! I was cooking some else as usual. Please calm down, by the end of this talk I will have three good news for you!

Let's speak about cultural diversity, localization, internalization and translations. In a few steps let's discuss about how (it is done) and how (best way to do it). On your marks... get set... ready.... GOOOOOOOOOOOO!!!!!!

Yes to API translations

I will be good to give a brief run down of the steps we are to go through.

Workshop RoadMap

Project Setup

mkdir TransTab

cd TransTab

python -m venv venv

source venv/bin/activate

pip install django boto3 djangorestframework polib python-dotenv pip-chill

pip-chill > requirements.txt --no-chill

django-admin startproject transtab .

python manage.py startapp translator

mkdir utils/

touch translator/serializer.py translator/urls.py utils.py middleware.py renderer.py .env
Enter fullscreen mode Exit fullscreen mode

At this point in time, we should have a folder structure and output as this when running python manage.py runserver:

    .
    ├── manage.py
+   ├── middleware.py
+   ├── requirements.txt
    ├── translator
    │   ├── __init__.py
    │   ├── admin.py
    │   ├── apps.py
    │   ├── migrations
    │   │   └── __init__.py
    │   ├── models.py
+   │   ├── serializer.py
    │   ├── tests.py
+   │   ├── urls.py
    │   └── views.py
    ├── transtab
    │   ├── __init__.py
    │   ├── asgi.py
    │   ├── settings.py
    │   ├── urls.py
    │   └── wsgi.py
    └── utils
+       ├── renderer.py
+       └── translate.py

    4 directories, 20 files
Enter fullscreen mode Exit fullscreen mode

Django Application Running

Creating Translation Middleware

Inside the middleware.py file, add the following content:

from django.utils import translation
from django.conf import settings

class TRSLocaleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        language = request.headers.get('Accept-Language', settings.LANGUAGE_CODE)
        translation.activate(language)
        request.LANGUAGE_CODE = translation.get_language()
        response = self.get_response(request)
        translation.deactivate()
        return response
Enter fullscreen mode Exit fullscreen mode

JSON Response Render

Inspired by a Postman Blog post, Let's customize the renderer.py file with the content below.

# utils/renderer.py
from rest_framework.renderers import JSONRenderer
from rest_framework.views import exception_handler
from django.utils import timezone
import uuid

class NewJSONRenderer(JSONRenderer):
    def render(self, data, accepted_media_type=None, renderer_context=None):
        status_code = renderer_context['response'].status_code

        if 200 <= status_code < 300:
            response = {
                "status": "success",
                "statusCode": status_code,
                "data": data
            }
        else:
            response = {
                "status": "error",
                "statusCode": status_code,
                "error": {
                    "code": data.get('code', 'UNKNOWN_ERROR'),
                    "message": data.get('detail', str(data)),
                    "details": data.get('details', None),
                    "timestamp": timezone.now().isoformat(),
                    "path": renderer_context['request'].path,
                    "suggestion": data.get('suggestion', None)
                }
            }

        response["requestId"] = str(uuid.uuid4())
        response["documentation_url"] = "https://api.example.com/docs/errors"

        return super().render(response, accepted_media_type, renderer_context)

def trans_exception_handler(exc, context):
    response = exception_handler(exc, context)

    if response is not None:
        response.data['status'] = 'error'
        response.data['statusCode'] = response.status_code

    return response
Enter fullscreen mode Exit fullscreen mode

AWS Translate Function

Enter the utils/translate.py file and change the content to:

import os
import boto3
import polib
from django.conf import settings

def translate_text(text, source_language, target_language):
    translate = boto3.client(
        "translate",
        region_name=settings.AWS_REGION,
        aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
        aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY
    )

    result = translate.translate_text(
        Text=text,
        SourceLanguageCode=source_language,
        TargetLanguageCode=target_language
    )
    return result.get("TranslatedText")


def translate_po_files():
    dirs = [
        d for d in os.listdir("locale") if os.path.isdir(os.path.join("locale", d))
    ]
    for dir in dirs:
        po = polib.pofile(f"locale/{dir}/LC_MESSAGES/django.po")
        target_language = po.metadata['Language']
        for entry in po:
            if not entry.translated():
                translated_text = translate_text(entry.msgid, 'auto', target_language)
                entry.msgstr = translated_text
        po.save()

Enter fullscreen mode Exit fullscreen mode

Settings configuration

Let's do a few changes here and there in the settings.py and .env

# settings.py
import os
from dotenv import load_dotenv

load_dotenv()

SECRET_KEY = os.getenv('SECRET_KEY')
DEBUG = True if os.getenv('DEBUG') == 'True' else False

#...

AWS_REGION = os.getenv('AWS_REGION')
AWS_ACCESS_KEY_ID = os.getenv('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.getenv('AWS_SECRET_ACCESS_KEY ')

MIDDLEWARE = [
    # ...
    'middleware.TRSLocaleMiddleware',
    # ...
]

INSTALLED_APPS = [
     # ...
     'translator',
]

REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': (
        'utils.renderer.NewJSONRenderer',
    ),
    'EXCEPTION_HANDLER': 'utils.renderer.trans_exception_handler'
}
Enter fullscreen mode Exit fullscreen mode

Then we add our environment variables:

DEBUG=True
SECRET_KEY='This should look like a secret key'
AWS_REGION=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
Enter fullscreen mode Exit fullscreen mode

Hey!!! Is there someone here? Hope we are together, don't sleep (yet)!!!

Thinking about automation

Automating the translation with a command

Now let's automate the translation by creating a custom manage.py command in django.

Creating the command file

mkdir -p translator/management/command

touch translator/management/commands/translate_po.py
Enter fullscreen mode Exit fullscreen mode

After this command running, we have this structure:

    .
    ├── db.sqlite3
    ├── manage.py
    ├── middleware.py
    ├── requirements.txt
    ├── translator
    │   ├── __init__.py
    │   ├── admin.py
    │   ├── apps.py
+   │   ├── management
+   │   │   └── commands
+   │   │       └── translate_po.py
    │   ├── migrations
    │   │   └── __init__.py
    │   ├── models.py
    │   ├── serializer.py
    │   ├── tests.py
    │   ├── urls.py
    │   └── views.py
    ├── transtab
    │   ├── __init__.py
    │   ├── asgi.py
    │   ├── settings.py
    │   ├── urls.py
    │   └── wsgi.py
    └── utils
        ├── renderer.py
        └── translate.py

    6 directories, 21 files
Enter fullscreen mode Exit fullscreen mode

Let's drop the command logic now in the code base

from django.core.management.base import BaseCommand
from utils.translate import translate_po_files

class Command(BaseCommand):
    help = 'Translates all .po files using AWS Translate'

    def handle(self, *args, **options):
        translate_po_files()languages.
        self.stdout.write(self.style.SUCCESS('Successfully translated .po files.'))
Enter fullscreen mode Exit fullscreen mode

Hello 👋, are you here? If you are missing anything refer to this repo

Some commands to run:

# make messages for a language
django-admin makemessages -l <lang_code> --ignore=venv/*

# compiling messages, converting from `.po` to `.mo`
django-admin compilemessages --ignore=venv/*

# our new command we made
python manage.py translate_po
Enter fullscreen mode Exit fullscreen mode

That's all folks!!!

Wait a minute.... Who remember about my first line talking about the 3 good news? Ohh you forgot about...
thinking meme pawpaw
So, a few days ago, I decided to be more social media focused and each content I make it directed towards learning with fun. I have a todo for you:

Ciao ciao!!!

Top comments (0)