Skip to content

Commit de8787e

Browse files
nickvergessenbackportbot[bot]
authored andcommitted
fix(translations): Split "Translation setup" into it's own page
Signed-off-by: Joas Schilling <coding@schilljs.com> [skip ci]
1 parent 5114851 commit de8787e

File tree

8 files changed

+231
-42
lines changed

8 files changed

+231
-42
lines changed

developer_manual/app_development/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ App development
1111
info
1212
init
1313
dependency_management
14+
translation_setup
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
.. _Translation setup:
2+
3+
=================
4+
Translation setup
5+
=================
6+
7+
Nextcloud's translation system is powered by `Transifex <https://explore.transifex.com/nextcloud/>`_. To start translating sign up and enter a group. If your community app should be translated by the `Nextcloud community on Transifex <https://explore.transifex.com/nextcloud/>`_ just follow the setup section below.
8+
9+
Translation tool
10+
----------------
11+
12+
.. note::
13+
14+
The tool-based translation currently only supports repositories hosted on ``github.com``. If your app is hosted elsewhere, you can try to follow the :ref:`manual-translation` instead.
15+
16+
The `translation tool <https://github.com/nextcloud/docker-ci/tree/master/translations/translationtool>`_ scrapes the source code for method calls to ``t()``
17+
or ``n()`` to extract the strings that should be translated. If you check
18+
in minified JS code for example then those method names are also quite
19+
common and could cause wrong extractions. For this reason we allow to
20+
specify a list of files that the translation tool will not scrape for
21+
strings. You simply need to add a file named :file:`.l10nignore` into
22+
the root folder of your app and specify the files one per line::
23+
24+
# compiled vue templates
25+
js/bruteforcesettings.js
26+
27+
28+
29+
Setup of the transifex sync
30+
---------------------------
31+
32+
Transifex configuration ``.tx/config``
33+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
34+
35+
To setup the transifex sync within the Nextcloud community you need to add first the
36+
transifex config to your app folder at :file:`.tx/config` (please replace ``MYAPP`` with your apps id):
37+
38+
.. code-block:: ini
39+
40+
[main]
41+
host = https://www.transifex.com
42+
lang_map = hu_HU: hu, nb_NO: nb, sk_SK: sk, th_TH: th, ja_JP: ja, bg_BG: bg, cs_CZ: cs, fi_FI: fi
43+
44+
[o:nextcloud:p:nextcloud:r:{{APPID}}]
45+
file_filter = translationfiles/<lang>/{{APPID}}.po
46+
source_file = translationfiles/templates/{{APPID}}.pot
47+
source_lang = en
48+
type = PO
49+
50+
Then create a folder :file:`l10n` and a file :file:`l10n/.gitkeep` to create an
51+
empty folder which later holds the translations.
52+
53+
Branch selection ``.tx/backport``
54+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
55+
56+
The bot will run every night and only push commits to the following branches branch once there is an update to the translation:
57+
58+
* main
59+
* master
60+
* stableX (X being the recent 3 versions of Nextcloud Server)
61+
62+
You can overwrite this list by creating a file ``.tx/backport`` in your repository with the following content::
63+
64+
develop stable
65+
66+
That would sync the translations for the branches (``main`` and ``master`` are added automatically):
67+
68+
* main
69+
* master
70+
* develop
71+
* stable
72+
73+
Excluding files ``.l10nignore``
74+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
75+
76+
Add one more file called :file:`.l10nignore` in root of the repository and the files and folders to ignore for translations.
77+
This should be used to exclude files that create false-positive translations, such as:
78+
79+
- Compiled JavaScript files ``js/``
80+
- 3rd-party PHP dependencies ``vendor/``
81+
- Non-shipped files and documentation ``docs/``
82+
83+
Validate source strings
84+
^^^^^^^^^^^^^^^^^^^^^^^
85+
86+
After finishing the setup, you can validate the translation source strings which outlines some common mistakes.
87+
Clone the `nextcloud/docker-ci <https://github.com/nextcloud/docker-ci/>`_ repository and afterwards run the following script:
88+
89+
.. code:: sh
90+
91+
bash translations/validateSyncSetup.sh Owner Repository
92+
93+
Repository permissions
94+
^^^^^^^^^^^^^^^^^^^^^^
95+
Now the GitHub account `@nextcloud-bot <https://github.com/nextcloud-bot>`_ needs to get ``write`` access to your repository.
96+
You can invite it from your repository settings::
97+
98+
https://github.com/<user-name>/<repo-name>/settings/access
99+
100+
After sending the invitation, please `open a ticket using the "Request translations" template <https://github.com/nextcloud/docker-ci/issues/new/choose>`_.
101+
102+
.. attention::
103+
104+
In general you should enable the
105+
`protected branches feature <https://help.github.com/articles/configuring-protected-branches/>`_
106+
for default and stable branches. If you do that, you need to grant the
107+
`@nextcloud-bot <https://github.com/nextcloud-bot>`_ ``admin`` permissions and allow administrators to bypass the protection.
108+
This feature however is only possible for repositories owned by organizations, not in repositories owned by individuals!
109+
You can `create your own organization <https://docs.github.com/en/organizations/collaborating-with-groups-in-organizations/creating-a-new-organization-from-scratch>`_
110+
111+
If you need help just `open a ticket with the request <https://github.com/nextcloud/docker-ci/issues/new/choose>`_
112+
and we can also guide you through the steps.
113+
114+
.. _manual-translation:
115+
116+
Manual translation
117+
------------------
118+
119+
If Transifex is not the right choice or the app is not accepted for translation,
120+
generate the gettext strings by yourself by executing our
121+
`translation tool <https://github.com/nextcloud/docker-ci/tree/master/translations/translationtool>`_
122+
in the app folder::
123+
124+
125+
cd /srv/http/nextcloud/apps/myapp
126+
translationtool.phar create-pot-files
127+
128+
The translation tool requires ``gettext``, installable via::
129+
130+
apt-get install gettext
131+
132+
The above tool generates a template that can be used to translate all strings
133+
of an app. This template is located in the folder :file:`translationfiles/template/` with the
134+
name :file:`myapp.pot`. It can be used by your favored translation tool such
135+
as `Poedit <https://poedit.net>`_. It then creates a :file:`.po` file.
136+
The :file:`.po` file needs to be placed in a folder named like the language code
137+
with the app name as filename - for example :file:`translationfiles/es/myapp.po`.
138+
After this step the tool needs to be invoked to transfer the po file into our
139+
own fileformat that is more easily readable by the server code::
140+
141+
translationtool.phar convert-po-files
142+
143+
Now the following folder structure is available::
144+
145+
myapp/l10n
146+
|-- es.js
147+
|-- es.json
148+
myapp/translationfiles
149+
|-- es
150+
| |-- myapp.po
151+
|-- templates
152+
|-- myapp.pot
153+
154+
You then just need the :file:`.json` and :file:`.js` files for a working localized app.

