Skip to content

Commit

Permalink
Merge pull request #563 from aurelia/i18n-service
Browse files Browse the repository at this point in the history
feat(i18n): core translation service and related auxiliary features
  • Loading branch information
fkleuver committed Aug 21, 2019
2 parents 73ca7b0 + b797d3f commit c3d4a85
Show file tree
Hide file tree
Showing 47 changed files with 3,403 additions and 263 deletions.
4 changes: 3 additions & 1 deletion packages/__tests__/e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
"@aurelia/kernel": "^0.3.0",
"@aurelia/runtime": "^0.3.0",
"@aurelia/runtime-html": "^0.3.0",
"@aurelia/runtime-html-browser": "^0.3.0"
"@aurelia/runtime-html-browser": "^0.3.0",
"i18next-intervalplural-postprocessor": "^1.0.0",
"relative-time-format": "^1.0.0"
},
"devDependencies": {
"@cypress/webpack-preprocessor": "^4.1.0",
Expand Down
77 changes: 1 addition & 76 deletions packages/__tests__/e2e/src/app.html
Original file line number Diff line number Diff line change
@@ -1,80 +1,5 @@
<template>
<div>${message}</div>

<!-- <button id="locale-changer-de" click.delegate="changeLocale('de')">DE</button>
<button id="rt-changer" click.delegate="changeMyDate()">Change my date</button> -->

<span id="i18n-simple" t="simple.text"></span>
<!-- <span id="i18n-attr" t="[title]simple.attr">attribute test</span>
<span id="i18n-multiple-attr" t="simple.text;[title]simple.attr"></span>
<span id="i18n-multiple-attr-same-key" t="simple.text;[title,data-foo]simple.attr"></span>
<span id="i18n-nested" t="$t(simple.text) $t(simple.attr)"></span>
<span id="i18n-ctx" t="status"></span>
<span id="i18n-ctx-dispatched" t="status" t-params.bind="{context: 'dispatched', date: dispatchedOn}"></span>
<span id="i18n-ctx-delivered" t="status" t-params.bind="{context: 'delivered', date: deliveredOn}"></span>
<span id="i18n-interpolation" t="status_delivered" t-params.bind="{date: deliveredOn}"></span>
<span id="i18n-interpolation-custom" t="custom_interpolation_brace"
t-params.bind="{date: deliveredOn, interpolation: { prefix: '{', suffix: '}' }}"></span>
<span id="i18n-interpolation-es6" t="custom_interpolation_es6_syntax"
t-params.bind="{date: deliveredOn, interpolation: { prefix: '${', suffix: '}' }}"></span>
<span id="i18n-items-plural-0" t="itemWithCount" t-params.bind="{count: 0}"></span>
<span id="i18n-items-plural-1" t="itemWithCount" t-params.bind="{count: 1}"></span>
<span id="i18n-items-plural-10" t="itemWithCount" t-params.bind="{count: 10}"></span>
<span id="i18n-interval-0" t="itemWithCount_interval" t-params.bind="{postProcess: 'interval', count: 0}"></span>
<span id="i18n-interval-1" t="itemWithCount_interval" t-params.bind="{postProcess: 'interval', count: 1}"></span>
<span id="i18n-interval-2" t="itemWithCount_interval" t-params.bind="{postProcess: 'interval', count: 2}"></span>
<span id="i18n-interval-3" t="itemWithCount_interval" t-params.bind="{postProcess: 'interval', count: 3}"></span>
<span id="i18n-interval-6" t="itemWithCount_interval" t-params.bind="{postProcess: 'interval', count: 6}"></span>
<span id="i18n-interval-7" t="itemWithCount_interval" t-params.bind="{postProcess: 'interval', count: 7}"></span>
<span id="i18n-interval-10" t="itemWithCount_interval" t-params.bind="{postProcess: 'interval', count: 10}"></span>
<span id="i18n-html" t="[html]html"></span>
<span id="i18n-prepend-append" t="[prepend]pretest;[append]post-test">Blue</span>
<img id="i18n-img" t="imgPath" />
<span id="i18n-t-vc"> ${'itemWithCount' | t : {count: 10}} </span>
<span id="i18n-t-bb"> ${'itemWithCount' & t : {count: 100}} </span>
<span id="i18n-nf-vc"> ${ 123456789.12 | nf : undefined : locale} </span>
<span id="i18n-nf-bb"> ${ 123456789.12 & nf : undefined : 'de'} </span>
<span id="i18n-nf-vc-cur"> ${ 123456789.12 | nf: {style:'currency', currency: 'EUR' } : 'de' } </span>
<span id="i18n-nf-bb-cur"> ${ 123456789.12 & nf: {style:'currency', currency: 'USD' } : locale } </span>
<span id="i18n-df-vc"> ${ dispatchedOn | df : undefined : locale} </span>
<span id="i18n-df-bb"> ${ dispatchedOn & df : undefined : 'de'} </span>
<span id="i18n-rt-vc"> ${ myDate | rt} </span>
<span id="i18n-rt-bb"> ${ myDate & rt} </span>-->

<!-- translations via code -->
<!-- <div>
<span id="i18n-code-simple">${translations.simple}</span>
<span id="i18n-code-context">${translations.context}</span>
<span id="i18n-code-plural">${translations.plural}</span>
<span id="i18n-code-interval">${translations.interval}</span>
<span id="i18n-code-num">${translations.num}</span>
<span id="i18n-code-num-uf-simple">${translations.numUfSimple}</span>
<span id="i18n-code-num-uf-locale">${translations.numUfLocale}</span>
<span id="i18n-code-num-uf-currency">${translations.numUfCurrency}</span>
<span id="i18n-code-num-uf-text">${translations.numUfText}</span>
<span id="i18n-code-num-uf-minus">${translations.numUfMinus}</span>
<span id="i18n-code-date">${translations.date}</span>
<span id="i18n-code-rtime">${translations.rtime}</span>
</div>
<custom-message data-test-id="custom-element-interpolated" message="${'simple.text' | t}"></custom-message>
<custom-message data-test-id="custom-element-target-bindable" t="[message]simple.text"></custom-message>
<custom-message data-test-id="custom-element-with-params" t="[message]itemWithCount" t-params.bind="{count: 0}">
</custom-message>
<span data-test-id="missing-key" t="missing-key">non-translated text</span> -->
<sut-i18n></sut-i18n>
</template>
9 changes: 9 additions & 0 deletions packages/__tests__/e2e/src/plugins/custom-message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { bindable, customElement } from '@aurelia/runtime';

@customElement({
name: 'custom-message',
template: `<div>\${message}</div>`
})
export class CustomMessage {
@bindable public message: string;
}
101 changes: 60 additions & 41 deletions packages/__tests__/e2e/src/plugins/sut-i18n.html
Original file line number Diff line number Diff line change
@@ -1,58 +1,77 @@
<template>
<!-- <button id="locale-changer-de" click.delegate="changeLocale('de')">DE</button>
<button id="rt-changer" click.delegate="changeMyDate()">Change my date</button> -->

<span id="i18n-simple" t="simple.text"></span>
<!-- <span id="i18n-attr" t="[title]simple.attr">attribute test</span>
<span id="i18n-multiple-attr" t="simple.text;[title]simple.attr"></span>
<span id="i18n-multiple-attr-same-key" t="simple.text;[title,data-foo]simple.attr"></span>
<span id="i18n-nested" t="$t(simple.text) $t(simple.attr)"></span>
<span id="i18n-ctx" t="status"></span>
<span id="i18n-ctx-dispatched" t="status" t-params.bind="{context: 'dispatched', date: dispatchedOn}"></span>
<span id="i18n-ctx-delivered" t="status" t-params.bind="{context: 'delivered', date: deliveredOn}"></span>
<span id="i18n-interpolation" t="status_delivered" t-params.bind="{date: deliveredOn}"></span>
<button id="key-changer" click.delegate="changeKey()">Change key</button><br>
<button id="params-changer" click.delegate="changeParams()">Change t-params</button><br>
<button id="locale-changer-de" click.delegate="changeLocale('de')">DE</button><br>
<button id="rt-changer" click.delegate="changeMyDate()">Change my date</button>

<span id="i18n-simple" t="simple.text"></span><br>
<span id="i18n-vm-bound" t.bind="obj.key"></span><br>
<span id="i18n-alias" i18n="simple.text"></span><br>
<span id="i18n-interpolated-key-expr" t="${obj.key}"></span><br>
<br>

<span id="i18n-attr" t="[title]simple.attr">attribute test</span><br>
<span id="i18n-multiple-attr" t="simple.text;[title]simple.attr"></span><br>
<span id="i18n-multiple-attr-same-key" t="simple.text;[title,data-foo]simple.attr"></span><br>
<span id="i18n-nested" t="$t(simple.text) $t(simple.attr)"></span><br>
<br>

<span id="i18n-ctx" t="status"></span><br>
<span id="i18n-ctx-dispatched" t="status" t-params.bind="{context: 'dispatched', date: dispatchedOn}"></span><br>
<span id="i18n-ctx-delivered" t="status" t-params.bind="{context: 'delivered', date: deliveredOn}"></span><br>
<span id="i18n-ctx-bound-vm-params" t="status" t-params.bind="params"></span><br>
<br>

<span id="i18n-interpolation" t="status_delivered" t-params.bind="{date: deliveredOn}"></span><br>
<span id="i18n-interpolation-custom" t="custom_interpolation_brace"
t-params.bind="{date: deliveredOn, interpolation: { prefix: '{', suffix: '}' }}"></span>
t-params.bind="{date: deliveredOn, interpolation: { prefix: '{', suffix: '}' }}"></span><br>
<span id="i18n-interpolation-es6" t="custom_interpolation_es6_syntax"
t-params.bind="{date: deliveredOn, interpolation: { prefix: '${', suffix: '}' }}"></span>
t-params.bind="{date: deliveredOn, interpolation: { prefix: '${', suffix: '}' }}"></span><br>
<br>

<span id="i18n-items-plural-0" t="itemWithCount" t-params.bind="{count: 0}"></span><br>
<span id="i18n-items-plural-1" t="itemWithCount" t-params.bind="{count: 1}"></span><br>
<span id="i18n-items-plural-10" t="itemWithCount" t-params.bind="{count: 10}"></span><br>
<br>

<span id="i18n-items-plural-0" t="itemWithCount" t-params.bind="{count: 0}"></span>
<span id="i18n-items-plural-1" t="itemWithCount" t-params.bind="{count: 1}"></span>
<span id="i18n-items-plural-10" t="itemWithCount" t-params.bind="{count: 10}"></span>
<span id="i18n-interval-0" t="itemWithCount_interval" t-params.bind="{postProcess: 'interval', count: 0}"></span><br>
<span id="i18n-interval-1" t="itemWithCount_interval" t-params.bind="{postProcess: 'interval', count: 1}"></span><br>
<span id="i18n-interval-2" t="itemWithCount_interval" t-params.bind="{postProcess: 'interval', count: 2}"></span><br>
<span id="i18n-interval-3" t="itemWithCount_interval" t-params.bind="{postProcess: 'interval', count: 3}"></span><br>
<span id="i18n-interval-6" t="itemWithCount_interval" t-params.bind="{postProcess: 'interval', count: 6}"></span><br>
<span id="i18n-interval-7" t="itemWithCount_interval" t-params.bind="{postProcess: 'interval', count: 7}"></span><br>
<span id="i18n-interval-10" t="itemWithCount_interval"
t-params.bind="{postProcess: 'interval', count: 10}"></span><br>

<span id="i18n-interval-0" t="itemWithCount_interval" t-params.bind="{postProcess: 'interval', count: 0}"></span>
<span id="i18n-interval-1" t="itemWithCount_interval" t-params.bind="{postProcess: 'interval', count: 1}"></span>
<span id="i18n-interval-2" t="itemWithCount_interval" t-params.bind="{postProcess: 'interval', count: 2}"></span>
<span id="i18n-interval-3" t="itemWithCount_interval" t-params.bind="{postProcess: 'interval', count: 3}"></span>
<span id="i18n-interval-6" t="itemWithCount_interval" t-params.bind="{postProcess: 'interval', count: 6}"></span>
<span id="i18n-interval-7" t="itemWithCount_interval" t-params.bind="{postProcess: 'interval', count: 7}"></span>
<span id="i18n-interval-10" t="itemWithCount_interval" t-params.bind="{postProcess: 'interval', count: 10}"></span>
<br>

<span id="i18n-html" t="[html]html"></span>
<span id="i18n-html" t="[html]html"></span><br><br>

<span id="i18n-prepend-append" t="[prepend]pretest;[append]post-test">Blue</span>
<span id="i18n-prepend-append" t="[prepend]pretest;[append]post-test">Blue</span><br><br>

<img id="i18n-img" t="imgPath" />
<img id="i18n-img" t="imgPath" /><br><br>

<span id="i18n-t-vc"> ${'itemWithCount' | t : {count: 10}} </span>
<span id="i18n-t-bb"> ${'itemWithCount' & t : {count: 100}} </span>
<span id="i18n-t-vc"> ${'itemWithCount' | t : {count: 10}} </span><br>
<span id="i18n-t-bb"> ${'itemWithCount' & t : {count: 100}} </span><br>
<span id="i18n-t-bb-attr" title.bind="'itemWithCount' & t : {count: 100}"></span><br><br>

<span id="i18n-nf-vc"> ${ 123456789.12 | nf : undefined : locale} </span>
<span id="i18n-nf-bb"> ${ 123456789.12 & nf : undefined : 'de'} </span>
<span id="i18n-nf-vc"> ${ 123456789.12 | nf } </span><br>
<span id="i18n-nf-bb"> ${ 123456789.12 & nf : undefined : 'de'} </span><br><br>

<span id="i18n-nf-vc-cur"> ${ 123456789.12 | nf: {style:'currency', currency: 'EUR' } : 'de' } </span>
<span id="i18n-nf-bb-cur"> ${ 123456789.12 & nf: {style:'currency', currency: 'USD' } : locale } </span>
<span id="i18n-nf-vc-cur"> ${ 123456789.12 | nf: {style:'currency', currency: 'EUR' } : 'de' } </span><br>
<span id="i18n-nf-bb-cur"> ${ 123456789.12 & nf: {style:'currency', currency: 'USD' } : locale } </span><br><br>

<span id="i18n-df-vc"> ${ dispatchedOn | df : undefined : locale} </span>
<span id="i18n-df-bb"> ${ dispatchedOn & df : undefined : 'de'} </span>
<span id="i18n-df-vc"> ${ dispatchedOn | df } </span><br>
<span id="i18n-df-vc-iso"> ${ '2019-08-10T13:42:35.209Z' | df } </span><br>
<span id="i18n-df-vc-int"> ${ 0 | df } </span><br>
<span id="i18n-df-vc-int-str"> ${ '0' | df } </span><br>
<span id="i18n-df-bb"> ${ dispatchedOn & df : undefined : 'de'} </span><br><br>

<span id="i18n-rt-vc"> ${ myDate | rt} </span>
<span id="i18n-rt-bb"> ${ myDate & rt} </span>-->
<span id="i18n-rt-vc"> ${ myDate | rt} </span><br>
<span id="i18n-rt-bb"> ${ myDate & rt} </span><br><br>

<!-- translations via code -->
<!-- <div>
<div>
<span id="i18n-code-simple">${translations.simple}</span>
<span id="i18n-code-context">${translations.context}</span>
<span id="i18n-code-plural">${translations.plural}</span>
Expand All @@ -74,5 +93,5 @@
<custom-message data-test-id="custom-element-with-params" t="[message]itemWithCount" t-params.bind="{count: 0}">
</custom-message>

<span data-test-id="missing-key" t="missing-key">non-translated text</span> -->
<span data-test-id="missing-key" t="missing-key">non-translated text</span>
</template>
56 changes: 56 additions & 0 deletions packages/__tests__/e2e/src/plugins/sut-i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { I18N, I18nService, RT_SIGNAL } from '@aurelia/i18n';
import { customElement, ISignaler } from '@aurelia/runtime';
import template from './sut-i18n.html';
import { Locale } from './translation-resources';

@customElement({ name: 'sut-i18n', template })
export class SutI18N {
public obj = { key: 'simple.text', foo: 'bar' };
public dispatchedOn = new Date(2020, 1, 10, 5, 15);
public deliveredOn = new Date(2021, 1, 10, 5, 15);
public params = { context: 'delivered', date: this.deliveredOn };
public translations: { [key: string]: string | number };
private readonly myDate: Date;
private locale: Locale;
constructor(
@I18N private readonly i18n: I18nService,
@ISignaler private readonly signaler: ISignaler
) {
this.locale = this.i18n.getLocale() as Locale;
this.myDate = new Date();
this.myDate.setHours(this.myDate.getHours() - 2);

this.translations = {
simple: this.i18n.tr('simple.text'),
context: this.i18n.tr('status', { context: 'dispatched', date: this.dispatchedOn }),
plural: this.i18n.tr('itemWithCount', { count: 10 }),
interval: this.i18n.tr('itemWithCount_interval', { postProcess: 'interval', count: 10 }),

num: this.i18n.nf(123456789),
numUfSimple: this.i18n.uf('123,456,789.12'),
numUfLocale: this.i18n.uf('123.456.789,12', 'de'),
numUfCurrency: this.i18n.uf('$ 123,456,789.12'),
numUfText: this.i18n.uf('123,456,789.12 foo bar'),
numUfMinus: this.i18n.uf('- 123,456,789.12'),

date: this.i18n.df(this.deliveredOn),
rtime: this.i18n.rt(this.myDate)
};
}
public changeKey() {
this.obj.key = 'simple.attr';
}

public changeParams() {
this.params = { ...this.params, context: 'dispatched' };
}
public async changeLocale(locale) {
await this.i18n.setLocale(locale);
this.locale = locale;
}

public changeMyDate() {
this.myDate.setFullYear(this.myDate.getFullYear() - 1);
this.signaler.dispatchSignal(RT_SIGNAL);
}
}
9 changes: 9 additions & 0 deletions packages/__tests__/e2e/src/plugins/translation-resources.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as de from '../locales/de/translations.json';
import * as en from '../locales/en/translations.json';

export const resources = {
en: { translation: en['default'] },
de: { translation: de['default'] },
};

export type Locale = keyof typeof resources;
50 changes: 34 additions & 16 deletions packages/__tests__/e2e/src/startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,38 @@ import { DebugConfiguration } from '@aurelia/debug';
import { I18nConfiguration } from '@aurelia/i18n';
import { BasicConfiguration } from '@aurelia/jit-html-browser';
import { Aurelia } from '@aurelia/runtime';
import { App } from './app';
import * as de from './locales/de/translations.json';
import * as en from './locales/en/translations.json';
import * as intervalPlural from 'i18next-intervalplural-postprocessor';
import { App as component } from './app';
import { CustomMessage } from './plugins/custom-message';
import { SutI18N } from './plugins/sut-i18n';
import { resources } from './plugins/translation-resources';

window['au'] = new Aurelia()
.register(
BasicConfiguration,
DebugConfiguration,
I18nConfiguration.customize(() => ({
resources: {
en: { translation: en['default'] },
de: { translation: de['default'] },
}
}))
)
.app({ host: document.querySelector('app'), component: new App() })
.start();
// Intl.RelativeTimeFormat polyfill is needed as Cypress uses electron and does not seems to work with puppeteer
import RelativeTimeFormat from 'relative-time-format';
import * as deRt from 'relative-time-format/locale/de.json';
import * as enRt from 'relative-time-format/locale/en.json';
RelativeTimeFormat.addLocale(enRt['default']);
RelativeTimeFormat.addLocale(deRt['default']);
Intl['RelativeTimeFormat'] = Intl['RelativeTimeFormat'] || RelativeTimeFormat;

(async function () {
const host = document.querySelector('app');

const au = new Aurelia()
.register(
BasicConfiguration,
DebugConfiguration,
I18nConfiguration.customize((options) => {
options.translationAttributeAliases = ['t', 'i18n'];
options.initOptions = {
plugins: [intervalPlural.default],
resources,
skipTranslationOnMissingKey: !!new URL(location.href).searchParams.get('skipkey')
};
})
);
au.container.register(SutI18N, CustomMessage);
au.app({ host, component });

await au.start().wait();
})().catch(console.error);
Loading

0 comments on commit c3d4a85

Please sign in to comment.