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
locale
directory 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
.po
files.The
.po
file 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_locales
This command scans the codebase and updates the
.po
files 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
.po
file 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=lang
in 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.