Skip to content

Commit cf21b91

Browse files
authored
Merge pull request #38 from rmariuzzo/fix-lang-choice
Implemented math intervals.
2 parents 117a9e0 + 1f604a8 commit cf21b91

File tree

3 files changed

+92
-10
lines changed

3 files changed

+92
-10
lines changed

dist/lang.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lang.js

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,19 @@
3030
}
3131
};
3232

33+
function convertNumber(str) {
34+
if (str === '-Inf') {
35+
return -Infinity;
36+
} else if (str === '+Inf' || str === 'Inf') {
37+
return Infinity;
38+
}
39+
return parseInt(str, 10);
40+
}
41+
42+
// Derived from: https://github.com/symfony/translation/blob/460390765eb7bb9338a4a323b8a4e815a47541ba/Interval.php
43+
var intervalRegexp = /^({\s*(\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*)\s*})|([\[\]])\s*(-Inf|\-?\d+(\.\d+)?)\s*,\s*(\+?Inf|\-?\d+(\.\d+)?)\s*([\[\]])$/;
44+
var anyIntervalRegexp = /({\s*(\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*)\s*})|([\[\]])\s*(-Inf|\-?\d+(\.\d+)?)\s*,\s*(\+?Inf|\-?\d+(\.\d+)?)\s*([\[\]])/;
45+
3346
// Default options //
3447

3548
var defaults = {
@@ -184,12 +197,11 @@
184197

185198
// Get the explicit rules, If any
186199
var explicitRules = [];
187-
var regex = /{\d+}\s(.+)|\[\d+,\d+\]\s(.+)|\[\d+,Inf\]\s(.+)/;
188200

189201
for (var i = 0; i < messageParts.length; i++) {
190202
messageParts[i] = messageParts[i].trim();
191203

192-
if (regex.test(messageParts[i])) {
204+
if (anyIntervalRegexp.test(messageParts[i])) {
193205
var messageSpaceSplit = messageParts[i].split(/\s/);
194206
explicitRules.push(messageSpaceSplit.shift());
195207
messageParts[i] = messageSpaceSplit.join(' ');
@@ -297,22 +309,63 @@
297309
/**
298310
* Checks if the given `count` is within the interval defined by the {string} `interval`
299311
*
300-
* @param count {int} The amount of items.
301-
* @param interval {string} The interval to be compared with the count.
302-
* @return {boolean} Returns true if count is within interval; false otherwise.
312+
* @param count {int} The amount of items.
313+
* @param interval {string} The interval to be compared with the count.
314+
* @return {boolean} Returns true if count is within interval; false otherwise.
303315
*/
304316
Lang.prototype._testInterval = function(count, interval) {
305317
/**
306318
* From the Symfony\Component\Translation\Interval Docs
307319
*
308320
* Tests if a given number belongs to a given math interval.
309-
* An interval can represent a finite set of numbers: {1,2,3,4}
310-
* An interval can represent numbers between two numbers: [1, +Inf] ]-1,2[
321+
*
322+
* An interval can represent a finite set of numbers:
323+
*
324+
* {1,2,3,4}
325+
*
326+
* An interval can represent numbers between two numbers:
327+
*
328+
* [1, +Inf]
329+
* ]-1,2[
330+
*
311331
* The left delimiter can be [ (inclusive) or ] (exclusive).
312332
* The right delimiter can be [ (exclusive) or ] (inclusive).
313333
* Beside numbers, you can use -Inf and +Inf for the infinite.
314334
*/
315335

336+
if (typeof interval !== 'string') {
337+
throw 'Invalid interval: should be a string.';
338+
}
339+
340+
interval = interval.trim();
341+
342+
var matches = interval.match(intervalRegexp);
343+
if (!matches) {
344+
throw new 'Invalid interval: ' + interval;
345+
}
346+
347+
if (matches[2]) {
348+
var items = matches[2].split(',');
349+
for (var i = 0; i < items.length; i++) {
350+
if (parseInt(items[i], 10) === count) {
351+
return true;
352+
}
353+
}
354+
} else {
355+
// Remove falsy values.
356+
matches = matches.filter(function(match) {
357+
return !!match;
358+
});
359+
360+
var leftDelimiter = matches[1];
361+
var leftNumber = convertNumber(matches[2]);
362+
var rightNumber = convertNumber(matches[3]);
363+
var rightDelimiter = matches[4];
364+
365+
return (leftDelimiter === '[' ? count >= leftNumber : count > leftNumber)
366+
&& (rightDelimiter === ']' ? count <= rightNumber : count < rightNumber);
367+
}
368+
316369
return false;
317370
};
318371

test/spec/lang_choice_spec.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,47 @@ describe('The lang.choice() method', function () {
3131
expect(lang.choice('messages.plural', 10)).toBe('a million apples');
3232
});
3333

34-
it('should return the expected message', function () {
35-
lang.setLocale('en');
34+
it('should return the expected message with different count', function () {
35+
lang.setMessages({
36+
'en.plural': {
37+
'year': ':count year|:count years'
38+
}
39+
});
3640
expect(lang.choice('plural.year', 1)).toBe('1 year');
3741
expect(lang.choice('plural.year', 2)).toBe('2 years');
3842
expect(lang.choice('plural.year', 5)).toBe('5 years');
3943

44+
lang.setMessages({
45+
'ru.plural': {
46+
'year': ':count год|:count года|:count лет'
47+
}
48+
});
4049
lang.setLocale('ru');
4150
expect(lang.choice('plural.year', 1)).toBe('1 год');
4251
expect(lang.choice('plural.year', 2)).toBe('2 года');
4352
expect(lang.choice('plural.year', 5)).toBe('5 лет');
4453
});
4554

55+
it('should return the expected message using math intervals', function() {
56+
lang.setMessages({
57+
'en.test': {
58+
'set': '{0} a|{1} :count b|[2,Inf] :count c',
59+
'range': '[0,10] a| [11,20] b|[21,30] c',
60+
'infinity': '[-Inf,-1] :count Negative|[0,+Inf] :count Positive',
61+
}
62+
});
63+
expect(lang.choice('test.set', 0)).toBe('a');
64+
expect(lang.choice('test.set', 1)).toBe('1 b');
65+
expect(lang.choice('test.set', 2)).toBe('2 c');
66+
expect(lang.choice('test.range', 0)).toBe('a');
67+
expect(lang.choice('test.range', 15)).toBe('b');
68+
expect(lang.choice('test.range', 30)).toBe('c');
69+
expect(lang.choice('test.infinity', -Infinity)).toBe('-Infinity Negative');
70+
expect(lang.choice('test.infinity', -5)).toBe('-5 Negative');
71+
expect(lang.choice('test.infinity', 5)).toBe('5 Positive');
72+
expect(lang.choice('test.infinity', Infinity)).toBe('Infinity Positive');
73+
});
74+
4675
it('should return the expected message with replacements', function () {
4776
expect(lang.choice('validation.accepted', 1)).toBe('The :attribute must be accepted.');
4877
expect(lang.choice('validation.accepted', 1, {

0 commit comments

Comments
 (0)