123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848 |
- // 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.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) {
- /**
- * 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<!Object>}
- * @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: ? }} */
- goog.i18n.MessageFormat.TypeVal_;
- /**
- * 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() {
- 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) {
- 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) {
- 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.TypeVal_>} 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: ' + 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) {
- 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 {!{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) {
- 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 {!{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) {
- 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;
- if (this.numberFormatter_.getMinimumFractionDigits) { // number formatter?
- // If we know the number of fractional digits we can make better decisions
- // We can decide (for instance) between "1 dollar" and "1.00 dollars".
- item = pluralSelector(
- diff, this.numberFormatter_.getMinimumFractionDigits());
- } else {
- 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 {
- 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() {
- 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) {
- 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() {
- 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<goog.i18n.MessageFormat.TypeVal_>}
- * @private
- */
- goog.i18n.MessageFormat.prototype.extractParts_ = function(pattern) {
- 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(
- 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;
- }
- } 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) {
- 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 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} 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<string>} 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 + '_';
- };
|