From ae4699f3bccf859938a7a3a7512d118200ae3b41 Mon Sep 17 00:00:00 2001 From: Victor Costa Date: Mon, 14 Mar 2016 17:12:32 -0300 Subject: [PATCH] Use messageformat to support pluralization in translations --- bower.json | 3 ++- gulp/build.js | 2 ++ src/app/components/language-selector/language-selector.component.spec.ts | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------ src/app/components/language-selector/language-selector.component.ts | 17 ++++++++++++----- src/app/components/noosfero-activities/activity/new_friendship.html | 2 +- src/app/index.config.ts | 1 + src/languages/en.json | 2 +- src/languages/pt.json | 2 +- src/spec/mocks.ts | 8 ++------ 9 files changed, 72 insertions(+), 45 deletions(-) diff --git a/bower.json b/bower.json index be9a351..af6cf93 100644 --- a/bower.json +++ b/bower.json @@ -33,7 +33,8 @@ "angular-translate-handler-log": "^2.10.0", "angular-dynamic-locale": "^0.1.30", "angular-i18n": "^1.5.0", - "angular-load": "^0.4.1" + "angular-load": "^0.4.1", + "angular-translate-interpolation-messageformat": "^2.10.0" }, "devDependencies": { "angular-mocks": "~1.5.0" diff --git a/gulp/build.js b/gulp/build.js index cff3aec..fae4fc6 100644 --- a/gulp/build.js +++ b/gulp/build.js @@ -48,6 +48,7 @@ gulp.task('html', ['inject', 'partials'], function () { .pipe($.replace('/languages/', noosferoThemePrefix + 'languages/')) .pipe($.replace('bower_components/angular-i18n/', noosferoThemePrefix + 'locale/angular-i18n/')) .pipe($.replace('bower_components/moment/', noosferoThemePrefix + 'locale/moment/')) + .pipe($.replace('bower_components/messageformat/', noosferoThemePrefix + 'locale/messageformat/')) .pipe($.sourcemaps.init()) .pipe($.ngAnnotate()) .pipe($.uglify({ preserveComments: $.uglifySaveLicense })).on('error', conf.errorHandler('Uglify')) @@ -88,6 +89,7 @@ gulp.task('locale', function () { return gulp.src([ path.join("bower_components/angular-i18n", '*.js'), path.join("bower_components/moment/locale", '*.js'), + path.join("bower_components/messageformat/locale", '*.js'), ], {base: 'bower_components/'}) .pipe(gulp.dest(path.join(conf.paths.dist, '/locale/'))); }); diff --git a/src/app/components/language-selector/language-selector.component.spec.ts b/src/app/components/language-selector/language-selector.component.spec.ts index 9ba1a65..f1da99f 100644 --- a/src/app/components/language-selector/language-selector.component.spec.ts +++ b/src/app/components/language-selector/language-selector.component.spec.ts @@ -1,42 +1,38 @@ -import {TestComponentBuilder, ComponentFixture} from 'ng-forward/cjs/testing/test-component-builder'; -import {Pipe, Input, provide, Component} from 'ng-forward'; +import {ComponentFixture} from 'ng-forward/cjs/testing/test-component-builder'; +import {provide} from 'ng-forward'; import {LanguageSelector} from './language-selector.component'; import * as helpers from "../../../spec/helpers"; -const tcb = new TestComponentBuilder(); - -const htmlTemplate: string = ''; - describe("Components", () => { describe("Language Selector Component", () => { beforeEach(angular.mock.module("templates")); - @Component({ - selector: 'test-container-component', - template: htmlTemplate, - directives: [LanguageSelector], - providers: [ - provide('$translate', { - useValue: helpers.mocks.$translate - }), - provide('tmhDynamicLocale', { - useValue: helpers.mocks.tmhDynamicLocale - }), - provide('amMoment', { - useValue: helpers.mocks.amMoment - }), - provide('angularLoad', { - useValue: helpers.mocks.angularLoad - }) - ].concat(helpers.provideFilters("translateFilter")) - }) - class BlockContainerComponent { } - - it("set available languages when change language", () => { + let buildComponent = (): Promise => { + return helpers.quickCreateComponent({ + template: "", + directives: [LanguageSelector], + providers: [ + provide('$translate', { + useValue: helpers.mocks.$translate + }), + provide('tmhDynamicLocale', { + useValue: helpers.mocks.tmhDynamicLocale + }), + provide('amMoment', { + useValue: helpers.mocks.amMoment + }), + provide('angularLoad', { + useValue: helpers.mocks.angularLoad + }) + ].concat(helpers.provideFilters("translateFilter")) + }); + } + + it("set available languages when change language", (done) => { let component: LanguageSelector = new LanguageSelector( helpers.mocks.$translate, helpers.mocks.tmhDynamicLocale, @@ -47,11 +43,35 @@ describe("Components", () => { expect(component.availableLanguages).toBeNull(); component.changeLanguage('en'); expect(component.availableLanguages).toBeDefined(); + done(); }); - it("display language options", () => { - helpers.createComponentFromClass(BlockContainerComponent).then(fixture => { + it("display language options", (done) => { + buildComponent().then(fixture => { expect(fixture.debugElement.queryAll('li.language').length).toEqual(2); + done(); + }); + }); + + it("change the language", (done) => { + buildComponent().then(fixture => { + let component: LanguageSelector = fixture.debugElement.componentViewChildren[0].componentInstance; + let $q = fixture.debugElement.getLocal("$q"); + let loadScripPromise = $q.defer(); + loadScripPromise.resolve(); + component["angularLoad"].loadScript = jasmine.createSpy("loadScript").and.returnValue(loadScripPromise.promise); + component["tmhDynamicLocale"].set = jasmine.createSpy("set"); + component["tmhDynamicLocale"].get = jasmine.createSpy("get").and.returnValue("en"); + component["$translate"].use = jasmine.createSpy("use"); + + component.changeLanguage('pt'); + fixture.debugElement.getLocal("$rootScope").$digest(); + + expect(component["angularLoad"].loadScript).toHaveBeenCalledWith("/bower_components/moment/locale/pt.js"); + expect(component["angularLoad"].loadScript).toHaveBeenCalledWith("/bower_components/messageformat/locale/pt.js"); + expect(component["tmhDynamicLocale"].set).toHaveBeenCalledWith("pt"); + expect(component["$translate"].use).toHaveBeenCalledWith("pt"); + done(); }); }); diff --git a/src/app/components/language-selector/language-selector.component.ts b/src/app/components/language-selector/language-selector.component.ts index 5e5663c..87c6060 100644 --- a/src/app/components/language-selector/language-selector.component.ts +++ b/src/app/components/language-selector/language-selector.component.ts @@ -14,6 +14,7 @@ export class LanguageSelector { private amMoment: any, private angularLoad: any) { + this.configAvailableLanguages(); this.changeLanguage(tmhDynamicLocale.get() || $translate.use()); } @@ -24,14 +25,20 @@ export class LanguageSelector { changeLanguage(language: string) { this.changeMomentLocale(language); this.tmhDynamicLocale.set(language); - this.$translate.use(language).then((lang) => { - this.availableLanguages = { - "en": this.$translate.instant("language.en"), - "pt": this.$translate.instant("language.pt") - }; + this.angularLoad.loadScript(`/bower_components/messageformat/locale/${language}.js`).then(() => { + return this.$translate.use(language); + }).then(() => { + this.configAvailableLanguages(); }); } + private configAvailableLanguages() { + this.availableLanguages = { + "en": this.$translate.instant("language.en"), + "pt": this.$translate.instant("language.pt") + }; + } + private changeMomentLocale(language: string) { let localePromise = Promise.resolve(); if (language != "en") { diff --git a/src/app/components/noosfero-activities/activity/new_friendship.html b/src/app/components/noosfero-activities/activity/new_friendship.html index 75c2ff0..c19d66d 100644 --- a/src/app/components/noosfero-activities/activity/new_friendship.html +++ b/src/app/components/noosfero-activities/activity/new_friendship.html @@ -5,7 +5,7 @@

- {{"activities.new_friendship.description" | translate:{friends: ctrl.activity.params.friend_name.length} }} + {{"activities.new_friendship.description" | translate:{friends: ctrl.activity.params.friend_name.length}:"messageformat" }} diff --git a/src/app/index.config.ts b/src/app/index.config.ts index 85528ae..7a657db 100644 --- a/src/app/index.config.ts +++ b/src/app/index.config.ts @@ -29,6 +29,7 @@ function configTranslation($translateProvider: angular.translate.ITranslateProvi prefix: '/languages/', suffix: '.json' }); + $translateProvider.addInterpolation('$translateMessageFormatInterpolation'); $translateProvider.useMissingTranslationHandlerLog(); $translateProvider.preferredLanguage('en'); $translateProvider.useSanitizeValueStrategy('escape'); diff --git a/src/languages/en.json b/src/languages/en.json index 0cc627d..ae094bb 100644 --- a/src/languages/en.json +++ b/src/languages/en.json @@ -12,7 +12,7 @@ "profile.wall": "Profile Wall", "activities.create_article.description": "has published on", "activities.add_member_in_community.description": "has joined the community", - "activities.new_friendship.description": "has made {{friends}} new friend(s):", + "activities.new_friendship.description": "has made {friends, plural, one{one new friend} other{# new friends}}:", "auth.title": "Login", "auth.form.login": "Login / Email address", "auth.form.password": "Password", diff --git a/src/languages/pt.json b/src/languages/pt.json index 4086a0f..00d2e7f 100644 --- a/src/languages/pt.json +++ b/src/languages/pt.json @@ -12,7 +12,7 @@ "profile.wall": "Mural do Perfil", "activities.create_article.description": "publicou em", "activities.add_member_in_community.description": "entrou na comunidade", - "activities.new_friendship.description": "fez {{friends}} novo(s) amigo(s):", + "activities.new_friendship.description": "fez {friends, plural, one{um novo amigo} other{# novos amigos}}:", "auth.title": "Login", "auth.form.login": "Login / Email", "auth.form.password": "Senha", diff --git a/src/spec/mocks.ts b/src/spec/mocks.ts index 14a89c7..cbea101 100644 --- a/src/spec/mocks.ts +++ b/src/spec/mocks.ts @@ -45,9 +45,7 @@ export var mocks = { }, $translate: { use: (lang?: string) => { - return { - then: (func?: any) => { if (func) func() } - } + return lang ? Promise.resolve(lang) : "en"; }, instant: () => { } }, @@ -60,9 +58,7 @@ export var mocks = { }, angularLoad: { loadScript: (script?: string) => { - return { - then: (func?: any) => { if (func) func() } - } + return Promise.resolve(); } } }; -- libgit2 0.21.2