From e5c025aa355e0e75de1100af8b902611b5c75918 Mon Sep 17 00:00:00 2001 From: Andy Stark <30621568+therealandeeee@users.noreply.github.com> Date: Fri, 2 Feb 2018 10:27:08 +0000 Subject: [PATCH] [ADF-2228] Added i18n guide (#2903) --- docs/README.md | 1 + docs/docassets/images/TransExDe.png | Bin 0 -> 3394 bytes docs/docassets/images/TransExEn.png | Bin 0 -> 2956 bytes docs/internationalization.md | 217 ++++++++++++++++++++++++++++ docs/language-menu.component.md | 4 + docs/translation.service.md | 122 +++++++++++++--- 6 files changed, 326 insertions(+), 18 deletions(-) create mode 100644 docs/docassets/images/TransExDe.png create mode 100644 docs/docassets/images/TransExEn.png create mode 100644 docs/internationalization.md diff --git a/docs/README.md b/docs/README.md index 0180a5a869..49074c3054 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,6 +22,7 @@ may be listed here before their documentation is available. - [Angular Material Design](angular-material-design.md) - [Theming](theming.md) - [Typography](typography.md) +- [Internationalization](internationalization.md) - [Walkthrough - adding indicators to highlight information about a node](metadata-indicators.md) diff --git a/docs/docassets/images/TransExDe.png b/docs/docassets/images/TransExDe.png new file mode 100644 index 0000000000000000000000000000000000000000..19b51f74260694e000c3baa9ac4c27a2c0fdfccb GIT binary patch literal 3394 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 Y9{|-sy8&KAp8x;=07*qoM6N<$f&}DtBLDyZ literal 0 HcmV?d00001 diff --git a/docs/docassets/images/TransExEn.png b/docs/docassets/images/TransExEn.png new file mode 100644 index 0000000000000000000000000000000000000000..ce1baec2dcab5c26401e825aefd51637c3810e98 GIT binary patch literal 2956 zcmV;73v={|P)D00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY4#NNd4#NS*Z>VGd000McNliru;sOB>A}HGhX@&p*3mHj7 zK~#9!?VWv0)b}07-(Lke%DA>TP(kn!v;(#C5vys$%{kX*6W!Wu#u!K3R@1fMbXH7k zY^U3#jo2;J`H*cw8+E=o1C2Xvp)bmfBqif9)3sO zujInr_kO?o9o+lgyuZ)t`kRo)9|0jwXa{f;Ku6EdcAeWz&HWCY^MX!H?;WN1DG}QU z02T041(XLquRTXX{l*6Ivmt#zX~Zi5<i5{)Ht+RZdMdD87S^;G;V_#nnZ2qZBj=hKQYZeKT?p*H#A2dI>o=i3}*0=2#9gU@C`*l)x!y5{&5KM7*pN z64$!2au7qpGWsrNK>n$x9|5uwfan|Q_<*9%e1Pv`pm7UuuFm|}UfSLU z4wq2Y#ty6%ymqxE3fdksfV44y{|=C|1gNRdKkWQi;B3$RM_Uz zC05ZQaCtDJ_Gymgu+5kkfw&KW(^*x22?v??q1wiI%;BuM0ml>jypD!&2 z8Z~7dj2tFn6_fK61#O4yWk6gEko9LEOY_`k-vAQIfdARZ;E$&Pw+?BN$lrnM%o#SF z*KA&4$Ok745J9H`qbC6^+ki74Xr4;~X72)?oB`CHG=G_lw}7O(z}dGcRr_&6+>o6I zmY|c@1CL$eq*dk{xaPy!} zuV8Nl?f@|HCqT9bIImOD>+LDLutL*QNNi+bWe`C`$H_xpIaIy7U~$tR;sEUqSb_KT zcEvK$gXglJ{N}*YET||}vqo7H!a5oIhLcC#zf+oSp&RVw)`9OXOnuOAw#h`+yZQWqU3UPIQwk!0o#Ssdc1XI{n zEf^v10;ix!D58f}+(wZk=-gWhOoX2f>61fSEi4mwT*y@x0{}N}@slBromosWvRGv3 z&T2JBE%m0gQv!21O zhMps9HXv=~5yM>{oKb@fX)AHW^2G{qR0N{AZO@~kTqJBWQR_Vp>6nff_>ZH!WCTht zu0?KoPibO0rsQwMu{qb!k-|@nSVj-4_U5p)M-M_<8?G2U8Jm)VBmgx<3Tl2SP`e7_ zm4Z@5=>D(oM^kk%=B0R`lm{vEwxaIz9?ZS>d<)9+6^g3sQNA(HAP>K1xxG zRpa{?A^c6%C|)=c-kz8=uecgb{@!PGu3g@Omhyb4qH5HauSAYlK`AeCUams3zX{dF z^N`|EP~IG@EU$;3g0$U9)sbm$cVc$P2mYl8iz*dhawydirzn)4FEqluqgF6cITpg_t9|8^xH$ z-7+WObspm8`?4-QPV!3Xq`6b1tG!+-3eqKc^?P#N?Ak{Q1&PV>^`7%On-9wB{Yxx_^(B_X;IFu0Rgm6k_n#7m+3rIWHo0B63(n-W8EOBJ!q)yec9^B2pkC-x869 zA~IV|4?G?xU$;`sf2dN}?fxxQ+fkCU7yH)c zBGm)LCT1dk+YZbx+l-1!7T4}thQgP-&-0`{i*GKwjZ05$#OAMd{~jp|zKsI!e$1}qPMYV!&}962dhf3UtIso3Mx8RTEIe6zQBM`3=F>cibtl0G$vMet<8cR#dF!c4Qh*yc& z*q80Ky-#CH?Lp*LK8sQD3PY0TU`NVcoGM*_B#JHE&34KnFNejQ0YHgxt_pFC)gInP z<=VZ#L)6MaNdL36)}1-PpG38gt+KV=!ZZ{#ab2a*sOwwx;mbfllZd*r+4yEg4iZ)F zthc3`-Z~t19GikBu8ACCBQpn*vLBhhjfED{w$cDj=+cflf28AT#Er;rfm zqBSZv+rN;T29c=9&3aX5rJ1xTRF&p5w_Kq$m}U3 zu8kX7feOqAEzzNgt6kad{wj { + this.translatedText = translation; + }); + } +} +``` + +...with very simple corresponding HTML: + +```html +{{translatedText}} +``` + +In the browser, this is displayed as: + +![English translation text](docassets/images/TransExEn.png) + +English is used by default but you can easily change the language with the +`use` method: + +```ts +ngOnInit() { + this.trans.use("de"); + + this.trans.get("FORM.START_FORM.TITLE").subscribe(translation => { + this.translatedText = translation; + }); + } +``` + +The user will now see: + +![German translation text](docassets/images/TransExDe.png) + +Note that an unrecognized key will be returned unchanged as the "translation". +If you see strings like "FORM.START_FORM.TITLE" displayed in your app then you +should check you are using the key correctly. + +## Using the translate pipe + +Using `TranslationService.get` is straightforward but it is often more +convenient to add translation keys directly into your page's HTML. +Use the `translate` pipe to convert a key in the page directly to the +corresponding text. For example, the following will display the +"Start Form" text as above but without any code or variables in the +component's `.ts` file: + + {{ "FORM.START_FORM.TITLE" | translate }} + +## Adding your own messages + +The built-in translations certainly won't cover everything you will need for +your app but you can easily replace them with your own lists. This involves +making copies of the existing lists in your app's folder and adding your +own keys. See the [Translation service](translation.service.md) page for +full details and examples. + +## Interpolations + +Translation messages have support for _interpolation_ (ie, including another +string at a specified position within a message). This is very useful for +messages whose content can change at runtime. For example, in the built-in +`en.json` there is the `CORE.PAGINATION.ITEMS_RANGE` key: + +```json + ... +"CORE": { + ... + "PAGINATION": { + "ITEMS_RANGE": "Showing {{ range }} of {{ total }}", + "ITEMS_PER_PAGE": "Items per page", + ... + }, + ... +``` + +The sections in curly braces are _interpolation variables_ that you supply +at runtime. You can specify them by passing an extra parameter to +`TranslationService.get`; this is an object whose properties have the same +names as the interpolation variables in the string: + +```ts +this.trans.get( + "CORE.PAGINATION.ITEMS_RANGE", + { + range: "1..10", + total: "122" + } + ).subscribe(translation => { + this.translatedText = translation; + }); +``` + +You can use interpolations with the `translate` pipe in a similar way: + + {{ "CORE.PAGINATION.ITEMS_RANGE" | translate: { range: "1..10", total: "122"} }} + +## Selecting the display language + +ADF provides a [Language Menu component](language-menu.component.md) that +you can add to a page to let the user choose their preferred language. The +available languages are defined in the `app.config.json` file for the app. + +Note that when the user selects an item from the menu, it simply changes the "locale" +preference (which you can get via the [User Preferences service](user-preferences.service.md)). +The `translate` pipe reacts automatically to this and changes the page text +immediately to the new language. However, text added via a variable set using +`TranslationService.get`, as in the example above, will not be updated like this; +you will need to get a new translation and set the variable's value again explicitly +from the code. + +See the [Language Menu component](language-menu.component.md) page for further +details and usage examples. + +## Support for i18n within ADF components + +Some components allow you to use translation keys in places where you would normally +supply your own messages directly. For example, the +[Data Column component](data-column.component.md) can accept a key instead of +normal text to specify the column title. Consult the documentation for a +component to see if it has built-in support for i18n. + +## See also + +- [Translation service](translation.service.md) +- [Language Menu component](language-menu.component.md) diff --git a/docs/language-menu.component.md b/docs/language-menu.component.md index 0692d71fec..252184e003 100644 --- a/docs/language-menu.component.md +++ b/docs/language-menu.component.md @@ -65,3 +65,7 @@ How to attach an ADF Language Menu as nested menu ### Nested menu details In the previous example we are using the ADF Language Menu as nested menu. + +## See Also + +- [Internationalization](internationalization.md) diff --git a/docs/translation.service.md b/docs/translation.service.md index b6ad36ba3a..56675f5a66 100644 --- a/docs/translation.service.md +++ b/docs/translation.service.md @@ -2,35 +2,117 @@ Supports localisation. +## Methods + +`addTranslationFolder(name: string = '', path: string = '')`
+Adds a new folder of translation source files. + +`use(lang: string): Observable`
+Sets the target language for translations. + +`get(key: string|Array, interpolateParams?: Object): Observable`
+Gets the translation for the supplied key. + +`instant(key: string | Array, interpolateParams?: Object): string | any`
+Directly returns the translation for the supplied key. + ## Details +In the `get` and `instant` methods, the `interpolateParams` parameter supplies +interpolation strings for keys that include them. For example, in the standard +`en.json`, the `CORE.PAGINATION.ITEMS_RANGE` key is defined as: + + "Showing {{ range }} of {{ total }}" + +The `range` and `total` interpolations are supplied to the `get` method using +an object with fields of the same name: + +```ts +this.trans.get( + "CORE.PAGINATION.ITEMS_RANGE", + { + range: "1..10", + total: "122" + } + ).subscribe(translation => { + this.translatedText = translation; + }); +``` + ### Registering translation sources -In order to enable localisation support you will need to create a `/resources/i18n/en.json` file -and register its parent `i18n` folder with your component or application module. +To supply your own set of translation source files, you +first need to create a subfolder for them within your application's +`assets` folder. The folder can have any name you like but it must also have +a sub-folder called `i18n` where the translation lists will be stored. So, the +general format of the path to this folder will be: -For example: +`/src/assets/my-translations/i18n` + +If you wanted English and French translations then you would copy the built-in +`en.json` and `fr.json` files into the `i18n` folder and add your new keys: + + // en.json + + ... + "WELCOME_MESSAGE": "Welcome!" + ... + + // fr.json + ... + "WELCOME_MESSAGE": "Bienvenue!" + ... + +To enable the new translations in your app, you also need to register them in your +`app.module.ts` file. Import `TRANSLATION_PROVIDER` and add the path of your +translations folder to the `providers`: ```ts -import { TRANSLATION_PROVIDER } from '@alfresco/adf-core'; +// Other imports... + +import { TRANSLATION_PROVIDER } from "@alfresco/adf-core"; + + ... @NgModule({ + imports: [ ... - providers: [ - ... - { - provide: TRANSLATION_PROVIDER, - multi: true, - useValue: { - name: 'ng2-alfresco-core', - source: 'assets/ng2-alfresco-core' - } - } - ] -}) + ], + declarations: [ + ... + ], + providers: [ + { + provide: TRANSLATION_PROVIDER, + multi: true, + useValue: { + name: 'my-translations', + source: 'assets/my-translations' + } + } + ... ``` -Note: the `source` property points to the web application root, please ensure you have webpack settings to copy all the i18n files at compile time. +You can now use your new keys in your component: + +```ts + ... +ngOnInit() { + this.trans.use("fr"); + + this.trans.get("WELCOME_MESSAGE").subscribe(translation => { + this.translatedText = translation; + }); + } + ... +``` + +The new translation files completely replace the built-in ones. +If you want to continue using the built-in keys then you must add your new +keys to copies of the existing files. + +Note: the `source` property points to the web application root. Ensure you have +webpack correctly set up to copy all the i18n files at compile time. ```text index.html @@ -38,7 +120,7 @@ assets/ng2-alfresco-core/i18n/en.json ... ``` -You can register as many entries as you would like. +You can register as many entries as you like. ### Switching languages @@ -56,3 +138,7 @@ class MyComponent { } } ``` + +## See Also + +- [Internationalization](internationalization.md)