-
Notifications
You must be signed in to change notification settings - Fork 43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update the i18n documentation #740
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,9 @@ | |
**Table of Contents** | ||
|
||
- [Internationalization (i18n)](#internationalization-i18n) | ||
- [Using Translations](#using-translations) | ||
- [URL Query Parameter](#url-query-parameter) | ||
- [Language Selector](#language-selector) | ||
- [Translations](#translations) | ||
- [The Workflow](#the-workflow) | ||
- [Staging Translation Repository](#staging-translation-repository) | ||
|
@@ -11,6 +14,15 @@ | |
- [Downloading Updated Translations](#downloading-updated-translations) | ||
- [Weblate Configuration](#weblate-configuration) | ||
- [Plugins](#plugins) | ||
- [Technical Details](#technical-details) | ||
- [The Web Frontend](#the-web-frontend) | ||
- [Marking Texts for Translation](#marking-texts-for-translation) | ||
- [Singular Form](#singular-form) | ||
- [Plural Form](#plural-form) | ||
- [Translating Constants](#translating-constants) | ||
- [Formatting Texts](#formatting-texts) | ||
- [TRANSLATORS Comments](#translators-comments) | ||
- [Missing Translations](#missing-translations) | ||
|
||
--- | ||
|
||
|
@@ -22,6 +34,31 @@ Each Agama part (the web frontend, the D-Bus backend and the command line | |
interface) needs to solve this problem separately, see more details for each | ||
part in the details below. | ||
|
||
## Using Translations | ||
|
||
Users have two ways how to change the used language in the Agama interface. | ||
|
||
### URL Query Parameter | ||
|
||
When using a remote installation it is possible to set the used language via an | ||
URL query parameter. This is an expert option. | ||
|
||
To change the language append the `?lang=<locale>` query to the URL when | ||
accessing the Agama installer. The `locale` string uses the usual Linux locale | ||
format, e.g. `cs_CZ`. | ||
|
||
It is the user responsibility to use a correct locale name. When using a wrong | ||
name the translations might be broken, displayed only partially or even not at | ||
all. | ||
|
||
Changing the language causes reloading the page, in some situations this could | ||
cause losing some entered values on the current page. Therefore it is | ||
recommended to change the language at the very beginning. | ||
|
||
### Language Selector | ||
|
||
*to be done...* | ||
|
||
## Translations | ||
|
||
For translation process Agama uses [Weblate](https://weblate.org/) tool running | ||
|
@@ -126,3 +163,231 @@ plugin which automatically updates all PO files to match the POT file. That | |
means after adding or updating any translatable text the changes are | ||
automatically applied to all existing PO files and translators can translate | ||
the new or updated texts immediately. | ||
|
||
## Technical Details | ||
|
||
This part describes technical and implementation details. It also describes the | ||
translation process for developers. | ||
|
||
### The Web Frontend | ||
|
||
Most of the translatable texts which can user see in Agama are defined in the | ||
web frontend. However, the web frontend can display some translated texts coming | ||
from the backend part so it is important to set the same language in both parts | ||
and make sure the translations are available there. | ||
|
||
### Marking Texts for Translation | ||
|
||
The texts are marked for translation using the usual functions like `_()`, | ||
`n_()` and others. It is similar to the GNU gettext style. | ||
|
||
Currently Agama uses the Cockpit implementation for loading and displaying | ||
translations. To make this process transparent for developers and to allow easy | ||
change of the implementation there is a simple [i18n.js]( ../web/src/i18n.js) | ||
wrapper which provides the translation functions: | ||
|
||
```js | ||
import { _, n_, N_, Nn_ } from "~/i18n"; | ||
``` | ||
|
||
It is important to use these functions only on string literals! Do not use them | ||
with string templates or string concatenation, that will not work properly. | ||
|
||
The `_` and `n_` functions can be used on variables or constants, but their | ||
content must marked for translation using the `N_` or `Nn_` functions | ||
on string literals. See the examples below. | ||
|
||
#### Singular Form | ||
|
||
This is the most common way: | ||
|
||
```js | ||
const title=_("Storage"); | ||
``` | ||
|
||
For using translated text in React components you need to use curly braces | ||
to switch to plain Javascript in the attributes and the content: | ||
|
||
```js | ||
<Section title={_("Storage")}></Section> | ||
``` | ||
|
||
or | ||
|
||
```js | ||
<Button>{_("Select")}</Button> | ||
``` | ||
|
||
For translating long texts you might use multiline string literals: | ||
|
||
```js | ||
const description = _("Select the device for installing the system. All the \ | ||
file systems will be created on the selected device."); | ||
``` | ||
|
||
If you need to insert some values into the text you should use the | ||
[formatting function](#formatting-texts). It is recommended to use | ||
[translator comments](#translators-comments) for short or ambiguous texts. | ||
|
||
#### Plural Form | ||
|
||
If the translated text contains a number or quantity it is good to use a plural | ||
form so the translated texts looks naturally. | ||
|
||
```js | ||
sprintf( | ||
// the first argument is a singular form used when errors.length === 1, | ||
// the second argument is used in all other cases | ||
n_("%d error found", "%d errors found", errors.length), | ||
errors.length | ||
) | ||
``` | ||
|
||
Although the English language has only a single plural form the translators can | ||
use more of them. The translation definition also defines a function that | ||
computes which plural form to use for a specific number. See more details about | ||
the [plural forms]( | ||
https://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html) in the | ||
GNU gettext documentation. | ||
|
||
#### Translating Constants | ||
|
||
The top-level constants are evaluated too early, at that time the translations | ||
are not available yet and the language is not set. | ||
|
||
The constant value must be marked with `N_()` function which does nothing | ||
and then later translated with the usual `_()` function. | ||
|
||
```js | ||
const LABELS = Object.freeze({ | ||
auto: N_("Auto"), | ||
fixed: N_("Fixed"), | ||
range: N_("Range") | ||
}); | ||
|
||
export default function Foo() { | ||
... | ||
<label>{_(LABELS[value])}</label> | ||
``` | ||
|
||
#### Formatting Texts | ||
|
||
For formatting complex texts use the C-like `sprintf()` function. | ||
|
||
```js | ||
import { sprintf } from "sprintf-js"; | ||
|
||
sprintf(_("User %s will be created"), user) | ||
``` | ||
|
||
See the [sprintf-js](https://www.npmjs.com/package/sprintf-js) documentation | ||
for more details about the formatting specifiers. | ||
|
||
It is recommended to avoid building texts from several parts translated | ||
separately. This is error prone because the translators will not see the | ||
complete final text and might translate some parts inaccurately. | ||
|
||
```js | ||
// do NOT use this! it is difficult to translate the parts correctly | ||
// so they build a nice text after merging | ||
return <div>{_("User ")}<b>{user}</b>{_(" will be created")}</div> | ||
|
||
// rather translate a complete text and then split it into parts | ||
// TRANSLATORS: %s will be replaced by the user name | ||
const [msg1, msg2] = _("User %s will be created").split("%s"); | ||
... | ||
return <div>{msg1}<b>{user}</b>{msg2}</div> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ... this is not much of an improvement to me. I think this is better: const bold_user = <b>{user}</b>;
return <div>{ sprintf(_("User %s will be created"), bold_user)}</div> or am I missing a catch related to differences between JSX and strings, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That does not work, that
To avoid code injection React by default escapes all inserted texts so you cannot use HTML tags in translations:
is rendered as
There is a way to inject unescaped text but it is insecure and not recommended: https://legacy.reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah so what I was missing is: in React you cannot make HTML from strings, only from React objects |
||
``` | ||
|
||
Text splitting might be quite complex in some cases, but still should be | ||
preferred. | ||
|
||
```js | ||
// TRANSLATORS: error message, the text in square brackets [] is a clickable link | ||
const [msgStart, msgLink, msgEnd] = _("An error occurred. \ | ||
Check the [details] for more information.").split(/[[\]]/); | ||
... | ||
return <p>{msgStart}<a>{msgLink}</a>{msgEnd}</p>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK here it makes more sense, especially if |
||
``` | ||
|
||
Building sentences from separate parts might be easy in English, but is some | ||
other languages it might much more complex. Always assume that the target | ||
language has more complex grammar and rules. | ||
|
||
```js | ||
// do NOT use this! it is difficult to translate "enabled" and "not enabled" | ||
// differently in the target language! | ||
const msgNot = enabled ? "" : _("not"); | ||
sprintf(_("Service is %s enabled"), msgNot); | ||
|
||
// this is better | ||
enabled ? _("Service is enabled") : _("Service is disabled"); | ||
``` | ||
|
||
#### TRANSLATORS Comments | ||
|
||
You can use a special `TRANSLATORS` keyword in the comment preceding the text | ||
marked for translation. All comments after the keyword are included in the | ||
translations file, this should help the translator to correctly translate the | ||
text. | ||
|
||
```js | ||
// this line is NOT included in the PO file | ||
// TRANSLATORS: this line is included in the PO file, | ||
// this line as well | ||
_("Back") | ||
``` | ||
|
||
The translators comments should be used especially for short texts to better | ||
describe the meaning and the context of the message. | ||
|
||
```js | ||
// TRANSLATORS: button label, going back to the main page | ||
_("Back") | ||
``` | ||
|
||
Also the formatting placeholders should be always described. | ||
|
||
```js | ||
// TRANSLATORS: %s will be replaced by the user name | ||
_("User %s will be created") | ||
``` | ||
|
||
The JSX code does not support comments natively, you have to write them in curly | ||
braces. | ||
|
||
```js | ||
<Button> | ||
{/* TRANSLATORS: button label */} | ||
{_("Remove")} | ||
</Button> | ||
``` | ||
|
||
But in the component attributes you can use usual Javascript comments. | ||
|
||
```js | ||
<TextInput | ||
id="port" | ||
name="port" | ||
value={data.port || ""} | ||
// TRANSLATORS: network port number | ||
label={_("Port")} | ||
/> | ||
``` | ||
|
||
The translators can change the order of the arguments if needed using additional | ||
positional arguments. For example to change the order of the arguments in `"Foo | ||
%s %s"` they can translate it as `"Bar %2$s %1$s"`. | ||
|
||
#### Missing Translations | ||
|
||
Here are some code examples which might look correct at the first sight. They | ||
even work, no crash. But there are still translation problems with them. | ||
|
||
- Do not use Javascript string templates, that does not work at all | ||
(~~``_(`User ${user} will be created`)``~~), use a string formatting function | ||
for that (`sprintf(_("User %s will be created"), user)`). | ||
|
||
- Use the translation functions only with string literals without any operators, | ||
texts like ~~`_("foo" + "bar")`~~ will not be extracted correctly to the POT | ||
file. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree this is wrong, but...