Extend lang with optional format and placeholders
This commit is contained in:
parent
54b25da4b7
commit
ca2edfa08b
@ -17,6 +17,8 @@
|
|||||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import uUtils from './utils.js';
|
||||||
|
|
||||||
export default class uLang {
|
export default class uLang {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.strings = {};
|
this.strings = {};
|
||||||
@ -35,10 +37,18 @@ export default class uLang {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_(name) {
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @param {...(string|number)=} params Optional parameters
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
_(name, ...params) {
|
||||||
if (typeof this.strings[name] === 'undefined') {
|
if (typeof this.strings[name] === 'undefined') {
|
||||||
throw new Error('Unknown localized string');
|
throw new Error('Unknown localized string');
|
||||||
}
|
}
|
||||||
|
if (params.length) {
|
||||||
|
return uUtils.sprintf(this.strings[name], ...params);
|
||||||
|
}
|
||||||
return this.strings[name];
|
return this.strings[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ export default class GoogleMapsApi {
|
|||||||
};
|
};
|
||||||
window.gm_authFailure = () => {
|
window.gm_authFailure = () => {
|
||||||
GoogleMapsApi.authError = true;
|
GoogleMapsApi.authError = true;
|
||||||
let message = uUtils.sprintf($._('apifailure'), 'Google Maps');
|
let message = $._('apifailure', 'Google Maps');
|
||||||
message += '<br><br>' + $._('gmauthfailure');
|
message += '<br><br>' + $._('gmauthfailure');
|
||||||
message += '<br><br>' + $._('gmapilink');
|
message += '<br><br>' + $._('gmapilink');
|
||||||
if (GoogleMapsApi.gmInitialized) {
|
if (GoogleMapsApi.gmInitialized) {
|
||||||
|
@ -90,7 +90,7 @@ export default class MapViewModel extends ViewModel {
|
|||||||
this.api.init()
|
this.api.init()
|
||||||
.then(() => this.onReady())
|
.then(() => this.onReady())
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
let txt = uUtils.sprintf($._('apifailure'), apiName);
|
let txt = $._('apifailure', apiName);
|
||||||
if (e && e.message) {
|
if (e && e.message) {
|
||||||
txt += ` (${e.message})`;
|
txt += ` (${e.message})`;
|
||||||
}
|
}
|
||||||
@ -181,7 +181,7 @@ export default class MapViewModel extends ViewModel {
|
|||||||
${(pos.altitude !== null) ? `<img class="icon" alt="${$._('altitude')}" title="${$._('altitude')}" src="images/altitude_dark.svg">${$.getLocaleAltitude(pos.altitude, true)}<br>` : ''}
|
${(pos.altitude !== null) ? `<img class="icon" alt="${$._('altitude')}" title="${$._('altitude')}" src="images/altitude_dark.svg">${$.getLocaleAltitude(pos.altitude, true)}<br>` : ''}
|
||||||
${(pos.accuracy !== null) ? `<img class="icon" alt="${$._('accuracy')}" title="${$._('accuracy')}" src="images/accuracy_dark.svg">${$.getLocaleAccuracy(pos.accuracy, true)}${provider}<br>` : ''}
|
${(pos.accuracy !== null) ? `<img class="icon" alt="${$._('accuracy')}" title="${$._('accuracy')}" src="images/accuracy_dark.svg">${$.getLocaleAccuracy(pos.accuracy, true)}${provider}<br>` : ''}
|
||||||
</div>${stats}</div>
|
</div>${stats}</div>
|
||||||
<div id="pfooter">${uUtils.sprintf($._('pointof'), id + 1, count)}</div>
|
<div id="pfooter">${$._('pointof', id + 1, count)}</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +158,7 @@ export default class TrackViewModel extends ViewModel {
|
|||||||
const form = this.importEl.parentElement;
|
const form = this.importEl.parentElement;
|
||||||
const sizeMax = form.elements['MAX_FILE_SIZE'].value;
|
const sizeMax = form.elements['MAX_FILE_SIZE'].value;
|
||||||
if (this.importEl.files && this.importEl.files.length === 1 && this.importEl.files[0].size > sizeMax) {
|
if (this.importEl.files && this.importEl.files.length === 1 && this.importEl.files[0].size > sizeMax) {
|
||||||
uUtils.error(uUtils.sprintf($._('isizefailure'), sizeMax));
|
uUtils.error($._('isizefailure', sizeMax));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!auth.isAuthenticated) {
|
if (!auth.isAuthenticated) {
|
||||||
@ -169,7 +169,7 @@ export default class TrackViewModel extends ViewModel {
|
|||||||
.then((trackList) => {
|
.then((trackList) => {
|
||||||
if (trackList.length) {
|
if (trackList.length) {
|
||||||
if (trackList.length > 1) {
|
if (trackList.length > 1) {
|
||||||
alert(uUtils.sprintf($._('imultiple'), trackList.length));
|
alert($._('imultiple', trackList.length));
|
||||||
}
|
}
|
||||||
this.model.trackList = trackList.concat(this.model.trackList);
|
this.model.trackList = trackList.concat(this.model.trackList);
|
||||||
this.model.currentTrackId = trackList[0].listValue;
|
this.model.currentTrackId = trackList[0].listValue;
|
||||||
|
@ -41,16 +41,23 @@ export default class uUtils {
|
|||||||
* @param {...(string|number)=} params Optional parameters
|
* @param {...(string|number)=} params Optional parameters
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
static sprintf(fmt, params) { // eslint-disable-line no-unused-vars
|
static sprintf(fmt, ...params) {
|
||||||
const args = Array.prototype.slice.call(arguments);
|
|
||||||
const format = args.shift();
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
return format.replace(/%%|%s|%d/g, (match) => {
|
const ret = fmt.replace(/%%|%s|%d/g, (match) => {
|
||||||
if (match === '%%') {
|
if (match === '%%') {
|
||||||
return '%';
|
return '%';
|
||||||
|
} else if (match === '%d' && isNaN(params[i])) {
|
||||||
|
throw new Error(`Wrong format specifier ${match} for ${params[i]} argument`);
|
||||||
}
|
}
|
||||||
return (typeof args[i] !== 'undefined') ? args[i++] : match;
|
if (typeof params[i] === 'undefined') {
|
||||||
|
throw new Error(`Missing argument for format specifier ${match}`);
|
||||||
|
}
|
||||||
|
return params[i++];
|
||||||
});
|
});
|
||||||
|
if (i < params.length) {
|
||||||
|
throw new Error(`Unused argument for format specifier ${fmt}`);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,6 +39,7 @@ describe('Lang tests', () => {
|
|||||||
};
|
};
|
||||||
mockStrings = {
|
mockStrings = {
|
||||||
string1: 'łańcuch1',
|
string1: 'łańcuch1',
|
||||||
|
placeholders: '%s : %d',
|
||||||
units: 'jp',
|
units: 'jp',
|
||||||
unitd: 'jo',
|
unitd: 'jo',
|
||||||
unitdm: 'jo / 1000',
|
unitdm: 'jo / 1000',
|
||||||
@ -66,6 +67,15 @@ describe('Lang tests', () => {
|
|||||||
expect(lang._('string1')).toBe(mockStrings.string1);
|
expect(lang._('string1')).toBe(mockStrings.string1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return localized string with replaced placeholders', () => {
|
||||||
|
// when
|
||||||
|
lang.init(mockConfig, mockStrings);
|
||||||
|
const p1 = 'str';
|
||||||
|
const p2 = 4;
|
||||||
|
// then
|
||||||
|
expect(lang._('placeholders', p1, p2)).toBe(`${p1} : ${p2}`);
|
||||||
|
});
|
||||||
|
|
||||||
it('should throw error on unknown string', () => {
|
it('should throw error on unknown string', () => {
|
||||||
// when
|
// when
|
||||||
lang.init(mockConfig, mockStrings);
|
lang.init(mockConfig, mockStrings);
|
||||||
|
@ -257,7 +257,6 @@ describe('MapViewModel tests', () => {
|
|||||||
it('should get popup html content', () => {
|
it('should get popup html content', () => {
|
||||||
// given
|
// given
|
||||||
const id = 0;
|
const id = 0;
|
||||||
spyOn(uUtils, 'sprintf');
|
|
||||||
state.currentTrack = TrackFactory.getTrack(2);
|
state.currentTrack = TrackFactory.getTrack(2);
|
||||||
// when
|
// when
|
||||||
const html = vm.getPopupHtml(id);
|
const html = vm.getPopupHtml(id);
|
||||||
@ -265,8 +264,8 @@ describe('MapViewModel tests', () => {
|
|||||||
// then
|
// then
|
||||||
expect(element).toBeInstanceOf(HTMLDivElement);
|
expect(element).toBeInstanceOf(HTMLDivElement);
|
||||||
expect(element.id).toBe('popup');
|
expect(element.id).toBe('popup');
|
||||||
expect(uUtils.sprintf.calls.mostRecent().args[1]).toBe(id + 1);
|
expect(lang._.calls.mostRecent().args[1]).toBe(id + 1);
|
||||||
expect(uUtils.sprintf.calls.mostRecent().args[2]).toBe(state.currentTrack.length);
|
expect(lang._.calls.mostRecent().args[2]).toBe(state.currentTrack.length);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get popup with stats when track does not contain only latest positions', () => {
|
it('should get popup with stats when track does not contain only latest positions', () => {
|
||||||
|
@ -451,7 +451,7 @@ describe('TrackViewModel tests', () => {
|
|||||||
return Promise.resolve(imported);
|
return Promise.resolve(imported);
|
||||||
});
|
});
|
||||||
spyOn(uPositionSet, 'fetch').and.returnValue(Promise.resolve(positions));
|
spyOn(uPositionSet, 'fetch').and.returnValue(Promise.resolve(positions));
|
||||||
spyOn(uUtils, 'sprintf');
|
spyOn(window, 'alert');
|
||||||
const options = '<option selected value="1">track1</option><option value="2">track2</option>';
|
const options = '<option selected value="1">track1</option><option value="2">track2</option>';
|
||||||
trackEl.insertAdjacentHTML('afterbegin', options);
|
trackEl.insertAdjacentHTML('afterbegin', options);
|
||||||
const optLength = trackEl.options.length;
|
const optLength = trackEl.options.length;
|
||||||
@ -476,7 +476,7 @@ describe('TrackViewModel tests', () => {
|
|||||||
expect(state.currentTrack).toBe(imported[0]);
|
expect(state.currentTrack).toBe(imported[0]);
|
||||||
expect(vm.model.currentTrackId).toBe(imported[0].listValue);
|
expect(vm.model.currentTrackId).toBe(imported[0].listValue);
|
||||||
expect(state.currentTrack.length).toBe(positions.length);
|
expect(state.currentTrack.length).toBe(positions.length);
|
||||||
expect(uUtils.sprintf.calls.mostRecent().args[1]).toBe(imported.length);
|
expect(window.alert).toHaveBeenCalledTimes(1);
|
||||||
expect(trackEl.options.length).toBe(optLength + imported.length);
|
expect(trackEl.options.length).toBe(optLength + imported.length);
|
||||||
expect(vm.model.trackList.length).toBe(optLength + imported.length);
|
expect(vm.model.trackList.length).toBe(optLength + imported.length);
|
||||||
expect(vm.model.inputFile).toBe('');
|
expect(vm.model.inputFile).toBe('');
|
||||||
@ -493,7 +493,6 @@ describe('TrackViewModel tests', () => {
|
|||||||
];
|
];
|
||||||
spyOn(uTrack, 'import').and.returnValue(Promise.resolve(imported));
|
spyOn(uTrack, 'import').and.returnValue(Promise.resolve(imported));
|
||||||
spyOn(uPositionSet, 'fetch').and.returnValue(Promise.resolve(positions));
|
spyOn(uPositionSet, 'fetch').and.returnValue(Promise.resolve(positions));
|
||||||
spyOn(uUtils, 'sprintf');
|
|
||||||
spyOn(uUtils, 'error');
|
spyOn(uUtils, 'error');
|
||||||
const options = '<option selected value="1">track1</option><option value="2">track2</option>';
|
const options = '<option selected value="1">track1</option><option value="2">track2</option>';
|
||||||
trackEl.insertAdjacentHTML('afterbegin', options);
|
trackEl.insertAdjacentHTML('afterbegin', options);
|
||||||
@ -517,7 +516,7 @@ describe('TrackViewModel tests', () => {
|
|||||||
expect(uTrack.import).not.toHaveBeenCalled();
|
expect(uTrack.import).not.toHaveBeenCalled();
|
||||||
expect(state.currentTrack).toBe(track1);
|
expect(state.currentTrack).toBe(track1);
|
||||||
expect(vm.model.currentTrackId).toBe(track1.listValue);
|
expect(vm.model.currentTrackId).toBe(track1.listValue);
|
||||||
expect(uUtils.sprintf.calls.mostRecent().args[1]).toBe(MAX_FILE_SIZE.toString());
|
expect(lang._.calls.mostRecent().args[1]).toBe(MAX_FILE_SIZE.toString());
|
||||||
expect(trackEl.options.length).toBe(optLength);
|
expect(trackEl.options.length).toBe(optLength);
|
||||||
expect(vm.model.trackList.length).toBe(optLength);
|
expect(vm.model.trackList.length).toBe(optLength);
|
||||||
done();
|
done();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user