/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Message/plural format library with locale support.
*
* Message format grammar:
*
* messageFormatPattern := string ( "{" messageFormatElement "}" string )*
* messageFormatElement := argumentIndex [ "," elementFormat ]
* elementFormat := "plural" "," pluralStyle
* | "selectordinal" "," ordinalStyle
* | "select" "," selectStyle
* pluralStyle := pluralFormatPattern
* ordinalStyle := selectFormatPattern
* selectStyle := selectFormatPattern
* pluralFormatPattern := [ "offset" ":" offsetIndex ] pluralForms*
* selectFormatPattern := pluralForms*
* pluralForms := stringKey "{" ( "{" messageFormatElement "}"|string )* "}"
*
* This is a subset of the ICU MessageFormatSyntax:
* http://userguide.icu-project.org/formatparse/messages
* See also http://go/plurals and http://go/ordinals for internal details.
*
*
* Message example:
*
* I see {NUM_PEOPLE, plural, offset:1
* =0 {no one at all}
* =1 {{WHO}}
* one {{WHO} and one other person}
* other {{WHO} and # other people}}
* in {PLACE}.
*
* Calling format({'NUM_PEOPLE': 2, 'WHO': 'Mark', 'PLACE': 'Athens'}) would
* produce "I see Mark and one other person in Athens." as output.
*
* OR:
*
* {NUM_FLOOR, selectordinal,
* one {Take the elevator to the #st floor.}
* two {Take the elevator to the #nd floor.}
* few {Take the elevator to the #rd floor.}
* other {Take the elevator to the #th floor.}}
*
* Calling format({'NUM_FLOOR': 22}) would produce
* "Take the elevator to the 22nd floor".
*
* See messageformat_test.html for more examples.
*/
goog.provide('goog.i18n.MessageFormat');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.i18n.CompactNumberFormatSymbols');
goog.require('goog.i18n.NumberFormat');
goog.require('goog.i18n.NumberFormatSymbols');
goog.require('goog.i18n.ordinalRules');
goog.require('goog.i18n.pluralRules');
/**
* Constructor of MessageFormat.
* @param {string} pattern The pattern we parse and apply positional parameters
* to.
* @constructor
* @final
*/
goog.i18n.MessageFormat = function(pattern) {
'use strict';
/**
* The pattern we parse and apply positional parameters to.
* @type {?string}
* @private
*/
this.pattern_ = pattern;
/**
* All encountered literals during parse stage. Indices tell us the order of
* replacement.
* @type {?Array<string>}
* @private
*/
this.initialLiterals_ = null;
/**
* Working array with all encountered literals during parse and format stages.
* Indices tell us the order of replacement.
* @type {?Array<string>}
* @private
*/
this.literals_ = null;
/**
* Input pattern gets parsed into objects for faster formatting.
* @type {?Array<!goog.i18n.MessageFormat.BlockTypeVal_>}
* @private
*/
this.parsedPattern_ = null;
/**
* Locale aware number formatter.
* @type {!goog.i18n.NumberFormat}
* @private
*/
this.numberFormatter_ = goog.i18n.MessageFormat.getNumberFormatter_();
};
/**
* Locale associated with the most recently created NumberFormat.
* @type {?Object}
* @private
*/
goog.i18n.MessageFormat.numberFormatterSymbols_ = null;
/**
* Locale associated with the most recently created NumberFormat.
* @type {?Object}
* @private
*/
goog.i18n.MessageFormat.compactNumberFormatterSymbols_ = null;
/**
* Locale aware number formatter. Reference to the most recently created
* NumberFormat for sharing between MessageFormat instances.
* @type {?goog.i18n.NumberFormat}
* @private
*/
goog.i18n.MessageFormat.numberFormatter_ = null;
/**
* Literal strings, including '', are replaced with \uFDDF_x_ for
* parsing purposes, and recovered during format phase.
* \uFDDF is a Unicode nonprinting character, not expected to be found in the
* typical message.
* @type {string}
* @private
*/
goog.i18n.MessageFormat.LITERAL_PLACEHOLDER_ = '\uFDDF_';
/**
* Marks a string and block during parsing.
* @enum {number}
* @private
*/
goog.i18n.MessageFormat.Element_ = {
STRING: 0,
BLOCK: 1
};
/**
* Block type.
* @enum {number}
* @private
*/
goog.i18n.MessageFormat.BlockType_ = {
PLURAL: 0,
ORDINAL: 1,
SELECT: 2,
SIMPLE: 3,
STRING: 4,
UNKNOWN: 5
};
/**
* Mandatory option in both select and plural form.
* @type {string}
* @private
*/
goog.i18n.MessageFormat.OTHER_ = 'other';
/**
* Regular expression for looking for string literals.
* @type {RegExp}
* @private
*/
goog.i18n.MessageFormat.REGEX_LITERAL_ = new RegExp("'([{}#].*?)'", 'g');
/**
* Regular expression for looking for '' in the message.
* @type {RegExp}
* @private
*/
goog.i18n.MessageFormat.REGEX_DOUBLE_APOSTROPHE_ = new RegExp("''", 'g');
/**
* @typedef {{ type: !goog.i18n.MessageFormat.Element_, value: ? }}
* @private
*/
goog.i18n.MessageFormat.TypeVal_;
/**
* @typedef {{ type: !goog.i18n.MessageFormat.BlockType_, value: ? }}
* @private
*/
goog.i18n.MessageFormat.BlockTypeVal_;
/**
* Gets the a NumberFormat instance for the current locale.
* If the locale is the same as the previous invocation, returns the same
* NumberFormat instance. Otherwise, creates a new one.
* @return {!goog.i18n.NumberFormat}
* @private
*/
goog.i18n.MessageFormat.getNumberFormatter_ = function() {
'use strict';
var currentSymbols = goog.i18n.NumberFormatSymbols;
var currentCompactSymbols = goog.i18n.CompactNumberFormatSymbols;
if (goog.i18n.MessageFormat.numberFormatterSymbols_ !== currentSymbols ||
goog.i18n.MessageFormat.compactNumberFormatterSymbols_ !==
currentCompactSymbols) {
goog.i18n.MessageFormat.numberFormatterSymbols_ = currentSymbols;
goog.i18n.MessageFormat.compactNumberFormatterSymbols_ =
currentCompactSymbols;
goog.i18n.MessageFormat.numberFormatter_ =
new goog.i18n.NumberFormat(goog.i18n.NumberFormat.Format.DECIMAL);
}
return /** @type {!goog.i18n.NumberFormat} */ (
goog.i18n.MessageFormat.numberFormatter_);
};
/**
* Formats a message, treating '#' with special meaning representing
* the number (plural_variable - offset).
* @param {!Object} namedParameters Parameters that either
* influence the formatting or are used as actual data.
* I.e. in call to fmt.format({'NUM_PEOPLE': 5, 'NAME': 'Angela'}),
* object {'NUM_PEOPLE': 5, 'NAME': 'Angela'} holds positional parameters.
* 1st parameter could mean 5 people, which could influence plural format,
* and 2nd parameter is just a data to be printed out in proper position.
* @return {string} Formatted message.
*/
goog.i18n.MessageFormat.prototype.format = function(namedParameters) {
'use strict';
return this.format_(namedParameters, false);
};
/**
* Formats a message, treating '#' as literary character.
* @param {!Object} namedParameters Parameters that either
* influence the formatting or are used as actual data.
* I.e. in call to fmt.format({'NUM_PEOPLE': 5, 'NAME': 'Angela'}),
* object {'NUM_PEOPLE': 5, 'NAME': 'Angela'} holds positional parameters.
* 1st parameter could mean 5 people, which could influence plural format,
* and 2nd parameter is just a data to be printed out in proper position.
* @return {string} Formatted message.
*/
goog.i18n.MessageFormat.prototype.formatIgnoringPound = function(
namedParameters) {
'use strict';
return this.format_(namedParameters, true);
};
/**
* Formats a message.
* @param {!Object} namedParameters Parameters that either
* influence the formatting or are used as actual data.
* I.e. in call to fmt.format({'NUM_PEOPLE': 5, 'NAME': 'Angela'}),
* object {'NUM_PEOPLE': 5, 'NAME': 'Angela'} holds positional parameters.
* 1st parameter could mean 5 people, which could influence plural format,
* and 2nd parameter is just a data to be printed out in proper position.
* @param {boolean} ignorePound If true, treat '#' in plural messages as a
* literary character, else treat it as an ICU syntax character, resolving
* to the number (plural_variable - offset).
* @return {string} Formatted message.
* @private
*/
goog.i18n.MessageFormat.prototype.format_ = function(
namedParameters, ignorePound) {
'use strict';
this.init_();
if (!this.parsedPattern_ || this.parsedPattern_.length == 0) {
return '';
}
this.literals_ = goog.array.clone(this.initialLiterals_);
var result = [];
this.formatBlock_(this.parsedPattern_, namedParameters, ignorePound, result);
var message = result.join('');
if (!ignorePound) {
goog.asserts.assert(message.search('#') == -1, 'Not all # were replaced.');
}
while (this.literals_.length > 0) {
message = message.replace(
this.buildPlaceholder_(this.literals_), this.literals_.pop());
}
return message;
};
/**
* Parses generic block and returns a formatted string.
* @param {!Array<!goog.i18n.MessageFormat.BlockTypeVal_>} parsedPattern
* Holds parsed tree.
* @param {!Object} namedParameters Parameters that either influence
* the formatting or are used as actual data.
* @param {boolean} ignorePound If true, treat '#' in plural messages as a
* literary character, else treat it as an ICU syntax character, resolving
* to the number (plural_variable - offset).
* @param {!Array<string>} result Each formatting stage appends its product
* to the result.
* @private
*/
goog.i18n.MessageFormat.prototype.formatBlock_ = function(
parsedPattern, namedParameters, ignorePound, result) {
'use strict';
for (var i = 0; i < parsedPattern.length; i++) {
switch (parsedPattern[i].type) {
case goog.i18n.MessageFormat.BlockType_.STRING:
result.push(parsedPattern[i].value);
break;
case goog.i18n.MessageFormat.BlockType_.SIMPLE:
var pattern = parsedPattern[i].value;
this.formatSimplePlaceholder_(pattern, namedParameters, result);
break;
case goog.i18n.MessageFormat.BlockType_.SELECT:
var pattern = parsedPattern[i].value;
this.formatSelectBlock_(pattern, namedParameters, ignorePound, result);
break;
case goog.i18n.MessageFormat.BlockType_.PLURAL:
var pattern = parsedPattern[i].value;
this.formatPluralOrdinalBlock_(
pattern, namedParameters, goog.i18n.pluralRules.select, ignorePound,
result);
break;
case goog.i18n.MessageFormat.BlockType_.ORDINAL:
var pattern = parsedPattern[i].value;
this.formatPluralOrdinalBlock_(
pattern, namedParameters, goog.i18n.ordinalRules.select,
ignorePound, result);
break;
default:
goog.asserts.fail('Unrecognized block type: ' + parsedPattern[i].type);
}
}
};
/**
* Formats simple placeholder.
* @param {!Object} parsedPattern JSON object containing placeholder info.
* @param {!Object} namedParameters Parameters that are used as actual data.
* @param {!Array<string>} result Each formatting stage appends its product
* to the result.
* @private
*/
goog.i18n.MessageFormat.prototype.formatSimplePlaceholder_ = function(
parsedPattern, namedParameters, result) {
'use strict';
var value = namedParameters[parsedPattern];
if (value === undefined) {
result.push('Undefined parameter - ' + parsedPattern);
return;
}
// Don't push the value yet, it may contain any of # { } in it which
// will break formatter. Insert a placeholder and replace at the end.
this.literals_.push(value);
result.push(this.buildPlaceholder_(this.literals_));
};
/**
* Formats select block. Only one option is selected.
* @param {{argumentIndex:?}} parsedPattern JSON object containing select
* block info.
* @param {!Object} namedParameters Parameters that either influence
* the formatting or are used as actual data.
* @param {boolean} ignorePound If true, treat '#' in plural messages as a
* literary character, else treat it as an ICU syntax character, resolving
* to the number (plural_variable - offset).
* @param {!Array<string>} result Each formatting stage appends its product
* to the result.
* @private
*/
goog.i18n.MessageFormat.prototype.formatSelectBlock_ = function(
parsedPattern, namedParameters, ignorePound, result) {
'use strict';
var argumentIndex = parsedPattern.argumentIndex;
if (namedParameters[argumentIndex] === undefined) {
result.push('Undefined parameter - ' + argumentIndex);
return;
}
var option = parsedPattern[namedParameters[argumentIndex]];
if (option === undefined) {
option = parsedPattern[goog.i18n.MessageFormat.OTHER_];
goog.asserts.assertArray(
option, 'Invalid option or missing other option for select block.');
}
this.formatBlock_(option, namedParameters, ignorePound, result);
};
/**
* Formats plural or selectordinal block. Only one option is selected and all #
* are replaced.
* @param {{argumentIndex, argumentOffset}} parsedPattern JSON object
* containing plural block info.
* @param {!Object} namedParameters Parameters that either influence
* the formatting or are used as actual data.
* @param {function(number, number=):string} pluralSelector A select function
* from goog.i18n.pluralRules or goog.i18n.ordinalRules which determines
* which plural/ordinal form to use based on the input number's cardinality.
* @param {boolean} ignorePound If true, treat '#' in plural messages as a
* literary character, else treat it as an ICU syntax character, resolving
* to the number (plural_variable - offset).
* @param {!Array<string>} result Each formatting stage appends its product
* to the result.
* @private
*/
goog.i18n.MessageFormat.prototype.formatPluralOrdinalBlock_ = function(
parsedPattern, namedParameters, pluralSelector, ignorePound, result) {
'use strict';
var argumentIndex = parsedPattern.argumentIndex;
var argumentOffset = parsedPattern.argumentOffset;
var pluralValue = +namedParameters[argumentIndex];
if (isNaN(pluralValue)) {
// TODO(user): Distinguish between undefined and invalid parameters.
result.push('Undefined or invalid parameter - ' + argumentIndex);
return;
}
var diff = pluralValue - argumentOffset;
// Check if there is an exact match.
var option = parsedPattern[namedParameters[argumentIndex]];
if (option === undefined) {
var item = pluralSelector(Math.abs(diff));
goog.asserts.assertString(item, 'Invalid plural key.');
option = parsedPattern[item];
// If option is not provided fall back to "other".
if (option === undefined) {
option = parsedPattern[goog.i18n.MessageFormat.OTHER_];
}
goog.asserts.assertArray(
option, 'Invalid option or missing other option for plural block.');
}
var pluralResult = [];
this.formatBlock_(option, namedParameters, ignorePound, pluralResult);
var plural = pluralResult.join('');
goog.asserts.assertString(plural, 'Empty block in plural.');
if (ignorePound) {
result.push(plural);
} else {
var localeAwareDiff = this.numberFormatter_.format(diff);
result.push(plural.replace(/#/g, localeAwareDiff));
}
};
/**
* Set up the MessageFormat.
* Parses input pattern into an array, for faster reformatting with
* different input parameters.
* Parsing is locale independent.
* @private
*/
goog.i18n.MessageFormat.prototype.init_ = function() {
'use strict';
if (this.pattern_) {
this.initialLiterals_ = [];
var pattern = this.insertPlaceholders_(this.pattern_);
this.parsedPattern_ = this.parseBlock_(pattern);
this.pattern_ = null;
}
};
/**
* Replaces string literals with literal placeholders.
* Literals are string of the form '}...', '{...' and '#...' where ... is
* set of characters not containing '
* Builds a dictionary so we can recover literals during format phase.
* @param {string} pattern Pattern to clean up.
* @return {string} Pattern with literals replaced with placeholders.
* @private
*/
goog.i18n.MessageFormat.prototype.insertPlaceholders_ = function(pattern) {
'use strict';
var literals = this.initialLiterals_;
var buildPlaceholder = goog.bind(this.buildPlaceholder_, this);
// First replace '' with single quote placeholder since they can be found
// inside other literals.
pattern = pattern.replace(
goog.i18n.MessageFormat.REGEX_DOUBLE_APOSTROPHE_, function() {
'use strict';
literals.push('\'');
return buildPlaceholder(literals);
});
pattern = pattern.replace(
goog.i18n.MessageFormat.REGEX_LITERAL_, function(match, text) {
'use strict';
literals.push(text);
return buildPlaceholder(literals);
});
return pattern;
};
/**
* Breaks pattern into strings and top level {...} blocks.
* @param {string} pattern (sub)Pattern to be broken.
* @return {!Array<goog.i18n.MessageFormat.TypeVal_>}
* @private
*/
goog.i18n.MessageFormat.prototype.extractParts_ = function(pattern) {
'use strict';
var prevPos = 0;
var braceStack = [];
var results = [];
var braces = /[{}]/g;
braces.lastIndex = 0; // lastIndex doesn't get set to 0 so we have to.
var match;
while (match = braces.exec(pattern)) {
var pos = match.index;
if (match[0] == '}') {
var brace = braceStack.pop();
goog.asserts.assert(
brace !== undefined && brace == '{', 'No matching { for }.');
if (braceStack.length == 0) {
// End of the block.
var part = {};
part.type = goog.i18n.MessageFormat.Element_.BLOCK;
part.value = pattern.substring(prevPos, pos);
results.push(part);
prevPos = pos + 1;
}
} else {
if (braceStack.length == 0) {
var substring = pattern.substring(prevPos, pos);
if (substring != '') {
results.push({
type: goog.i18n.MessageFormat.Element_.STRING,
value: substring
});
}
prevPos = pos + 1;
}
braceStack.push('{');
}
}
// Take care of the final string, and check if the braceStack is empty.
goog.asserts.assert(
braceStack.length == 0, 'There are mismatched { or } in the pattern.');
var substring = pattern.substring(prevPos);
if (substring != '') {
results.push(
{type: goog.i18n.MessageFormat.Element_.STRING, value: substring});
}
return results;
};
/**
* A regular expression to parse the plural block, extracting the argument
* index and offset (if any).
* @type {RegExp}
* @private
*/
goog.i18n.MessageFormat.PLURAL_BLOCK_RE_ =
/^\s*(\w+)\s*,\s*plural\s*,(?:\s*offset:(\d+))?/;
/**
* A regular expression to parse the ordinal block, extracting the argument
* index.
* @type {RegExp}
* @private
*/
goog.i18n.MessageFormat.ORDINAL_BLOCK_RE_ = /^\s*(\w+)\s*,\s*selectordinal\s*,/;
/**
* A regular expression to parse the select block, extracting the argument
* index.
* @type {RegExp}
* @private
*/
goog.i18n.MessageFormat.SELECT_BLOCK_RE_ = /^\s*(\w+)\s*,\s*select\s*,/;
/**
* Detects which type of a block is the pattern.
* @param {string} pattern Content of the block.
* @return {goog.i18n.MessageFormat.BlockType_} One of the block types.
* @private
*/
goog.i18n.MessageFormat.prototype.parseBlockType_ = function(pattern) {
'use strict';
if (goog.i18n.MessageFormat.PLURAL_BLOCK_RE_.test(pattern)) {
return goog.i18n.MessageFormat.BlockType_.PLURAL;
}
if (goog.i18n.MessageFormat.ORDINAL_BLOCK_RE_.test(pattern)) {
return goog.i18n.MessageFormat.BlockType_.ORDINAL;
}
if (goog.i18n.MessageFormat.SELECT_BLOCK_RE_.test(pattern)) {
return goog.i18n.MessageFormat.BlockType_.SELECT;
}
if (/^\s*\w+\s*/.test(pattern)) {
return goog.i18n.MessageFormat.BlockType_.SIMPLE;
}
return goog.i18n.MessageFormat.BlockType_.UNKNOWN;
};
/**
* Parses generic block.
* @param {string} pattern Content of the block to parse.
* @return {!Array<!goog.i18n.MessageFormat.BlockTypeVal_>} Subblocks marked as
* strings, select...
* @private
*/
goog.i18n.MessageFormat.prototype.parseBlock_ = function(pattern) {
'use strict';
var result = [];
var parts = this.extractParts_(pattern);
for (var i = 0; i < parts.length; i++) {
var block = {};
if (goog.i18n.MessageFormat.Element_.STRING == parts[i].type) {
block.type = goog.i18n.MessageFormat.BlockType_.STRING;
block.value = parts[i].value;
} else if (goog.i18n.MessageFormat.Element_.BLOCK == parts[i].type) {
var blockType = this.parseBlockType_(parts[i].value);
switch (blockType) {
case goog.i18n.MessageFormat.BlockType_.SELECT:
block.type = goog.i18n.MessageFormat.BlockType_.SELECT;
block.value = this.parseSelectBlock_(parts[i].value);
break;
case goog.i18n.MessageFormat.BlockType_.PLURAL:
block.type = goog.i18n.MessageFormat.BlockType_.PLURAL;
block.value = this.parsePluralBlock_(parts[i].value);
break;
case goog.i18n.MessageFormat.BlockType_.ORDINAL:
block.type = goog.i18n.MessageFormat.BlockType_.ORDINAL;
block.value = this.parseOrdinalBlock_(parts[i].value);
break;
case goog.i18n.MessageFormat.BlockType_.SIMPLE:
block.type = goog.i18n.MessageFormat.BlockType_.SIMPLE;
block.value = parts[i].value;
break;
default:
goog.asserts.fail(
'Unknown block type for pattern: ' + parts[i].value);
}
} else {
goog.asserts.fail('Unknown part of the pattern.');
}
result.push(block);
}
return result;
};
/**
* Parses a select type of a block and produces JSON object for it.
* @param {string} pattern Subpattern that needs to be parsed as select pattern.
* @return {!Object<string, !Array<!goog.i18n.MessageFormat.BlockTypeVal_>>}
* Object with select block info.
* @private
*/
goog.i18n.MessageFormat.prototype.parseSelectBlock_ = function(pattern) {
'use strict';
var argumentIndex = '';
var replaceRegex = goog.i18n.MessageFormat.SELECT_BLOCK_RE_;
pattern = pattern.replace(replaceRegex, function(string, name) {
'use strict';
argumentIndex = name;
return '';
});
var result = {};
result.argumentIndex = argumentIndex;
var parts = this.extractParts_(pattern);
// Looking for (key block)+ sequence. One of the keys has to be "other".
var pos = 0;
while (pos < parts.length) {
var key = parts[pos].value;
goog.asserts.assertString(key, 'Missing select key element.');
pos++;
goog.asserts.assert(
pos < parts.length, 'Missing or invalid select value element.');
var value;
if (goog.i18n.MessageFormat.Element_.BLOCK == parts[pos].type) {
value = this.parseBlock_(parts[pos].value);
} else {
goog.asserts.fail('Expected block type.');
}
result[key.replace(/\s/g, '')] = value;
pos++;
}
goog.asserts.assertArray(
result[goog.i18n.MessageFormat.OTHER_],
'Missing other key in select statement.');
return result;
};
/**
* Parses a plural type of a block and produces JSON object for it.
* @param {string} pattern Subpattern that needs to be parsed as plural pattern.
* @return {!Object<string, !Array<!goog.i18n.MessageFormat.BlockTypeVal_>>}
* Object with select block info.
* @private
*/
goog.i18n.MessageFormat.prototype.parsePluralBlock_ = function(pattern) {
'use strict';
var argumentIndex = '';
var argumentOffset = 0;
var replaceRegex = goog.i18n.MessageFormat.PLURAL_BLOCK_RE_;
pattern = pattern.replace(replaceRegex, function(string, name, offset) {
'use strict';
argumentIndex = name;
if (offset) {
argumentOffset = parseInt(offset, 10);
}
return '';
});
var result = {};
result.argumentIndex = argumentIndex;
result.argumentOffset = argumentOffset;
var parts = this.extractParts_(pattern);
// Looking for (key block)+ sequence.
var pos = 0;
while (pos < parts.length) {
var key = parts[pos].value;
goog.asserts.assertString(key, 'Missing plural key element.');
pos++;
goog.asserts.assert(
pos < parts.length, 'Missing or invalid plural value element.');
var value;
if (goog.i18n.MessageFormat.Element_.BLOCK == parts[pos].type) {
value = this.parseBlock_(parts[pos].value);
} else {
goog.asserts.fail('Expected block type.');
}
result[key.replace(/\s*(?:=)?(\w+)\s*/, '$1')] = value;
pos++;
}
goog.asserts.assertArray(
result[goog.i18n.MessageFormat.OTHER_],
'Missing other key in plural statement.');
return result;
};
/**
* Parses an ordinal type of a block and produces JSON object for it.
* For example the input string:
* '{FOO, selectordinal, one {Message A}other {Message B}}'
* Should result in the output object:
* {
* argumentIndex: 'FOO',
* argumentOffest: 0,
* one: [ { type: 4, value: 'Message A' } ],
* other: [ { type: 4, value: 'Message B' } ]
* }
* @param {string} pattern Subpattern that needs to be parsed as plural pattern.
* @return {!Object} Object with select block info.
* @private
*/
goog.i18n.MessageFormat.prototype.parseOrdinalBlock_ = function(pattern) {
'use strict';
var argumentIndex = '';
var replaceRegex = goog.i18n.MessageFormat.ORDINAL_BLOCK_RE_;
pattern = pattern.replace(replaceRegex, function(string, name) {
'use strict';
argumentIndex = name;
return '';
});
var result = {};
result.argumentIndex = argumentIndex;
result.argumentOffset = 0;
var parts = this.extractParts_(pattern);
// Looking for (key block)+ sequence.
var pos = 0;
while (pos < parts.length) {
var key = parts[pos].value;
goog.asserts.assertString(key, 'Missing ordinal key element.');
pos++;
goog.asserts.assert(
pos < parts.length, 'Missing or invalid ordinal value element.');
if (goog.i18n.MessageFormat.Element_.BLOCK == parts[pos].type) {
var value = this.parseBlock_(parts[pos].value);
} else {
goog.asserts.fail('Expected block type.');
}
result[key.replace(/\s*(?:=)?(\w+)\s*/, '$1')] = value;
pos++;
}
goog.asserts.assertArray(
result[goog.i18n.MessageFormat.OTHER_],
'Missing other key in selectordinal statement.');
return result;
};
/**
* Builds a placeholder from the last index of the array.
* @param {!Array<string>} literals All literals encountered during parse.
* @return {string} \uFDDF_ + last index + _.
* @private
*/
goog.i18n.MessageFormat.prototype.buildPlaceholder_ = function(literals) {
'use strict';
goog.asserts.assert(literals.length > 0, 'Literal array is empty.');
var index = (literals.length - 1).toString(10);
return goog.i18n.MessageFormat.LITERAL_PLACEHOLDER_ + index + '_';
};