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:
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:
- The Cripsy Forms Template pack (in our case bootstrap5)
- The languages we want to target
- 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:
- The name of the user submiting the message
- The message itself
- 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:
- The index view which displays a list of all messages (and their translations)
- 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
GitHub Repository
The whole source code can be retrieved from the GitHub repository codewithbas/django-babelbox.