developer_manual/basics/front-end/index.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,4 @@ Front-end
88
templates
99
js
1010
css
11-
l10n
1211
theming

developer_manual/basics/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Basic concepts
1212
middlewares
1313
events
1414
front-end/index
15+
translations
1516
backgroundjobs
1617
caching
1718
logging

developer_manual/basics/front-end/l10n.rst renamed to developer_manual/basics/translations.rst

Lines changed: 70 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
===========
2-
Translation
3-
===========
1+
.. _Translations:
2+
3+
============
4+
Translations
5+
============
46

57
.. sectionauthor:: Bernhard Posselt <dev@bernhard-posselt.com>, Kristof Hamann
68

@@ -60,27 +62,6 @@ Strings can then be translated in the following way:
6062
}
6163
}
6264
63-
FIXME
64-
-----
65-
66-
Correct plurals
67-
^^^^^^^^^^^^^^^
68-
69-
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.
70-
71-
Example:
72-
73-
.. code-block:: php
74-
75-
<?php
76-
77-
// BAD: Plural without count
78-
$title = $l->n('Import calendar', 'Import calendars', $selectionLength)
79-
// BETTER: Plural has count, but disrupting to read and unnecessary information
80-
$title = $l->n('Import %n calendar', 'Import %n calendars', $selectionLength)
81-
// BEST: Simple string with undefined plural
82-
$title = $l->t('Import calendars')
83-
8465
Language of other users
8566
^^^^^^^^^^^^^^^^^^^^^^^
8667

@@ -121,7 +102,7 @@ In every template the global variable ``$l`` can be used to translate the string
121102
// Date string
122103
<em><?php p($l->l('date', time())); ?></em>
123104
124-
JavaScript / Typescript / Vue
105+
JavaScript / TypeScript / Vue
125106
-----------------------------
126107

127108
There are global functions ``t()`` and ``n()`` available for translating strings in javascript code.
@@ -138,13 +119,15 @@ They differ a bit in terms of usage compared to php:
138119
t('myapp', '{name} is available. Get {linkstart}more information{linkend}', {name: 'Nextcloud 16', linkstart: '<a href="...">', linkend: '</a>'});
139120
n('myapp', 'Import %n calendar into {collection}', 'Import %n calendars into {collection}', selectionLength, {collection: 'Nextcloud'});
140121
141-
142122
Guidelines
143123
----------
144124

145125
Please also look through the following hints to improve your strings and make them better translatable by the community
146126
and therefore improving the experience for non-english users.
147127

128+
Dos and Don'ts
129+
^^^^^^^^^^^^^^
130+
148131
.. list-table::
149132
:header-rows: 1
150133

@@ -191,17 +174,49 @@ and therefore improving the experience for non-english users.
191174
- "Error: %s"
192175
- Instead of concatenating errors or part messages, make them a proper placeholder
193176

