Skip to content

Commit b1d967e

Browse files
committed
improve translation system:
- detect language - configure moment - use dedicated language service - add example to welcome view
1 parent 60a1038 commit b1d967e

File tree

11 files changed

+139
-14
lines changed

11 files changed

+139
-14
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
"aurelia-templating-router": "^1.0.0",
9292
"bootstrap-sass": "^3.3.7",
9393
"font-awesome": "^4.7.0",
94+
"i18next-browser-languagedetector": "^1.0.1",
9495
"isomorphic-fetch": "^2.2.1",
9596
"jquery": "^3.1.1",
9697
"moment": "^2.16.0"

src/app/app.vm.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
import { Lazy, inject } from 'aurelia-framework';
22
import { Router, RouterConfiguration } from 'aurelia-router';
33
import { I18N } from 'aurelia-i18n';
4+
45
import { AppConfigService } from './services/app-config.service';
56
import { CordovaService } from './services/cordova.service';
7+
import { EventBusService, EventBusEvents } from './services/event-bus.service';
8+
import { LanguageService } from './services/language.service';
69

7-
@inject(I18N, AppConfigService, Lazy.of(CordovaService))
10+
@inject(I18N, AppConfigService, Lazy.of(CordovaService), EventBusService, LanguageService)
811
export class AppViewModel {
912
public router: Router;
1013

1114
constructor(
1215
private i18n: I18N,
1316
private appConfigService: AppConfigService,
14-
private cordovaServiceFn: () => CordovaService
17+
private cordovaServiceFn: () => CordovaService,
18+
private eventBusService: EventBusService,
19+
private languageService: LanguageService,
1520
) { }
1621

1722
public configureRouter(config: RouterConfiguration, router: Router): void {
@@ -45,7 +50,14 @@ export class AppViewModel {
4550
title: 'Child Router'
4651
}
4752
]);
53+
config.mapUnknownRoutes({ route: '', redirect: '' });
4854

4955
this.router = router;
5056
}
57+
58+
private configureMoment(): void {
59+
const locale = this.languageService.getCurrentLocale();
60+
moment.locale(locale);
61+
this.eventBusService.addSubscription(EventBusEvents.IDS.i18n.locale.changed, (a) => moment.locale(a.newValue));
62+
}
5163
}

src/app/main.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ import { ConsoleAppender } from 'aurelia-logging-console';
1818
/**
1919
* Locals i18n imports
2020
*/
21-
import i18nEnglish from './../locales/en.json';
21+
import en_USTranslation from './../locales/en_US.json';
22+
import de_CHTranslation from './../locales/de_CH.json';
2223

2324
/**
24-
* Polyfill fetch
25+
* Third Party Libraries and polyfill
2526
*/
2627
import 'isomorphic-fetch';
28+
import LanguageDetector from 'i18next-browser-languagedetector';
2729

