209 lines
5.9 KiB
JavaScript
209 lines
5.9 KiB
JavaScript
/*
|
|
Copyright (c) 2014, Yahoo! Inc. All rights reserved.
|
|
Copyrights licensed under the New BSD License.
|
|
See the accompanying LICENSE file for terms.
|
|
*/
|
|
|
|
/* jslint esnext: true */
|
|
|
|
"use strict";
|
|
exports["default"] = Compiler;
|
|
|
|
function Compiler(locales, formats, pluralFn) {
|
|
this.locales = locales;
|
|
this.formats = formats;
|
|
this.pluralFn = pluralFn;
|
|
}
|
|
|
|
Compiler.prototype.compile = function (ast) {
|
|
this.pluralStack = [];
|
|
this.currentPlural = null;
|
|
this.pluralNumberFormat = null;
|
|
|
|
return this.compileMessage(ast);
|
|
};
|
|
|
|
Compiler.prototype.compileMessage = function (ast) {
|
|
if (!(ast && ast.type === 'messageFormatPattern')) {
|
|
throw new Error('Message AST is not of type: "messageFormatPattern"');
|
|
}
|
|
|
|
var elements = ast.elements,
|
|
pattern = [];
|
|
|
|
var i, len, element;
|
|
|
|
for (i = 0, len = elements.length; i < len; i += 1) {
|
|
element = elements[i];
|
|
|
|
switch (element.type) {
|
|
case 'messageTextElement':
|
|
pattern.push(this.compileMessageText(element));
|
|
break;
|
|
|
|
case 'argumentElement':
|
|
pattern.push(this.compileArgument(element));
|
|
break;
|
|
|
|
default:
|
|
throw new Error('Message element does not have a valid type');
|
|
}
|
|
}
|
|
|
|
return pattern;
|
|
};
|
|
|
|
Compiler.prototype.compileMessageText = function (element) {
|
|
// When this `element` is part of plural sub-pattern and its value contains
|
|
// an unescaped '#', use a `PluralOffsetString` helper to properly output
|
|
// the number with the correct offset in the string.
|
|
if (this.currentPlural && /(^|[^\\])#/g.test(element.value)) {
|
|
// Create a cache a NumberFormat instance that can be reused for any
|
|
// PluralOffsetString instance in this message.
|
|
if (!this.pluralNumberFormat) {
|
|
this.pluralNumberFormat = new Intl.NumberFormat(this.locales);
|
|
}
|
|
|
|
return new PluralOffsetString(
|
|
this.currentPlural.id,
|
|
this.currentPlural.format.offset,
|
|
this.pluralNumberFormat,
|
|
element.value);
|
|
}
|
|
|
|
// Unescape the escaped '#'s in the message text.
|
|
return element.value.replace(/\\#/g, '#');
|
|
};
|
|
|
|
Compiler.prototype.compileArgument = function (element) {
|
|
var format = element.format;
|
|
|
|
if (!format) {
|
|
return new StringFormat(element.id);
|
|
}
|
|
|
|
var formats = this.formats,
|
|
locales = this.locales,
|
|
pluralFn = this.pluralFn,
|
|
options;
|
|
|
|
switch (format.type) {
|
|
case 'numberFormat':
|
|
options = formats.number[format.style];
|
|
return {
|
|
id : element.id,
|
|
format: new Intl.NumberFormat(locales, options).format
|
|
};
|
|
|
|
case 'dateFormat':
|
|
options = formats.date[format.style];
|
|
return {
|
|
id : element.id,
|
|
format: new Intl.DateTimeFormat(locales, options).format
|
|
};
|
|
|
|
case 'timeFormat':
|
|
options = formats.time[format.style];
|
|
return {
|
|
id : element.id,
|
|
format: new Intl.DateTimeFormat(locales, options).format
|
|
};
|
|
|
|
case 'pluralFormat':
|
|
options = this.compileOptions(element);
|
|
return new PluralFormat(
|
|
element.id, format.ordinal, format.offset, options, pluralFn
|
|
);
|
|
|
|
case 'selectFormat':
|
|
options = this.compileOptions(element);
|
|
return new SelectFormat(element.id, options);
|
|
|
|
default:
|
|
throw new Error('Message element does not have a valid format type');
|
|
}
|
|
};
|
|
|
|
Compiler.prototype.compileOptions = function (element) {
|
|
var format = element.format,
|
|
options = format.options,
|
|
optionsHash = {};
|
|
|
|
// Save the current plural element, if any, then set it to a new value when
|
|
// compiling the options sub-patterns. This conforms the spec's algorithm
|
|
// for handling `"#"` syntax in message text.
|
|
this.pluralStack.push(this.currentPlural);
|
|
this.currentPlural = format.type === 'pluralFormat' ? element : null;
|
|
|
|
var i, len, option;
|
|
|
|
for (i = 0, len = options.length; i < len; i += 1) {
|
|
option = options[i];
|
|
|
|
// Compile the sub-pattern and save it under the options's selector.
|
|
optionsHash[option.selector] = this.compileMessage(option.value);
|
|
}
|
|
|
|
// Pop the plural stack to put back the original current plural value.
|
|
this.currentPlural = this.pluralStack.pop();
|
|
|
|
return optionsHash;
|
|
};
|
|
|
|
// -- Compiler Helper Classes --------------------------------------------------
|
|
|
|
function StringFormat(id) {
|
|
this.id = id;
|
|
}
|
|
|
|
StringFormat.prototype.format = function (value) {
|
|
if (!value) {
|
|
return '';
|
|
}
|
|
|
|
return typeof value === 'string' ? value : String(value);
|
|
};
|
|
|
|
function PluralFormat(id, useOrdinal, offset, options, pluralFn) {
|
|
this.id = id;
|
|
this.useOrdinal = useOrdinal;
|
|
this.offset = offset;
|
|
this.options = options;
|
|
this.pluralFn = pluralFn;
|
|
}
|
|
|
|
PluralFormat.prototype.getOption = function (value) {
|
|
var options = this.options;
|
|
|
|
var option = options['=' + value] ||
|
|
options[this.pluralFn(value - this.offset, this.useOrdinal)];
|
|
|
|
return option || options.other;
|
|
};
|
|
|
|
function PluralOffsetString(id, offset, numberFormat, string) {
|
|
this.id = id;
|
|
this.offset = offset;
|
|
this.numberFormat = numberFormat;
|
|
this.string = string;
|
|
}
|
|
|
|
PluralOffsetString.prototype.format = function (value) {
|
|
var number = this.numberFormat.format(value - this.offset);
|
|
|
|
return this.string
|
|
.replace(/(^|[^\\])#/g, '$1' + number)
|
|
.replace(/\\#/g, '#');
|
|
};
|
|
|
|
function SelectFormat(id, options) {
|
|
this.id = id;
|
|
this.options = options;
|
|
}
|
|
|
|
SelectFormat.prototype.getOption = function (value) {
|
|
var options = this.options;
|
|
return options[value] || options.other;
|
|
};
|
|
|
|
//# sourceMappingURL=compiler.js.map
|