399 lines
13 KiB
Plaintext
399 lines
13 KiB
Plaintext
/**
|
|
* @author Matthew Caruana Galizia <mattcg@gmail.com>
|
|
* @license MIT: http://mattcg.mit-license.org/
|
|
* @copyright Copyright (c) 2013, Matthew Caruana Galizia
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
|
|
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
|
|
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
|
|
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
|
var index = require('language-subtag-registry/data/json/index.json');
|
|
var registry = require('language-subtag-registry/data/json/registry.json');
|
|
var Subtag = require('./Subtag.js');
|
|
var Tag = /*#__PURE__*/function () {
|
|
/** @param {string} tag */
|
|
function Tag(tag) {
|
|
_classCallCheck(this, Tag);
|
|
var types;
|
|
|
|
// Lowercase for consistency (case is only a formatting convention, not a standard requirement).
|
|
tag = tag.trim().toLowerCase();
|
|
this.data = {
|
|
tag: tag
|
|
};
|
|
|
|
// Check if the input tag is grandfathered or redundant.
|
|
types = index[tag];
|
|
if (types && (types.grandfathered || types.redundant)) {
|
|
this.data.record = registry[types.grandfathered || types.redundant];
|
|
}
|
|
}
|
|
_createClass(Tag, [{
|
|
key: "preferred",
|
|
value: function preferred() {
|
|
var preferred = this.data.record['Preferred-Value'];
|
|
if (preferred) {
|
|
return new Tag(preferred);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** @return {Subtag[]} */
|
|
}, {
|
|
key: "subtags",
|
|
value: function subtags() {
|
|
var codes,
|
|
data = this.data,
|
|
subtags = [];
|
|
|
|
// No subtags if the tag is grandfathered.
|
|
if (data.record && this.type() === 'grandfathered') {
|
|
return subtags;
|
|
}
|
|
codes = data.tag.split('-');
|
|
if (!codes.length) {
|
|
return subtags;
|
|
}
|
|
|
|
// Try and find the language tag.
|
|
codes.some(function (code, i) {
|
|
var types;
|
|
|
|
// Singletons and anything after are unhandled.
|
|
if (code.length < 2) {
|
|
return true; // Stop the loop (stop processing after a singleton).
|
|
}
|
|
|
|
types = index[code];
|
|
|
|
// Check for non-existent tag.
|
|
if (!types) {
|
|
return; // Skip to the next item.
|
|
}
|
|
|
|
// Check against undefined because value could be 0.
|
|
// Language subtags may only appear at the beginning of the tag, otherwise the subtag type is indeterminate.
|
|
if (0 === i && undefined !== types.language) {
|
|
subtags.push(new Subtag(code, 'language'));
|
|
return;
|
|
}
|
|
switch (code.length) {
|
|
case 2:
|
|
// Should be a region.
|
|
if (types.region) {
|
|
subtags.push(new Subtag(code, 'region'));
|
|
|
|
// Error case: language subtag in the wrong place.
|
|
} else if (types.language) {
|
|
subtags.push(new Subtag(code, 'language'));
|
|
}
|
|
break;
|
|
case 3:
|
|
// Could be a numeric region code e.g. '001' for 'World'.
|
|
if (types.region) {
|
|
subtags.push(new Subtag(code, 'region'));
|
|
} else if (types.extlang) {
|
|
subtags.push(new Subtag(code, 'extlang'));
|
|
|
|
// Error case: language subtag in the wrong place.
|
|
} else if (types.language) {
|
|
subtags.push(new Subtag(code, 'language'));
|
|
}
|
|
break;
|
|
case 4:
|
|
// Could be a numeric variant.
|
|
if (types.variant) {
|
|
subtags.push(new Subtag(code, 'variant'));
|
|
} else if (types.script) {
|
|
subtags.push(new Subtag(code, 'script'));
|
|
}
|
|
break;
|
|
default:
|
|
// Should be a variant.
|
|
if (types.variant) {
|
|
subtags.push(new Subtag(code, 'variant'));
|
|
}
|
|
break;
|
|
}
|
|
});
|
|
return subtags;
|
|
}
|
|
}, {
|
|
key: "language",
|
|
value: function language() {
|
|
return this.find('language');
|
|
}
|
|
}, {
|
|
key: "region",
|
|
value: function region() {
|
|
return this.find('region');
|
|
}
|
|
}, {
|
|
key: "script",
|
|
value: function script() {
|
|
return this.find('script');
|
|
}
|
|
|
|
/** @param {string} type */
|
|
}, {
|
|
key: "find",
|
|
value: function find(type) {
|
|
var i,
|
|
l,
|
|
subtag,
|
|
subtags = this.subtags();
|
|
for (i = 0, l = subtags.length; i < l; i++) {
|
|
subtag = subtags[i];
|
|
if (subtag.type() === type) {
|
|
return subtag;
|
|
}
|
|
}
|
|
}
|
|
}, {
|
|
key: "valid",
|
|
value: function valid() {
|
|
return this.errors().length < 1;
|
|
}
|
|
}, {
|
|
key: "errors",
|
|
value: function errors() {
|
|
var error,
|
|
subtags,
|
|
data = this.data,
|
|
errors = [];
|
|
error = function error(code, subtag) {
|
|
var err, message;
|
|
switch (code) {
|
|
case Tag.ERR_DEPRECATED:
|
|
message = 'The tag \'' + data.tag + '\' is deprecated.';
|
|
|
|
// Note that a record that contains a 'Deprecated' field and no corresponding 'Preferred-Value' field has no replacement mapping (RFC 5646 section 3.1.6).
|
|
if (data.record['Preferred-Value']) {
|
|
message += ' Use \'' + data.record['Preferred-Value'] + '\' instead.';
|
|
}
|
|
break;
|
|
case Tag.ERR_SUBTAG_DEPRECATED:
|
|
message = 'The subtag \'' + subtag + '\' is deprecated.';
|
|
break;
|
|
case Tag.ERR_NO_LANGUAGE:
|
|
if (!data.tag) {
|
|
message = 'Empty tag.';
|
|
} else {
|
|
message = 'Missing language tag in \'' + data.tag + '\'.';
|
|
}
|
|
break;
|
|
case Tag.ERR_UNKNOWN:
|
|
message = 'Unknown code \'' + subtag + '\'';
|
|
break;
|
|
case Tag.ERR_TOO_LONG:
|
|
message = 'The private-use subtag \'' + subtag + '\' is too long.';
|
|
break;
|
|
case Tag.ERR_EXTRA_LANGUAGE:
|
|
case Tag.ERR_EXTRA_EXTLANG:
|
|
case Tag.ERR_EXTRA_REGION:
|
|
case Tag.ERR_EXTRA_SCRIPT:
|
|
message = 'Extra ' + subtag.type() + ' subtag \'' + subtag + '\' found.';
|
|
break;
|
|
case Tag.ERR_DUPLICATE_VARIANT:
|
|
message = 'Duplicate variant subtag \'' + subtag + '\' found.';
|
|
break;
|
|
case Tag.ERR_WRONG_ORDER:
|
|
message = 'The subtag \'' + subtag[0] + '\' should not appear before \'' + subtag[1] + '\'.';
|
|
break;
|
|
case Tag.ERR_SUPPRESS_SCRIPT:
|
|
message = 'The script subtag \'' + subtag + '\' is the same as the language suppress-script.';
|
|
break;
|
|
}
|
|
err = new Error(message);
|
|
err.code = code;
|
|
err.tag = data.tag;
|
|
err.subtag = subtag;
|
|
errors.push(err);
|
|
};
|
|
|
|
// Check if the tag is grandfathered and if the grandfathered tag is deprecated (e.g. no-nyn).
|
|
if (data.record) {
|
|
if (data.record.Deprecated) {
|
|
error(Tag.ERR_DEPRECATED);
|
|
}
|
|
|
|
// Only check every subtag if the tag is not explicitly listed as grandfathered or redundant.
|
|
return errors;
|
|
}
|
|
|
|
// Check that all subtag codes are meaningful.
|
|
data.tag.split('-').some(function (code, i, codes) {
|
|
var types;
|
|
|
|
// Ignore anything after a singleton.
|
|
if (code.length < 2) {
|
|
// Check that each private-use subtag is within the maximum allowed length.
|
|
codes.slice(i).forEach(function (code) {
|
|
if (code.length > 8) {
|
|
error(Tag.ERR_TOO_LONG, code);
|
|
}
|
|
});
|
|
return true;
|
|
}
|
|
types = index[code];
|
|
if (!types) {
|
|
error(Tag.ERR_UNKNOWN, code);
|
|
}
|
|
return false; // Continue to the next item.
|
|
});
|
|
|
|
// Check that first tag is a language tag.
|
|
subtags = this.subtags();
|
|
if (!subtags.length || 'language' !== subtags[0].type()) {
|
|
error(Tag.ERR_NO_LANGUAGE);
|
|
return errors;
|
|
}
|
|
|
|
// Check for more than one of some types and for deprecation.
|
|
subtags.forEach(function (subtag, i) {
|
|
var type = subtag.type(),
|
|
language,
|
|
script,
|
|
found = this;
|
|
if (subtag.deprecated()) {
|
|
error(Tag.ERR_SUBTAG_DEPRECATED, subtag);
|
|
}
|
|
if (found[type]) {
|
|
found[type].push(subtag);
|
|
}
|
|
switch (type) {
|
|
case 'language':
|
|
if (found.language.length > 1) {
|
|
error(Tag.ERR_EXTRA_LANGUAGE, subtag);
|
|
}
|
|
break;
|
|
case 'region':
|
|
if (found.region.length > 1) {
|
|
error(Tag.ERR_EXTRA_REGION, subtag);
|
|
}
|
|
break;
|
|
case 'extlang':
|
|
if (found.extlang.length > 1) {
|
|
error(Tag.ERR_EXTRA_EXTLANG, subtag);
|
|
}
|
|
break;
|
|
case 'script':
|
|
if (found.script.length > 1) {
|
|
error(Tag.ERR_EXTRA_SCRIPT, subtag);
|
|
|
|
// Check if script is same as language suppress-script.
|
|
} else {
|
|
language = subtags[0];
|
|
if ('language' === language.type()) {
|
|
script = language.script();
|
|
if (script && script.format() === subtag.format()) {
|
|
error(Tag.ERR_SUPPRESS_SCRIPT, subtag);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case 'variant':
|
|
if (found.variant.length > 1 && found.variant.some(function (variant) {
|
|
return variant.format() === subtag.format();
|
|
})) {
|
|
error(Tag.ERR_DUPLICATE_VARIANT, subtag);
|
|
}
|
|
}
|
|
}, {
|
|
language: [],
|
|
extlang: [],
|
|
variant: [],
|
|
script: [],
|
|
region: []
|
|
});
|
|
|
|
// Check for correct order.
|
|
subtags.forEach(function (subtag, i, subtags) {
|
|
var priority = this,
|
|
next = subtags[i + 1];
|
|
if (next && priority[subtag.type()] > priority[next.type()]) {
|
|
error(Tag.ERR_WRONG_ORDER, [subtag, next]);
|
|
}
|
|
}, {
|
|
language: 4,
|
|
extlang: 5,
|
|
script: 6,
|
|
region: 7,
|
|
variant: 8
|
|
});
|
|
return errors;
|
|
}
|
|
}, {
|
|
key: "type",
|
|
value: function type() {
|
|
var record = this.data.record;
|
|
if (record) {
|
|
return record.Type;
|
|
}
|
|
return 'tag';
|
|
}
|
|
}, {
|
|
key: "added",
|
|
value: function added() {
|
|
var record = this.data.record;
|
|
return record && record.Added;
|
|
}
|
|
}, {
|
|
key: "deprecated",
|
|
value: function deprecated() {
|
|
var record = this.data.record;
|
|
return record && record.Deprecated;
|
|
}
|
|
}, {
|
|
key: "descriptions",
|
|
value: function descriptions() {
|
|
var record = this.data.record;
|
|
if (record && record.Description) {
|
|
return record.Description;
|
|
}
|
|
return [];
|
|
}
|
|
}, {
|
|
key: "format",
|
|
value: function format() {
|
|
var tag = this.data.tag;
|
|
|
|
// Format according to algorithm defined in RFC 5646 section 2.1.1.
|
|
return tag.split('-').reduce(function (p, c, i, a) {
|
|
if (i === 0) {
|
|
return c;
|
|
}
|
|
if (a[i - 1].length === 1) {
|
|
return p + '-' + c;
|
|
}
|
|
switch (c.length) {
|
|
case 2:
|
|
return p + '-' + c.toUpperCase();
|
|
case 4:
|
|
return p + '-' + c[0].toUpperCase() + c.substr(1);
|
|
}
|
|
return p + '-' + c;
|
|
});
|
|
}
|
|
}]);
|
|
return Tag;
|
|
}();
|
|
_defineProperty(Tag, "ERR_DEPRECATED", 1);
|
|
_defineProperty(Tag, "ERR_NO_LANGUAGE", 2);
|
|
_defineProperty(Tag, "ERR_UNKNOWN", 3);
|
|
_defineProperty(Tag, "ERR_TOO_LONG", 4);
|
|
_defineProperty(Tag, "ERR_EXTRA_REGION", 5);
|
|
_defineProperty(Tag, "ERR_EXTRA_EXTLANG", 6);
|
|
_defineProperty(Tag, "ERR_EXTRA_SCRIPT", 7);
|
|
_defineProperty(Tag, "ERR_DUPLICATE_VARIANT", 8);
|
|
_defineProperty(Tag, "ERR_WRONG_ORDER", 9);
|
|
_defineProperty(Tag, "ERR_SUPPRESS_SCRIPT", 10);
|
|
_defineProperty(Tag, "ERR_SUBTAG_DEPRECATED", 11);
|
|
_defineProperty(Tag, "ERR_EXTRA_LANGUAGE", 12);
|
|
module.exports = Tag; |