Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/i18n/messageformat.js
2868 views
1
// Copyright 2010 The Closure Library Authors. All Rights Reserved
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS-IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
/**
16
* @fileoverview Message/plural format library with locale support.
17
*
18
* Message format grammar:
19
*
20
* messageFormatPattern := string ( "{" messageFormatElement "}" string )*
21
* messageFormatElement := argumentIndex [ "," elementFormat ]
22
* elementFormat := "plural" "," pluralStyle
23
* | "selectordinal" "," ordinalStyle
24
* | "select" "," selectStyle
25
* pluralStyle := pluralFormatPattern
26
* ordinalStyle := selectFormatPattern
27
* selectStyle := selectFormatPattern
28
* pluralFormatPattern := [ "offset" ":" offsetIndex ] pluralForms*
29
* selectFormatPattern := pluralForms*
30
* pluralForms := stringKey "{" ( "{" messageFormatElement "}"|string )* "}"
31
*
32
* This is a subset of the ICU MessageFormatSyntax:
33
* http://userguide.icu-project.org/formatparse/messages
34
* See also http://go/plurals and http://go/ordinals for internal details.
35
*
36
*
37
* Message example:
38
*
39
* I see {NUM_PEOPLE, plural, offset:1
40
* =0 {no one at all}
41
* =1 {{WHO}}
42
* one {{WHO} and one other person}
43
* other {{WHO} and # other people}}
44
* in {PLACE}.
45
*
46
* Calling format({'NUM_PEOPLE': 2, 'WHO': 'Mark', 'PLACE': 'Athens'}) would
47
* produce "I see Mark and one other person in Athens." as output.
48
*
49
* OR:
50
*
51
* {NUM_FLOOR, selectordinal,
52
* one {Take the elevator to the #st floor.}
53
* two {Take the elevator to the #nd floor.}
54
* few {Take the elevator to the #rd floor.}
55
* other {Take the elevator to the #th floor.}}
56
*
57
* Calling format({'NUM_FLOOR': 22}) would produce
58
* "Take the elevator to the 22nd floor".
59
*
60
* See messageformat_test.html for more examples.
61
*/
62
63
goog.provide('goog.i18n.MessageFormat');
64
65
goog.require('goog.array');
66
goog.require('goog.asserts');
67
goog.require('goog.i18n.CompactNumberFormatSymbols');
68
goog.require('goog.i18n.NumberFormat');
69
goog.require('goog.i18n.NumberFormatSymbols');
70
goog.require('goog.i18n.ordinalRules');
71
goog.require('goog.i18n.pluralRules');
72
73
74
75
/**
76
* Constructor of MessageFormat.
77
* @param {string} pattern The pattern we parse and apply positional parameters
78
* to.
79
* @constructor
80
* @final
81
*/
82
goog.i18n.MessageFormat = function(pattern) {
83
/**
84
* The pattern we parse and apply positional parameters to.
85
* @type {?string}
86
* @private
87
*/
88
this.pattern_ = pattern;
89
90
/**
91
* All encountered literals during parse stage. Indices tell us the order of
92
* replacement.
93
* @type {?Array<string>}
94
* @private
95
*/
96
this.initialLiterals_ = null;
97
98
/**
99
* Working array with all encountered literals during parse and format stages.
100
* Indices tell us the order of replacement.
101
* @type {?Array<string>}
102
* @private
103
*/
104
this.literals_ = null;
105
106
/**
107
* Input pattern gets parsed into objects for faster formatting.
108
* @type {?Array<!Object>}
109
* @private
110
*/
111
this.parsedPattern_ = null;
112
113
/**
114
* Locale aware number formatter.
115
* @type {!goog.i18n.NumberFormat}
116
* @private
117
*/
118
this.numberFormatter_ = goog.i18n.MessageFormat.getNumberFormatter_();
119
};
120
121
122
/**
123
* Locale associated with the most recently created NumberFormat.
124
* @type {?Object}
125
* @private
126
*/
127
goog.i18n.MessageFormat.numberFormatterSymbols_ = null;
128
129
130
/**
131
* Locale associated with the most recently created NumberFormat.
132
* @type {?Object}
133
* @private
134
*/
135
goog.i18n.MessageFormat.compactNumberFormatterSymbols_ = null;
136
137
138
/**
139
* Locale aware number formatter. Reference to the most recently created
140
* NumberFormat for sharing between MessageFormat instances.
141
* @type {?goog.i18n.NumberFormat}
142
* @private
143
*/
144
goog.i18n.MessageFormat.numberFormatter_ = null;
145
146
147
/**
148
* Literal strings, including '', are replaced with \uFDDF_x_ for
149
* parsing purposes, and recovered during format phase.
150
* \uFDDF is a Unicode nonprinting character, not expected to be found in the
151
* typical message.
152
* @type {string}
153
* @private
154
*/
155
goog.i18n.MessageFormat.LITERAL_PLACEHOLDER_ = '\uFDDF_';
156
157
158
/**
159
* Marks a string and block during parsing.
160
* @enum {number}
161
* @private
162
*/
163
goog.i18n.MessageFormat.Element_ = {
164
STRING: 0,
165
BLOCK: 1
166
};
167
168
169
/**
170
* Block type.
171
* @enum {number}
172
* @private
173
*/
174
goog.i18n.MessageFormat.BlockType_ = {
175
PLURAL: 0,
176
ORDINAL: 1,
177
SELECT: 2,
178
SIMPLE: 3,
179
STRING: 4,
180
UNKNOWN: 5
181
};
182
183
184
/**
185
* Mandatory option in both select and plural form.
186
* @type {string}
187
* @private
188
*/
189
goog.i18n.MessageFormat.OTHER_ = 'other';
190
191
192
/**
193
* Regular expression for looking for string literals.
194
* @type {RegExp}
195
* @private
196
*/
197
goog.i18n.MessageFormat.REGEX_LITERAL_ = new RegExp("'([{}#].*?)'", 'g');
198
199
200
/**
201
* Regular expression for looking for '' in the message.
202
* @type {RegExp}
203
* @private
204
*/
205
goog.i18n.MessageFormat.REGEX_DOUBLE_APOSTROPHE_ = new RegExp("''", 'g');
206
207
/** @typedef {{ type: goog.i18n.MessageFormat.Element_, value: ? }} */
208
goog.i18n.MessageFormat.TypeVal_;
209
210
211
/**
212
* Gets the a NumberFormat instance for the current locale.
213
* If the locale is the same as the previous invocation, returns the same
214
* NumberFormat instance. Otherwise, creates a new one.
215
* @return {!goog.i18n.NumberFormat}
216
* @private
217
*/
218
goog.i18n.MessageFormat.getNumberFormatter_ = function() {
219
var currentSymbols = goog.i18n.NumberFormatSymbols;
220
var currentCompactSymbols = goog.i18n.CompactNumberFormatSymbols;
221
222
if (goog.i18n.MessageFormat.numberFormatterSymbols_ !== currentSymbols ||
223
goog.i18n.MessageFormat.compactNumberFormatterSymbols_ !==
224
currentCompactSymbols) {
225
goog.i18n.MessageFormat.numberFormatterSymbols_ = currentSymbols;
226
goog.i18n.MessageFormat.compactNumberFormatterSymbols_ =
227
currentCompactSymbols;
228
goog.i18n.MessageFormat.numberFormatter_ =
229
new goog.i18n.NumberFormat(goog.i18n.NumberFormat.Format.DECIMAL);
230
}
231
232
return /** @type {!goog.i18n.NumberFormat} */ (
233
goog.i18n.MessageFormat.numberFormatter_);
234
};
235
236
237
/**
238
* Formats a message, treating '#' with special meaning representing
239
* the number (plural_variable - offset).
240
* @param {!Object} namedParameters Parameters that either
241
* influence the formatting or are used as actual data.
242
* I.e. in call to fmt.format({'NUM_PEOPLE': 5, 'NAME': 'Angela'}),
243
* object {'NUM_PEOPLE': 5, 'NAME': 'Angela'} holds positional parameters.
244
* 1st parameter could mean 5 people, which could influence plural format,
245
* and 2nd parameter is just a data to be printed out in proper position.
246
* @return {string} Formatted message.
247
*/
248
goog.i18n.MessageFormat.prototype.format = function(namedParameters) {
249
return this.format_(namedParameters, false);
250
};
251
252
253
/**
254
* Formats a message, treating '#' as literary character.
255
* @param {!Object} namedParameters Parameters that either
256
* influence the formatting or are used as actual data.
257
* I.e. in call to fmt.format({'NUM_PEOPLE': 5, 'NAME': 'Angela'}),
258
* object {'NUM_PEOPLE': 5, 'NAME': 'Angela'} holds positional parameters.
259
* 1st parameter could mean 5 people, which could influence plural format,
260
* and 2nd parameter is just a data to be printed out in proper position.
261
* @return {string} Formatted message.
262
*/
263
goog.i18n.MessageFormat.prototype.formatIgnoringPound = function(
264
namedParameters) {
265
return this.format_(namedParameters, true);
266
};
267
268
269
/**
270
* Formats a message.
271
* @param {!Object} namedParameters Parameters that either
272
* influence the formatting or are used as actual data.
273
* I.e. in call to fmt.format({'NUM_PEOPLE': 5, 'NAME': 'Angela'}),
274
* object {'NUM_PEOPLE': 5, 'NAME': 'Angela'} holds positional parameters.
275
* 1st parameter could mean 5 people, which could influence plural format,
276
* and 2nd parameter is just a data to be printed out in proper position.
277
* @param {boolean} ignorePound If true, treat '#' in plural messages as a
278
* literary character, else treat it as an ICU syntax character, resolving
279
* to the number (plural_variable - offset).
280
* @return {string} Formatted message.
281
* @private
282
*/
283
goog.i18n.MessageFormat.prototype.format_ = function(
284
namedParameters, ignorePound) {
285
this.init_();
286
if (!this.parsedPattern_ || this.parsedPattern_.length == 0) {
287
return '';
288
}
289
this.literals_ = goog.array.clone(this.initialLiterals_);
290
291
var result = [];
292
this.formatBlock_(this.parsedPattern_, namedParameters, ignorePound, result);
293
var message = result.join('');
294
295
if (!ignorePound) {
296
goog.asserts.assert(message.search('#') == -1, 'Not all # were replaced.');
297
}
298
299
while (this.literals_.length > 0) {
300
message = message.replace(
301
this.buildPlaceholder_(this.literals_), this.literals_.pop());
302
}
303
304
return message;
305
};
306
307
308
/**
309
* Parses generic block and returns a formatted string.
310
* @param {!Array<!goog.i18n.MessageFormat.TypeVal_>} parsedPattern
311
* Holds parsed tree.
312
* @param {!Object} namedParameters Parameters that either influence
313
* the formatting or are used as actual data.
314
* @param {boolean} ignorePound If true, treat '#' in plural messages as a
315
* literary character, else treat it as an ICU syntax character, resolving
316
* to the number (plural_variable - offset).
317
* @param {!Array<string>} result Each formatting stage appends its product
318
* to the result.
319
* @private
320
*/
321
goog.i18n.MessageFormat.prototype.formatBlock_ = function(
322
parsedPattern, namedParameters, ignorePound, result) {
323
for (var i = 0; i < parsedPattern.length; i++) {
324
switch (parsedPattern[i].type) {
325
case goog.i18n.MessageFormat.BlockType_.STRING:
326
result.push(parsedPattern[i].value);
327
break;
328
case goog.i18n.MessageFormat.BlockType_.SIMPLE:
329
var pattern = parsedPattern[i].value;
330
this.formatSimplePlaceholder_(pattern, namedParameters, result);
331
break;
332
case goog.i18n.MessageFormat.BlockType_.SELECT:
333
var pattern = parsedPattern[i].value;
334
this.formatSelectBlock_(pattern, namedParameters, ignorePound, result);
335
break;
336
case goog.i18n.MessageFormat.BlockType_.PLURAL:
337
var pattern = parsedPattern[i].value;
338
this.formatPluralOrdinalBlock_(
339
pattern, namedParameters, goog.i18n.pluralRules.select, ignorePound,
340
result);
341
break;
342
case goog.i18n.MessageFormat.BlockType_.ORDINAL:
343
var pattern = parsedPattern[i].value;
344
this.formatPluralOrdinalBlock_(
345
pattern, namedParameters, goog.i18n.ordinalRules.select,
346
ignorePound, result);
347
break;
348
default:
349
goog.asserts.fail('Unrecognized block type: ' + parsedPattern[i].type);
350
}
351
}
352
};
353
354
355
/**
356
* Formats simple placeholder.
357
* @param {!Object} parsedPattern JSON object containing placeholder info.
358
* @param {!Object} namedParameters Parameters that are used as actual data.
359
* @param {!Array<string>} result Each formatting stage appends its product
360
* to the result.
361
* @private
362
*/
363
goog.i18n.MessageFormat.prototype.formatSimplePlaceholder_ = function(
364
parsedPattern, namedParameters, result) {
365
var value = namedParameters[parsedPattern];
366
if (!goog.isDef(value)) {
367
result.push('Undefined parameter - ' + parsedPattern);
368
return;
369
}
370
371
// Don't push the value yet, it may contain any of # { } in it which
372
// will break formatter. Insert a placeholder and replace at the end.
373
this.literals_.push(value);
374
result.push(this.buildPlaceholder_(this.literals_));
375
};
376
377
378
/**
379
* Formats select block. Only one option is selected.
380
* @param {!{argumentIndex:?}} parsedPattern JSON object containing select
381
* block info.
382
* @param {!Object} namedParameters Parameters that either influence
383
* the formatting or are used as actual data.
384
* @param {boolean} ignorePound If true, treat '#' in plural messages as a
385
* literary character, else treat it as an ICU syntax character, resolving
386
* to the number (plural_variable - offset).
387
* @param {!Array<string>} result Each formatting stage appends its product
388
* to the result.
389
* @private
390
*/
391
goog.i18n.MessageFormat.prototype.formatSelectBlock_ = function(
392
parsedPattern, namedParameters, ignorePound, result) {
393
var argumentIndex = parsedPattern.argumentIndex;
394
if (!goog.isDef(namedParameters[argumentIndex])) {
395
result.push('Undefined parameter - ' + argumentIndex);
396
return;
397
}
398
399
var option = parsedPattern[namedParameters[argumentIndex]];
400
if (!goog.isDef(option)) {
401
option = parsedPattern[goog.i18n.MessageFormat.OTHER_];
402
goog.asserts.assertArray(
403
option, 'Invalid option or missing other option for select block.');
404
}
405
406
this.formatBlock_(option, namedParameters, ignorePound, result);
407
};
408
409
410
/**
411
* Formats plural or selectordinal block. Only one option is selected and all #
412
* are replaced.
413
* @param {!{argumentIndex, argumentOffset}} parsedPattern JSON object
414
* containing plural block info.
415
* @param {!Object} namedParameters Parameters that either influence
416
* the formatting or are used as actual data.
417
* @param {function(number, number=):string} pluralSelector A select function
418
* from goog.i18n.pluralRules or goog.i18n.ordinalRules which determines
419
* which plural/ordinal form to use based on the input number's cardinality.
420
* @param {boolean} ignorePound If true, treat '#' in plural messages as a
421
* literary character, else treat it as an ICU syntax character, resolving
422
* to the number (plural_variable - offset).
423
* @param {!Array<string>} result Each formatting stage appends its product
424
* to the result.
425
* @private
426
*/
427
goog.i18n.MessageFormat.prototype.formatPluralOrdinalBlock_ = function(
428
parsedPattern, namedParameters, pluralSelector, ignorePound, result) {
429
var argumentIndex = parsedPattern.argumentIndex;
430
var argumentOffset = parsedPattern.argumentOffset;
431
var pluralValue = +namedParameters[argumentIndex];
432
if (isNaN(pluralValue)) {
433
// TODO(user): Distinguish between undefined and invalid parameters.
434
result.push('Undefined or invalid parameter - ' + argumentIndex);
435
return;
436
}
437
var diff = pluralValue - argumentOffset;
438
439
// Check if there is an exact match.
440
var option = parsedPattern[namedParameters[argumentIndex]];
441
if (!goog.isDef(option)) {
442
goog.asserts.assert(diff >= 0, 'Argument index smaller than offset.');
443
var item;
444
if (this.numberFormatter_.getMinimumFractionDigits) { // number formatter?
445
// If we know the number of fractional digits we can make better decisions
446
// We can decide (for instance) between "1 dollar" and "1.00 dollars".
447
item = pluralSelector(
448
diff, this.numberFormatter_.getMinimumFractionDigits());
449
} else {
450
item = pluralSelector(diff);
451
}
452
goog.asserts.assertString(item, 'Invalid plural key.');
453
454
option = parsedPattern[item];
455
456
// If option is not provided fall back to "other".
457
if (!goog.isDef(option)) {
458
option = parsedPattern[goog.i18n.MessageFormat.OTHER_];
459
}
460
461
goog.asserts.assertArray(
462
option, 'Invalid option or missing other option for plural block.');
463
}
464
465
var pluralResult = [];
466
this.formatBlock_(option, namedParameters, ignorePound, pluralResult);
467
var plural = pluralResult.join('');
468
goog.asserts.assertString(plural, 'Empty block in plural.');
469
if (ignorePound) {
470
result.push(plural);
471
} else {
472
var localeAwareDiff = this.numberFormatter_.format(diff);
473
result.push(plural.replace(/#/g, localeAwareDiff));
474
}
475
};
476
477
478
/**
479
* Set up the MessageFormat.
480
* Parses input pattern into an array, for faster reformatting with
481
* different input parameters.
482
* Parsing is locale independent.
483
* @private
484
*/
485
goog.i18n.MessageFormat.prototype.init_ = function() {
486
if (this.pattern_) {
487
this.initialLiterals_ = [];
488
var pattern = this.insertPlaceholders_(this.pattern_);
489
490
this.parsedPattern_ = this.parseBlock_(pattern);
491
this.pattern_ = null;
492
}
493
};
494
495
496
/**
497
* Replaces string literals with literal placeholders.
498
* Literals are string of the form '}...', '{...' and '#...' where ... is
499
* set of characters not containing '
500
* Builds a dictionary so we can recover literals during format phase.
501
* @param {string} pattern Pattern to clean up.
502
* @return {string} Pattern with literals replaced with placeholders.
503
* @private
504
*/
505
goog.i18n.MessageFormat.prototype.insertPlaceholders_ = function(pattern) {
506
var literals = this.initialLiterals_;
507
var buildPlaceholder = goog.bind(this.buildPlaceholder_, this);
508
509
// First replace '' with single quote placeholder since they can be found
510
// inside other literals.
511
pattern = pattern.replace(
512
goog.i18n.MessageFormat.REGEX_DOUBLE_APOSTROPHE_, function() {
513
literals.push("'");
514
return buildPlaceholder(literals);
515
});
516
517
pattern = pattern.replace(
518
goog.i18n.MessageFormat.REGEX_LITERAL_, function(match, text) {
519
literals.push(text);
520
return buildPlaceholder(literals);
521
});
522
523
return pattern;
524
};
525
526
527
/**
528
* Breaks pattern into strings and top level {...} blocks.
529
* @param {string} pattern (sub)Pattern to be broken.
530
* @return {!Array<goog.i18n.MessageFormat.TypeVal_>}
531
* @private
532
*/
533
goog.i18n.MessageFormat.prototype.extractParts_ = function(pattern) {
534
var prevPos = 0;
535
var braceStack = [];
536
var results = [];
537
538
var braces = /[{}]/g;
539
braces.lastIndex = 0; // lastIndex doesn't get set to 0 so we have to.
540
var match;
541
542
while (match = braces.exec(pattern)) {
543
var pos = match.index;
544
if (match[0] == '}') {
545
var brace = braceStack.pop();
546
goog.asserts.assert(
547
goog.isDef(brace) && brace == '{', 'No matching { for }.');
548
549
if (braceStack.length == 0) {
550
// End of the block.
551
var part = {};
552
part.type = goog.i18n.MessageFormat.Element_.BLOCK;
553
part.value = pattern.substring(prevPos, pos);
554
results.push(part);
555
prevPos = pos + 1;
556
}
557
} else {
558
if (braceStack.length == 0) {
559
var substring = pattern.substring(prevPos, pos);
560
if (substring != '') {
561
results.push({
562
type: goog.i18n.MessageFormat.Element_.STRING,
563
value: substring
564
});
565
}
566
prevPos = pos + 1;
567
}
568
braceStack.push('{');
569
}
570
}
571
572
// Take care of the final string, and check if the braceStack is empty.
573
goog.asserts.assert(
574
braceStack.length == 0, 'There are mismatched { or } in the pattern.');
575
576
var substring = pattern.substring(prevPos);
577
if (substring != '') {
578
results.push(
579
{type: goog.i18n.MessageFormat.Element_.STRING, value: substring});
580
}
581
582
return results;
583
};
584
585
586
/**
587
* A regular expression to parse the plural block, extracting the argument
588
* index and offset (if any).
589
* @type {RegExp}
590
* @private
591
*/
592
goog.i18n.MessageFormat.PLURAL_BLOCK_RE_ =
593
/^\s*(\w+)\s*,\s*plural\s*,(?:\s*offset:(\d+))?/;
594
595
596
/**
597
* A regular expression to parse the ordinal block, extracting the argument
598
* index.
599
* @type {RegExp}
600
* @private
601
*/
602
goog.i18n.MessageFormat.ORDINAL_BLOCK_RE_ = /^\s*(\w+)\s*,\s*selectordinal\s*,/;
603
604
605
/**
606
* A regular expression to parse the select block, extracting the argument
607
* index.
608
* @type {RegExp}
609
* @private
610
*/
611
goog.i18n.MessageFormat.SELECT_BLOCK_RE_ = /^\s*(\w+)\s*,\s*select\s*,/;
612
613
614
/**
615
* Detects which type of a block is the pattern.
616
* @param {string} pattern Content of the block.
617
* @return {goog.i18n.MessageFormat.BlockType_} One of the block types.
618
* @private
619
*/
620
goog.i18n.MessageFormat.prototype.parseBlockType_ = function(pattern) {
621
if (goog.i18n.MessageFormat.PLURAL_BLOCK_RE_.test(pattern)) {
622
return goog.i18n.MessageFormat.BlockType_.PLURAL;
623
}
624
625
if (goog.i18n.MessageFormat.ORDINAL_BLOCK_RE_.test(pattern)) {
626
return goog.i18n.MessageFormat.BlockType_.ORDINAL;
627
}
628
629
if (goog.i18n.MessageFormat.SELECT_BLOCK_RE_.test(pattern)) {
630
return goog.i18n.MessageFormat.BlockType_.SELECT;
631
}
632
633
if (/^\s*\w+\s*/.test(pattern)) {
634
return goog.i18n.MessageFormat.BlockType_.SIMPLE;
635
}
636
637
return goog.i18n.MessageFormat.BlockType_.UNKNOWN;
638
};
639
640
641
/**
642
* Parses generic block.
643
* @param {string} pattern Content of the block to parse.
644
* @return {!Array<!Object>} Subblocks marked as strings, select...
645
* @private
646
*/
647
goog.i18n.MessageFormat.prototype.parseBlock_ = function(pattern) {
648
var result = [];
649
var parts = this.extractParts_(pattern);
650
for (var i = 0; i < parts.length; i++) {
651
var block = {};
652
if (goog.i18n.MessageFormat.Element_.STRING == parts[i].type) {
653
block.type = goog.i18n.MessageFormat.BlockType_.STRING;
654
block.value = parts[i].value;
655
} else if (goog.i18n.MessageFormat.Element_.BLOCK == parts[i].type) {
656
var blockType = this.parseBlockType_(parts[i].value);
657
658
switch (blockType) {
659
case goog.i18n.MessageFormat.BlockType_.SELECT:
660
block.type = goog.i18n.MessageFormat.BlockType_.SELECT;
661
block.value = this.parseSelectBlock_(parts[i].value);
662
break;
663
case goog.i18n.MessageFormat.BlockType_.PLURAL:
664
block.type = goog.i18n.MessageFormat.BlockType_.PLURAL;
665
block.value = this.parsePluralBlock_(parts[i].value);
666
break;
667
case goog.i18n.MessageFormat.BlockType_.ORDINAL:
668
block.type = goog.i18n.MessageFormat.BlockType_.ORDINAL;
669
block.value = this.parseOrdinalBlock_(parts[i].value);
670
break;
671
case goog.i18n.MessageFormat.BlockType_.SIMPLE:
672
block.type = goog.i18n.MessageFormat.BlockType_.SIMPLE;
673
block.value = parts[i].value;
674
break;
675
default:
676
goog.asserts.fail(
677
'Unknown block type for pattern: ' + parts[i].value);
678
}
679
} else {
680
goog.asserts.fail('Unknown part of the pattern.');
681
}
682
result.push(block);
683
}
684
685
return result;
686
};
687
688
689
/**
690
* Parses a select type of a block and produces JSON object for it.
691
* @param {string} pattern Subpattern that needs to be parsed as select pattern.
692
* @return {!Object} Object with select block info.
693
* @private
694
*/
695
goog.i18n.MessageFormat.prototype.parseSelectBlock_ = function(pattern) {
696
var argumentIndex = '';
697
var replaceRegex = goog.i18n.MessageFormat.SELECT_BLOCK_RE_;
698
pattern = pattern.replace(replaceRegex, function(string, name) {
699
argumentIndex = name;
700
return '';
701
});
702
var result = {};
703
result.argumentIndex = argumentIndex;
704
705
var parts = this.extractParts_(pattern);
706
// Looking for (key block)+ sequence. One of the keys has to be "other".
707
var pos = 0;
708
while (pos < parts.length) {
709
var key = parts[pos].value;
710
goog.asserts.assertString(key, 'Missing select key element.');
711
712
pos++;
713
goog.asserts.assert(
714
pos < parts.length, 'Missing or invalid select value element.');
715
716
if (goog.i18n.MessageFormat.Element_.BLOCK == parts[pos].type) {
717
var value = this.parseBlock_(parts[pos].value);
718
} else {
719
goog.asserts.fail('Expected block type.');
720
}
721
result[key.replace(/\s/g, '')] = value;
722
pos++;
723
}
724
725
goog.asserts.assertArray(
726
result[goog.i18n.MessageFormat.OTHER_],
727
'Missing other key in select statement.');
728
return result;
729
};
730
731
732
/**
733
* Parses a plural type of a block and produces JSON object for it.
734
* @param {string} pattern Subpattern that needs to be parsed as plural pattern.
735
* @return {!Object} Object with select block info.
736
* @private
737
*/
738
goog.i18n.MessageFormat.prototype.parsePluralBlock_ = function(pattern) {
739
var argumentIndex = '';
740
var argumentOffset = 0;
741
var replaceRegex = goog.i18n.MessageFormat.PLURAL_BLOCK_RE_;
742
pattern = pattern.replace(replaceRegex, function(string, name, offset) {
743
argumentIndex = name;
744
if (offset) {
745
argumentOffset = parseInt(offset, 10);
746
}
747
return '';
748
});
749
750
var result = {};
751
result.argumentIndex = argumentIndex;
752
result.argumentOffset = argumentOffset;
753
754
var parts = this.extractParts_(pattern);
755
// Looking for (key block)+ sequence.
756
var pos = 0;
757
while (pos < parts.length) {
758
var key = parts[pos].value;
759
goog.asserts.assertString(key, 'Missing plural key element.');
760
761
pos++;
762
goog.asserts.assert(
763
pos < parts.length, 'Missing or invalid plural value element.');
764
765
if (goog.i18n.MessageFormat.Element_.BLOCK == parts[pos].type) {
766
var value = this.parseBlock_(parts[pos].value);
767
} else {
768
goog.asserts.fail('Expected block type.');
769
}
770
result[key.replace(/\s*(?:=)?(\w+)\s*/, '$1')] = value;
771
pos++;
772
}
773
774
goog.asserts.assertArray(
775
result[goog.i18n.MessageFormat.OTHER_],
776
'Missing other key in plural statement.');
777
778
return result;
779
};
780
781
782
/**
783
* Parses an ordinal type of a block and produces JSON object for it.
784
* For example the input string:
785
* '{FOO, selectordinal, one {Message A}other {Message B}}'
786
* Should result in the output object:
787
* {
788
* argumentIndex: 'FOO',
789
* argumentOffest: 0,
790
* one: [ { type: 4, value: 'Message A' } ],
791
* other: [ { type: 4, value: 'Message B' } ]
792
* }
793
* @param {string} pattern Subpattern that needs to be parsed as plural pattern.
794
* @return {!Object} Object with select block info.
795
* @private
796
*/
797
goog.i18n.MessageFormat.prototype.parseOrdinalBlock_ = function(pattern) {
798
var argumentIndex = '';
799
var replaceRegex = goog.i18n.MessageFormat.ORDINAL_BLOCK_RE_;
800
pattern = pattern.replace(replaceRegex, function(string, name) {
801
argumentIndex = name;
802
return '';
803
});
804
805
var result = {};
806
result.argumentIndex = argumentIndex;
807
result.argumentOffset = 0;
808
809
var parts = this.extractParts_(pattern);
810
// Looking for (key block)+ sequence.
811
var pos = 0;
812
while (pos < parts.length) {
813
var key = parts[pos].value;
814
goog.asserts.assertString(key, 'Missing ordinal key element.');
815
816
pos++;
817
goog.asserts.assert(
818
pos < parts.length, 'Missing or invalid ordinal value element.');
819
820
if (goog.i18n.MessageFormat.Element_.BLOCK == parts[pos].type) {
821
var value = this.parseBlock_(parts[pos].value);
822
} else {
823
goog.asserts.fail('Expected block type.');
824
}
825
result[key.replace(/\s*(?:=)?(\w+)\s*/, '$1')] = value;
826
pos++;
827
}
828
829
goog.asserts.assertArray(
830
result[goog.i18n.MessageFormat.OTHER_],
831
'Missing other key in selectordinal statement.');
832
833
return result;
834
};
835
836
837
/**
838
* Builds a placeholder from the last index of the array.
839
* @param {!Array<string>} literals All literals encountered during parse.
840
* @return {string} \uFDDF_ + last index + _.
841
* @private
842
*/
843
goog.i18n.MessageFormat.prototype.buildPlaceholder_ = function(literals) {
844
goog.asserts.assert(literals.length > 0, 'Literal array is empty.');
845
846
var index = (literals.length - 1).toString(10);
847
return goog.i18n.MessageFormat.LITERAL_PLACEHOLDER_ + index + '_';
848
};
849
850