Skip to content

Commit

Permalink
OAM-89: add util function to handle translations in react components (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
olewandowski1 authored May 8, 2024
1 parent 39c485f commit 6d5abd3
Show file tree
Hide file tree
Showing 2 changed files with 326 additions and 0 deletions.
150 changes: 150 additions & 0 deletions src/openlmis-i18n/message.service.js
Original file line number Diff line number Diff line change
@@ -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 [email protected]
*/

(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;
}
}

})();
176 changes: 176 additions & 0 deletions src/openlmis-i18n/message.service.spec.js
Original file line number Diff line number Diff line change
@@ -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 [email protected]
*/

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);
});
});
});

0 comments on commit 6d5abd3

Please sign in to comment.