diff --git a/src/openlmis-i18n/message.service.js b/src/openlmis-i18n/message.service.js new file mode 100644 index 0000000..d9276ad --- /dev/null +++ b/src/openlmis-i18n/message.service.js @@ -0,0 +1,150 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + *   + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  + * See the GNU Affero General Public License for more details. You should have received a copy of + * the GNU Affero General Public License along with this program. If not, see + * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.  + */ + +(function() { + + 'use strict'; + + /** + * @ngdoc service + * @name openlmis-i18n.messageService + * + * @description + * Responsible for retrieving messages. + */ + angular + .module('openlmis-i18n') + .factory('messageService', messageService); + + var LOCALE_STORAGE_KEY = 'current_locale'; + + messageService.$inject = ['$q', '$rootScope', 'OPENLMIS_MESSAGES', 'DEFAULT_LANGUAGE', 'localStorageService']; + + function messageService($q, $rootScope, OPENLMIS_MESSAGES, DEFAULT_LANGUAGE, localStorageService) { + + var service = { + getCurrentLocale: getCurrentLocale, + populate: populate, + get: get, + formatMessage: formatMessage + }; + + return service; + + /** + * @ngdoc method + * @methodOf openlmis-i18n.messageService + * @name getCurrentLocale + * + * @description + * Returns current locale. + * + * @return {String} current locale + */ + function getCurrentLocale() { + return localStorageService.get(LOCALE_STORAGE_KEY); + } + + /** + * @ngdoc method + * @methodOf openlmis-i18n.messageService + * @name populate + * + * @description + * Returns current locale. + * + * @param {String} locale (optional) locale to populate + * @return {Promise} Promise + */ + function populate(locale) { + if (!locale) { + locale = DEFAULT_LANGUAGE; + } + + if (OPENLMIS_MESSAGES[locale]) { + localStorageService.add(LOCALE_STORAGE_KEY, locale); + $rootScope.$broadcast('openlmis.messages.populated'); + return $q.when(); + } + return $q.reject(); + + } + + /** + * @ngdoc method + * @methodOf openlmis-i18n.messageService + * @name get + * + * @description + * Returns message for current locale. + * + * @return {String} display message + */ + function get() { + var keyWithArgs = Array.prototype.slice.call(arguments); + var displayMessage = keyWithArgs[0]; + var parameters = keyWithArgs[1]; + var currentLocale = getCurrentLocale(); + if (OPENLMIS_MESSAGES[currentLocale] && OPENLMIS_MESSAGES[currentLocale][keyWithArgs[0]]) { + displayMessage = OPENLMIS_MESSAGES[currentLocale][keyWithArgs[0]]; + } + if (parameters) { + //eslint-disable-next-line no-useless-escape + displayMessage = displayMessage.replace(/\$\{([\s]*[^;\s\{]+[\s]*)\}/g, function(_, match) { + return parameters[match.trim()]; + }); + } + return displayMessage; + } + + /** + * @ngdoc method + * @methodOf openlmis-i18n.messageService + * @name formatMessage + * + * @description + * Used in React components to get a translated message from the messageService. + * + * @param {String} key - key of the message + * @param {Object} params - parameters to be used in the message + */ + function formatMessage(key, params) { + var currentLocale = getCurrentLocale(); + var displayMessage = key; + + if (OPENLMIS_MESSAGES[currentLocale] && OPENLMIS_MESSAGES[currentLocale][key]) { + displayMessage = OPENLMIS_MESSAGES[currentLocale][key]; + + if (params) { + //eslint-disable-next-line no-useless-escape + var REPLACE_PLACEHOLDERS_REGEX = /\$\{([\s]*[^;\s\{]+[\s]*)\}/g; + + displayMessage = displayMessage.replace(REPLACE_PLACEHOLDERS_REGEX, function(_, match) { + var MISSING_PARAM = 'MISSING_PARAM'; + var paramValue = params[match.trim()]; + + return paramValue ? paramValue : MISSING_PARAM; + }); + } + + } else { + console.error('[ERROR]: Translation message not found for: ' + key); + } + + return displayMessage; + } + } + +})(); diff --git a/src/openlmis-i18n/message.service.spec.js b/src/openlmis-i18n/message.service.spec.js new file mode 100644 index 0000000..edba94f --- /dev/null +++ b/src/openlmis-i18n/message.service.spec.js @@ -0,0 +1,176 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + *   + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  + * See the GNU Affero General Public License for more details. You should have received a copy of + * the GNU Affero General Public License along with this program. If not, see + * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.  + */ + +describe('MessageService', function() { + + beforeEach(function() { + module('openlmis-i18n', function($provide) { + $provide.constant('DEFAULT_LANGUAGE', 'en'); + $provide.constant('MISSING_PARAM', 'MISSING_PARAM'); + + $provide.constant('OPENLMIS_MESSAGES', { + en: { + 'language.name': 'English', + sample: 'message', + messageWithParam: 'hello ${name}!', + messageWithParams: 'Object with id: ${id}, status: ${status}', + messageWithParams2: 'Object with id: ${0}, status: ${1}' + }, + test: { + 'language.name': 'Test', + sample: 'foo' + } + }); + }); + + inject(function($injector) { + this.$rootScope = $injector.get('$rootScope'); + this.messageService = $injector.get('messageService'); + this.localStorageService = $injector.get('localStorageService'); + }); + + spyOn(this.localStorageService, 'get').andReturn('en'); + spyOn(this.localStorageService, 'add'); + spyOn(this.$rootScope, '$broadcast'); + }); + + it('loads a default language when populated without any parameters', function() { + this.messageService.populate(); + + expect(this.localStorageService.add).toHaveBeenCalledWith('current_locale', 'en'); + }); + + it('returns existing translation', function() { + expect(this.messageService.get('sample')).toBe('message'); + }); + + it('returns the message string when a translation doesn\'t exist', function() { + expect(this.messageService.get('foobar')).toBe('foobar'); + }); + + it('returns the message string with parameter', function() { + var person = { + name: 'Jane' + }; + var expected = 'hello Jane!'; + + expect(this.messageService.get('messageWithParam', person)).toBe(expected); + }); + + it('returns the message string with multiple parameters', function() { + var object = { + id: '123', + status: 'NEW' + }; + var expected = 'Object with id: 123, status: NEW'; + + expect(this.messageService.get('messageWithParams', object)).toBe(expected); + }); + + it('returns the message string with parameters in array', function() { + var array = ['123', 'NEW']; + var expected = 'Object with id: 123, status: NEW'; + + expect(this.messageService.get('messageWithParams2', array)).toBe(expected); + }); + + it('can change the current locale', function() { + this.messageService.populate('test'); + this.localStorageService.get.andReturn('test'); + + expect(this.localStorageService.add).toHaveBeenCalledWith('current_locale', 'test'); + expect(this.messageService.get('sample')).toBe('foo'); + }); + + it('broadcasts an event when the locale is successfully changed', function() { + this.messageService.populate('test'); + + expect(this.$rootScope.$broadcast).toHaveBeenCalledWith('openlmis.messages.populated'); + }); + + it('resolves a promise when the locale is changed', function() { + var success = false; + + var promise = this.messageService.populate('test'); + promise.then(function() { + success = true; + }); + + this.$rootScope.$apply(); + + expect(success).toBe(true); + }); + + it('rejects the promise when locale isn\'t changed', function() { + var success = false; + + var promise = this.messageService.populate('foo'); + promise.catch(function() { + success = true; + }); + + this.$rootScope.$apply(); + + expect(success).toBe(true); + }); + + describe('formatMessage', function() { + it('returns the translated message with parameters', function() { + var key = 'messageWithParam'; + var params = { + name: 'John' + }; + var expected = 'hello John!'; + + var result = this.messageService.formatMessage(key, params); + + expect(result).toBe(expected); + }); + + it('returns the translated message with missing parameters', function() { + var key = 'messageWithParam'; + var params = { + age: 25 + }; + var expected = 'hello MISSING_PARAM!'; + + var result = this.messageService.formatMessage(key, params); + + expect(result).toBe(expected); + }); + + it('returns the translated message if key exists but no parameters are passed', function() { + var key = 'sample'; + + var expected = 'message'; + + var result = this.messageService.formatMessage(key); + + expect(result).toBe(expected); + }); + + it('returns the key when translation is not found', function() { + var key = 'nonExistentKey'; + var params = { + name: 'John' + }; + var expected = 'nonExistentKey'; + + var result = this.messageService.formatMessage(key, params); + + expect(result).toBe(expected); + }); + }); +}); \ No newline at end of file