All Articles

A Simple Shoutbox with Automated Translations in Django

In this tutorial we will create a shoutbox with automated translations. Users can enter a message and this message can be read by other users in different languages. We use Django for the web app and the Translator API of the Azure Cognitive Services.

We will send the messages directly to the translation services and store the result in our database. In contrast to website translation services, the API is therefore not hit on every page request. This enables us to protect visitor’s information as much as possible.

Signing up for Azure

Before we start, we need an active Azure Account and create a translation service. If you don’t already have an Azure account, you can find all the information about free tiers i my Getting Started with Azure post.

Once you’re logged in in the Azure Portal, you can easily create a Translation Service like so:

Creating an Azure Translation Service

Setup

We start with an empty Django project which we call babelbox_project and an empty app called babelbox. We use the django-crispy-forms package to style our forms with Bootstrap.

In our settings, we have to define three things:

  1. The Cripsy Forms Template pack (in our case bootstrap5)
  2. The languages we want to target
  3. The access configuration for the Azure Translation Service

To our settings.py file we append these lines (remember to replace the AZURE_KEY variables with the access key provided by Azure):

CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
CRISPY_TEMPLATE_PACK = "bootstrap5"

TRANSLATED_LANGUAGES = (
    ('🇬🇧', 'EN', 'English'),
    ('🇪🇸', 'ES', 'Español'),
    ('🇫🇷', 'FR', 'Français')
)


AZURE_KEY_1 = "<>"
AZURE_LOCATION = "global"
AZURE_ENDPOINT = "https://api.cognitive.microsofttranslator.com/"

Creating the Models

A message in our case consists of only three attributes:

  1. The name of the user submiting the message
  2. The message itself
  3. The language the message is written in

Let’s create a simple model for this in our models.py file:

class Message(models.Model):
    """This model represents the original user input."""
    class Meta:
        ordering =['-created_at']
    created_at = models.DateTimeField(auto_now_add=True)

    name = models.CharField(max_length=255)
    message_language = models.CharField(max_length=2, choices=((lang_id, flag) for flag,lang_id,_ in settings.TRANSLATED_LANGUAGES))
    message = models.TextField()

In addition to the original messages we need a model to store its translations. We can create a TranslatedMessage model like so:

class TranslatedMessage(models.Model):
    """This model contains the translated message."""
    class Meta:
        """Model Meta options"""
        unique_together = ['message', 'language']
    message = models.ForeignKey(Message, on_delete=models.CASCADE, related_name="translated_messages")
    language = models.CharField(max_length=2, choices=((lang_id, flag) for flag,lang_id,_ in settings.TRANSLATED_LANGUAGES))
    translated_message = models.TextField()

Signals

Signals are a useful feature provided by the Django framework. Signals allow senders to notify a set of receivers that some action has taken place. One of such actions could be saving a model instance to the database. In our case we can use a receiver to request a translation from the Azure Translation service whenever a new message has been saved.

Our signals.py looks like this:

@receiver(post_save, sender=Message)
def my_handler(sender, instance, **kwargs):
    target_languages = [lang_id.lower() for _,lang_id,_ in settings.TRANSLATED_LANGUAGES]
    request = requests.post(settings.AZURE_ENDPOINT + '/translate',
                            params={
                                'api-version': '3.0',
                                'from': instance.message_language.lower(),
                                'to': target_languages
                            },
                            headers={'Ocp-Apim-Subscription-Key': settings.AZURE_KEY_1},
                            json=[{
                                'text': instance.message
                            }])
    translations = request.json().pop().get('translations', [])
    for translation in translations:
        translation_object = TranslatedMessage(
            message=instance,
            language=translation.get('to').upper(),
            translated_message=translation.get('text')
        )
        translation_object.save()

In order for this code to be excuted automatically on every save event, we need to import the signals.py file on our app startup. We can achieve this by adding a ready method to our AppConfig located in babelbox/app.py. This ensures that the file is loaded and the receiver is registered. Everything else is handled by Django automatically.

from django.apps import AppConfig


class BabelboxConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'babelbox'

    def ready(self):
        from . import signals

Views and Templates

Now that we have created a database design for our shoutbox app, we need to create the views and templates for it. For our simple case we just need two views:

  1. The index view which displays a list of all messages (and their translations)
  2. The add view which displays a form to enter a new message.

Inside our views.py we first define the index method. This method gets a list of all message objects and passes that list to our template.

def index(request):
    messages = Message.objects.all()
    languages = settings.TRANSLATED_LANGUAGES
    return render(request, 'babelbox/index.html', dict(messages=messages, languages=languages))

Next, we create the add message that handles our MessageForm:

def add(request):
    form = MessageForm(request.POST or None)
    if request.method=="POST":
        if form.is_valid():
            form.save()
            return HttpResponseRedirect("/")
    return render(request, 'babelbox/add.html', dict(form=form))

Of course, we need to create the MessageForm class itself, which is as easy as it can get:

class MessageForm(forms.ModelForm):
    class Meta:
        model = Message
        fields = ['name', 'message', 'message_language']
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_id = 'id-exampleForm'
        self.helper.form_method = 'post'
        self.helper.form_action = '/add'

        self.helper.add_input(Submit('submit', 'Submit'))

Final Result

Screen Shot of the final Shoutbox

GitHub Repository

The whole source code can be retrieved from the GitHub repository codewithbas/django-babelbox.