2830
/**
2931
* Aurelia configruation
@@ -42,16 +44,30 @@ export async function configure(aurelia: Aurelia): Promise<void> {
4244
* See: https://github.com/aurelia/i18n
4345
*/
4446
.plugin('aurelia-i18n', (instance) => {
47+
instance.i18next.use(LanguageDetector);
4548
// adapt options to your needs (see http://i18next.com/docs/options/)
4649
// make sure to return the promise of the setup method, in order to guarantee proper loading
4750
return instance.setup({
4851
resources: {
49-
en: {
50-
translation: i18nEnglish
52+
'en-US': {
53+
translation: en_USTranslation
54+
},
55+
'de-CH': {
56+
translation: de_CHTranslation
5157
}
5258
},
53-
lng: 'en',
54-
fallbackLng: 'en'
59+
fallbackLng: {
60+
'de': ['de-CH', 'en-US'],
61+
'de-DE': ['de-CH', 'en-US'],
62+
'default': ['en-US']
63+
},
64+
debug: false,
65+
detection: {
66+
order: ['localStorage', 'navigator'],
67+
lookupCookie: 'i18next',
68+
lookupLocalStorage: 'i18nextLng',
69+
caches: ['localStorage']
70+
}
5571
});
5672
})
5773
// Uncomment the line below to enable animation.

src/app/modules/welcome/welcome.vm.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,11 @@ <h3>Custom global attribute</h3>
2525

2626
<h3>Global value converter</h3>
2727
<p>${currentDate | dateFormat}</p>
28+
29+
<h3>Switch Language</h3>
30+
<button click.delegate="switchLanguage()" class="btn btn-primary">Click me!</button>
31+
<br/><br/>
32+
<p>Everything is: <span t="TR_ATTR"></span></p>
33+
<p>Signalr example: ${ 'TR_SIGNALER' | t & signal:'locale:changed' }</p>
2834
</section>
2935
</template>

src/app/modules/welcome/welcome.vm.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { autoinject } from 'aurelia-framework';
2+
23
import { LogManager, Logger} from './../../services/logger.service';
34
import { AppConfigService } from './../../services/app-config.service';
5+
import { LanguageService } from './../../services/language.service';
46

57
@autoinject
68
export class Welcome {
@@ -13,7 +15,8 @@ export class Welcome {
1315
public currentDate: Date = new Date();
1416

1517
constructor(
16-
private appConfigService: AppConfigService
18+
private appConfigService: AppConfigService,
19+
private languageService: LanguageService
1720
) {
1821
this.logger = LogManager.getLogger('Welcome VM');
1922
this.logger.info('appConfig => name:', appConfigService.getName());
@@ -43,6 +46,15 @@ export class Welcome {
4346
}
4447
return true;
4548
}
49+
50+
public switchLanguage(): void {
51+
const lang = this.languageService.getCurrentLang();
52+
if (lang === this.languageService.getSupportedLanguages()[0]) {
53+
this.languageService.setLocale(this.languageService.getSupportedLanguages()[1]);
54+
} else {
55+
this.languageService.setLocale(this.languageService.getSupportedLanguages()[0]);
56+
}
57+
}
4658
}
4759

4860
export class UpperValueConverter {
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { autoinject } from 'aurelia-framework';
2+
import { I18N } from 'aurelia-i18n';
3+
import { BindingSignaler } from 'aurelia-templating-resources';
4+
import * as moment from 'moment/moment';
5+
6+
import { Logger, LogManager} from './logger.service';
7+
8+
export const LocaleChangedSignal: string = 'locale:changed';
9+
10+
@autoinject
11+
export class LanguageService {
12+
13+
private logger: Logger;
14+
15+
constructor(
16+
private i18n: I18N,
17+
private bindingSignaler: BindingSignaler
18+
) {
19+
this.logger = LogManager.getLogger('LanguageService');
20+
this.logger.debug('initialized');
21+
this.logger.debug(`current locale: ${this.getCurrentLocale()}`);
22+
}
23+
24+
/**
25+
* Return the language: en, de, fr, it
26+
*/
27+
public getCurrentLang(): string {
28+
return this.getCurrentLocale().split('-')[0];
29+
}
30+
31+
/**
32+
* Get the list of all supported locales from our translations,
33+
* then get the current browser locale, if we have a translation
34+
* returns the language or fallback to default -> en
35+
*/
36+
public getCurrentUILanguage(): string {
37+
const supported = this.getSupportedLanguages();
38+
const currentLanguage = this.getCurrentLang();
39+
return supported.indexOf(currentLanguage) > -1 ? currentLanguage : 'en';
40+
}
41+
42+
/**
43+
* Get a list of current configured languages which has a translation.
44+
*/
45+
public getSupportedLanguages(): string[] {
46+
const languages = Object.keys(this.i18n.i18next.options.fallbackLng)
47+
.map(key => this.i18n.i18next.options.fallbackLng[key][0].split('-')[0])
48+
.filter((value, index, array) => {
49+
return array.indexOf (value) === index;
50+
});
51+
return languages;
52+
}
53+
54+
public getCurrentLocale(): string {
55+
return this.i18n.getLocale();
56+
}
57+
58+
public setLocale(locale: string): void {
59+
moment.locale(locale);
60+
this.i18n.setLocale(locale);
61+
this.bindingSignaler.signal(LocaleChangedSignal);
62+
this.logger.debug(`changed locale to: ${locale}`);
63+
}
64+
}

src/locales/de_CH.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"TITLE": "Übersetztungs Titel",
3+
"TR_ATTR": "...in Deutsch",
4+
"TR_SIGNALER": "Signaler Beispiel funktioniert..."
5+
}

src/locales/en.json

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/locales/en_US.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"TITLE": "Transaltion Title",
3+
"TR_ATTR": "...in English",
4+
"TR_SIGNALER": "Signaler Example works..."
5+
}

test/unit/app.spec.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { AppViewModel } from '../../src/app/app.vm';
33
import { I18N } from 'aurelia-i18n';
44
import { BindingSignaler } from 'aurelia-templating-resources';
55
import { EventAggregator } from 'aurelia-event-aggregator';
6-
import EnglishTranslation from '../../src/locales/en.json';
6+
import EnglishTranslation from '../../src/locales/en_US.json';
77
import { AppConfigService } from '../../src/app/services/app-config.service';
88

99
class RouterStub {
@@ -19,6 +19,10 @@ class RouterStub {
1919
public map(routes): void {
2020
this.routes = routes;
2121
}
22+
23+
public mapUnknownRoutes(config): void {
24+
this.routes.push(config);
25+
}
2226
}
2327

2428
describe('the App module', () => {
@@ -29,6 +33,8 @@ describe('the App module', () => {
2933
beforeEach(() => {
3034
mockedRouter = new RouterStub();
3135

36+
const AnyMock: any = undefined;
37+
3238
// Translation setup
3339
sut = new I18N(new EventAggregator(), new BindingSignaler());
3440
sut.setup({
@@ -44,7 +50,7 @@ describe('the App module', () => {
4450

4551
appConfigSub = new AppConfigService();
4652

47-
sut = new AppViewModel(sut, appConfigSub, (<any>undefined));
53+
sut = new AppViewModel(sut, appConfigSub, AnyMock, AnyMock, AnyMock);
4854
sut.configureRouter(mockedRouter, mockedRouter);
4955
});
5056

0 commit comments

Comments
 (0)