Skip to content

Commit e068add

Browse files
Di PengIgorMinar
Di Peng
authored andcommitted
feat(widget): add ng:pluralize as an Angular widget
1 parent 0da4902 commit e068add

File tree

2 files changed

+318
-0
lines changed

2 files changed

+318
-0
lines changed

src/widgets.js

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1445,3 +1445,195 @@ angularWidget('ng:view', function(element) {
14451445
compiler.directives(true);
14461446
}
14471447
});
1448+
1449+
1450+
/**
1451+
* @ngdoc widget
1452+
* @name angular.widget.ng:pluralize
1453+
*
1454+
* @description
1455+
* # Overview
1456+
* ng:pluralize is a widget that displays messages according to en-US localization rules.
1457+
* These rules are bundled with angular.js and the rules can be overridden
1458+
* (see {@link guide/dev_guide.i18n Angular i18n} dev guide). You configure ng:pluralize by
1459+
* specifying the mappings between
1460+
* {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
1461+
* plural categories} and the strings to be displayed.
1462+
*
1463+
* # Plural categories and explicit number rules
1464+
* There are two
1465+
* {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
1466+
* plural categories} in Angular's default en-US locale: "one" and "other".
1467+
*
1468+
* While a pural category may match many numbers (for example, in en-US locale, "other" can match
1469+
* any number that is not 1), an explicit number rule can only match one number. For example, the
1470+
* explicit number rule for "3" matches the number 3. You will see the use of plural categories
1471+
* and explicit number rules throughout later parts of this documentation.
1472+
*
1473+
* # Configuring ng:pluralize
1474+
* You configure ng:pluralize by providing 2 attributes: `count` and `when`.
1475+
* You can also provide an optional attribute, `offset`.
1476+
*
1477+
* The value of the `count` attribute can be either a string or an {@link guide/dev_guide.expressions
1478+
* Angular expression}; these are evaluated on the current scope for its binded value.
1479+
*
1480+
* The `when` attribute specifies the mappings between plural categories and the actual
1481+
* string to be displayed. The value of the attribute should be a JSON object so that Angular
1482+
* can interpret it correctly.
1483+
*
1484+
* The following example shows how to configure ng:pluralize:
1485+
*
1486+
* <pre>
1487+
* <ng:pluralize count="personCount"
1488+
when="{'0': 'Nobody is viewing.',
1489+
* 'one': '1 person is viewing.',
1490+
* 'other': '{} people are viewing.'}">
1491+
* </ng:pluralize>
1492+
*</pre>
1493+
*
1494+
* In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not
1495+
* specify this rule, 0 would be matched to the "other" category and "0 people are viewing"
1496+
* would be shown instead of "Nobody is viewing". You can specify an explicit number rule for
1497+
* other numbers, for example 12, so that instead of showing "12 people are viewing", you can
1498+
* show "a dozen people are viewing".
1499+
*
1500+
* You can use a set of closed braces(`{}`) as a placeholder for the number that you want substituted
1501+
* into pluralized strings. In the previous example, Angular will replace `{}` with
1502+
* <span ng:non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
1503+
* for <span ng:non-bindable>{{numberExpression}}</span>.
1504+
*
1505+
* # Configuring ng:pluralize with offset
1506+
* The `offset` attribute allows further customization of pluralized text, which can result in
1507+
* a better user experience. For example, instead of the message "4 people are viewing this document",
1508+
* you might display "John, Kate and 2 others are viewing this document".
1509+
* The offset attribute allows you to offset a number by any desired value.
1510+
* Let's take a look at an example:
1511+
*
1512+
* <pre>
1513+
* <ng:pluralize count="personCount" offset=2
1514+
* when="{'0': 'Nobody is viewing.',
1515+
* '1': '{{person1}} is viewing.',
1516+
* '2': '{{person1}} and {{person2}} are viewing.',
1517+
* 'one': '{{person1}}, {{person2}} and one other person are viewing.',
1518+
* 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
1519+
* </ng:pluralize>
1520+
* </pre>
1521+
*
1522+
* Notice that we are still using two plural categories(one, other), but we added
1523+
* three explicit number rules 0, 1 and 2.
1524+
* When one person, perhaps John, views the document, "John is viewing" will be shown.
1525+
* When three people view the document, no explicit number rule is found, so
1526+
* an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category.
1527+
* In this case, plural category 'one' is matched and "John, Marry and one other person are viewing"
1528+
* is shown.
1529+
*
1530+
* Note that when you specify offsets, you must provide explicit number rules for
1531+
* numbers from 0 up to and including the offset. If you use an offset of 3, for example,
1532+
* you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for
1533+
* plural categories "one" and "other".
1534+
*
1535+
* @param {string|expression} count The variable to be bounded to.
1536+
* @param {string} when The mapping between plural category to its correspoding strings.
1537+
* @param {number=} offset Offset to deduct from the total number.
1538+
*
1539+
* @example
1540+
<doc:example>
1541+
<doc:source>
1542+
Person 1:<input type="text" name="person1" value="Igor" /><br/>
1543+
Person 2:<input type="text" name="person2" value="Misko" /><br/>
1544+
Number of People:<input type="text" name="personCount" value="1" /><br/>
1545+
1546+
<!--- Example with simple pluralization rules for en locale --->
1547+
Without Offset:
1548+
<ng:pluralize count="personCount"
1549+
when="{'0': 'Nobody is viewing.',
1550+
'one': '1 person is viewing.',
1551+
'other': '{} people are viewing.'}">
1552+
</ng:pluralize><br>
1553+
1554+
<!--- Example with offset --->
1555+
With Offset(2):
1556+
<ng:pluralize count="personCount" offset=2
1557+
when="{'0': 'Nobody is viewing.',
1558+
'1': '{{person1}} is viewing.',
1559+
'2': '{{person1}} and {{person2}} are viewing.',
1560+
'one': '{{person1}}, {{person2}} and one other person are viewing.',
1561+
'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
1562+
</ng:pluralize>
1563+
</doc:source>
1564+
<doc:scenario>
1565+
it('should show correct pluralized string', function(){
1566+
expect(element('.doc-example-live .ng-pluralize:first').text()).
1567+
toBe('1 person is viewing.');
1568+
expect(element('.doc-example-live .ng-pluralize:last').text()).
1569+
toBe('Igor is viewing.');
1570+
1571+
using('.doc-example-live').input('personCount').enter('0');
1572+
expect(element('.doc-example-live .ng-pluralize:first').text()).
1573+
toBe('Nobody is viewing.');
1574+
expect(element('.doc-example-live .ng-pluralize:last').text()).
1575+
toBe('Nobody is viewing.');
1576+
1577+
using('.doc-example-live').input('personCount').enter('2');
1578+
expect(element('.doc-example-live .ng-pluralize:first').text()).
1579+
toBe('2 people are viewing.');
1580+
expect(element('.doc-example-live .ng-pluralize:last').text()).
1581+
toBe('Igor and Misko are viewing.');
1582+
1583+
using('.doc-example-live').input('personCount').enter('3');
1584+
expect(element('.doc-example-live .ng-pluralize:first').text()).
1585+
toBe('3 people are viewing.');
1586+
expect(element('.doc-example-live .ng-pluralize:last').text()).
1587+
toBe('Igor, Misko and one other person are viewing.');
1588+
1589+
using('.doc-example-live').input('personCount').enter('4');
1590+
expect(element('.doc-example-live .ng-pluralize:first').text()).
1591+
toBe('4 people are viewing.');
1592+
expect(element('.doc-example-live .ng-pluralize:last').text()).
1593+
toBe('Igor, Misko and 2 other people are viewing.');
1594+
});
1595+
1596+
it('should show data-binded names', function(){
1597+
using('.doc-example-live').input('personCount').enter('4');
1598+
expect(element('.doc-example-live .ng-pluralize:last').text()).
1599+
toBe('Igor, Misko and 2 other people are viewing.');
1600+
1601+
using('.doc-example-live').input('person1').enter('Di');
1602+
using('.doc-example-live').input('person2').enter('Vojta');
1603+
expect(element('.doc-example-live .ng-pluralize:last').text()).
1604+
toBe('Di, Vojta and 2 other people are viewing.');
1605+
});
1606+
</doc:scenario>
1607+
</doc:example>
1608+
*/
1609+
angularWidget('ng:pluralize', function(element) {
1610+
var numberExp = element.attr('count'),
1611+
whenExp = element.attr('when'),
1612+
offset = element.attr('offset') || 0;
1613+
1614+
return annotate('$locale', function($locale, element) {
1615+
var scope = this,
1616+
whens = scope.$eval(whenExp),
1617+
whensExpFns = {};
1618+
1619+
forEach(whens, function(expression, key) {
1620+
whensExpFns[key] = compileBindTemplate(expression.replace(/{}/g,
1621+
'{{' + numberExp + '-' + offset + '}}'));
1622+
});
1623+
1624+
scope.$watch(function() {
1625+
var value = parseFloat(scope.$eval(numberExp));
1626+
1627+
if (!isNaN(value)) {
1628+
//if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise,
1629+
//check it against pluralization rules in $locale service
1630+
if (!whens[value]) value = $locale.pluralCat(value - offset);
1631+
return whensExpFns[value](scope, element, true);
1632+
} else {
1633+
return '';
1634+
}
1635+
}, function(scope, newVal) {
1636+
element.text(newVal);
1637+
});
1638+
});
1639+
});

