NGO-DB/ndb-core

View on GitHub
doc/compodoc_sources/how-to-guides/build-localizable-components.md

Summary

Maintainability
Test Coverage
# How to build components that can be localized

## Short Answer

1. In your html-components, mark any text that should be translated
   using the `i18n` directive:

   ```html
   <button i18n-title="<meaning>|<description>" title="I am a button" i18n="<meaning>|<description>">Click me!</button>
   ```

2. In your other components, mark strings that may be translated
   using the `localize` function:
   ```typescript
   const notification = $localize`I have eaten all ${number} Muffins`;
   ```

## Elaborate Answer

### The i18n-directive

The `i18n` directive allows up to three optional arguments. The first two -
meaning and description - are used to aid translators while analyzing the
texts. The third one is an optional ID. This ID is generated by angular and
does not have to be provided.

If different components have the same content and the same meaning, they will
be merged automatically, it is therefore not required (and will, in fact,
generate a warning) to assign a custom id to components that should be
translated in the same way. To enable this behavior, the same content and
meaning should be provided.

The meaning of a text message is important so that translators can understand
what the text should represent, even without having to see the actual website.
Take this example:

```html
<span i18n>Address of {{user}}: {{user.address}}</span>
```

Without further knowledge, what does address refer to? The physical address
of the user, or his internet-address?
It is therefore always good practice including a meaning and further description
like so:

```html
<span i18n="Address field|The address that a user lives at"> Address of {{user}} </span>
```

The `i18n` directive can also be used to translate tooltips, titles and other
attributes of the element. This is done by writing `i18n-title`,
`i18n-matTooltip`, e.t.c. The rules are the same as for the regular directive.

### The localize-function

While html-elements can be marked for translation using the `i18n` directive,
the same is not true for strings in non-html components. To enable localization
here, the function `$localize()` is used. It is comparable to the directive,
however some small changes have to be taken into account:

1. Meaning and description of the string to translate are in front of the actual
   string and are marked using two colons (`:`).
2. The translate-function is special in that it conceptually does not have
   parameters. Instead, the string is directly written after the identifier

Example:

```typescript
alertService.warn($localize`:Illegal input warning:Please provide the correct input for ${field}`);
```

Variables inside the string can be of arbitrary complexity and are escaped using the
`${}` syntax.

### Generate translation files

_This is just a quick overiew.For more information, refer to the
[How to add another language](add-another-language.md) guide
and the [How to edit, update and work with XLF files](work-with-xlf.md) guide_.

