Roman Anasal
10 years ago
1 changed files with 233 additions and 0 deletions
@ -0,0 +1,233 @@ |
|||||||
|
# Localization guide for Webogram |
||||||
|
|
||||||
|
## Adding/updating a new locale/language |
||||||
|
|
||||||
|
Adding a new locale is pretty easy, all you got to do is: |
||||||
|
|
||||||
|
1. ensure that the angular-locale file `vendor/angular/i18n/angular-locale_<locale>.js` exists. If not copy one of the others being most similar to your target locale and adapt it accordingly. See also the [angular docs](https://docs.angularjs.org/guide/i18n). |
||||||
|
2. copy `js/locales/en-us.json` to `js/locales/<locale>.json` |
||||||
|
3. without changing the key strings translate and change the value strings into your target locale |
||||||
|
4. add your locale in js/i18n.js to the object of supported languages with locale and its native name so it will be listed in the settings |
||||||
|
5. enjoy your awesome new webogram in your own language! |
||||||
|
|
||||||
|
You may also want to join the project on [transifex](https://www.transifex.com/projects/p/telegram-web/). |
||||||
|
|
||||||
|
### Details |
||||||
|
#### The locale string |
||||||
|
The locale string which is also part of the filenames consists of the lower case two character language code and the two character country code separated by a hyphen with the country code being optional, e.g: |
||||||
|
* en-us |
||||||
|
* en-au |
||||||
|
* de-de |
||||||
|
|
||||||
|
#### Step 1: the angular locale |
||||||
|
The files in `vendor/angular/i18n/` contain the `ngLocale` module in its various localizations which provides the `$locale` service which in turn is used by many angular functions especially for formatting times and dates and a little pluralization. |
||||||
|
There should already be a file for almost every language so you most probably won't have to create your own. |
||||||
|
For more see the [angular docs](https://docs.angularjs.org/guide/i18n). |
||||||
|
|
||||||
|
#### Step 2: copy the template |
||||||
|
`js/locales/en-us.json` should provide you with all the localization strings currently in use in webogram and therefore be a complete template to begin with. |
||||||
|
It's basically just a json-encoded javascript object with the keys being the message ids and the values being the corresponding translations. |
||||||
|
|
||||||
|
#### Step 3: adjusting the translations |
||||||
|
The values may contain very simple markdown syntax, numbered and named parameters depending on how and where the messages are used (see also below). |
||||||
|
Parameters are enclosed in curly braces and can be numbered (starting with 0) and/or named, but the order in the strings doesn't matter. |
||||||
|
Here a few examples: |
||||||
|
> "user_status_last_seen": "last seen {0}", |
||||||
|
> "settings_modal_recent_updates": "Recent updates (ver. {version})" |
||||||
|
|
||||||
|
The parameters can be parameterized too in which case the parameter name/number is followed by a colon and its parameters which are delimited by a pipe (`|`). |
||||||
|
|
||||||
|
Example: |
||||||
|
|
||||||
|
> Please, install {chrome-link: http://google.com/chrome | Google Chrome} or use {telegram-link: https://telegram.org/ | mobile app} instead. |
||||||
|
|
||||||
|
which would end up as |
||||||
|
|
||||||
|
> Please, install <a href="http://google.com/chrome" target="_blank">Google Chrome</a> or use <a href="https://telegram.org/" target="_blank">mobile app</a> instead. |
||||||
|
|
||||||
|
You could alter the url in this example to point to the language specific version of the original url. |
||||||
|
|
||||||
|
Then there are some strings like this: |
||||||
|
> "contacts_modal_pluralize_new_group_members": "{'one': '1 participant', 'other': '{} participants'}", |
||||||
|
|
||||||
|
which are used in the `when` attributes of elements compiled by the [ngPluralize](https://docs.angularjs.org/api/ng/directive/ngPluralize) module and allow for different strings depending on the (number) value of the bound parameter and there must be valid json syntax. |
||||||
|
Here again see the [angular docs](https://docs.angularjs.org/guide/i18n). |
||||||
|
Such strings will have the word _pluralize_ in their key. |
||||||
|
|
||||||
|
And finally: strings which keys have a `_md` suffix get parsed by a simple markdown parser that places everything between `**`s in `<strong/>` tags and replaces newlines with `<br/>` e.g.: |
||||||
|
> "welcome_text_1_md": "This is an unofficial web-client for the **Telegram Messenger**.", |
||||||
|
|
||||||
|
becomes |
||||||
|
```html |
||||||
|
This is an unofficial web-client for the <strong>Telegram Messenger</strong>. |
||||||
|
``` |
||||||
|
|
||||||
|
Including html markup in the messages directly isn't supported and any contained markup will be escaped before inserting. |
||||||
|
|
||||||
|
#### Step 4: adding the newly created locale |
||||||
|
The final step is to add the new locale to the list with supported (i.e. existing) locales for webogram in the factory of the localization function `_` in `js/i18n.js`. |
||||||
|
The key for this is the locale string while the value is the name of the language to be displayed in the settings. |
||||||
|
After adding to the list (and perhaps restarting the app) it appears in the select input and can be used. |
||||||
|
|
||||||
|
Additionally you can add your locale to the `aliases` object. This object is used when there is no locale configured yet and we're trying to guess the best fit from the browsers current language. |
||||||
|
Since we're retrieving the browser language through `navigator.language` which may contain a locale with or without country code, we use `aliases` here to map between these and our supported locales whereby the keys are the lookup values and the values a locale we support, e.g: |
||||||
|
```javascript |
||||||
|
var aliases = { |
||||||
|
'en': 'en-us' |
||||||
|
}; |
||||||
|
``` |
||||||
|
This maps a `navigator.language == 'en'` to `en-us` as locale to use. |
||||||
|
|
||||||
|
|
||||||
|
## Using the i18n module while developing |
||||||
|
|
||||||
|
All the i18n functionality is located in the `myApp.i18n` module in `js/i18n.js` and exposed via various ways whereby all the localization is done by the `_` service. |
||||||
|
It takes a message key and optional parameters for that key and returns the localized string with parameters incorporated. |
||||||
|
|
||||||
|
When adding new messages the key for that messages should be prefixed with the view name it appears in. If the message (should) contain(s) markdown-like markup (see above) it should additionally have a `_md` suffix. |
||||||
|
|
||||||
|
### The service: call the localization function (`_`) directly |
||||||
|
The Service `_` can be injected into other modules and offers some ways to interact: |
||||||
|
|
||||||
|
```javascript |
||||||
|
_('welcome_text_1_md'); // get a simple string |
||||||
|
_('user_status_last_seen', '1 minute ago', ...); // with numbered parameters or |
||||||
|
_('format_size_progress', {done: '10kB', total: '2048kB'}); // with named parameters |
||||||
|
_.locale(); // retrieve the currently active locale, e.g. "en-us" |
||||||
|
_.locale("en-us"); // set and load a new locale |
||||||
|
_.supported(); // get the list of all supported locales |
||||||
|
``` |
||||||
|
|
||||||
|
Note that `_` escapes any html entities appearing in the messages strings. If you want to retrieve an unmasked message - e.g. because it will be inserted in a way that escapes html again like `text()` - you can add a `_raw` suffix to the message key: |
||||||
|
```javascript |
||||||
|
_('welcome_text_1_md_raw'); // get a simple unmasked string |
||||||
|
``` |
||||||
|
|
||||||
|
### The i18n filter |
||||||
|
The i18n filter is basically just an alias/other way to call `_` and can be used in attributes and text nodes: |
||||||
|
|
||||||
|
```html |
||||||
|
<input placeholder="{{'modal_serach' | i18n}}" /> |
||||||
|
<span>{{"user_status_last_seen" | i18n:relativeTime}}</span> |
||||||
|
<div>{{"format_size_progress" | i18n:{done: bytesLoaded, total: totalSize} }}</div> |
||||||
|
``` |
||||||
|
|
||||||
|
### Together with `ngPluralize` |
||||||
|
`myApp.i18n` automatically intercepts the compilation of the [ngPluralize](https://docs.angularjs.org/api/ng/directive/ngPluralize) directive and pipes the value of the `when` attribute through `_` before `ngPluralize` evaluates it. |
||||||
|
Therefore you have to replace the content of the `when` attribute with the key of the corresponding message for `ngPluralize`. The key should contain the word `pluralize` to indicate its usage. |
||||||
|
|
||||||
|
```html |
||||||
|
<ng-pluralize count="selectedCount" when="contacts_modal_pluralize_new_group_members"> |
||||||
|
</ng-pluralize> |
||||||
|
``` |
||||||
|
may evaluate to |
||||||
|
```html |
||||||
|
<ng-pluralize count="selectedCount" when="{'one': '1 participant', 'other': '{} participants'}"> |
||||||
|
5 participants |
||||||
|
</ng-pluralize> |
||||||
|
``` |
||||||
|
|
||||||
|
### The my-i18n directive |
||||||
|
The `my-i18n` directive can be used as attribute or as element and replaces the contents of the element with the localized message. |
||||||
|
|
||||||
|
Simple usage: |
||||||
|
```html |
||||||
|
<div my-i18n="modal_done"></div> |
||||||
|
<my-i18n msgid="modal_done"></my-i18n> |
||||||
|
become |
||||||
|
<div my-i18n="modal_done">Done</div> |
||||||
|
<my-i18n msgid="modal_done">Done</my-i18n> |
||||||
|
``` |
||||||
|
|
||||||
|
For messages which take parameters these are provided via (direct) child elements with a `my-i18n-param` directive. The params can be named or numbered if no name is provided. |
||||||
|
|
||||||
|
Simple formats: |
||||||
|
> "settings_modal_recent_updates": "Recent updates (ver. {version})", |
||||||
|
> "user_status_last_seen": "last seen {0}", |
||||||
|
|
||||||
|
```html |
||||||
|
<a ng-click="openChangelog()" my-i18n="settings_modal_recent_updates"> |
||||||
|
<my-i18n-param name="version" ng-bind="version"></my-i18n-param> |
||||||
|
</a> |
||||||
|
|
||||||
|
<my-i18n msgid="settings_modal_recent_updates"> |
||||||
|
<span my-i18n-param="version" ng-bind="version"></span> |
||||||
|
</my-i18n> |
||||||
|
|
||||||
|
<my-i18n msgid="user_status_last_seen"> |
||||||
|
<my-i18n-param>1 minute ago</my-i18n-param> |
||||||
|
</my-i18n> |
||||||
|
|
||||||
|
evaluate to |
||||||
|
|
||||||
|
<a ng-click="openChangelog()" my-i18n="settings_modal_recent_updates"> |
||||||
|
Recent updates (ver. <my-i18n-param name="version" ng-bind="version"></my-i18n-param>) |
||||||
|
</a> |
||||||
|
|
||||||
|
<my-i18n msgid="settings_modal_recent_updates"> |
||||||
|
Recent updates (ver. <span my-i18n-param="version" ng-bind="version"></span>) |
||||||
|
</my-i18n> |
||||||
|
|
||||||
|
<my-i18n msgid="user_status_last_seen"> |
||||||
|
last seen <my-i18n-param>1 minute ago</my-i18n-param> |
||||||
|
</my-i18n> |
||||||
|
``` |
||||||
|
|
||||||
|
As you see any other directives applied to the elements are preserved. |
||||||
|
|
||||||
|
Finally, if there is no message key passed to the `my-i18n` directive it looks for (direct) child elements with a `my-i18n-format` directive and takes the msgid of that instead, e.g.: |
||||||
|
|
||||||
|
> "im_one_typing": "{name1} is typing{dots}", |
||||||
|
|
||||||
|
```html |
||||||
|
<div class="im_history_typing" my-i18n> |
||||||
|
<span ng-switch-when="1" my-i18n-format="im_one_typing"></span> |
||||||
|
<my-i18n-param name="name1"><a class="im_history_typing_author" my-user-link="historyState.typing[0]"></a></my-i18n-param> |
||||||
|
<my-i18n-param name="dots"><span my-loading-dots></span></my-i18n-param> |
||||||
|
</div> |
||||||
|
``` |
||||||
|
|
||||||
|
This way it is even possible to group together several formats which take (mostly) the same parameters: |
||||||
|
|
||||||
|
> "im_one_typing": "{name1} is typing{dots}", |
||||||
|
> "im_two_typing": "{name1} and {name2} are typing{dots}", |
||||||
|
> "im_many_typing": "{name1}, {name2} and {count} more are typing{dots}", |
||||||
|
|
||||||
|
```html |
||||||
|
<div class="im_history_typing" my-i18n> |
||||||
|
<span ng-switch-when="1" my-i18n-format="im_one_typing"></span> |
||||||
|
<span ng-switch-when="2" my-i18n-format="im_two_typing"></span> |
||||||
|
<span ng-switch-default my-i18n-format="im_many_typing"></span> |
||||||
|
<my-i18n-param name="name1"><a class="im_history_typing_author" my-user-link="historyState.typing[0]"></a></my-i18n-param> |
||||||
|
<my-i18n-param name="name2"><a class="im_history_typing_author" my-user-link="historyState.typing[1]"></a></my-i18n-param> |
||||||
|
<my-i18n-param name="count">{{historyState.typing.length - 2}}</my-i18n-param> |
||||||
|
<my-i18n-param name="dots"><span my-loading-dots></span></my-i18n-param> |
||||||
|
</div> |
||||||
|
|
||||||
|
will evaluate to (whitespace added for readability): |
||||||
|
|
||||||
|
<div class="im_history_typing" my-i18n> |
||||||
|
<span ng-switch-when="1" my-i18n-format="im_one_typing"> |
||||||
|
<my-i18n-param name="name1"><a class="im_history_typing_author" my-user-link="historyState.typing[0]"></a></my-i18n-param> |
||||||
|
is typing |
||||||
|
<my-i18n-param name="dots"><span my-loading-dots></span></my-i18n-param> |
||||||
|
</span> |
||||||
|
<span ng-switch-when="2" my-i18n-format="im_two_typing"> |
||||||
|
<my-i18n-param name="name1"><a class="im_history_typing_author" my-user-link="historyState.typing[0]"></a></my-i18n-param> |
||||||
|
and |
||||||
|
<my-i18n-param name="name2"><a class="im_history_typing_author" my-user-link="historyState.typing[1]"></a></my-i18n-param> |
||||||
|
are typing |
||||||
|
<my-i18n-param name="dots"><span my-loading-dots></span></my-i18n-param> |
||||||
|
</span> |
||||||
|
<span ng-switch-default my-i18n-format="im_many_typing"> |
||||||
|
<my-i18n-param name="name1"><a class="im_history_typing_author" my-user-link="historyState.typing[0]"></a></my-i18n-param>, |
||||||
|
<my-i18n-param name="name2"><a class="im_history_typing_author" my-user-link="historyState.typing[1]"></a></my-i18n-param> |
||||||
|
and |
||||||
|
<my-i18n-param name="count">{{historyState.typing.length - 2}}</my-i18n-param> |
||||||
|
more are typing |
||||||
|
<my-i18n-param name="dots"><span my-loading-dots></span></my-i18n-param> |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
``` |
||||||
|
|
||||||
|
Also note the `ng-switch` directives on the `my-i18n-format` elements here so only one of the `<span>`s is visible at a time. |
Loading…
Reference in new issue