Skip to content
This repository was archived by the owner on Aug 30, 2023. It is now read-only.

Commit 5644b15

Browse files
Dart Teamalan-knight
Dart Team
authored andcommitted
Updating plural rules to support num instead of int
Subset of cr/247298271 See this document for more context: https://docs.google.com/document/d/103PiFk2oopzG2ZT8n1TbWL2lMPgiXiEI3o05Eq45Lr4 The solution part is not up to date, but the problems identified are still valid. That is what I am trying to solve, by improving the existing class, no a rewrite. PiperOrigin-RevId: 258316144
1 parent 64450cb commit 5644b15

File tree

3 files changed

+145
-40
lines changed

3 files changed

+145
-40
lines changed

lib/intl.dart

+42-23
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ class Intl {
173173
/// the placeholder automatically translated.
174174
static String message(String message_str,
175175
{String desc: '',
176-
Map<String, Object> examples: const {},
176+
Map<String, Object> examples,
177177
String locale,
178178
String name,
179179
List<Object> args,
@@ -267,11 +267,17 @@ class Intl {
267267
return '${aLocale[0]}${aLocale[1]}_$region';
268268
}
269269

270-
/// Format a message differently depending on [howMany]. Normally used
271-
/// as part of an `Intl.message` text that is to be translated.
272-
/// Selects the correct plural form from
273-
/// the provided alternatives. The [other] named argument is mandatory.
274-
static String plural(int howMany,
270+
/// Formats a message differently depending on [howMany].
271+
///
272+
/// Selects the correct plural form from the provided alternatives.
273+
/// The [other] named argument is mandatory.
274+
/// The [precision] is the number of fractional digits that would be rendered
275+
/// when [howMany] is formatted. In some cases just knowing the numeric value
276+
/// of [howMany] itsef is not enough, for example "1 mile" vs "1.00 miles"
277+
///
278+
/// For an explanation of plurals and the [zero], [one], [two], [few], [many]
279+
/// categories see http://cldr.unicode.org/index/cldr-spec/plural-rules
280+
static String plural(num howMany,
275281
{String zero,
276282
String one,
277283
String two,
@@ -281,6 +287,7 @@ class Intl {
281287
String desc,
282288
Map<String, Object> examples,
283289
String locale,
290+
int precision,
284291
String name,
285292
List<Object> args,
286293
String meaning,
@@ -295,19 +302,21 @@ class Intl {
295302
many: many,
296303
other: other,
297304
locale: locale,
305+
precision: precision,
298306
name: name,
299307
args: args,
300308
meaning: meaning);
301309
}
302310

303-
static String _plural(int howMany,
311+
static String _plural(num howMany,
304312
{String zero,
305313
String one,
306314
String two,
307315
String few,
308316
String many,
309317
String other,
310318
String locale,
319+
int precision,
311320
String name,
312321
List<Object> args,
313322
String meaning}) {
@@ -325,29 +334,40 @@ class Intl {
325334
few: few,
326335
many: many,
327336
other: other,
328-
locale: locale);
337+
locale: locale,
338+
precision: precision);
329339
}
330340

331341
/// Internal: Implements the logic for plural selection - use [plural] for
332342
/// normal messages.
333-
static pluralLogic(int howMany,
334-
{zero, one, two, few, many, other, String locale, String meaning}) {
343+
static pluralLogic(num howMany,
344+
{zero, one, two, few, many, other, String locale, int precision,
345+
String meaning}) {
335346
if (other == null) {
336347
throw new ArgumentError("The 'other' named argument must be provided");
337348
}
338349
if (howMany == null) {
339350
throw new ArgumentError("The howMany argument to plural cannot be null");
340351
}
341-
// If there's an explicit case for the exact number, we use it. This is not
342-
// strictly in accord with the CLDR rules, but it seems to be the
343-
// expectation. At least I see e.g. Russian translations that have a zero
344-
// case defined. The rule for that locale will never produce a zero, and
345-
// treats it as other. But it seems reasonable that, even if the language
346-
// rules treat zero as other, we might want a special message for zero.
347-
if (howMany == 0 && zero != null) return zero;
348-
if (howMany == 1 && one != null) return one;
349-
if (howMany == 2 && two != null) return two;
350-
var pluralRule = _pluralRule(locale, howMany);
352+
353+
// This is for backward compatibility.
354+
// We interpret the presence of [precision] parameter as an "opt-in" to
355+
// the new behavior, since [precision] did not exist before.
356+
// For an English example: if the precision is 2 then the formatted string
357+
// would not map to 'one' (for example "1.00 miles")
358+
if (precision == null || precision == 0) {
359+
// If there's an explicit case for the exact number, we use it. This is
360+
// not strictly in accord with the CLDR rules, but it seems to be the
361+
// expectation. At least I see e.g. Russian translations that have a zero
362+
// case defined. The rule for that locale will never produce a zero, and
363+
// treats it as other. But it seems reasonable that, even if the language
364+
// rules treat zero as other, we might want a special message for zero.
365+
if (howMany == 0 && zero != null) return zero;
366+
if (howMany == 1 && one != null) return one;
367+
if (howMany == 2 && two != null) return two;
368+
}
369+
370+
var pluralRule = _pluralRule(locale, howMany, precision);
351371
var pluralCase = pluralRule();
352372
switch (pluralCase) {
353373
case plural_rules.PluralCase.ZERO:
@@ -371,8 +391,8 @@ class Intl {
371391
static var _cachedPluralRule;
372392
static String _cachedPluralLocale;
373393

374-
static _pluralRule(String locale, int howMany) {
375-
plural_rules.startRuleEvaluation(howMany);
394+
static _pluralRule(String locale, num howMany, int precision) {
395+
plural_rules.startRuleEvaluation(howMany, precision);
376396
var verifiedLocale = Intl.verifiedLocale(
377397
locale, plural_rules.localeHasPluralRules,
378398
onFailure: (locale) => 'default');
@@ -414,7 +434,6 @@ class Intl {
414434
String male,
415435
String other,
416436
String desc,
417-
Map<String, Object> examples,
418437
String locale,
419438
String name,
420439
List<Object> args,

lib/src/plural_rules.dart

+77-17
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
/// * t - visible fractional digits in n, without trailing zeros.
1717
library plural_rules;
1818

19+
import 'dart:math' as math;
20+
1921
typedef PluralCase PluralRule();
2022

2123
/// The possible cases used in a plural rule.
@@ -26,8 +28,12 @@ PluralCase _default_rule() => OTHER;
2628

2729
/// This must be called before evaluating a new rule, because we're using
2830
/// library-global state to both keep the rules terse and minimize space.
29-
startRuleEvaluation(int howMany) {
31+
startRuleEvaluation(num howMany, [int precision = 0]) {
3032
_n = howMany;
33+
_precision = precision;
34+
_i = _n.round();
35+
_updateVF(_n, _precision);
36+
_updateWT(_f, _v);
3137
}
3238

3339
/// The number whose [PluralCase] we are trying to find.
@@ -37,27 +43,81 @@ startRuleEvaluation(int howMany) {
3743
// not introduce a subclass per locale or have instance tear-offs which
3844
// we can't cache. This is fine as long as these methods aren't async, which
3945
// they should never be.
40-
int _n;
46+
num _n;
47+
48+
/// The integer part of [_n]
49+
int _i;
50+
int _precision;
51+
52+
/// Returns the number of digits in the fractional part of a number
53+
/// (3.1416 => 4)
54+
///
55+
/// Takes the item count [n] and a [precision].
56+
/// That's because a just looking at the value of a number is not enough to
57+
/// decide the plural form. For example "1 dollar" vs "1.00 dollars", the
58+
/// value is 1, but how it is formatted also matters.
59+
int _decimals(num n, int precision) {
60+
var str = _precision == null ? '$n' : n.toStringAsFixed(precision);
61+
var result = str.indexOf('.');
62+
return (result == -1) ? 0 : str.length - result - 1;
63+
}
64+
65+
/// Calculates and sets the _v and _f as per CLDR plural rules.
66+
///
67+
/// The short names for parameters / return match the CLDR syntax and UTS #35
68+
/// (https://unicode.org/reports/tr35/tr35-numbers.html#Plural_rules_syntax)
69+
/// Takes the item count [n] and a [precision].
70+
_updateVF(num n, int precision) {
71+
int defaultDigits = 3;
72+
73+
_v = precision ?? math.min(_decimals(n, precision), defaultDigits);
74+
75+
int base = math.pow(10, _v);
76+
_f = (n * base).floor() % base;
77+
}
78+
79+
/// Calculates and sets _w and _t as per CLDR plural rules.
80+
///
81+
/// The short names for parameters / return match the CLDR syntax and UTS #35
82+
/// (https://unicode.org/reports/tr35/tr35-numbers.html#Plural_rules_syntax)
83+
/// @param v Calculated previously.
84+
/// @param f Calculated previously.
85+
_updateWT(int v, int f) {
86+
if (f == 0) {
87+
// Unused, for now _w = 0;
88+
_t = 0;
89+
return;
90+
}
91+
92+
while ((f % 10) == 0) {
93+
f = (f / 10).floor();
94+
v--;
95+
}
96+
97+
// Unused, for now _w = v;
98+
_t = f;
99+
}
41100

42-
/// The integer part of [_n] - since we only support integers, it's the same as
43-
/// [_n].
44-
int get _i => _n;
45-
int opt_precision; // Not currently used.
101+
/// Number of visible fraction digits.
102+
int _v = 0;
46103

47-
/// Number of visible fraction digits. Always zero since we only support int.
48-
int get _v => 0;
104+
/// Number of visible fraction digits without trailing zeros.
105+
// Unused, for now int _w = 0;
49106

50-
/// Number of visible fraction digits without trailing zeros. Always zero
51-
/// since we only support int.
52-
//int get _w => 0;
107+
/// The visible fraction digits in n, with trailing zeros.
108+
int _f = 0;
53109

54-
/// The visible fraction digits in n, with trailing zeros. Always zero since
55-
/// we only support int.
56-
int get _f => 0;
110+
/// The visible fraction digits in n, without trailing zeros.
111+
int _t = 0;
57112

58-
/// The visible fraction digits in n, without trailing zeros. Always zero since
59-
/// we only support int.
60-
int get _t => 0;
113+
// An example, for precision n = 3.1415 and precision = 7)
114+
// n : 3.1415
115+
// str n: 3.1415000 (the "formatted" n, 7 fractional digits)
116+
// i : 3 (the integer part of n)
117+
// f : 1415000 (the fractional part of n)
118+
// v : 7 (how many digits in f)
119+
// t : 1415 (f, without trailing 0s)
120+
// w : 4 (how many digits in t)
61121

62122
PluralCase get ZERO => PluralCase.ZERO;
63123
PluralCase get ONE => PluralCase.ONE;

test/plural_test.dart

+26
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,20 @@ main() {
195195
expect(() => plural(null, null), throwsArgumentError);
196196
expect(() => plural(null, "ru"), throwsArgumentError);
197197
});
198+
199+
verify_with_precision('1 dollar', 'en', 1, 0);
200+
// This would not work in back-compatibility for one vs. =1 in plurals,
201+
// because of this test in intl.dart:
202+
// if (howMany == 1 && one != null) return one;
203+
// That one will ignore the precision and always return one, while the
204+
// test below requires the result to be 'other'
205+
// verify_with_precision('1.00 dollars', 'en', 1, 2);
206+
207+
verify_with_precision('1 dollar', 'en', 1.2, 0);
208+
verify_with_precision('1.20 dollars', 'en', 1.2, 2);
209+
210+
verify_with_precision('3 dollars', 'en', 3.14, 0);
211+
verify_with_precision('3.14 dollars', 'en', 3.14, 2);
198212
}
199213

200214
verify(String expectedValues, String locale, pluralFunction) {
@@ -206,3 +220,15 @@ verify(String expectedValues, String locale, pluralFunction) {
206220
});
207221
}
208222
}
223+
224+
verify_with_precision(String expected, String locale, num n, int precision) {
225+
test('verify_with_precision(howMany: $n, precision: $precision)', () {
226+
var nString = n.toStringAsFixed(precision);
227+
var actual = Intl.plural(n,
228+
locale: locale,
229+
precision: precision,
230+
one: '$nString dollar',
231+
other: '$nString dollars');
232+
expect(actual, expected);
233+
});
234+
}

0 commit comments

Comments
 (0)