diff options
Diffstat (limited to 'chromium/chrome/third_party/chromevox/third_party/closure-library/closure/goog/i18n/messageformat.js')
-rw-r--r-- | chromium/chrome/third_party/chromevox/third_party/closure-library/closure/goog/i18n/messageformat.js | 764 |
1 files changed, 764 insertions, 0 deletions
diff --git a/chromium/chrome/third_party/chromevox/third_party/closure-library/closure/goog/i18n/messageformat.js b/chromium/chrome/third_party/chromevox/third_party/closure-library/closure/goog/i18n/messageformat.js new file mode 100644 index 00000000000..923af98b218 --- /dev/null +++ b/chromium/chrome/third_party/chromevox/third_party/closure-library/closure/goog/i18n/messageformat.js @@ -0,0 +1,764 @@ +// Copyright 2010 The Closure Library Authors. All Rights Reserved +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @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.asserts'); +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) { + /** + * All encountered literals during parse stage. Indices tell us the order of + * replacement. + * @type {!Array.<string>} + * @private + */ + this.literals_ = []; + + /** + * Input pattern gets parsed into objects for faster formatting. + * @type {!Array.<!Object>} + * @private + */ + this.parsedPattern_ = []; + + this.parsePattern_(pattern); +}; + + +/** + * 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'); + + +/** + * 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) { + 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) { + 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) { + if (this.parsedPattern_.length == 0) { + return ''; + } + + 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.<!Object>} 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) { + 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.'); + } + } +}; + + +/** + * 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) { + var value = namedParameters[parsedPattern]; + if (!goog.isDef(value)) { + 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 {!Object} 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) { + var argumentIndex = parsedPattern.argumentIndex; + if (!goog.isDef(namedParameters[argumentIndex])) { + result.push('Undefined parameter - ' + argumentIndex); + return; + } + + var option = parsedPattern[namedParameters[argumentIndex]]; + if (!goog.isDef(option)) { + 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 {!Object} 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) { + 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 (!goog.isDef(option)) { + goog.asserts.assert(diff >= 0, 'Argument index smaller than offset.'); + var item; + item = pluralSelector(diff); + goog.asserts.assertString(item, 'Invalid plural key.'); + + option = parsedPattern[item]; + + // If option is not provided fall back to "other". + if (!goog.isDef(option)) { + 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 { + // TODO(plundblad): Might want to use a more specific number formatter. + var localeAwareDiff = diff.toLocaleString(); + result.push(plural.replace(/#/g, localeAwareDiff)); + } +}; + + +/** + * Parses input pattern into an array, for faster reformatting with + * different input parameters. + * Parsing is locale independent. + * @param {string} pattern MessageFormat pattern to parse. + * @private + */ +goog.i18n.MessageFormat.prototype.parsePattern_ = function(pattern) { + if (pattern) { + pattern = this.insertPlaceholders_(pattern); + + this.parsedPattern_ = this.parseBlock_(pattern); + } +}; + + +/** + * 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) { + var literals = this.literals_; + 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() { + literals.push("'"); + return buildPlaceholder(literals); + }); + + pattern = pattern.replace( + goog.i18n.MessageFormat.REGEX_LITERAL_, + function(match, text) { + 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.<Object>} Each item is {type, value}. + * @private + */ +goog.i18n.MessageFormat.prototype.extractParts_ = function(pattern) { + var prevPos = 0; + var inBlock = false; + 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(goog.isDef(brace) && 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; + inBlock = false; + } + } else { + if (braceStack.length == 0) { + inBlock = true; + 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) { + 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.<!Object>} Subblocks marked as strings, select... + * @private + */ +goog.i18n.MessageFormat.prototype.parseBlock_ = function(pattern) { + 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.'); + } + } 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} Object with select block info. + * @private + */ +goog.i18n.MessageFormat.prototype.parseSelectBlock_ = function(pattern) { + var argumentIndex = ''; + var replaceRegex = goog.i18n.MessageFormat.SELECT_BLOCK_RE_; + pattern = pattern.replace(replaceRegex, function(string, name) { + 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.'); + + 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/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} Object with select block info. + * @private + */ +goog.i18n.MessageFormat.prototype.parsePluralBlock_ = function(pattern) { + var argumentIndex = ''; + var argumentOffset = 0; + var replaceRegex = goog.i18n.MessageFormat.PLURAL_BLOCK_RE_; + pattern = pattern.replace(replaceRegex, function(string, name, offset) { + 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.'); + + 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 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) { + var argumentIndex = ''; + var replaceRegex = goog.i18n.MessageFormat.ORDINAL_BLOCK_RE_; + pattern = pattern.replace(replaceRegex, function(string, name) { + 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} literals All literals encountered during parse. + * @return {string} \uFDDF_ + last index + _. + * @private + */ +goog.i18n.MessageFormat.prototype.buildPlaceholder_ = function(literals) { + goog.asserts.assert(literals.length > 0, 'Literal array is empty.'); + + var index = (literals.length - 1).toString(10); + return goog.i18n.MessageFormat.LITERAL_PLACEHOLDER_ + index + '_'; +}; |