One of the things that is remarkable with Drupal 8 (and Drupal 9) is the support for multiple languages. You can translate anything, so making a page completely Norwegian is something Drupal supports out of the box.
If you download Drupal and a couple of modules, you can with just a few clicks have almost all of the interface translated into Norwegian, since translation is a community effort. This means that most of the strings we and our users see during a normal work day, are already translated by us, or someone else in the community. A very special shout out here to svenryen, who almost by himself went through all the strings in Drupal 8.
Today I want to introduce another interesting challenge, and how we are able to solve that with Drupal. Namely deploying new features that should contain translations.
At Ny Media we develop tailored and highly customized solutions for our clients. This way we ensure that their result will make their job and their business easier and more profitable. To do this, we often end up writing custom code for use either between our own projects, or for a specific client.
Translating Drupal Core is a community effort. But brand new strings coming from features that we just created in custom code is not something we can rely on the community to get translated. So we have to do it ourselves. The challenge here lies in how we can get the strings translated on the production site, when the strings are only found in this specific project. Let’s look at a couple of theoretical solutions, and then look at the solution we use. Which we by coincidence think is the best, and now we want to share this with you.
Let’s imagine that we are developing a feature where we display some Copyright information on every page, and we want to translate that. Let’s just say that we call this module “copyright_info”. And maybe we have some code that looks like this:
/**
* Implements hook_preprocess_HOOK().
*/
function copyright_info_preprocess_page(&$variables) {
$variables["page"]["content"][] = [
'#markup' => t('Thank you for visiting this site, and we hope you like the content. However it is copyrighted so please do not steal it.'),
];
}
Now on every page we will have the whole interface in Norwegian. The only exception is the part with copyright, since that is something we just wrote ourselves.
Option 1: Translating “by hand” when the new feature is deployed.
This is the easiest and most obvious way to do it. After you have deployed your new feature, you go into the administration form for your site. This part is located under Administration -> Configuration -> Regional and language -> User interface translation, or path admin/config/regional/translate. So we search up the string(s) we just made. Then we can translate them one by one.
This is a very transparent and easy solution. And that has its advantages:
- Requires no additional build steps to import translations
- Requires no additional modules to enable
However, it has some disadvantages as well:
- Tedious to do for more than one translation.
- Error prone (Copy-paste errors, or typos)
- You end up having a deployed site with a temporarily untranslated string while you are manually editing the translation(s).
In this example case this might be a good solution since we only have that one string. But once you get more than 1 string to translate, you really do not want to do that by hand. And if you have a high traffic site, you probably do not want your site do be untranslated while you work.
Option 2: Exporting translation on a dev/staging site, and importing it on the live site
Another option that is available out of the box, is to export all the translated strings you have on one site, and then import them on another.
This is also fairly easy to do so it does have similar advantages:
- Requires no additional build steps to import translations
- Require no additional modules to enable
- Supports importing of huge amounts of text
It does however come with some disadvantages:
- Still manual work, and might leave your site temporarily untranslated
- Requires manual intervention
- The translations batches are not version controlled
Option 3: Importing custom translations as a part of your build process.
As you probably have figured, this is the option I wanted to highlight in this blog post, and this is the method we are currently using in Ny Media. To achieve this we use a Drupal module called Custom translation deployments. This is a module we developed as part of a client project, which in turn ended up being our standard setup for deploying translations on our projects.
The module provides two ways of providing translations to easily deploy as part of the build process. We will look at the most simple here, and then briefly mention a slightly more advanced way.
The first step of being able to deploy translations is making sure your translations are committed to your version control system. A default Drupal installation will set the translation directory to sites/default/files/translations,. This is typically a directory ignored by the version control system. So we start this process by changing it to somewhere we can check in to our version control system. Like ../translations. To change it, we go to the page for File system located under Administration -> Configuration -> Media, or at path admin/config/media/file-system.
Here in Norway we are usually looking for Norwegian translations for our Norwegian clients. The module Custom translation deployments comes with a predefined translation file pattern defined. Meaning if we place a file called project_specifc-custom.LANGUAGE.po (or in the case of Norwegian project_specifc-custom.nb.po) in the newly defined and created translation folder, it will get imported. Let’s try that.
First let's make sure the file is in place.
$ ls ../translations
project_specific-custom.nb.po
Now one can either use the interface (under Administration -> Reports -> Available translation updates - admin/reports/translations) or do this with drush.
The other convenient thing is to add this as part of your deployment script. Maybe you have some sort of Continuous Deployment tool for it, or maybe you just use some manual and run it on your server. Whatever boat you are in, you can always do something like this:
$ drush sdel locale.translation_last_checked && drush locale-update && drush cr
> [notice] Translation file not found: http://ftp.drupal.org/files/translations/8.x/project_specific/project_specific-custom.nb.po.
> [notice] Checked nb translation for project_specific.
> [notice] Imported nb translation for project_specific.
> [notice] Translations imported: 1 added, 0 updated, 0 removed.
> [notice] Message: En oversettelsesfil importert. /1/ oversettelser ble lagt til, /0/
> oversettelser ble oppdatert og /0/ oversettelser ble fjernet.
This is actually 3 commands. The first one makes sure that Drush is forced to think it is time to update the translations. The second one updates the translations, and the third one clears the cache. This third step is important if you have translated strings in your JavaScript.
But it is also possible to do this through the interface. Which would look something like this.
One other thing to note is that having the project defined as a locale project, means you can have a translation file on a server, and Drupal will look for updates to it. As you can see from the drush output above, it will look for it in a specific pattern. This is defined as part of the hook implementation for custom_translation_deployments. So if you want a translation file for your organization you can have a custom module implementing this hook, and therefore also make it available to be updated and downloaded automatically. Her is one such example:
/**
* Implements hook_custom_translation_deployments_files().
*/
function my_org_custom_translation_deployments_files() {
$items = [];
$items[] = [
'name' => 'my_org',
'project_type' => 'module',
'core' => '8.x',
// We set the version to something static, but not to "dev".
'version' => 'custom',
'server_pattern' => 'http://my_org.com/files/translations/%core/%project/%project-%version.%language.po',
'status' => 1,
];
return $items;
}
This way we can now either ship a translation file called my_org-custom.nb.po or a file on http://my_org.com/files/translations/8.x/my_org/my_org-custom.nb.po.
The last part I would recommend is to only look for translation updates in the local filesystem on your production server, and only manually. This way, Drupal will never try to download new translations on your live site, creating conflicts with your version controlled translations. The setting for this is at Administration -> Configuration -> Regional and language -> User interface translation -> Interface translation settings, or path admin/config/regional/translate/settings.
You can also do this as part of the settings on the live site, and instead keep this setting on for development sites. To do that in settings.php you would do this:
$config['locale.settings']['translation']['use_source'] = 'local';
Flexible translation methods
However your advanced methods of deployment and translation is, Drupal has a way for you to handle it. And when Drupal out of the box can not support your preferred method of deployment or automation, contributed projects like Custom translation deployments can help you the last steps of the way. The module is fully tested, used in production on many sites, and supports Drupal 9 out of the box!
If you are looking for a partner in developing your localized Drupal site, contact Ny Media!