Internationalization
Omeka S includes support for presenting interfaces to users in different languages. The source strings for Omeka and its modules are written in English, and we rely on volunteers to translate those strings.
Basics
Omeka S stores translated strings in the gettext format. There are three basic types of file:
- a
.pot
file is a translation template: it contains only the English-language source strings. .po
files are where translated strings are stored. They're the same as the.pot
file, except the translations for each string are filled in. Each language has its own.po
file..mo
files are compiled versions of.po
files. The.mo
file is what Omeka S actually uses to read and use the translated strings for a particular language.
All three of these types of file are found in the languages
directory
(application/languages
for the core). Generally, translators will deal with the first two
types through Transifex (see next section).
Transifex
Omeka S uses the Transifex platform to allow a simpler way for people to contribute translations. The core and modules written by the Omeka team are handled through the same project on Transifex. Through the Transifex site, you can join existing translation teams, request new languages, and make changes or contributions to the translations.
Making strings translatable
When writing code in Omeka S, strings that will be presented to the user need some special handling so that they can be translated.
Direct translation: within a view
Instead of writing strings directly in the markup, use the translate()
helper.
As a simple real example, see this line from the installer view:
<h1><?php echo $this->translate('Install Omeka S'); ?></h1>
Using translate()
will handle actually replacing the English source string with the
translated equivalent, and also serve as a marker that a string is translatable for
when we build the translation template that makes strings available to the translators.
Indirect translation: within forms and other classes
Code that uses the Zend\Form
classes will automatically translate strings that get
presented to the user like labels, legends, and descriptions.
Several other areas where user-facing strings are defined are also automatically translated, such as labels for site blocks and media ingesters, labels for navigation entries defined in config files, and error messages.
In these situations, all that's necessary is to comment the line to be translated with
// @translate
at the end of the line. The translation itself will happen with or without
the comment, but the comment is needed so our automated template processing will pick up the
string to be translated and make it available to the translation teams.
The // @translate
comment applies only to the immediately-previous string. If a single
line contains multiple strings that need to be marked as translatable, or has one
translatable string that appears in between untranslatable ones, it may be necessary to
split the original line into multiple lines.
Here's an example of using the // @translate
comment in the site navigation section of
a module's module.config.php
file:
return [
'navigation' => [
'site' => [
[
'label' => 'Collecting', // @translate
'route' => 'admin/site/slug/collecting',
'action' => 'index',
'useRouteMatch' => true,
'pages' => [
// .....
],
],
]
]
];
And here's one from a form class that extends Zend\Form\Form
:
public function init()
{
$this->add([
'name' => 'o:email',
'type' => 'Email',
'options' => [
'label' => 'Email', // @translate
],
'attributes' => [
'id' => 'email',
'required' => true,
],
]);
};
Handling strings with dynamic parts
The previous examples are focused on simple, static strings, but often it's necessary to have strings with parts that change, like data that comes from users, or counts of search results. Simply passing the existing string as-is to the translation system won't work correctly: every change in the data will be treated as a "new" string that won't get translated.
In those situations, the dynamic part of the string must be replaced by a "placeholder," leading to a translatable part that stays the same, which the dynamic parts are then inserted into. To do this, Omeka S uses the PHP function sprintf.
The basic placeholder for sprintf is %s
. It's simplest to show an example of how this
gets used to make a dynamic string translatable. The following untranslated code:
There are <?php echo $this->escapeHtml($results); ?> results.
becomes this when translated:
<?php echo sprintf($this->translate('There are %s results.'), $this->escapeHtml($results)); ?>
Note that the call to translate()
is inside the call to sprintf
.
There's a slightly more complex style of placeholder that's used when one string contains
multiple dynamic parts. Some languages may need to reorder these parts in a different way
than the original English string, which means there needs to be something that differentiates
the first placeholder from the second, and so on. These placeholders look like this: %1$s
,
%2$s
, and so on.
There's a slightly different method used when dealing with these "dynamic" strings outside
of views (in places like class files, for example). Instead of using sprintf
directly,
there's a class Omeka\Stdlib\Message
that is used to pass the static and dynamic parts
of the string around together.
<?php
use Omeka\Stdlib\Message;
// ...
function exampleAction()
{
$message = new Message('There are %s results.', $results); // @translate
$this->messenger()->addSuccess($message);
}
?>
The resulting Message object can be passed directly to translate()
in a view. This
pattern is used often for error messages that can originate from deep within backend code
but still need to be presented to the user, and therefore translated.
Updating translation files
Omeka S includes several Gulp tasks for working with the translation files. The tasks all
start with i18n
(an abbreviation for "internationalization"). In addition to having Gulp
set up, these tasks require the gettext
package, which provides commands such as msgfmt
and xgettext
that are used to work with the translation files.
gulp i18n:template
updates the core's .pot
translation template. When strings are added
or changed, this task updates the template so it can be pushed to translators to in turn
update their translations. The Omeka team runs this command and then pushes the result to
Transifex.
gulp i18n:compile
is run after updating .po
files to compile them into .mo
files. Just
updating the .po
translation file isn't enough to get Omeka S to pick up the changes; the
.mo
file is what's actually used. The Omeka team runs this command after pulling down
updates from Transifex.
For further commands used for working with modules rather than the core, see the documentation on internationalization in modules.