177+
Correct plurals
178+
^^^^^^^^^^^^^^^
179+
180+
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.
181+
182+
PHP Example:
183+
184+
.. code-block:: php
185+
186+
// BAD: Plural without count
187+
$title = $l->n('Import calendar', 'Import calendars', $selectionLength)
188+
// BETTER: Plural has count, but disrupting to read and unnecessary information
189+
$title = $l->n('Import %n calendar', 'Import %n calendars', $selectionLength)
190+
// BEST: Simple string with undefined plural not using any number in the string
191+
$title = $l->t('Import calendars')
192+
193+
Opposed to the normal placeholders in javascript, the plural number also uses the ``%n`` syntax:
194+
195+
JS Example:
196+
197+
.. code-block:: js
198+
199+
/* BAD: Plural without count */
200+
n('myapp', 'Import calendar', 'Import calendars', selected.length)
201+
/* BETTER: Plural has count, but disrupting to read and unnecessary information */
202+
n('myapp', 'Import %n calendar', 'Import %n calendars', selected.length)
203+
/* BEST: Simple string with undefined plural not using any number in the string */
204+
t('myapp', 'Import calendars')
205+
194206
Improving your translations
195207
^^^^^^^^^^^^^^^^^^^^^^^^^^^
196208

197-
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.
198-
199-
Bad example:
209+
Starting with the following example, improving it step by step:
200210

201211
.. code-block:: php
202212
203213
<?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>
204214
215+
Step 1: String split
216+
""""""""""""""""""""
217+
218+
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.
219+
205220
Translators will translate:
206221

207222
* ``Select file from``
@@ -217,13 +232,21 @@ So the following code is a bit better, but suffers from another issue:
217232
218233
<?php p($l->t('Select file from <a href="#" id="browselink">local filesystem</a> or <a href="#" id="cloudlink">cloud</a>'));?>
219234
235+
Step 2: HTML Markup
236+
"""""""""""""""""""
237+
220238
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:
221239

222240
.. code-block:: php
223241
224242
<?php p($l->t('Select file from %slocal filesystem%s or %scloud%s', ['<a href="#" id="browselink">', '</a>', '<a href="#" id="cloudlink">', '</a>']));?>
225243
226-
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``:
244+
But there is one last problem with this.
245+
246+
Step 3: Placeholders
247+
""""""""""""""""""""
248+
249+
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``:
227250

228251
.. code-block:: php
229252
@@ -234,11 +257,15 @@ This allows translators to have the cloudlink before the browselink in case the
234257
.. _Hints:
235258

236259
Provide context hints for translators
237-
-------------------------------------
260+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
238261

239-
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:
262+
In case some translation strings may be translated wrongly because they have multiple meanings.
263+
Especially translations strings that only contain a single word often result in problems.
264+
The most famous example in the Nextcloud code base is ``Share`` which can which can be the verb and action ``To share something`` or the noun ``A share``.
265+
The added hints will be shown in the Transifex web-interface:
240266

241-
**PHP**
267+
PHP
268+
"""
242269

243270
.. code-block:: php
244271
@@ -251,14 +278,16 @@ In case some translation strings may be translated wrongly because they have mul
251278
</li>
252279
</ul>
253280
254-
**Javascript / Typescript**
281+
JavaScript / TypeScript
282+
"""""""""""""""""""""""
255283

256284
.. code-block:: javascript
257285
258286
// TRANSLATORS name that is appended to copied files with the same name, will be put in parenthesis and appended with a number if it is the second+ copy
259287
var copyNameLocalized = t('files', 'copy');
260288
261-
**Vue**
289+
Vue
290+
"""
262291

263292
This covers vue html templates in vue sfc components.
264293
For vue js code, see the javascript section.
@@ -270,21 +299,24 @@ For vue js code, see the javascript section.
270299
{{ t('forms', 'Required') }}
271300
</NcActionCheckbox>
272301

273-
**C++ (Qt)**
302+
C++ (Qt) / Desktop client
303+
"""""""""""""""""""""""""
274304

275305
.. code-block:: c++
276306

277307
//: Example text: "Progress of sync process. Shows the currently synced filename"
278308
fileProgressString = tr("Syncing %1").arg(allFilenames);
279309

280-
**Android Strings**
310+
Android
311+
"""""""
281312

282313
.. code-block:: xml
283314
284315
<!-- TRANSLATORS List of deck boards -->
285316
<string name="simple_boards">Boards</string>
286317
287-
**iOS**
318+
iOS
319+
"""
288320

289321
.. code-block:: swift
290322

developer_manual/conf.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,5 +325,7 @@
325325
"core/static-analysis": "../server/static-analysis.html",
326326
"core/unit-testing": "../server/unit-testing.html",
327327
# Removed 2024-09
328-
"digging_deeper/changelog": "../app_publishing_maintenance/app_upgrade_guide/index.html"
328+
"digging_deeper/changelog": "../app_publishing_maintenance/app_upgrade_guide/index.html",
329+
# Removed 2025-04
330+
"basics/front-end/l10n": "../translations.html"
329331
}

developer_manual/exapp_development/tech_details/Translations.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Translations
22
============
33

4-
ExApps translations work in the :doc:`same way as for PHP apps <../../basics/front-end/l10n>` with a few adjustments and differences.
4+
ExApps translations work in the :ref:`same way as for PHP apps<Translations>` with a few adjustments and differences.
55

66
In short, you just have to provide the ``l10n/<lang>.js`` (for front-end) and ``l10n/<lang>.json`` (for back-end) files for your app.
77

0 commit comments

Comments
 (0)