language-tags
Advanced tools
+6
-6
@@ -11,7 +11,7 @@ /** | ||
| var Tag = require('./Tag'); | ||
| var Subtag = require('./Subtag'); | ||
| var Tag = require('./Tag.js'); | ||
| var Subtag = require('./Subtag.js'); | ||
| var index = require('language-subtag-registry/data/json/index'); | ||
| var registry = require('language-subtag-registry/data/json/registry'); | ||
| var index = require('language-subtag-registry/data/json/index.json'); | ||
| var registry = require('language-subtag-registry/data/json/registry.json'); | ||
@@ -109,3 +109,3 @@ var tags = function(tag) { | ||
| macrolanguage = macrolanguage.toLowerCase(); | ||
| if (!require('language-subtag-registry/data/json/macrolanguage')[macrolanguage]) { | ||
| if (!require('language-subtag-registry/data/json/macrolanguage.json')[macrolanguage]) { | ||
| throw new Error('\'' + macrolanguage + '\' is not a macrolanguage.'); | ||
@@ -143,3 +143,3 @@ } | ||
| tags.date = function() { | ||
| return require('language-subtag-registry/data/json/meta')['File-Date']; | ||
| return require('language-subtag-registry/data/json/meta.json')['File-Date']; | ||
| }; |
+83
-82
@@ -11,115 +11,116 @@ /** | ||
| var index = require('language-subtag-registry/data/json/index'); | ||
| var registry = require('language-subtag-registry/data/json/registry'); | ||
| var index = require('language-subtag-registry/data/json/index.json'); | ||
| var registry = require('language-subtag-registry/data/json/registry.json'); | ||
| module.exports = Subtag; | ||
| class Subtag { | ||
| static ERR_NONEXISTENT = 1; | ||
| static ERR_TAG = 2; | ||
| Subtag.ERR_NONEXISTENT = 1; | ||
| Subtag.ERR_TAG = 2; | ||
| /** | ||
| * @param {string} subtag | ||
| * @param {string} type | ||
| */ | ||
| constructor (subtag, type) { | ||
| var types, i, record; | ||
| function Subtag(subtag, type) { | ||
| var types, i, record, error; | ||
| // Lowercase for consistency (case is only a formatting convention, not a standard requirement). | ||
| subtag = subtag.toLowerCase(); | ||
| type = type.toLowerCase(); | ||
| // Lowercase for consistency (case is only a formatting convention, not a standard requirement). | ||
| subtag = subtag.toLowerCase(); | ||
| type = type.toLowerCase(); | ||
| function error (code, message) { | ||
| var err; | ||
| error = function(code, message) { | ||
| var err; | ||
| err = new Error(message); | ||
| err.code = code; | ||
| err.subtag = subtag; | ||
| throw err; | ||
| }; | ||
| err = new Error(message); | ||
| err.code = code; | ||
| err.subtag = subtag; | ||
| throw err; | ||
| }; | ||
| types = index[subtag]; | ||
| if (!types) { | ||
| error(Subtag.ERR_NONEXISTENT, 'Non-existent subtag \'' + subtag + '\'.'); | ||
| } | ||
| types = index[subtag]; | ||
| if (!types) { | ||
| error(Subtag.ERR_NONEXISTENT, 'Non-existent subtag \'' + subtag + '\'.'); | ||
| i = types[type]; | ||
| if (!i && 0 !== i) { | ||
| error(Subtag.ERR_NONEXISTENT, 'Non-existent subtag \'' + subtag + '\' of type \'' + type + '\'.'); | ||
| } | ||
| record = registry[i]; | ||
| if (!record.Subtag) { | ||
| error(Subtag.ERR_TAG, '\'' + subtag + '\' is a \'' + type + '\' tag.'); | ||
| } | ||
| this.data = { subtag, record, type }; | ||
| } | ||
| i = types[type]; | ||
| if (!i && 0 !== i) { | ||
| error(Subtag.ERR_NONEXISTENT, 'Non-existent subtag \'' + subtag + '\' of type \'' + type + '\'.'); | ||
| type () { | ||
| return this.data.type; | ||
| } | ||
| record = registry[i]; | ||
| if (!record.Subtag) { | ||
| error(Subtag.ERR_TAG, '\'' + subtag + '\' is a \'' + type + '\' tag.'); | ||
| descriptions () { | ||
| // Every record has one or more descriptions (stored as an array). | ||
| return this.data.record.Description; | ||
| } | ||
| this.data = {}; | ||
| this.data.subtag = subtag; | ||
| this.data.record = record; | ||
| this.data.type = type; | ||
| } | ||
| preferred () { | ||
| var type, preferred = this.data.record['Preferred-Value']; | ||
| Subtag.prototype.type = function() { | ||
| return this.data.type; | ||
| }; | ||
| if (preferred) { | ||
| type = this.data.type; | ||
| if (type === 'extlang') { | ||
| type = 'language'; | ||
| } | ||
| Subtag.prototype.descriptions = function() { | ||
| return new Subtag(preferred, type); | ||
| } | ||
| // Every record has one or more descriptions (stored as an array). | ||
| return this.data.record.Description; | ||
| }; | ||
| return null; | ||
| } | ||
| Subtag.prototype.preferred = function() { | ||
| var type, preferred = this.data.record['Preferred-Value']; | ||
| script () { | ||
| var script = this.data.record['Suppress-Script']; | ||
| if (preferred) { | ||
| type = this.data.type; | ||
| if (type === 'extlang') { | ||
| type = 'language'; | ||
| if (script) { | ||
| return new Subtag(script, 'script'); | ||
| } | ||
| return new Subtag(preferred, type); | ||
| return null; | ||
| } | ||
| return null; | ||
| }; | ||
| scope () { | ||
| return this.data.record.Scope || null; | ||
| } | ||
| Subtag.prototype.script = function() { | ||
| var script = this.data.record['Suppress-Script']; | ||
| deprecated () { | ||
| return this.data.record.Deprecated || null; | ||
| } | ||
| if (script) { | ||
| return new Subtag(script, 'script'); | ||
| added () { | ||
| return this.data.record.Added; | ||
| } | ||
| return null; | ||
| }; | ||
| comments () { | ||
| // Comments don't always occur for records, so switch to an empty array if missing. | ||
| return this.data.record.Comments || []; | ||
| } | ||
| Subtag.prototype.scope = function() { | ||
| return this.data.record.Scope || null; | ||
| }; | ||
| format () { | ||
| var subtag = this.data.subtag; | ||
| Subtag.prototype.deprecated = function() { | ||
| return this.data.record.Deprecated || null; | ||
| }; | ||
| switch (this.data.type) { | ||
| case 'region': | ||
| return subtag.toUpperCase(); | ||
| case 'script': | ||
| return subtag[0].toUpperCase() + subtag.slice(1); | ||
| } | ||
| Subtag.prototype.added = function() { | ||
| return this.data.record.Added; | ||
| }; | ||
| return subtag; | ||
| } | ||
| Subtag.prototype.comments = function() { | ||
| // Comments don't always occur for records, so switch to an empty array if missing. | ||
| return this.data.record.Comments || []; | ||
| }; | ||
| Subtag.prototype.format = function() { | ||
| var subtag = this.data.subtag; | ||
| switch (this.data.type) { | ||
| case 'region': | ||
| return subtag.toUpperCase(); | ||
| case 'script': | ||
| return subtag[0].toUpperCase() + subtag.substr(1); | ||
| toString () { | ||
| return this.format(); | ||
| } | ||
| } | ||
| return subtag; | ||
| }; | ||
| Subtag.prototype.toString = function() { | ||
| return this.format(); | ||
| }; | ||
| module.exports = Subtag; |
+309
-305
@@ -11,396 +11,400 @@ /** | ||
| var index = require('language-subtag-registry/data/json/index'); | ||
| var registry = require('language-subtag-registry/data/json/registry'); | ||
| var index = require('language-subtag-registry/data/json/index.json'); | ||
| var registry = require('language-subtag-registry/data/json/registry.json'); | ||
| var Subtag = require('./Subtag'); | ||
| var Subtag = require('./Subtag.js'); | ||
| module.exports = Tag; | ||
| class Tag { | ||
| static ERR_DEPRECATED = 1; | ||
| static ERR_NO_LANGUAGE = 2; | ||
| static ERR_UNKNOWN = 3; | ||
| static ERR_TOO_LONG = 4; | ||
| static ERR_EXTRA_REGION = 5; | ||
| static ERR_EXTRA_EXTLANG = 6; | ||
| static ERR_EXTRA_SCRIPT = 7; | ||
| static ERR_DUPLICATE_VARIANT = 8; | ||
| static ERR_WRONG_ORDER = 9; | ||
| static ERR_SUPPRESS_SCRIPT = 10; | ||
| static ERR_SUBTAG_DEPRECATED = 11; | ||
| static ERR_EXTRA_LANGUAGE = 12; | ||
| Tag.ERR_DEPRECATED = 1; | ||
| Tag.ERR_NO_LANGUAGE = 2; | ||
| Tag.ERR_UNKNOWN = 3; | ||
| Tag.ERR_TOO_LONG = 4; | ||
| Tag.ERR_EXTRA_REGION = 5; | ||
| Tag.ERR_EXTRA_EXTLANG = 6; | ||
| Tag.ERR_EXTRA_SCRIPT = 7; | ||
| Tag.ERR_DUPLICATE_VARIANT = 8; | ||
| Tag.ERR_WRONG_ORDER = 9; | ||
| Tag.ERR_SUPPRESS_SCRIPT = 10; | ||
| Tag.ERR_SUBTAG_DEPRECATED = 11; | ||
| Tag.ERR_EXTRA_LANGUAGE = 12; | ||
| /** @param {string} tag */ | ||
| constructor (tag) { | ||
| var types; | ||
| function Tag(tag) { | ||
| var types; | ||
| // Lowercase for consistency (case is only a formatting convention, not a standard requirement). | ||
| tag = tag.trim().toLowerCase(); | ||
| // Lowercase for consistency (case is only a formatting convention, not a standard requirement). | ||
| tag = tag.trim().toLowerCase(); | ||
| this.data = { tag }; | ||
| this.data = {}; | ||
| 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]; | ||
| // 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]; | ||
| } | ||
| } | ||
| } | ||
| Tag.prototype.preferred = function() { | ||
| var preferred = this.data.record['Preferred-Value']; | ||
| preferred () { | ||
| var preferred = this.data.record['Preferred-Value']; | ||
| if (preferred) { | ||
| return new Tag(preferred); | ||
| } | ||
| if (preferred) { | ||
| return new Tag(preferred); | ||
| } | ||
| return null; | ||
| }; | ||
| Tag.prototype.subtags = function() { | ||
| var codes, data = this.data, subtags = []; | ||
| // No subtags if the tag is grandfathered. | ||
| if (data.record && this.type() === 'grandfathered') { | ||
| return subtags; | ||
| return null; | ||
| } | ||
| codes = data.tag.split('-'); | ||
| if (!codes.length) { | ||
| return subtags; | ||
| } | ||
| /** @return {Subtag[]} */ | ||
| subtags () { | ||
| var codes, data = this.data, 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). | ||
| // No subtags if the tag is grandfathered. | ||
| if (data.record && this.type() === 'grandfathered') { | ||
| return subtags; | ||
| } | ||
| types = index[code]; | ||
| // Check for non-existent tag. | ||
| if (!types) { | ||
| return; // Skip to the next item. | ||
| codes = data.tag.split('-'); | ||
| if (!codes.length) { | ||
| return subtags; | ||
| } | ||
| // 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; | ||
| } | ||
| // Try and find the language tag. | ||
| codes.some(function (code, i) { | ||
| var types; | ||
| switch (code.length) { | ||
| case 2: | ||
| // Singletons and anything after are unhandled. | ||
| if (code.length < 2) { | ||
| return true; // Stop the loop (stop processing after a singleton). | ||
| } | ||
| // Should be a region. | ||
| if (types.region) { | ||
| subtags.push(new Subtag(code, 'region')); | ||
| types = index[code]; | ||
| // Error case: language subtag in the wrong place. | ||
| } else if (types.language) { | ||
| subtags.push(new Subtag(code, 'language')); | ||
| // Check for non-existent tag. | ||
| if (!types) { | ||
| return; // Skip to the next item. | ||
| } | ||
| 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) { | ||
| // 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; | ||
| } | ||
| break; | ||
| case 4: | ||
| switch (code.length) { | ||
| case 2: | ||
| // Could be a numeric variant. | ||
| if (types.variant) { | ||
| subtags.push(new Subtag(code, 'variant')); | ||
| } else if (types.script) { | ||
| subtags.push(new Subtag(code, 'script')); | ||
| } | ||
| // Should be a region. | ||
| if (types.region) { | ||
| subtags.push(new Subtag(code, 'region')); | ||
| break; | ||
| default: | ||
| // Error case: language subtag in the wrong place. | ||
| } else if (types.language) { | ||
| subtags.push(new Subtag(code, 'language')); | ||
| } | ||
| // Should be a variant. | ||
| if (types.variant) { | ||
| subtags.push(new Subtag(code, 'variant')); | ||
| } | ||
| break; | ||
| case 3: | ||
| break; | ||
| } | ||
| }); | ||
| // 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')); | ||
| return subtags; | ||
| }; | ||
| // Error case: language subtag in the wrong place. | ||
| } else if (types.language) { | ||
| subtags.push(new Subtag(code, 'language')); | ||
| } | ||
| Tag.prototype.language = function() { | ||
| return this.find('language'); | ||
| }; | ||
| break; | ||
| case 4: | ||
| Tag.prototype.region = function() { | ||
| return this.find('region'); | ||
| }; | ||
| // Could be a numeric variant. | ||
| if (types.variant) { | ||
| subtags.push(new Subtag(code, 'variant')); | ||
| } else if (types.script) { | ||
| subtags.push(new Subtag(code, 'script')); | ||
| } | ||
| Tag.prototype.script = function() { | ||
| return this.find('script'); | ||
| }; | ||
| break; | ||
| default: | ||
| Tag.prototype.find = function(type) { | ||
| var i, l, subtag, subtags = this.subtags(); | ||
| // Should be a variant. | ||
| if (types.variant) { | ||
| subtags.push(new Subtag(code, 'variant')); | ||
| } | ||
| for (i = 0, l = subtags.length; i < l; i++) { | ||
| subtag = subtags[i]; | ||
| break; | ||
| } | ||
| }); | ||
| if (subtag.type() === type) { | ||
| return subtag; | ||
| } | ||
| return subtags; | ||
| } | ||
| }; | ||
| Tag.prototype.valid = function() { | ||
| return this.errors().length < 1; | ||
| }; | ||
| language () { | ||
| return this.find('language'); | ||
| } | ||
| Tag.prototype.errors = function() { | ||
| var error, subtags, data = this.data, errors = []; | ||
| region () { | ||
| return this.find('region'); | ||
| } | ||
| error = function(code, subtag) { | ||
| var err, message; | ||
| script () { | ||
| return this.find('script'); | ||
| } | ||
| switch (code) { | ||
| case Tag.ERR_DEPRECATED: | ||
| message = 'The tag \'' + data.tag + '\' is deprecated.'; | ||
| /** @param {string} type */ | ||
| find (type) { | ||
| var i, l, subtag, subtags = this.subtags(); | ||
| // 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.'; | ||
| } | ||
| for (i = 0, l = subtags.length; i < l; i++) { | ||
| subtag = subtags[i]; | ||
| 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 + '\'.'; | ||
| if (subtag.type() === type) { | ||
| return subtag; | ||
| } | ||
| 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); | ||
| }; | ||
| valid () { | ||
| return this.errors().length < 1; | ||
| } | ||
| // 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); | ||
| } | ||
| errors () { | ||
| var error, subtags, data = this.data, errors = []; | ||
| // Only check every subtag if the tag is not explicitly listed as grandfathered or redundant. | ||
| return errors; | ||
| } | ||
| error = function (code, subtag) { | ||
| var err, message; | ||
| // Check that all subtag codes are meaningful. | ||
| data.tag.split('-').some(function(code, i, codes) { | ||
| var types; | ||
| switch (code) { | ||
| case Tag.ERR_DEPRECATED: | ||
| message = 'The tag \'' + data.tag + '\' is deprecated.'; | ||
| // Ignore anything after a singleton. | ||
| if (code.length < 2) { | ||
| // 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.'; | ||
| } | ||
| // 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); | ||
| } | ||
| }); | ||
| 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 + '\'.'; | ||
| } | ||
| return true; | ||
| } | ||
| 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; | ||
| } | ||
| types = index[code]; | ||
| if (!types) { | ||
| error(Tag.ERR_UNKNOWN, code); | ||
| 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; | ||
| } | ||
| return false; // Continue to the next item. | ||
| }); | ||
| // Check that all subtag codes are meaningful. | ||
| data.tag.split('-').some(function (code, i, codes) { | ||
| var types; | ||
| // 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; | ||
| } | ||
| // Ignore anything after a singleton. | ||
| if (code.length < 2) { | ||
| // Check for more than one of some types and for deprecation. | ||
| subtags.forEach(function(subtag, i) { | ||
| var type = subtag.type(), language, script, found = this; | ||
| // 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); | ||
| } | ||
| }); | ||
| if (subtag.deprecated()) { | ||
| error(Tag.ERR_SUBTAG_DEPRECATED, subtag); | ||
| } | ||
| return true; | ||
| } | ||
| if (found[type]) { | ||
| found[type].push(subtag); | ||
| 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; | ||
| } | ||
| switch (type) { | ||
| case 'language': | ||
| if (found.language.length > 1) { | ||
| error(Tag.ERR_EXTRA_LANGUAGE, subtag); | ||
| } | ||
| // Check for more than one of some types and for deprecation. | ||
| subtags.forEach(function (subtag, i) { | ||
| var type = subtag.type(), language, script, found = this; | ||
| break; | ||
| case 'region': | ||
| if (found.region.length > 1) { | ||
| error(Tag.ERR_EXTRA_REGION, subtag); | ||
| if (subtag.deprecated()) { | ||
| error(Tag.ERR_SUBTAG_DEPRECATED, subtag); | ||
| } | ||
| break; | ||
| case 'extlang': | ||
| if (found.extlang.length > 1) { | ||
| error(Tag.ERR_EXTRA_EXTLANG, subtag); | ||
| if (found[type]) { | ||
| found[type].push(subtag); | ||
| } | ||
| break; | ||
| case 'script': | ||
| if (found.script.length > 1) { | ||
| error(Tag.ERR_EXTRA_SCRIPT, subtag); | ||
| switch (type) { | ||
| case 'language': | ||
| if (found.language.length > 1) { | ||
| error(Tag.ERR_EXTRA_LANGUAGE, 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 '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: [] | ||
| }); | ||
| break; | ||
| case 'variant': | ||
| if (found.variant.length > 1 && found.variant.some(function(variant) { | ||
| return variant.format() === subtag.format(); | ||
| })) { | ||
| error(Tag.ERR_DUPLICATE_VARIANT, subtag); | ||
| // 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: [], | ||
| extlang: [], | ||
| variant: [], | ||
| script: [], | ||
| region: [] | ||
| }); | ||
| }, { | ||
| language: 4, | ||
| extlang: 5, | ||
| script: 6, | ||
| region: 7, | ||
| variant: 8 | ||
| }); | ||
| // Check for correct order. | ||
| subtags.forEach(function(subtag, i, subtags) { | ||
| var priority = this, next = subtags[i + 1]; | ||
| return errors; | ||
| } | ||
| if (next && priority[subtag.type()] > priority[next.type()]) { | ||
| error(Tag.ERR_WRONG_ORDER, [subtag, next]); | ||
| type () { | ||
| var record = this.data.record; | ||
| if (record) { | ||
| return record.Type; | ||
| } | ||
| }, { | ||
| language: 4, | ||
| extlang: 5, | ||
| script: 6, | ||
| region: 7, | ||
| variant: 8 | ||
| }); | ||
| return errors; | ||
| }; | ||
| return 'tag'; | ||
| } | ||
| Tag.prototype.type = function() { | ||
| var record = this.data.record; | ||
| added () { | ||
| var record = this.data.record; | ||
| if (record) { | ||
| return record.Type; | ||
| return record && record.Added; | ||
| } | ||
| return 'tag'; | ||
| }; | ||
| deprecated () { | ||
| var record = this.data.record; | ||
| Tag.prototype.added = function() { | ||
| var record = this.data.record; | ||
| return record && record.Deprecated; | ||
| } | ||
| return record && record.Added; | ||
| }; | ||
| descriptions () { | ||
| var record = this.data.record; | ||
| Tag.prototype.deprecated = function() { | ||
| var record = this.data.record; | ||
| if (record && record.Description) { | ||
| return record.Description; | ||
| } | ||
| return record && record.Deprecated; | ||
| }; | ||
| return []; | ||
| } | ||
| Tag.prototype.descriptions = function() { | ||
| var record = this.data.record; | ||
| format () { | ||
| var tag = this.data.tag; | ||
| if (record && record.Description) { | ||
| return record.Description; | ||
| } | ||
| // 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; | ||
| } | ||
| return []; | ||
| }; | ||
| if (a[i - 1].length === 1) { | ||
| return p + '-' + c; | ||
| } | ||
| Tag.prototype.format = function() { | ||
| var tag = this.data.tag; | ||
| switch (c.length) { | ||
| case 2: | ||
| return p + '-' + c.toUpperCase(); | ||
| case 4: | ||
| return p + '-' + c[0].toUpperCase() + c.substr(1); | ||
| } | ||
| // 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; | ||
| }); | ||
| }; | ||
| module.exports = Tag; |
+12
-7
| { | ||
| "name": "language-tags", | ||
| "version": "1.0.5", | ||
| "implements": ["CommonJS/Modules/1.0"], | ||
| "version": "1.0.6", | ||
| "implements": [ | ||
| "CommonJS/Modules/1.0" | ||
| ], | ||
| "description": "Work with IANA language tags.", | ||
| "main": "lib/index.js", | ||
| "homepage": "https://github.com/mattcg/language-tags", | ||
| "author": "Matthew Caruana Galizia <m@m.cg>", | ||
| "author": "Matthew Caruana Galizia <mattcg@gmail.com>", | ||
| "repository": { | ||
@@ -29,9 +31,12 @@ "type": "git", | ||
| "dependencies": { | ||
| "language-subtag-registry": "~0.3.2" | ||
| "language-subtag-registry": "^0.3.20" | ||
| }, | ||
| "devDependencies": { | ||
| "mocha": "~2.3.4", | ||
| "mocha": "~6.2.0", | ||
| "istanbul": "~0.4.2", | ||
| "coveralls": "~2.11.6" | ||
| } | ||
| "coveralls": "~3.0.5" | ||
| }, | ||
| "files": [ | ||
| "/lib" | ||
| ] | ||
| } |
+1
-1
@@ -6,3 +6,3 @@ # IANA Language Tags for JavaScript # | ||
| Based on [BCP 47](http://tools.ietf.org/html/bcp47) ([RFC 5646](http://tools.ietf.org/html/rfc5646)) and the latest [IANA language subtag registry](http://www.iana.org/assignments/language-subtag-registry). | ||
| Based on [BCP 47](https://www.rfc-editor.org/info/bcp47) ([RFC 5646](https://www.rfc-editor.org/rfc/rfc5646.html)) and the latest [IANA language subtag registry](http://www.iana.org/assignments/language-subtag-registry). | ||
@@ -9,0 +9,0 @@ This project will be updated as the standards change. |
Sorry, the diff of this file is not supported yet
-12
| language: node_js | ||
| node_js: | ||
| - "4.1" | ||
| - "4.0" | ||
| - "0.12" | ||
| - "0.10" | ||
| branches: | ||
| only: | ||
| - master | ||
| script: | ||
| - "make test" | ||
| - "make test-coveralls" |
-32
| test: lib test/lib node_modules | ||
| TEST_LIB_PATH="../../lib" ./node_modules/.bin/_mocha \ | ||
| --timeout 3000 \ | ||
| --reporter spec \ | ||
| --check-leaks \ | ||
| --ui tdd \ | ||
| --recursive | ||
| test-coverage: lib test/lib node_modules | ||
| TEST_LIB_PATH="../../lib" ./node_modules/.bin/istanbul \ | ||
| cover ./node_modules/.bin/_mocha \ | ||
| -- \ | ||
| --timeout 3000 \ | ||
| --reporter spec \ | ||
| --check-leaks \ | ||
| --ui tdd \ | ||
| --recursive | ||
| view-coverage: test-coverage | ||
| open coverage/lcov-report/index.html | ||
| test-coveralls: test-coverage | ||
| cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js | ||
| node_modules: package.json | ||
| npm install | ||
| touch $@ | ||
| clean: | ||
| rm -rf coverage | ||
| .PHONY: test test-coverage test-coveralls view-coverage clean |
| /** | ||
| * @author Matthew Caruana Galizia <m@m.cg> | ||
| * @license MIT: http://mattcg.mit-license.org/ | ||
| * @copyright Copyright (c) 2013, Matthew Caruana Galizia | ||
| */ | ||
| /*jshint node:true*/ | ||
| /*global test, suite*/ | ||
| 'use strict'; | ||
| var assert = require('assert'); | ||
| var tags = require(process.env.TEST_LIB_PATH); | ||
| suite('tags', function() { | ||
| test('date() returns file date', function() { | ||
| assert(/\d{4}\-\d{2}\-\d{2}/.test(tags.date())); | ||
| }); | ||
| test('type() returns subtag by type', function() { | ||
| var subtag; | ||
| subtag = tags.type('Latn', 'script'); | ||
| assert(subtag); | ||
| assert.equal(subtag.format(), 'Latn'); | ||
| assert.equal(subtag.type(), 'script'); | ||
| assert.equal(tags.type('en', 'script'), null); | ||
| }); | ||
| test('region() returns subtag by region', function() { | ||
| var subtag; | ||
| subtag = tags.region('IQ'); | ||
| assert(subtag); | ||
| assert.equal(subtag.format(), 'IQ'); | ||
| assert.equal(subtag.type(), 'region'); | ||
| assert.equal(tags.region('en'), null); | ||
| }); | ||
| test('language() returns subtag by language', function() { | ||
| var subtag; | ||
| subtag = tags.language('en'); | ||
| assert(subtag); | ||
| assert.equal(subtag.format(), 'en'); | ||
| assert.equal(subtag.type(), 'language'); | ||
| assert.equal(tags.language('GB'), null); | ||
| }); | ||
| test('languages() returns all languages for macrolanguage', function() { | ||
| var subtags, err; | ||
| subtags = tags.languages('zh'); | ||
| assert(subtags.length > 0); | ||
| try { | ||
| assert.equal(tags.languages('en')); | ||
| } catch (e) { | ||
| err = e; | ||
| } | ||
| assert(err); | ||
| assert.equal(err.message, '\'en\' is not a macrolanguage.'); | ||
| }); | ||
| test('search() matches descriptions', function() { | ||
| var subtags; | ||
| subtags = tags.search('Maltese'); | ||
| assert(subtags.length > 0); | ||
| assert.equal(subtags[0].type(), 'language'); | ||
| assert.equal(subtags[0].format(), 'mt'); | ||
| assert.equal(subtags[1].type(), 'language'); | ||
| assert.equal(subtags[1].format(), 'mdl'); | ||
| assert.equal(subtags[2].type(), 'extlang'); | ||
| assert.equal(subtags[2].format(), 'mdl'); | ||
| subtags = tags.search('Gibberish'); | ||
| assert.deepEqual(subtags, []); | ||
| }); | ||
| test('search() puts exact match at the top', function() { | ||
| var subtags; | ||
| subtags = tags.search('Dari'); | ||
| assert(subtags.length > 0); | ||
| assert.equal(subtags[0].type(), 'language'); | ||
| assert.equal(subtags[0].format(), 'prs'); | ||
| }); | ||
| test('subtags() returns subtags', function() { | ||
| var subtags; | ||
| subtags = tags.subtags('whatever'); | ||
| assert.deepEqual(subtags, []); | ||
| subtags = tags.subtags('mt'); | ||
| assert.equal(subtags.length, 2); | ||
| assert.equal(subtags[0].type(), 'language'); | ||
| assert.equal(subtags[0].format(), 'mt'); | ||
| assert.equal(subtags[1].type(), 'region'); | ||
| assert.equal(subtags[1].format(), 'MT'); | ||
| }); | ||
| test('check() checks tag validity', function() { | ||
| assert(tags.check('en')); | ||
| assert(!tags.check('mo')); | ||
| }); | ||
| test('gets tag', function() { | ||
| var tag; | ||
| tag = tags('en'); | ||
| assert(tag); | ||
| tag = tags('en-gb'); | ||
| assert(tag); | ||
| assert.equal(tag.format(), 'en-GB'); | ||
| }); | ||
| }); |
| /** | ||
| * @author Matthew Caruana Galizia <m@m.cg> | ||
| * @license MIT: http://mattcg.mit-license.org/ | ||
| * @copyright Copyright (c) 2013, Matthew Caruana Galizia | ||
| */ | ||
| /*jshint node:true*/ | ||
| /*global test, suite*/ | ||
| 'use strict'; | ||
| var assert = require('assert'); | ||
| var Subtag = require(process.env.TEST_LIB_PATH + '/Subtag'); | ||
| suite('Subtag', function() { | ||
| test('subtag.type() returns type', function() { | ||
| assert.equal(new Subtag('zh', 'language').type(), 'language'); | ||
| assert.equal(new Subtag('IQ', 'region').type(), 'region'); | ||
| }); | ||
| test('subtag.descriptions() returns descriptions', function() { | ||
| assert.deepEqual(new Subtag('IQ', 'region').descriptions(), ['Iraq']); | ||
| assert.deepEqual(new Subtag('vsv', 'extlang').descriptions(), ['Valencian Sign Language', 'Llengua de signes valenciana']); | ||
| }); | ||
| test('subtag.preferred() returns preferred subtag', function() { | ||
| var subtag, preferred; | ||
| // Extlang | ||
| subtag = new Subtag('vsv', 'extlang'); | ||
| preferred = subtag.preferred(); | ||
| assert(preferred); | ||
| assert.equal(preferred.type(), 'language'); | ||
| assert.equal(preferred.format(), 'vsv'); | ||
| // Language | ||
| // Moldovan -> Romanian | ||
| subtag = new Subtag('mo', 'language'); | ||
| preferred = subtag.preferred(); | ||
| assert(preferred); | ||
| assert.equal(preferred.type(), 'language'); | ||
| assert.equal(preferred.format(), 'ro'); | ||
| // Region | ||
| // Burma -> Myanmar | ||
| subtag = new Subtag('BU', 'region'); | ||
| preferred = subtag.preferred(); | ||
| assert(preferred); | ||
| assert.equal(preferred.type(), 'region'); | ||
| assert.equal(preferred.format(), 'MM'); | ||
| // Variant | ||
| subtag = new Subtag('heploc', 'variant'); | ||
| preferred = subtag.preferred(); | ||
| assert(preferred); | ||
| assert.equal(preferred.type(), 'variant'); | ||
| assert.equal(preferred.format(), 'alalc97'); | ||
| // Should return null if no preferred value. | ||
| // Latin America and the Caribbean | ||
| subtag = new Subtag('419', 'region'); | ||
| assert.equal(subtag.preferred(), null); | ||
| }); | ||
| test('subtag.script() returns suppress-script as subtag', function() { | ||
| var subtag, script; | ||
| subtag = new Subtag('en', 'language'); | ||
| script = subtag.script(); | ||
| assert(script); | ||
| assert.equal(script.type(), 'script'); | ||
| assert.equal(script.format(), 'Latn'); | ||
| // Should return null if no script. | ||
| // A macrolanguage like 'zh' should have no suppress-script. | ||
| subtag = new Subtag('zh', 'language'); | ||
| script = subtag.script(); | ||
| assert.equal(script, null); | ||
| }); | ||
| test('subtag.scope() returns scope', function() { | ||
| assert.equal(new Subtag('zh', 'language').scope(), 'macrolanguage'); | ||
| assert.equal(new Subtag('nah', 'language').scope(), 'collection'); | ||
| assert.equal(new Subtag('en', 'language').scope(), null); | ||
| assert.equal(new Subtag('IQ', 'region').scope(), null); | ||
| }); | ||
| test('subtag.deprecated() returns deprecation date if available', function() { | ||
| // German Democratic Republic | ||
| assert.equal(new Subtag('DD', 'region').deprecated(), '1990-10-30'); | ||
| assert.equal(new Subtag('DE', 'region').deprecated(), null); | ||
| }); | ||
| test('subtag.added() returns date added', function() { | ||
| assert.equal(new Subtag('DD', 'region').added(), '2005-10-16'); | ||
| assert.equal(new Subtag('DG', 'region').added(), '2009-07-29'); | ||
| }); | ||
| test('subtag.comments() returns comments', function() { | ||
| // Yugoslavia | ||
| assert.deepEqual(new Subtag('YU', 'region').comments(), ['see BA, HR, ME, MK, RS, or SI']); | ||
| }); | ||
| test('subtag.format() formats subtag according to conventions', function() { | ||
| // Language | ||
| assert.equal(new Subtag('en', 'language').format(), 'en'); | ||
| assert.equal(new Subtag('EN', 'language').format(), 'en'); | ||
| // Region | ||
| assert.equal(new Subtag('GB', 'region').format(), 'GB'); | ||
| assert.equal(new Subtag('gb', 'region').format(), 'GB'); | ||
| // Script | ||
| assert.equal(new Subtag('Latn', 'script').format(), 'Latn'); | ||
| assert.equal(new Subtag('latn', 'script').format(), 'Latn'); | ||
| }); | ||
| }); |
| /** | ||
| * @author Matthew Caruana Galizia <m@m.cg> | ||
| * @license MIT: http://mattcg.mit-license.org/ | ||
| * @copyright Copyright (c) 2013, Matthew Caruana Galizia | ||
| */ | ||
| /*jshint node:true*/ | ||
| /*global test, suite*/ | ||
| 'use strict'; | ||
| var assert = require('assert'); | ||
| var Tag = require(process.env.TEST_LIB_PATH + '/Tag'); | ||
| suite('Tag', function() { | ||
| test('tag.type() returns \'grandfathered\'', function() { | ||
| // Classified as grandfathered in the registry. | ||
| assert.equal(new Tag('en-GB-oed').type(), 'grandfathered'); | ||
| }); | ||
| test('tag.type() returns \'redundant\'', function() { | ||
| // Classified as redundant in the registry. | ||
| assert.equal(new Tag('az-Arab').type(), 'redundant'); | ||
| assert.equal(new Tag('uz-Cyrl').type(), 'redundant'); | ||
| assert.equal(new Tag('zh-cmn-Hant').type(), 'redundant'); | ||
| }); | ||
| test('tag.type() returns \'tag\'', function() { | ||
| // Maltese (mt) is a subtag but valid as a standalone tag. | ||
| assert.equal(new Tag('mt').type(), 'tag'); | ||
| }); | ||
| test('tag.subtags() returns subtags with correct type', function() { | ||
| var tag, subtags; | ||
| tag = new Tag('en'); | ||
| subtags = tag.subtags(); | ||
| assert.equal(subtags.length, 1); | ||
| assert.equal(subtags[0].type(), 'language'); | ||
| assert.equal(subtags[0].format(), 'en'); | ||
| // Lowercase - lookup should be case insensitive. | ||
| tag = new Tag('en-mt'); | ||
| subtags = tag.subtags(); | ||
| assert.equal(subtags.length, 2); | ||
| assert.equal(subtags[0].type(), 'language'); | ||
| assert.equal(subtags[0].format(), 'en'); | ||
| assert.equal(subtags[1].type(), 'region'); | ||
| assert.equal(subtags[1].format(), 'MT'); | ||
| tag = new Tag('en-mt-arab'); | ||
| subtags = tag.subtags(); | ||
| assert.equal(subtags.length, 3); | ||
| assert.equal(subtags[0].type(), 'language'); | ||
| assert.equal(subtags[0].format(), 'en'); | ||
| assert.equal(subtags[1].type(), 'region'); | ||
| assert.equal(subtags[1].format(), 'MT'); | ||
| assert.equal(subtags[2].type(), 'script'); | ||
| assert.equal(subtags[2].format(), 'Arab'); | ||
| }); | ||
| test('tag.subtags() returns only existent subtags', function() { | ||
| var tag, subtags; | ||
| tag = new Tag('hello'); | ||
| assert.deepEqual(tag.subtags(), []); | ||
| tag = new Tag('en-hello'); | ||
| subtags = tag.subtags(); | ||
| assert.equal(subtags.length, 1); | ||
| assert.equal(subtags[0].type(), 'language'); | ||
| assert.equal(subtags[0].format(), 'en'); | ||
| }); | ||
| test('tag.subtags() handles private tags', function() { | ||
| var tag, subtags; | ||
| tag = new Tag('en-GB-x-Beano'); | ||
| subtags = tag.subtags(); | ||
| assert.equal(subtags.length, 2); | ||
| assert.equal(subtags[0].type(), 'language'); | ||
| assert.equal(subtags[0].format(), 'en'); | ||
| assert.equal(subtags[1].type(), 'region'); | ||
| assert.equal(subtags[1].format(), 'GB'); | ||
| }); | ||
| test('tag.subtags() returns empty array for grandfathered tag', function() { | ||
| var tag, subtags; | ||
| tag = new Tag('en-GB-oed'); | ||
| assert.equal(tag.type(), 'grandfathered'); | ||
| subtags = tag.subtags(); | ||
| assert.deepEqual(subtags, []); | ||
| assert.equal(undefined, tag.region()); | ||
| assert.equal(undefined, tag.language()); | ||
| }); | ||
| test('tag.subtags() returns array for redundant tag', function() { | ||
| var tag, subtags; | ||
| tag = new Tag('az-Arab'); | ||
| assert.equal(tag.type(), 'redundant'); | ||
| subtags = tag.subtags(); | ||
| assert.equal(2, subtags.length); | ||
| assert.equal(subtags[0].format(), 'az'); | ||
| assert.equal(subtags[1].format(), 'Arab'); | ||
| }); | ||
| test('tag.errors() returns error for deprecated grandfathered tag', function() { | ||
| var tag, errs, err; | ||
| // Grandfathered and deprecated, therefore invalid. | ||
| tag = new Tag('art-lojban'); | ||
| assert.equal(tag.type(), 'grandfathered'); | ||
| assert(tag.deprecated()); | ||
| errs = tag.errors(); | ||
| assert.equal(errs.length, 1); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_DEPRECATED); | ||
| assert.equal(err.tag, 'art-lojban'); | ||
| }); | ||
| test('tag.errors() returns error for deprecated redundant tag', function() { | ||
| var tag, errs, err; | ||
| // Redundant and deprecated, therefore invalid. | ||
| tag = new Tag('zh-cmn'); | ||
| assert.equal(tag.type(), 'redundant'); | ||
| assert(tag.deprecated()); | ||
| errs = tag.errors(); | ||
| assert.equal(errs.length, 1); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_DEPRECATED); | ||
| assert.equal(err.tag, 'zh-cmn'); | ||
| }); | ||
| test('tag.errors() returns error if contains deprecated subtags', function() { | ||
| var errs, err; | ||
| // Moldovan (mo) is deprecated as a language. | ||
| errs = new Tag('mo').errors(); | ||
| assert.equal(errs.length, 1); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_SUBTAG_DEPRECATED); | ||
| assert.equal(err.message, 'The subtag \'mo\' is deprecated.'); | ||
| // Neutral Zone (NT) is deprecated as a region. | ||
| errs = new Tag('en-NT').errors(); | ||
| assert.equal(errs.length, 1); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_SUBTAG_DEPRECATED); | ||
| assert.equal(err.message, 'The subtag \'NT\' is deprecated.'); | ||
| }); | ||
| test('tag.errors() returns empty array for valid tag', function() { | ||
| assert.equal(new Tag('en').errors().length, 0); | ||
| }); | ||
| test('tag.errors() returns error if no language tag and not grandfathered or redundant', function() { | ||
| var errs, err; | ||
| // Test with empty tag. | ||
| errs = new Tag('').errors(); | ||
| assert.equal(errs.length, 1); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_NO_LANGUAGE); | ||
| assert.equal(err.message, 'Empty tag.'); | ||
| errs = new Tag('IQ-Arab').errors(); | ||
| assert.equal(errs.length, 1); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_NO_LANGUAGE); | ||
| assert.equal(err.message, 'Missing language tag in \'iq-arab\'.'); | ||
| errs = new Tag('419').errors(); | ||
| assert.equal(errs.length, 1); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_NO_LANGUAGE); | ||
| assert.equal(err.message, 'Missing language tag in \'419\'.'); | ||
| }); | ||
| test('tag.errors() returns error if language subtag not at front of tag', function() { | ||
| var errs, err; | ||
| errs = new Tag('GB-en').errors(); | ||
| assert.equal(errs.length, 1); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_NO_LANGUAGE); | ||
| assert.equal(err.message, 'Missing language tag in \'gb-en\'.'); | ||
| }); | ||
| test('tag.errors() returns error if more than one language subtag appears', function() { | ||
| var errs, err; | ||
| errs = new Tag('en-en').errors(); | ||
| assert.equal(errs.length, 1); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_EXTRA_LANGUAGE); | ||
| assert.equal(err.message, 'Extra language subtag \'en\' found.'); | ||
| errs = new Tag('en-en-GB').errors(); | ||
| assert.equal(errs.length, 1); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_EXTRA_LANGUAGE); | ||
| assert.equal(err.message, 'Extra language subtag \'en\' found.'); | ||
| errs = new Tag('ko-en').errors(); | ||
| assert.equal(errs.length, 1); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_EXTRA_LANGUAGE); | ||
| assert.equal(err.message, 'Extra language subtag \'en\' found.'); | ||
| }); | ||
| test('tag.errors() returns error if more than one region subtag appears', function() { | ||
| var errs, err; | ||
| errs = new Tag('en-GB-GB').errors(); | ||
| assert.equal(errs.length, 1); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_EXTRA_REGION); | ||
| assert.equal(err.message, 'Extra region subtag \'GB\' found.'); | ||
| errs = new Tag('ko-mt-mt').errors(); | ||
| assert.equal(errs.length, 1); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_EXTRA_REGION); | ||
| assert.equal(err.message, 'Extra region subtag \'MT\' found.'); | ||
| }); | ||
| test('tag.errors() returns error if more than one script subtag appears', function() { | ||
| var errs, err; | ||
| errs = new Tag('mt-Arab-Arab').errors(); | ||
| assert.equal(errs.length, 1); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_EXTRA_SCRIPT); | ||
| assert.equal(err.message, 'Extra script subtag \'Arab\' found.'); | ||
| errs = new Tag('en-Cyrl-Latn').errors(); | ||
| assert.equal(errs.length, 1); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_EXTRA_SCRIPT); | ||
| assert.equal(err.message, 'Extra script subtag \'Latn\' found.'); | ||
| // First error should be regarding suppress-script, second should be regarding extra script. | ||
| errs = new Tag('en-Latn-Cyrl').errors(); | ||
| assert.equal(errs.length, 2); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_SUPPRESS_SCRIPT); | ||
| assert.equal(err.message, 'The script subtag \'Latn\' is the same as the language suppress-script.'); | ||
| err = errs[1]; | ||
| assert.equal(err.code, Tag.ERR_EXTRA_SCRIPT); | ||
| assert.equal(err.message, 'Extra script subtag \'Cyrl\' found.'); | ||
| }, | ||
| 'tag.errors() returns error if more than one extlang subtag appears', function() { | ||
| var errs, err; | ||
| errs = new Tag('en-asp-bog').errors(); | ||
| assert.equal(errs.length, 1); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_EXTRA_EXTLANG); | ||
| assert.equal(err.message, 'Extra extlang subtag \'bog\' found.'); | ||
| }); | ||
| test('tag.errors() returns error if a duplicate variant subtag appears', function() { | ||
| var errs, err; | ||
| errs = new Tag('ca-valencia-valencia').errors(); | ||
| assert.equal(errs.length, 1); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_DUPLICATE_VARIANT); | ||
| assert.equal(err.message, 'Duplicate variant subtag \'valencia\' found.'); | ||
| }); | ||
| test('tag.errors() returns error if private-use subtag contains more than 8 characters', function() { | ||
| var errs, err; | ||
| // i.e. more than 8 in each component, not in total. | ||
| errs = new Tag('en-x-more-than-eight-chars').errors(); | ||
| assert.equal(errs.length, 0); | ||
| errs = new Tag('en-x-morethaneightchars').errors(); | ||
| assert.equal(errs.length, 1); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_TOO_LONG); | ||
| assert.equal(err.message, 'The private-use subtag \'morethaneightchars\' is too long.'); | ||
| }); | ||
| test('tag.errors() returns error if script subtag is same as language suppress-script', function() { | ||
| var errs, err; | ||
| errs = new Tag('gsw-Latn').errors(); | ||
| assert.equal(errs.length, 1); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_SUPPRESS_SCRIPT); | ||
| assert.equal(err.message, 'The script subtag \'Latn\' is the same as the language suppress-script.'); | ||
| errs = new Tag('en-Latn-GB').errors(); | ||
| assert.equal(errs.length, 1); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_SUPPRESS_SCRIPT); | ||
| assert.equal(err.message, 'The script subtag \'Latn\' is the same as the language suppress-script.'); | ||
| }); | ||
| test('tag.errors() returns error if subtags are in wrong order', function() { | ||
| var errs, err; | ||
| errs = new Tag('mt-MT-Arab').errors(); | ||
| assert.equal(errs.length, 1); | ||
| err = errs[0]; | ||
| assert.equal(err.code, Tag.ERR_WRONG_ORDER); | ||
| assert.equal(err.message, 'The subtag \'MT\' should not appear before \'Arab\'.'); | ||
| }); | ||
| test('tag.valid() returns true for valid tag', function() { | ||
| assert(new Tag('en').valid()); | ||
| assert(new Tag('en-GB').valid()); | ||
| assert(new Tag('gsw').valid()); | ||
| assert(new Tag('de-CH').valid()); | ||
| }); | ||
| test('tag.valid() returns true for subtag followed by private tag', function() { | ||
| assert(new Tag('en-x-whatever').valid()); | ||
| }); | ||
| test('tag.valid() returns true for non-deprecated grandfathered tag', function() { | ||
| var tag; | ||
| // Grandfathered but not deprecated, therefore valid. | ||
| tag = new Tag('i-default'); | ||
| assert.equal(tag.type(), 'grandfathered'); | ||
| assert(!tag.deprecated()); | ||
| assert(tag.valid()); | ||
| }); | ||
| test('tag.valid() returns true for non-deprecated redundant tag', function() { | ||
| var tag; | ||
| // Redundant but not deprecated, therefore valid. | ||
| tag = new Tag('zh-Hans'); | ||
| assert.equal(tag.type(), 'redundant'); | ||
| assert(!tag.deprecated()); | ||
| assert(tag.valid()); | ||
| tag = new Tag('es-419'); | ||
| assert.equal(tag.type(), 'redundant'); | ||
| assert(!tag.deprecated()); | ||
| assert(tag.valid()); | ||
| }); | ||
| test('tag.valid() returns false for non-existent tag', function() { | ||
| assert(!new Tag('zzz').valid()); | ||
| assert(!new Tag('zzz-Latn').valid()); | ||
| assert(!new Tag('en-Lzzz').valid()); | ||
| }); | ||
| test('tag.valid() returns false for deprecated grandfathered tag', function() { | ||
| var tag; | ||
| // Grandfathered and deprecated, therefore invalid. | ||
| tag = new Tag('art-lojban'); | ||
| assert.equal(tag.type(), 'grandfathered'); | ||
| assert(tag.deprecated()); | ||
| assert(!tag.valid()); | ||
| }); | ||
| test('tag.valid() returns false for deprecated redundant tag', function() { | ||
| var tag; | ||
| // Redundant and deprecated, therefore invalid. | ||
| tag = new Tag('zh-cmn'); | ||
| assert.equal(tag.type(), 'redundant'); | ||
| assert(tag.deprecated()); | ||
| assert(!tag.valid()); | ||
| tag = new Tag('zh-cmn-Hans'); | ||
| assert.equal(tag.type(), 'redundant'); | ||
| assert(tag.deprecated()); | ||
| assert(!tag.valid()); | ||
| }); | ||
| test('tag.valid() returns false if contains deprecated subtags', function() { | ||
| // Moldovan (mo) is deprecated as a language. | ||
| assert(!new Tag('mo').valid()); | ||
| // Neutral Zone (NT) is deprecated as a region. | ||
| assert(!new Tag('en-NT').valid()); | ||
| }); | ||
| test('tag.valid() returns false for tag with redundant script subtag', function() { | ||
| // Swiss German (gsw) has a suppress script of Latn. | ||
| assert(!new Tag('gsw-Latn').valid()); | ||
| }); | ||
| test('tag.valid() returns false if tag contains no language tag and is not grandfathered or redundant', function() { | ||
| assert(!new Tag('IQ-Arab').valid()); | ||
| assert(!new Tag('419').valid()); | ||
| }); | ||
| test('tag.valid() returns false if language subtag is not front of tag', function() { | ||
| assert(!new Tag('GB-en').valid()); | ||
| }); | ||
| test('tag.valid() returns false if more than one language subtag appears', function() { | ||
| assert(!new Tag('en-en').valid()); | ||
| assert(!new Tag('ko-en').valid()); | ||
| }); | ||
| test('tag.valid() returns false if more than one region subtag appears', function() { | ||
| assert(!new Tag('en-001-gb').valid()); | ||
| assert(!new Tag('gb-001').valid()); | ||
| }); | ||
| test('tag.valid() returns false if more than one extlang subtag appears', function() { | ||
| assert(!new Tag('en-asp-bog').valid()); | ||
| }); | ||
| test('tag.valid() returns false if more than one script subtag appears', function() { | ||
| assert(!new Tag('arb-Latn-Cyrl').valid()); | ||
| }); | ||
| test('tag.valid() returns false if a duplicate variant subtag appears', function() { | ||
| assert(!new Tag('ca-valencia-valencia').valid()); | ||
| }); | ||
| test('tag.valid() returns false if private-use subtag contains more than 8 characters', function() { | ||
| // i.e. more than 8 in each component, not in total. | ||
| assert(new Tag('en-x-more-than-eight-chars').valid()); | ||
| assert(!new Tag('en-x-morethaneightchars').valid()); | ||
| }); | ||
| test('tag.valid() returns false if script subtag is same as language suppress-script', function() { | ||
| assert(!new Tag('en-Latn').valid()); | ||
| assert(!new Tag('en-GB-Latn').valid()); | ||
| assert(!new Tag('gsw-Latn').valid()); | ||
| }); | ||
| test('tag.deprecated() returns deprecation date when available', function() { | ||
| var tag; | ||
| // Redundant and deprecated. | ||
| tag = new Tag('zh-cmn-Hant'); | ||
| assert.equal(tag.type(), 'redundant'); | ||
| assert.equal(tag.deprecated(), '2009-07-29'); | ||
| // Redundant but not deprecated. | ||
| tag = new Tag('zh-Hans'); | ||
| assert.equal(tag.type(), 'redundant'); | ||
| assert(!tag.deprecated()); | ||
| // Grandfathered and deprecated. | ||
| tag = new Tag('zh-xiang'); | ||
| assert.equal(tag.type(), 'grandfathered'); | ||
| assert.equal(tag.deprecated(), '2009-07-29'); | ||
| // Grandfathered but not deprecated. | ||
| tag = new Tag('i-default'); | ||
| assert.equal(tag.type(), 'grandfathered'); | ||
| assert(!tag.deprecated()); | ||
| }); | ||
| test('tag.added() returns add date when available', function() { | ||
| var tag; | ||
| // Redundant and deprecated. | ||
| tag = new Tag('zh-cmn-Hant'); | ||
| assert.equal(tag.type(), 'redundant'); | ||
| assert.equal(tag.added(), '2005-07-15'); | ||
| // Redundant but not deprecated. | ||
| tag = new Tag('zh-Hans'); | ||
| assert.equal(tag.type(), 'redundant'); | ||
| assert(!tag.deprecated()); | ||
| assert.equal(tag.added(), '2003-05-30'); | ||
| // Grandfathered and deprecated. | ||
| tag = new Tag('zh-xiang'); | ||
| assert.equal(tag.type(), 'grandfathered'); | ||
| assert.equal(tag.added(), '1999-12-18'); | ||
| // Grandfathered but not deprecated. | ||
| tag = new Tag('i-default'); | ||
| assert.equal(tag.type(), 'grandfathered'); | ||
| assert(!tag.deprecated()); | ||
| assert.equal(tag.added(), '1998-03-10'); | ||
| }); | ||
| test('tag.descriptions() returns descriptions when available', function() { | ||
| var tag; | ||
| tag = new Tag('i-default'); | ||
| assert.equal(tag.type(), 'grandfathered'); | ||
| assert(!tag.deprecated()); | ||
| assert.deepEqual(tag.descriptions(), ['Default Language']); | ||
| // Otherwise returns an empty array. | ||
| assert.deepEqual(new Tag('en').descriptions(), []); | ||
| }); | ||
| test('tag.format() formats tag according to conventions', function() { | ||
| assert.equal(new Tag('en').format(), 'en'); | ||
| assert.equal(new Tag('En').format(), 'en'); | ||
| assert.equal(new Tag('EN').format(), 'en'); | ||
| assert.equal(new Tag('eN').format(), 'en'); | ||
| assert.equal(new Tag('en-gb').format(), 'en-GB'); | ||
| assert.equal(new Tag('en-gb-oed').format(), 'en-GB-oed'); | ||
| assert.equal(new Tag('az-latn').format(), 'az-Latn'); | ||
| assert.equal(new Tag('ZH-hant-hK').format(), 'zh-Hant-HK'); | ||
| }); | ||
| test('tag.preferred() returns preferred tag if available', function() { | ||
| var tag = new Tag('zh-cmn-Hant'); | ||
| assert.equal(tag.type(), 'redundant'); | ||
| assert(tag.deprecated()); | ||
| assert(tag.preferred()); | ||
| assert.equal(tag.preferred().format(), 'cmn-Hant'); | ||
| assert.equal(new Tag('zh-Hans').preferred(), null); | ||
| }); | ||
| test('tag.region() and tag.language() return subtags for redundant tags', function() { | ||
| var tag; | ||
| tag = new Tag('es-419'); | ||
| assert.deepEqual(tag.region().descriptions(), ['Latin America and the Caribbean']); | ||
| assert.deepEqual(tag.language().descriptions(), ['Spanish', 'Castilian']); | ||
| tag = new Tag('sgn-NL'); | ||
| assert.deepEqual(tag.region().descriptions(), ['Netherlands']); | ||
| assert.deepEqual(tag.language().descriptions(), ['Sign languages']); | ||
| }); | ||
| }); |
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
0
-100%24182
-50.48%5
-54.55%531
-53.71%