Translation
Nextcloud provides mechanisms for internationalization (make an application translatable) and localization (add translations for specific languages). This section provides detailed instructions for both aspects.
Make text translatable
In order to make your app translatable (internationalization), you should use Nextcloud’s methods for translating strings. They are available for both the server-side (PHP, Templates) as well as for the client-side (JavaScript).
PHP
If localized strings are used in the backend code, simply inject the``OCPIL10N`` class into your service via type hinting it in the constructor. You will automatically get the language object containing the translations of your app:
<?php
namespace OCA\MyApp\Service;
use \OCP\IL10N;
class AuthorService {
/** @var IL10N */
private $l;
public function __construct(IL10N $l) {
$this->l = $l;
}
…
}
Strings can then be translated in the following way:
<?php
…
class AuthorService {
…
public function getLanguageCode() {
return $this->l->getLanguageCode();
}
public sayHello() {
// Simple string
return $this->l->t('Hello');
}
public function getAuthorName($name) {
// String using a parameter
return $this->l->t('Getting author %1$s', [$name]);
}
public function getAuthors($count, $city) {
// Translation with plural
return $this->l->n(
'%n author is currently in the city %1$s', // singular string
'%n authors are currently in the city %1$s', // plural string
$count, // number to decide which plural to use
[$city] // further parameters are possible
);
}
}
Correct plurals
If you use a plural, you must also use the %n
placeholder. The placeholder defines the plural and the word without the number preceding is wrong. If you don’t know/have a number for your translation, e.g. because you don’t know how many items are going to be selected, just use an undefined plural. They exist in every language and have one form. They do not follow the normal plural pattern.
Example:
// BAD: Plural without count
$title = $l->n('Import calendar', 'Import calendars', $selectionLength)
// BETTER: Plural has count, but disrupting to read and unnecessary information
$title = $l->n('Import %n calendar', 'Import %n calendars', $selectionLength)
// BEST: Simple string with undefined plural
$title = $l->t('Import calendars')
Templates
In every template the global variable $l can be used to translate the strings using its methods t() and n():
<div><?php p($l->t('Showing %$1s files', $_['count'])); ?></div>
<button><?php p($l->t('Hide')); ?></button>
For the right date format use <?php p($l->l('date', time()));?>
.
JavaScript
There are global functions t() and n() available for translating strings in javascript code. They differ a bit in terms of usage compared to php:
- First argument is the appId e.g.
'myapp'
- Placeholders (apart from the count in plurals) use single-mustache brackets with meaning-full descriptors.
- The parameter list is an object with the descriptors as key.
t('myapp', 'Hello World!');
t('myapp', '{name} is available. Get {linkstart}more information{linkend}', {name: 'Nextcloud 16', linkstart: '<a href="...">', linkend: '</a>'});
n('myapp', 'Import %n calendar into {collection}', 'Import %n calendars into {collection}', selectionLength, {collection: 'Nextcloud'});
Important notes
Please also look through the following steps to improve your strings and make them better translatable by others
Improving your translations
You shall never split sentences and never concatenate two translations (e.g. “Enable” and “dark mode” can not be combined to “Enable dark mode”, because languages might have to use different cases)! Translators lose the context and they have no chance to possibly re-arrange words/parts as needed.
Bad example:
<?php p($l->t('Select file from')) . ' '; ?><a href='#' id="browselink"><?php p($l->t('local filesystem'));?></a><?php p($l->t(' or ')); ?><a href='#' id="cloudlink"><?php p($l->t('cloud'));?></a>
Translators will translate:
- Select file from
- local filesystem
- ‘ or ‘
- cloud
Translating these individual strings results in local filesystem
and cloud
losing case. The two white spaces surrounding or
will get lost while translating as well. For languages that have a different grammatical order it prevents the translators from reordering the sentence components.
So the following code is a bit better, but suffers from another issue:
<?php p($l->t('Select file from <a href="#" id="browselink">local filesystem</a> or <a href="#" id="cloudlink">cloud</a>'));?>
In this case the translators can re-arrange as they like, but have to deal with your markup and can mess it up easily. It is better to keep the markup out of your code, so the following translation is even better:
<?php p($l->t('Select file from %slocal filesystem%s or %scloud%s', ['<a href="#" id="browselink">', '</a>', '<a href="#" id="cloudlink">', '</a>']));?>
But there is one last problem with this. In case the language has to turn things around, your code will still insert the parameters in the given order and they can not re-order them. To prevent this last hurdle simply use positioned placeholders like %1$s
:
<?php p($l->t('Select file from %1$slocal filesystem%2$s or %3$scloud%4$s', ['<a href="#" id="browselink">', '</a>', '<a href="#" id="cloudlink">', '</a>']));?>
This allows translators to have the cloudlink before the browselink in case the language is e.g. right-to-left.
Hints
In case some translation strings may be translated wrongly because they have multiple meanings, you can add hints which will be shown in the Transifex web-interface:
<ul id="translations">
<li id="add-new">
<?php
// TRANSLATORS Will be shown inside a popup and asks the user to add a new file
p($l->t('Add new file'));
?>
</li>
</ul>
Adding translations
Nextcloud’s translation system is powered by Transifex. To start translating sign up and enter a group. If your community app should be translated by the Nextcloud community on Transifex just follow the setup section below.
Translation tool
The translation tool scrapes the source code for method calls to t() or n() to extract the strings that should be translated. If you check in minified JS code for example then those method names are also quite common and could cause wrong extractions. For this reason we allow to specify a list of files that the translation tool will not scrape for strings. You simply need to add a file named .l10nignore
into the root folder of your app and specify the files one per line:
# compiled vue templates
js/bruteforcesettings.js
Setup of the transifex sync
To setup the transifex sync within the Nextcloud community you need to add first the transifex config to your app folder at .tx/config
(please replace MYAPP with your apps id):
[main]
host = https://www.transifex.com
lang_map = bg_BG: bg, cs_CZ: cs, fi_FI: fi, hu_HU: hu, nb_NO: nb, sk_SK: sk, th_TH: th, ja_JP: ja
[nextcloud.MYAPP]
file_filter = translationfiles/<lang>/MYAPP.po
source_file = translationfiles/templates/MYAPP.pot
source_lang = en
type = PO
Then create a folder l10n
and a file l10n/.gitkeep
to create an empty folder which later holds the translations.
Add one more file called .l10nignore
in root of the repository and the files and folders to ignore for translations. Mostly used to ignore packed js files.
Now the GitHub account @nextcloud-bot needs to get write access to your repository. It will run every night and only push commits to the master branch once there is an update to the translation. In general you should enable the protected branches feature at least for the master branch.
For the sync job there is a configuration file available in our docker-ci repository. Adding there the repo owner and repo name to the section named app via pull request is enough. Once this change is in one member of the sysadmin team will deploy it to the sync server and the job will then run once a day.
If you need help then just open a ticket with the request and we can also guide you through the steps.
Manual translation
If Transifex is not the right choice or the app is not accepted for translation, generate the gettext strings by yourself by executing our translation tool in the app folder:
cd /srv/http/nextcloud/apps/myapp
translationtool.phar create-pot-files
The translation tool requires gettext, installable via:
apt-get install gettext
The above tool generates a template that can be used to translate all strings of an app. This template is located in the folder translationfiles/template/
with the name myapp.pot
. It can be used by your favored translation tool which then creates a .po
file. The .po
file needs to be placed in a folder named like the language code with the app name as filename - for example translationfiles/es/myapp.po
. After this step the tool needs to be invoked to transfer the po file into our own fileformat that is more easily readable by the server code:
translationtool.phar convert-po-files
Now the following folder structure is available:
myapp/l10n
|-- es.js
|-- es.json
myapp/translationfiles
|-- es
| |-- myapp.po
|-- templates
|-- myapp.pot
You then just need the .json
and .js
files for a working localized app.