Localization and Internationalization
Localization and internationalization are important aspects of the addons-server project, ensuring that the application can support multiple languages and locales. This section covers the key concepts and processes for managing localization and internationalization.
Locale Management
Locale management involves compiling and managing translation files. The addons-server project uses a structured approach to handle localization files efficiently.
Compiling Locales:
The Makefile provides commands to compile locale files, ensuring that translations are up-to-date.
Use the following command to compile locales:
make compile_locales
Managing Locale Files:
Locale files are typically stored in the
localedirectory within the project.The project structure ensures that all locale files are organized and easily accessible for updates and maintenance.
Adding New Translations
We write the english translations of our strings directly in the code, using the gettext function. For example:
from django.utils.translation import gettext_lazy as _
def my_view(request):
output = _('Welcome to my site.')
return HttpResponse(output)
When developing locally you should not really need to do anything special to see the translations. The gettext function will return the string as is if it can’t find a translation for it. In CI translation strings are automatically extracted and uploaded to Pontoon for translation.
Translation Management
Translation management involves handling translation strings and merging them as needed. The addons-server project follows best practices to ensure that translations are accurate and consistent.
Handling Translation Strings:
Translation strings are extracted from the source code and stored in
.pofiles.The
.pofile format is used to manage locale strings, providing a standard way to handle translations.
Merging Translation Strings:
To extract new locales from the codebase, use the following command:
make extract_localesThis command scans the codebase and updates the
.pofiles with new or changed translation strings.After extraction, scripts are used to merge new or updated translation strings into the existing locale files.
This process ensures that all translations are properly integrated and maintained.
Additional Tools and Practices
Pontoon:
The addons-server project uses Pontoon, Mozilla’s localization service, to manage translations.
Pontoon provides an interface for translators to contribute translations and review changes, ensuring high-quality localization.
.po File Format:
The
.pofile format is a widely used standard for managing translation strings.It allows for easy editing and updating of translations, facilitating collaboration among translators.
Translating Fields on Models
The olympia.translations app defines a olympia.translations.models.Translation model, but for the most part, you shouldn’t have to use that directly. When you want to create a foreign key to the translations table, use olympia.translations.fields.TranslatedField. This subclasses Django’s django.db.models.ForeignKey to make it work with our special handling of translation rows.
Minimal Model Example
A minimal model with translations in addons-server would look like this:
from django.db import models
from olympia.amo.models import ModelBase
from olympia.translations.fields import TranslatedField, save_signal
class MyModel(ModelBase):
description = TranslatedField()
models.signals.pre_save.connect(save_signal,
sender=MyModel,
dispatch_uid='mymodel_translations')
How It Works Behind the Scenes
A TranslatedField is actually a ForeignKey to the translations table. To support multiple languages, we use a special feature of MySQL allowing a ForeignKey to point to multiple rows.
When Querying
Our base manager has a _with_translations() method that is automatically called when you instantiate a queryset. It does two things:
Adds an extra
lang=langin the query to prevent query caching from returning objects in the wrong language.Calls
olympia.translations.transformers.get_trans()which builds a custom SQL query to fetch translations in the current language and fallback language.
This custom query ensures that only the specified languages are considered and uses a double join with IF/ELSE for each field. The results are fetched using a slave database connection to improve performance.
When Setting
Every time you set a translated field to a string value, the TranslationDescriptor __set__ method is called. It determines whether it’s a new translation or an update to an existing translation and updates the relevant Translation objects accordingly. These objects are queued for saving, which happens on the pre_save signal to avoid foreign key constraint errors.
When Deleting
Deleting all translations for a field is done using olympia.translations.models.delete_translation(), which sets the field to NULL and deletes all attached translations. Deleting a specific translation is possible but not recommended due to potential issues with fallback languages and foreign key constraints.
Ordering by a Translated Field
olympia.translations.query.order_by_translation allows you to order a QuerySet by a translated field, honoring the current and fallback locales like when querying.
By following these practices, the addons-server project ensures that the application can support multiple languages and locales effectively. For more detailed instructions, refer to the project’s Makefile and locale management scripts in the repository.