Once content with the process of marking fields as translatable and providing
good and understandable meaning to these fields, translation-files can be
generated. This is done using the `extract-i18n`-script (Can be found in the
file [package.json](package.json)). This script will generate one
"Base file" and will also generate and update existing translation files
for all locales given in the script. These will be located in the
[locale](../../../src/locale) directory.
XLF files are standardized and can be read using standard tools such as
the [Online XLIFF Editor](https://xliff.brightec.co.uk)

The translation files are in xml-format. After some preambles, there are
characteristic blocks that follow all the same schema; blocks like this:

```xml
<trans-unit id="947fb7a14ab3d88dbe2ab508622f33bfcae44ad5" datatype="html">
  <source> Change Password </source>
  <context-group purpose="location">
    <context context-type="sourcefile">src/app/core/user/user-account/user-account.component.html</context>
    <context context-type="linenumber">126,127</context>
  </context-group>
  <note priority="1" from="description">Change password button</note>
</trans-unit>
```

The `<source>` section is this the original string that we defined in code or html.
If you are not in the `messages.xlf` file but in any other `messages.<locale>.xlf` file,
you can provide translations. for the locale. For a list of common and standardized locales,
see the [ISO-639-2](https://www.loc.gov/standards/iso639-2/) standard.

To provide a translation, add the `<target>` tag inside the `<trans-unit>`
tag (assuming we are inside `messages.de.xlf` and therefore looking for a
german translation):

```xml
<trans-unit id="947fb7a14ab3d88dbe2ab508622f33bfcae44ad5" datatype="html">
  <source> Change Password </source>
  <target> Passwort ändern </target>
  <context-group purpose="location">
    <context context-type="sourcefile">src/app/core/user/user-account/user-account.component.html</context>
    <context context-type="linenumber">126,127</context>
  </context-group>
  <note priority="1" from="description">Change password button</note>
</trans-unit>
```

If all source tags are processed, the translation-process is done!

### The building process

To build the app for a specific locale, enter that locale inside
[angular.json](../../../angular.json) at

```json
{
  ...
  "options": {
    "localize": ["<your locale>"]
  }
}
```

For the German version for example, just add

```json
{
  "localize": ["de"]
}
```

at the end of "projects" --> "ndb-core" --> "architect" --> "build" before the "configuration"-section starts.

Since the build process is fairly complex, only one locale is allowed during
development. The locale must be inside the locale-folder and be of type
`messages.<your locale>.xlf`

Angular will not translate the components on the fly. Instead, it will
generate a separate app for each and every component provided in the
`localize` array.

### Other types of localization

Texts are not the only thing characterizing an international app. Other data
has a different expression in different cultures. To enable this, several pipes
can be used to transform that data automatically. For example:

```angular2html
<span>It's a nice day {{today | date}}</span>
```

Will transform the date to different locales.
Pre-built pipes are

- `DatePipe`: Formats a date
- `CurrencyPipe`: Transforms a number to a currency string
- `DecimalPipe`: Transforms a number into a decimal string with given accuracy
- `PercentPipe`: Transforms a number to a percent-string

### ICU-Components

ICU is another standard that Angular uses to localize strings. There
are two ICU-standards of interest: Pluralization and Selection

#### Pluralization

Some phrases contain plurals, such as

```angular2html
<span>The game starts in {{minutesTillStart}} minutes</span>
```

This can produce the incorrect result of "The game starts in 1 minutes". To
improve this, one should use plural forms. These have the following syntax:

```angular2html
{ amount, plural, =0 {none} =1 {one} other {other cases} }
```

**amount** is the selector - how much actually exists. Following this,
the **plural** selector follows, indicating that this is a pluralized form.
Finally, the different values that one can pluralize are described. Legal
values are

- `=x` matches, when the amount is exactly that value
- `one`
- `two`
- `few`
- `many`
- `others`

`others` matches all amounts that cannot be matched by anything else. So the
text above could be pluralized like this:

```angular2html
<span>The game starts { minutesTillStart, plural,
  =0 {right now}
  one {in one minute}
  others {in {{minutesTillStart}} minutes} }
</span>
```

#### Selection

Selection selects one option of a set of selections. A suitable example
for this is the gender of a person:

```html
This is {{person}}. {gender, select, male {He} female {She}} is a nice person.
```

Can be translated to the variants "This is Tom. He is a nice person" as well as
"This is Nancy. She is a nice person". The syntax is:

```html
{variable, select optionA {value_optionA} optionB {value_optionB} ...}
```

The variable's type is arbitrary, however the options have to make sense.
Types that are known to work well are the standard types of strings, numbers
and booleans.

### Do's and dont's

- Try to keep your translatable texts as simple as possible concerning the
  amount of string-interpolation or computation inside the `{{}}`-Blocks. This
  makes it easier for translators
- It may be tempting sometimes to do a 'string-in string' interpolation like so:

  ```angular2html
  <div>
  My cat likes milk {{catLikesCheese ? "and cheese" : ""}}
  </div>
  ```

  This **must** not be done. It is hard for translators to understand what's
  going on inside the interpolation, and the translated text might not
  be accepted by Angular's localize-package. Instead, you could use a
  custom getter:

  ```typescript
  get catYumYum(): string {
    return $localize`My cat likes milk${andMore}`;
  }
  get andMore(): string {
    return catLikesCheese ? " " + $localize`and cheese` : "";
  }
  ```

  ```angular2html
  <div>{{catYumYum}}</div>
  ```

  where both parts can be localized individually. It is important
  to provide a good description here.

  Another possibility is to use the ICU selection syntax:

  ```angular2html
  <div>
  My cat likes milk { catLikesCheese, select, true {And Cheese} false {} }
  </div>
  ```

- Using the `$localize` function, the placeholders can accept id's that
  might simplify the translation process:

  ```typescript
  $localize`Hello, ${v123}:HELLO_VAR:`;
  ```

  In this example, `HELLO_VAR` is the id for the variable-substitution
  `v123`. A descriptive id might aid in the translation process.

  This is not possible using the `i18n` tag. In this case, the id
  will implicitly be `INTERPOLATION` and cannot be changed