test/widgetsSpec.js

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,5 +1236,131 @@ describe("widget", function(){
12361236
expect(rootScope.log).toEqual(['parent', 'init', 'child']);
12371237
});
12381238
});
1239+
1240+
1241+
describe('ng:pluralize', function() {
1242+
describe('deal with pluralized strings without offset', function() {
1243+
beforeEach(function() {
1244+
compile('<ng:pluralize count="email"' +
1245+
"when=\"{'0': 'You have no new email'," +
1246+
"'one': 'You have one new email'," +
1247+
"'other': 'You have {} new emails'}\">" +
1248+
'</ng:pluralize>');
1249+
});
1250+
1251+
it('should show single/plural strings', function() {
1252+
scope.email = 0;
1253+
scope.$digest();
1254+
expect(element.text()).toBe('You have no new email');
1255+
1256+
scope.email = '0';
1257+
scope.$digest();
1258+
expect(element.text()).toBe('You have no new email');
1259+
1260+
scope.email = 1;
1261+
scope.$digest();
1262+
expect(element.text()).toBe('You have one new email');
1263+
1264+
scope.email = 0.01;
1265+
scope.$digest();
1266+
expect(element.text()).toBe('You have 0.01 new emails');
1267+
1268+
scope.email = '0.1';
1269+
scope.$digest();
1270+
expect(element.text()).toBe('You have 0.1 new emails');
1271+
1272+
scope.email = 2;
1273+
scope.$digest();
1274+
expect(element.text()).toBe('You have 2 new emails');
1275+
1276+
scope.email = -0.1;
1277+
scope.$digest();
1278+
expect(element.text()).toBe('You have -0.1 new emails');
1279+
1280+
scope.email = '-0.01';
1281+
scope.$digest();
1282+
expect(element.text()).toBe('You have -0.01 new emails');
1283+
1284+
scope.email = -2;
1285+
scope.$digest();
1286+
expect(element.text()).toBe('You have -2 new emails');
1287+
});
1288+
1289+
1290+
it('should show single/plural strings with mal-formed inputs', function() {
1291+
scope.email = '';
1292+
scope.$digest();
1293+
expect(element.text()).toBe('');
1294+
1295+
scope.email = null;
1296+
scope.$digest();
1297+
expect(element.text()).toBe('');
1298+
1299+
scope.email = undefined;
1300+
scope.$digest();
1301+
expect(element.text()).toBe('');
1302+
1303+
scope.email = 'a3';
1304+
scope.$digest();
1305+
expect(element.text()).toBe('');
1306+
1307+
scope.email = '011';
1308+
scope.$digest();
1309+
expect(element.text()).toBe('You have 11 new emails');
1310+
1311+
scope.email = '-011';
1312+
scope.$digest();
1313+
expect(element.text()).toBe('You have -11 new emails');
1314+
1315+
scope.email = '1fff';
1316+
scope.$digest();
1317+
expect(element.text()).toBe('You have one new email');
1318+
1319+
scope.email = '0aa22';
1320+
scope.$digest();
1321+
expect(element.text()).toBe('You have no new email');
1322+
1323+
scope.email = '000001';
1324+
scope.$digest();
1325+
expect(element.text()).toBe('You have one new email');
1326+
});
1327+
});
1328+
1329+
1330+
describe('deal with pluralized strings with offset', function() {
1331+
it('should show single/plural strings with offset', function() {
1332+
compile("<ng:pluralize count=\"viewCount\" offset=2 " +
1333+
"when=\"{'0': 'Nobody is viewing.'," +
1334+
"'1': '{{p1}} is viewing.'," +
1335+
"'2': '{{p1}} and {{p2}} are viewing.'," +
1336+
"'one': '{{p1}}, {{p2}} and one other person are viewing.'," +
1337+
"'other': '{{p1}}, {{p2}} and {} other people are viewing.'}\">" +
1338+
"</ng:pluralize>");
1339+
scope.p1 = 'Igor';
1340+
scope.p2 = 'Misko';
1341+
1342+
scope.viewCount = 0;
1343+
scope.$digest();
1344+
expect(element.text()).toBe('Nobody is viewing.');
1345+
1346+
scope.viewCount = 1;
1347+
scope.$digest();
1348+
expect(element.text()).toBe('Igor is viewing.');
1349+
1350+
scope.viewCount = 2;
1351+
scope.$digest();
1352+
expect(element.text()).toBe('Igor and Misko are viewing.');
1353+
1354+
scope.viewCount = 3;
1355+
scope.$digest();
1356+
expect(element.text()).toBe('Igor, Misko and one other person are viewing.');
1357+
1358+
scope.viewCount = 4;
1359+
scope.$digest();
1360+
expect(element.text()).toBe('Igor, Misko and 2 other people are viewing.');
1361+
});
1362+
});
1363+
1364+
});
12391365
});
12401366

0 commit comments

Comments
 (0)