diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c6c8b36 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fdefc8c --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +*.log +.nyc_output/ +coverage/ +node_modules/ +yarn.lock diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..43c97e7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..404abb2 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +coverage/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ec88751 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - lts/dubnium + - node +after_success: bash <(curl -s https://codecov.io/bash) diff --git a/build.js b/build.js new file mode 100644 index 0000000..62eaec9 --- /dev/null +++ b/build.js @@ -0,0 +1,160 @@ +'use strict' + +var fs = require('fs') +var path = require('path') +var fetch = require('node-fetch') +var fromXml = require('xast-util-from-xml') +var visit = require('unist-util-visit') +var normalize = require('bcp-47/lib/normalize') + +var own = {}.hasOwnProperty + +var endpoint = + 'https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/supplementalMetadata.xml' + +fetch(endpoint) + .then(res => res.text()) + .then(onbody, console.error) + +function onbody(doc) { + var fields = [] + var match = [] + var defaults = [] + var many = {} + var suffix = 'Alias' + var seenHeploc = false + var ignore = [ + // Subdivisions (ISO 3166-2) are not used in BCP 47 tags. + 'subdivision', + // Timezones. + 'zone' + ] + + visit(fromXml(doc), 'element', onelement) + + write('defaults', defaults) + write('fields', fields) + write('many', many) + write('matches', match) + + /* eslint-disable-next-line complexity */ + function onelement(node) { + var name = node.name + var pos = name.indexOf(suffix) + var from + var to + + if (name === 'defaultContent') { + defaults = defaults.concat(clean(node.attributes.locales)) + } + + if (pos === -1) { + return + } + + name = name.slice(0, pos) + + if (name === 'territory') { + name = 'region' + } + + if (name === 'variant') { + name = 'variants' + } + + if (ignore.includes(name)) { + return + } + + from = clean(node.attributes.type) + to = clean(node.attributes.replacement) + + if (from.length === 1) { + from = from[0] + } else { + throw new Error('Cannot map from many: ' + from) + } + + if (to.length === 1) { + to = to[0] + } else { + if (!many[name]) { + many[name] = {} + } + + many[name][from] = to + return + } + + if (name === 'region' && from.length === 3 && isNaN(from)) { + console.log( + 'ISO 3166-1 alpha 3 codes cannot be represented in BCP 47: %s', + from + ) + return + } + + if (name === 'language') { + if (own.call(normalize, from)) { + console.warn('Ignoring normalized value: %s -> %s', from, to) + return + } + + match.push({from, to}) + } else if (name === 'variants') { + if (from === 'aaland' && to === 'ax') { + fields.push({ + from: {field: name, value: from}, + to: {field: 'region', value: to} + }) + } else if (from === 'heploc' && to === 'alalc97') { + if (seenHeploc) { + console.warn('Ignoring double heploc alias') + return + } + + seenHeploc = true + fields.push({ + from: {field: name, value: from}, + to: {field: name, value: to} + }) + } else if (from === 'polytoni' && to === 'polyton') { + fields.push({ + from: {field: name, value: from}, + to: {field: name, value: to} + }) + } else if ( + (from === 'arevela' && to === 'hy') || + (from === 'arevmda' && to === 'hyw') + ) { + fields.push({ + from: {field: name, value: from}, + to: {field: 'language', value: to} + }) + } else { + console.warn('Ignoring unknown variant alias', from, to) + } + } else { + fields.push({ + from: {field: name, value: from}, + to: {field: name, value: to} + }) + } + } +} + +function clean(value) { + return value + .toLowerCase() + .replace(/_/g, '-') + .replace(/\s+/g, ' ') + .trim() + .split(' ') +} + +function write(name, values) { + fs.writeFileSync( + path.join('lib', name + '.json'), + JSON.stringify(values, null, 2) + '\n' + ) +} diff --git a/funding.yml b/funding.yml new file mode 100644 index 0000000..dee132d --- /dev/null +++ b/funding.yml @@ -0,0 +1 @@ +github: wooorm diff --git a/index.js b/index.js new file mode 100644 index 0000000..3541ac5 --- /dev/null +++ b/index.js @@ -0,0 +1,3 @@ +'use strict' + +module.exports = require('./lib') diff --git a/lib/defaults.json b/lib/defaults.json new file mode 100644 index 0000000..6659dfa --- /dev/null +++ b/lib/defaults.json @@ -0,0 +1,308 @@ +[ + "aa-et", + "af-za", + "agq-cm", + "ak-gh", + "am-et", + "an-es", + "ar-001", + "arn-cl", + "as-in", + "asa-tz", + "ast-es", + "az-arab-ir", + "az-cyrl-az", + "az-latn", + "az-latn-az", + "ba-ru", + "bas-cm", + "be-by", + "bem-zm", + "bez-tz", + "bg-bg", + "bgn-pk", + "blt-vn", + "bm-ml", + "bm-nkoo-ml", + "bn-bd", + "bo-cn", + "br-fr", + "brx-in", + "bs-cyrl-ba", + "bs-latn", + "bs-latn-ba", + "bss-cm", + "byn-er", + "ca-es", + "cad-us", + "cch-ng", + "ccp-bd", + "ce-ru", + "ceb-ph", + "cgg-ug", + "chr-us", + "cic-us", + "ckb-iq", + "co-fr", + "cs-cz", + "cu-ru", + "cv-ru", + "cy-gb", + "da-dk", + "dav-ke", + "de-de", + "dje-ne", + "dsb-de", + "dua-cm", + "dv-mv", + "dyo-sn", + "dz-bt", + "ebu-ke", + "ee-gh", + "el-gr", + "en-dsrt-us", + "en-us", + "eo-001", + "es-es", + "et-ee", + "eu-es", + "ewo-cm", + "fa-ir", + "ff-adlm-gn", + "ff-latn", + "ff-latn-sn", + "fi-fi", + "fil-ph", + "fo-fo", + "fr-fr", + "fur-it", + "fy-nl", + "ga-ie", + "gaa-gh", + "gd-gb", + "gez-et", + "gl-es", + "gn-py", + "gsw-ch", + "gu-in", + "guz-ke", + "gv-im", + "ha-arab-ng", + "ha-ng", + "haw-us", + "he-il", + "hi-in", + "hi-latn-in", + "hr-hr", + "hsb-de", + "hu-hu", + "hy-am", + "ia-001", + "id-id", + "ife-tg", + "ig-ng", + "ii-cn", + "io-001", + "is-is", + "it-it", + "iu-ca", + "iu-latn-ca", + "ja-jp", + "jbo-001", + "jgo-cm", + "jmc-tz", + "jv-id", + "ka-ge", + "kab-dz", + "kaj-ng", + "kam-ke", + "kcg-ng", + "kde-tz", + "kea-cv", + "ken-cm", + "khq-ml", + "ki-ke", + "kk-kz", + "kkj-cm", + "kl-gl", + "kln-ke", + "km-kh", + "kn-in", + "ko-kr", + "kok-in", + "kpe-lr", + "ks-arab", + "ks-arab-in", + "ks-deva-in", + "ksb-tz", + "ksf-cm", + "ksh-de", + "ku-tr", + "kw-gb", + "ky-kg", + "lag-tz", + "lb-lu", + "lg-ug", + "lkt-us", + "ln-cd", + "lo-la", + "lrc-ir", + "lt-lt", + "lu-cd", + "luo-ke", + "luy-ke", + "lv-lv", + "mai-in", + "mas-ke", + "mer-ke", + "mfe-mu", + "mg-mg", + "mgh-mz", + "mgo-cm", + "mi-nz", + "mk-mk", + "ml-in", + "mn-mn", + "mn-mong-cn", + "mni-beng", + "mni-beng-in", + "mni-mtei-in", + "moh-ca", + "mr-in", + "ms-arab-my", + "ms-my", + "mt-mt", + "mua-cm", + "mus-us", + "my-mm", + "myv-ru", + "mzn-ir", + "naq-na", + "nb-no", + "nd-zw", + "nds-de", + "ne-np", + "nl-nl", + "nmg-cm", + "nn-no", + "nnh-cm", + "nqo-gn", + "nr-za", + "nso-za", + "nus-ss", + "ny-mw", + "nyn-ug", + "oc-fr", + "om-et", + "or-in", + "os-ge", + "osa-us", + "pa-arab-pk", + "pa-guru", + "pa-guru-in", + "pcm-ng", + "pl-pl", + "prg-001", + "ps-af", + "pt-br", + "qu-pe", + "quc-gt", + "rm-ch", + "rn-bi", + "ro-ro", + "rof-tz", + "ru-ru", + "rw-rw", + "rwk-tz", + "sa-in", + "sah-ru", + "saq-ke", + "sat-deva-in", + "sat-olck", + "sat-olck-in", + "sbp-tz", + "sc-it", + "scn-it", + "sd-arab", + "sd-arab-pk", + "sd-deva-in", + "sdh-ir", + "se-no", + "seh-mz", + "ses-ml", + "sg-cf", + "shi-latn-ma", + "shi-tfng", + "shi-tfng-ma", + "si-lk", + "sid-et", + "sk-sk", + "sl-si", + "sma-se", + "smj-se", + "smn-fi", + "sms-fi", + "sn-zw", + "so-so", + "sq-al", + "sr-cyrl", + "sr-cyrl-rs", + "sr-latn-rs", + "ss-za", + "ssy-er", + "st-za", + "su-latn", + "su-latn-id", + "sv-se", + "sw-tz", + "syr-iq", + "szl-pl", + "ta-in", + "te-in", + "teo-ug", + "tg-tj", + "th-th", + "ti-et", + "tig-er", + "tk-tm", + "tn-za", + "to-to", + "tr-tr", + "trv-tw", + "ts-za", + "tt-ru", + "twq-ne", + "tzm-ma", + "ug-cn", + "uk-ua", + "ur-pk", + "uz-arab-af", + "uz-cyrl-uz", + "uz-latn", + "uz-latn-uz", + "vai-latn-lr", + "vai-vaii", + "vai-vaii-lr", + "ve-za", + "vi-vn", + "vo-001", + "vun-tz", + "wa-be", + "wae-ch", + "wal-et", + "wbp-au", + "wo-sn", + "xh-za", + "xog-ug", + "yav-cm", + "yi-001", + "yo-ng", + "yue-hans-cn", + "yue-hant", + "yue-hant-hk", + "zgh-ma", + "zh-hans", + "zh-hans-cn", + "zh-hant-tw", + "zu-za" +] diff --git a/lib/fields.json b/lib/fields.json new file mode 100644 index 0000000..6da8920 --- /dev/null +++ b/lib/fields.json @@ -0,0 +1,3232 @@ +[ + { + "from": { + "field": "script", + "value": "qaai" + }, + "to": { + "field": "script", + "value": "zinh" + } + }, + { + "from": { + "field": "region", + "value": "bu" + }, + "to": { + "field": "region", + "value": "mm" + } + }, + { + "from": { + "field": "region", + "value": "ct" + }, + "to": { + "field": "region", + "value": "ki" + } + }, + { + "from": { + "field": "region", + "value": "dd" + }, + "to": { + "field": "region", + "value": "de" + } + }, + { + "from": { + "field": "region", + "value": "dy" + }, + "to": { + "field": "region", + "value": "bj" + } + }, + { + "from": { + "field": "region", + "value": "fx" + }, + "to": { + "field": "region", + "value": "fr" + } + }, + { + "from": { + "field": "region", + "value": "hv" + }, + "to": { + "field": "region", + "value": "bf" + } + }, + { + "from": { + "field": "region", + "value": "jt" + }, + "to": { + "field": "region", + "value": "um" + } + }, + { + "from": { + "field": "region", + "value": "mi" + }, + "to": { + "field": "region", + "value": "um" + } + }, + { + "from": { + "field": "region", + "value": "nh" + }, + "to": { + "field": "region", + "value": "vu" + } + }, + { + "from": { + "field": "region", + "value": "nq" + }, + "to": { + "field": "region", + "value": "aq" + } + }, + { + "from": { + "field": "region", + "value": "pu" + }, + "to": { + "field": "region", + "value": "um" + } + }, + { + "from": { + "field": "region", + "value": "pz" + }, + "to": { + "field": "region", + "value": "pa" + } + }, + { + "from": { + "field": "region", + "value": "qu" + }, + "to": { + "field": "region", + "value": "eu" + } + }, + { + "from": { + "field": "region", + "value": "rh" + }, + "to": { + "field": "region", + "value": "zw" + } + }, + { + "from": { + "field": "region", + "value": "tp" + }, + "to": { + "field": "region", + "value": "tl" + } + }, + { + "from": { + "field": "region", + "value": "uk" + }, + "to": { + "field": "region", + "value": "gb" + } + }, + { + "from": { + "field": "region", + "value": "vd" + }, + "to": { + "field": "region", + "value": "vn" + } + }, + { + "from": { + "field": "region", + "value": "wk" + }, + "to": { + "field": "region", + "value": "um" + } + }, + { + "from": { + "field": "region", + "value": "yd" + }, + "to": { + "field": "region", + "value": "ye" + } + }, + { + "from": { + "field": "region", + "value": "zr" + }, + "to": { + "field": "region", + "value": "cd" + } + }, + { + "from": { + "field": "region", + "value": "230" + }, + "to": { + "field": "region", + "value": "et" + } + }, + { + "from": { + "field": "region", + "value": "280" + }, + "to": { + "field": "region", + "value": "de" + } + }, + { + "from": { + "field": "region", + "value": "736" + }, + "to": { + "field": "region", + "value": "sd" + } + }, + { + "from": { + "field": "region", + "value": "886" + }, + "to": { + "field": "region", + "value": "ye" + } + }, + { + "from": { + "field": "region", + "value": "958" + }, + "to": { + "field": "region", + "value": "aa" + } + }, + { + "from": { + "field": "region", + "value": "020" + }, + "to": { + "field": "region", + "value": "ad" + } + }, + { + "from": { + "field": "region", + "value": "784" + }, + "to": { + "field": "region", + "value": "ae" + } + }, + { + "from": { + "field": "region", + "value": "004" + }, + "to": { + "field": "region", + "value": "af" + } + }, + { + "from": { + "field": "region", + "value": "028" + }, + "to": { + "field": "region", + "value": "ag" + } + }, + { + "from": { + "field": "region", + "value": "660" + }, + "to": { + "field": "region", + "value": "ai" + } + }, + { + "from": { + "field": "region", + "value": "008" + }, + "to": { + "field": "region", + "value": "al" + } + }, + { + "from": { + "field": "region", + "value": "051" + }, + "to": { + "field": "region", + "value": "am" + } + }, + { + "from": { + "field": "region", + "value": "024" + }, + "to": { + "field": "region", + "value": "ao" + } + }, + { + "from": { + "field": "region", + "value": "010" + }, + "to": { + "field": "region", + "value": "aq" + } + }, + { + "from": { + "field": "region", + "value": "032" + }, + "to": { + "field": "region", + "value": "ar" + } + }, + { + "from": { + "field": "region", + "value": "016" + }, + "to": { + "field": "region", + "value": "as" + } + }, + { + "from": { + "field": "region", + "value": "040" + }, + "to": { + "field": "region", + "value": "at" + } + }, + { + "from": { + "field": "region", + "value": "036" + }, + "to": { + "field": "region", + "value": "au" + } + }, + { + "from": { + "field": "region", + "value": "533" + }, + "to": { + "field": "region", + "value": "aw" + } + }, + { + "from": { + "field": "region", + "value": "248" + }, + "to": { + "field": "region", + "value": "ax" + } + }, + { + "from": { + "field": "region", + "value": "031" + }, + "to": { + "field": "region", + "value": "az" + } + }, + { + "from": { + "field": "region", + "value": "070" + }, + "to": { + "field": "region", + "value": "ba" + } + }, + { + "from": { + "field": "region", + "value": "052" + }, + "to": { + "field": "region", + "value": "bb" + } + }, + { + "from": { + "field": "region", + "value": "050" + }, + "to": { + "field": "region", + "value": "bd" + } + }, + { + "from": { + "field": "region", + "value": "056" + }, + "to": { + "field": "region", + "value": "be" + } + }, + { + "from": { + "field": "region", + "value": "854" + }, + "to": { + "field": "region", + "value": "bf" + } + }, + { + "from": { + "field": "region", + "value": "100" + }, + "to": { + "field": "region", + "value": "bg" + } + }, + { + "from": { + "field": "region", + "value": "048" + }, + "to": { + "field": "region", + "value": "bh" + } + }, + { + "from": { + "field": "region", + "value": "108" + }, + "to": { + "field": "region", + "value": "bi" + } + }, + { + "from": { + "field": "region", + "value": "204" + }, + "to": { + "field": "region", + "value": "bj" + } + }, + { + "from": { + "field": "region", + "value": "652" + }, + "to": { + "field": "region", + "value": "bl" + } + }, + { + "from": { + "field": "region", + "value": "060" + }, + "to": { + "field": "region", + "value": "bm" + } + }, + { + "from": { + "field": "region", + "value": "096" + }, + "to": { + "field": "region", + "value": "bn" + } + }, + { + "from": { + "field": "region", + "value": "068" + }, + "to": { + "field": "region", + "value": "bo" + } + }, + { + "from": { + "field": "region", + "value": "535" + }, + "to": { + "field": "region", + "value": "bq" + } + }, + { + "from": { + "field": "region", + "value": "076" + }, + "to": { + "field": "region", + "value": "br" + } + }, + { + "from": { + "field": "region", + "value": "044" + }, + "to": { + "field": "region", + "value": "bs" + } + }, + { + "from": { + "field": "region", + "value": "064" + }, + "to": { + "field": "region", + "value": "bt" + } + }, + { + "from": { + "field": "region", + "value": "104" + }, + "to": { + "field": "region", + "value": "mm" + } + }, + { + "from": { + "field": "region", + "value": "074" + }, + "to": { + "field": "region", + "value": "bv" + } + }, + { + "from": { + "field": "region", + "value": "072" + }, + "to": { + "field": "region", + "value": "bw" + } + }, + { + "from": { + "field": "region", + "value": "112" + }, + "to": { + "field": "region", + "value": "by" + } + }, + { + "from": { + "field": "region", + "value": "084" + }, + "to": { + "field": "region", + "value": "bz" + } + }, + { + "from": { + "field": "region", + "value": "124" + }, + "to": { + "field": "region", + "value": "ca" + } + }, + { + "from": { + "field": "region", + "value": "166" + }, + "to": { + "field": "region", + "value": "cc" + } + }, + { + "from": { + "field": "region", + "value": "180" + }, + "to": { + "field": "region", + "value": "cd" + } + }, + { + "from": { + "field": "region", + "value": "140" + }, + "to": { + "field": "region", + "value": "cf" + } + }, + { + "from": { + "field": "region", + "value": "178" + }, + "to": { + "field": "region", + "value": "cg" + } + }, + { + "from": { + "field": "region", + "value": "756" + }, + "to": { + "field": "region", + "value": "ch" + } + }, + { + "from": { + "field": "region", + "value": "384" + }, + "to": { + "field": "region", + "value": "ci" + } + }, + { + "from": { + "field": "region", + "value": "184" + }, + "to": { + "field": "region", + "value": "ck" + } + }, + { + "from": { + "field": "region", + "value": "152" + }, + "to": { + "field": "region", + "value": "cl" + } + }, + { + "from": { + "field": "region", + "value": "120" + }, + "to": { + "field": "region", + "value": "cm" + } + }, + { + "from": { + "field": "region", + "value": "156" + }, + "to": { + "field": "region", + "value": "cn" + } + }, + { + "from": { + "field": "region", + "value": "170" + }, + "to": { + "field": "region", + "value": "co" + } + }, + { + "from": { + "field": "region", + "value": "188" + }, + "to": { + "field": "region", + "value": "cr" + } + }, + { + "from": { + "field": "region", + "value": "192" + }, + "to": { + "field": "region", + "value": "cu" + } + }, + { + "from": { + "field": "region", + "value": "132" + }, + "to": { + "field": "region", + "value": "cv" + } + }, + { + "from": { + "field": "region", + "value": "531" + }, + "to": { + "field": "region", + "value": "cw" + } + }, + { + "from": { + "field": "region", + "value": "162" + }, + "to": { + "field": "region", + "value": "cx" + } + }, + { + "from": { + "field": "region", + "value": "196" + }, + "to": { + "field": "region", + "value": "cy" + } + }, + { + "from": { + "field": "region", + "value": "203" + }, + "to": { + "field": "region", + "value": "cz" + } + }, + { + "from": { + "field": "region", + "value": "278" + }, + "to": { + "field": "region", + "value": "de" + } + }, + { + "from": { + "field": "region", + "value": "276" + }, + "to": { + "field": "region", + "value": "de" + } + }, + { + "from": { + "field": "region", + "value": "262" + }, + "to": { + "field": "region", + "value": "dj" + } + }, + { + "from": { + "field": "region", + "value": "208" + }, + "to": { + "field": "region", + "value": "dk" + } + }, + { + "from": { + "field": "region", + "value": "212" + }, + "to": { + "field": "region", + "value": "dm" + } + }, + { + "from": { + "field": "region", + "value": "214" + }, + "to": { + "field": "region", + "value": "do" + } + }, + { + "from": { + "field": "region", + "value": "012" + }, + "to": { + "field": "region", + "value": "dz" + } + }, + { + "from": { + "field": "region", + "value": "218" + }, + "to": { + "field": "region", + "value": "ec" + } + }, + { + "from": { + "field": "region", + "value": "233" + }, + "to": { + "field": "region", + "value": "ee" + } + }, + { + "from": { + "field": "region", + "value": "818" + }, + "to": { + "field": "region", + "value": "eg" + } + }, + { + "from": { + "field": "region", + "value": "732" + }, + "to": { + "field": "region", + "value": "eh" + } + }, + { + "from": { + "field": "region", + "value": "232" + }, + "to": { + "field": "region", + "value": "er" + } + }, + { + "from": { + "field": "region", + "value": "724" + }, + "to": { + "field": "region", + "value": "es" + } + }, + { + "from": { + "field": "region", + "value": "231" + }, + "to": { + "field": "region", + "value": "et" + } + }, + { + "from": { + "field": "region", + "value": "246" + }, + "to": { + "field": "region", + "value": "fi" + } + }, + { + "from": { + "field": "region", + "value": "242" + }, + "to": { + "field": "region", + "value": "fj" + } + }, + { + "from": { + "field": "region", + "value": "238" + }, + "to": { + "field": "region", + "value": "fk" + } + }, + { + "from": { + "field": "region", + "value": "583" + }, + "to": { + "field": "region", + "value": "fm" + } + }, + { + "from": { + "field": "region", + "value": "234" + }, + "to": { + "field": "region", + "value": "fo" + } + }, + { + "from": { + "field": "region", + "value": "250" + }, + "to": { + "field": "region", + "value": "fr" + } + }, + { + "from": { + "field": "region", + "value": "249" + }, + "to": { + "field": "region", + "value": "fr" + } + }, + { + "from": { + "field": "region", + "value": "266" + }, + "to": { + "field": "region", + "value": "ga" + } + }, + { + "from": { + "field": "region", + "value": "826" + }, + "to": { + "field": "region", + "value": "gb" + } + }, + { + "from": { + "field": "region", + "value": "308" + }, + "to": { + "field": "region", + "value": "gd" + } + }, + { + "from": { + "field": "region", + "value": "268" + }, + "to": { + "field": "region", + "value": "ge" + } + }, + { + "from": { + "field": "region", + "value": "254" + }, + "to": { + "field": "region", + "value": "gf" + } + }, + { + "from": { + "field": "region", + "value": "831" + }, + "to": { + "field": "region", + "value": "gg" + } + }, + { + "from": { + "field": "region", + "value": "288" + }, + "to": { + "field": "region", + "value": "gh" + } + }, + { + "from": { + "field": "region", + "value": "292" + }, + "to": { + "field": "region", + "value": "gi" + } + }, + { + "from": { + "field": "region", + "value": "304" + }, + "to": { + "field": "region", + "value": "gl" + } + }, + { + "from": { + "field": "region", + "value": "270" + }, + "to": { + "field": "region", + "value": "gm" + } + }, + { + "from": { + "field": "region", + "value": "324" + }, + "to": { + "field": "region", + "value": "gn" + } + }, + { + "from": { + "field": "region", + "value": "312" + }, + "to": { + "field": "region", + "value": "gp" + } + }, + { + "from": { + "field": "region", + "value": "226" + }, + "to": { + "field": "region", + "value": "gq" + } + }, + { + "from": { + "field": "region", + "value": "300" + }, + "to": { + "field": "region", + "value": "gr" + } + }, + { + "from": { + "field": "region", + "value": "239" + }, + "to": { + "field": "region", + "value": "gs" + } + }, + { + "from": { + "field": "region", + "value": "320" + }, + "to": { + "field": "region", + "value": "gt" + } + }, + { + "from": { + "field": "region", + "value": "316" + }, + "to": { + "field": "region", + "value": "gu" + } + }, + { + "from": { + "field": "region", + "value": "624" + }, + "to": { + "field": "region", + "value": "gw" + } + }, + { + "from": { + "field": "region", + "value": "328" + }, + "to": { + "field": "region", + "value": "gy" + } + }, + { + "from": { + "field": "region", + "value": "344" + }, + "to": { + "field": "region", + "value": "hk" + } + }, + { + "from": { + "field": "region", + "value": "334" + }, + "to": { + "field": "region", + "value": "hm" + } + }, + { + "from": { + "field": "region", + "value": "340" + }, + "to": { + "field": "region", + "value": "hn" + } + }, + { + "from": { + "field": "region", + "value": "191" + }, + "to": { + "field": "region", + "value": "hr" + } + }, + { + "from": { + "field": "region", + "value": "332" + }, + "to": { + "field": "region", + "value": "ht" + } + }, + { + "from": { + "field": "region", + "value": "348" + }, + "to": { + "field": "region", + "value": "hu" + } + }, + { + "from": { + "field": "region", + "value": "360" + }, + "to": { + "field": "region", + "value": "id" + } + }, + { + "from": { + "field": "region", + "value": "372" + }, + "to": { + "field": "region", + "value": "ie" + } + }, + { + "from": { + "field": "region", + "value": "376" + }, + "to": { + "field": "region", + "value": "il" + } + }, + { + "from": { + "field": "region", + "value": "833" + }, + "to": { + "field": "region", + "value": "im" + } + }, + { + "from": { + "field": "region", + "value": "356" + }, + "to": { + "field": "region", + "value": "in" + } + }, + { + "from": { + "field": "region", + "value": "086" + }, + "to": { + "field": "region", + "value": "io" + } + }, + { + "from": { + "field": "region", + "value": "368" + }, + "to": { + "field": "region", + "value": "iq" + } + }, + { + "from": { + "field": "region", + "value": "364" + }, + "to": { + "field": "region", + "value": "ir" + } + }, + { + "from": { + "field": "region", + "value": "352" + }, + "to": { + "field": "region", + "value": "is" + } + }, + { + "from": { + "field": "region", + "value": "380" + }, + "to": { + "field": "region", + "value": "it" + } + }, + { + "from": { + "field": "region", + "value": "832" + }, + "to": { + "field": "region", + "value": "je" + } + }, + { + "from": { + "field": "region", + "value": "388" + }, + "to": { + "field": "region", + "value": "jm" + } + }, + { + "from": { + "field": "region", + "value": "400" + }, + "to": { + "field": "region", + "value": "jo" + } + }, + { + "from": { + "field": "region", + "value": "392" + }, + "to": { + "field": "region", + "value": "jp" + } + }, + { + "from": { + "field": "region", + "value": "404" + }, + "to": { + "field": "region", + "value": "ke" + } + }, + { + "from": { + "field": "region", + "value": "417" + }, + "to": { + "field": "region", + "value": "kg" + } + }, + { + "from": { + "field": "region", + "value": "116" + }, + "to": { + "field": "region", + "value": "kh" + } + }, + { + "from": { + "field": "region", + "value": "296" + }, + "to": { + "field": "region", + "value": "ki" + } + }, + { + "from": { + "field": "region", + "value": "174" + }, + "to": { + "field": "region", + "value": "km" + } + }, + { + "from": { + "field": "region", + "value": "659" + }, + "to": { + "field": "region", + "value": "kn" + } + }, + { + "from": { + "field": "region", + "value": "408" + }, + "to": { + "field": "region", + "value": "kp" + } + }, + { + "from": { + "field": "region", + "value": "410" + }, + "to": { + "field": "region", + "value": "kr" + } + }, + { + "from": { + "field": "region", + "value": "414" + }, + "to": { + "field": "region", + "value": "kw" + } + }, + { + "from": { + "field": "region", + "value": "136" + }, + "to": { + "field": "region", + "value": "ky" + } + }, + { + "from": { + "field": "region", + "value": "398" + }, + "to": { + "field": "region", + "value": "kz" + } + }, + { + "from": { + "field": "region", + "value": "418" + }, + "to": { + "field": "region", + "value": "la" + } + }, + { + "from": { + "field": "region", + "value": "422" + }, + "to": { + "field": "region", + "value": "lb" + } + }, + { + "from": { + "field": "region", + "value": "662" + }, + "to": { + "field": "region", + "value": "lc" + } + }, + { + "from": { + "field": "region", + "value": "438" + }, + "to": { + "field": "region", + "value": "li" + } + }, + { + "from": { + "field": "region", + "value": "144" + }, + "to": { + "field": "region", + "value": "lk" + } + }, + { + "from": { + "field": "region", + "value": "430" + }, + "to": { + "field": "region", + "value": "lr" + } + }, + { + "from": { + "field": "region", + "value": "426" + }, + "to": { + "field": "region", + "value": "ls" + } + }, + { + "from": { + "field": "region", + "value": "440" + }, + "to": { + "field": "region", + "value": "lt" + } + }, + { + "from": { + "field": "region", + "value": "442" + }, + "to": { + "field": "region", + "value": "lu" + } + }, + { + "from": { + "field": "region", + "value": "428" + }, + "to": { + "field": "region", + "value": "lv" + } + }, + { + "from": { + "field": "region", + "value": "434" + }, + "to": { + "field": "region", + "value": "ly" + } + }, + { + "from": { + "field": "region", + "value": "504" + }, + "to": { + "field": "region", + "value": "ma" + } + }, + { + "from": { + "field": "region", + "value": "492" + }, + "to": { + "field": "region", + "value": "mc" + } + }, + { + "from": { + "field": "region", + "value": "498" + }, + "to": { + "field": "region", + "value": "md" + } + }, + { + "from": { + "field": "region", + "value": "499" + }, + "to": { + "field": "region", + "value": "me" + } + }, + { + "from": { + "field": "region", + "value": "663" + }, + "to": { + "field": "region", + "value": "mf" + } + }, + { + "from": { + "field": "region", + "value": "450" + }, + "to": { + "field": "region", + "value": "mg" + } + }, + { + "from": { + "field": "region", + "value": "584" + }, + "to": { + "field": "region", + "value": "mh" + } + }, + { + "from": { + "field": "region", + "value": "807" + }, + "to": { + "field": "region", + "value": "mk" + } + }, + { + "from": { + "field": "region", + "value": "466" + }, + "to": { + "field": "region", + "value": "ml" + } + }, + { + "from": { + "field": "region", + "value": "496" + }, + "to": { + "field": "region", + "value": "mn" + } + }, + { + "from": { + "field": "region", + "value": "446" + }, + "to": { + "field": "region", + "value": "mo" + } + }, + { + "from": { + "field": "region", + "value": "580" + }, + "to": { + "field": "region", + "value": "mp" + } + }, + { + "from": { + "field": "region", + "value": "474" + }, + "to": { + "field": "region", + "value": "mq" + } + }, + { + "from": { + "field": "region", + "value": "478" + }, + "to": { + "field": "region", + "value": "mr" + } + }, + { + "from": { + "field": "region", + "value": "500" + }, + "to": { + "field": "region", + "value": "ms" + } + }, + { + "from": { + "field": "region", + "value": "470" + }, + "to": { + "field": "region", + "value": "mt" + } + }, + { + "from": { + "field": "region", + "value": "480" + }, + "to": { + "field": "region", + "value": "mu" + } + }, + { + "from": { + "field": "region", + "value": "462" + }, + "to": { + "field": "region", + "value": "mv" + } + }, + { + "from": { + "field": "region", + "value": "454" + }, + "to": { + "field": "region", + "value": "mw" + } + }, + { + "from": { + "field": "region", + "value": "484" + }, + "to": { + "field": "region", + "value": "mx" + } + }, + { + "from": { + "field": "region", + "value": "458" + }, + "to": { + "field": "region", + "value": "my" + } + }, + { + "from": { + "field": "region", + "value": "508" + }, + "to": { + "field": "region", + "value": "mz" + } + }, + { + "from": { + "field": "region", + "value": "516" + }, + "to": { + "field": "region", + "value": "na" + } + }, + { + "from": { + "field": "region", + "value": "540" + }, + "to": { + "field": "region", + "value": "nc" + } + }, + { + "from": { + "field": "region", + "value": "562" + }, + "to": { + "field": "region", + "value": "ne" + } + }, + { + "from": { + "field": "region", + "value": "574" + }, + "to": { + "field": "region", + "value": "nf" + } + }, + { + "from": { + "field": "region", + "value": "566" + }, + "to": { + "field": "region", + "value": "ng" + } + }, + { + "from": { + "field": "region", + "value": "558" + }, + "to": { + "field": "region", + "value": "ni" + } + }, + { + "from": { + "field": "region", + "value": "528" + }, + "to": { + "field": "region", + "value": "nl" + } + }, + { + "from": { + "field": "region", + "value": "578" + }, + "to": { + "field": "region", + "value": "no" + } + }, + { + "from": { + "field": "region", + "value": "524" + }, + "to": { + "field": "region", + "value": "np" + } + }, + { + "from": { + "field": "region", + "value": "520" + }, + "to": { + "field": "region", + "value": "nr" + } + }, + { + "from": { + "field": "region", + "value": "570" + }, + "to": { + "field": "region", + "value": "nu" + } + }, + { + "from": { + "field": "region", + "value": "554" + }, + "to": { + "field": "region", + "value": "nz" + } + }, + { + "from": { + "field": "region", + "value": "512" + }, + "to": { + "field": "region", + "value": "om" + } + }, + { + "from": { + "field": "region", + "value": "591" + }, + "to": { + "field": "region", + "value": "pa" + } + }, + { + "from": { + "field": "region", + "value": "604" + }, + "to": { + "field": "region", + "value": "pe" + } + }, + { + "from": { + "field": "region", + "value": "258" + }, + "to": { + "field": "region", + "value": "pf" + } + }, + { + "from": { + "field": "region", + "value": "598" + }, + "to": { + "field": "region", + "value": "pg" + } + }, + { + "from": { + "field": "region", + "value": "608" + }, + "to": { + "field": "region", + "value": "ph" + } + }, + { + "from": { + "field": "region", + "value": "586" + }, + "to": { + "field": "region", + "value": "pk" + } + }, + { + "from": { + "field": "region", + "value": "616" + }, + "to": { + "field": "region", + "value": "pl" + } + }, + { + "from": { + "field": "region", + "value": "666" + }, + "to": { + "field": "region", + "value": "pm" + } + }, + { + "from": { + "field": "region", + "value": "612" + }, + "to": { + "field": "region", + "value": "pn" + } + }, + { + "from": { + "field": "region", + "value": "630" + }, + "to": { + "field": "region", + "value": "pr" + } + }, + { + "from": { + "field": "region", + "value": "275" + }, + "to": { + "field": "region", + "value": "ps" + } + }, + { + "from": { + "field": "region", + "value": "620" + }, + "to": { + "field": "region", + "value": "pt" + } + }, + { + "from": { + "field": "region", + "value": "585" + }, + "to": { + "field": "region", + "value": "pw" + } + }, + { + "from": { + "field": "region", + "value": "600" + }, + "to": { + "field": "region", + "value": "py" + } + }, + { + "from": { + "field": "region", + "value": "634" + }, + "to": { + "field": "region", + "value": "qa" + } + }, + { + "from": { + "field": "region", + "value": "959" + }, + "to": { + "field": "region", + "value": "qm" + } + }, + { + "from": { + "field": "region", + "value": "960" + }, + "to": { + "field": "region", + "value": "qn" + } + }, + { + "from": { + "field": "region", + "value": "962" + }, + "to": { + "field": "region", + "value": "qp" + } + }, + { + "from": { + "field": "region", + "value": "963" + }, + "to": { + "field": "region", + "value": "qq" + } + }, + { + "from": { + "field": "region", + "value": "964" + }, + "to": { + "field": "region", + "value": "qr" + } + }, + { + "from": { + "field": "region", + "value": "965" + }, + "to": { + "field": "region", + "value": "qs" + } + }, + { + "from": { + "field": "region", + "value": "966" + }, + "to": { + "field": "region", + "value": "qt" + } + }, + { + "from": { + "field": "region", + "value": "967" + }, + "to": { + "field": "region", + "value": "eu" + } + }, + { + "from": { + "field": "region", + "value": "968" + }, + "to": { + "field": "region", + "value": "qv" + } + }, + { + "from": { + "field": "region", + "value": "969" + }, + "to": { + "field": "region", + "value": "qw" + } + }, + { + "from": { + "field": "region", + "value": "970" + }, + "to": { + "field": "region", + "value": "qx" + } + }, + { + "from": { + "field": "region", + "value": "971" + }, + "to": { + "field": "region", + "value": "qy" + } + }, + { + "from": { + "field": "region", + "value": "972" + }, + "to": { + "field": "region", + "value": "qz" + } + }, + { + "from": { + "field": "region", + "value": "638" + }, + "to": { + "field": "region", + "value": "re" + } + }, + { + "from": { + "field": "region", + "value": "642" + }, + "to": { + "field": "region", + "value": "ro" + } + }, + { + "from": { + "field": "region", + "value": "688" + }, + "to": { + "field": "region", + "value": "rs" + } + }, + { + "from": { + "field": "region", + "value": "643" + }, + "to": { + "field": "region", + "value": "ru" + } + }, + { + "from": { + "field": "region", + "value": "646" + }, + "to": { + "field": "region", + "value": "rw" + } + }, + { + "from": { + "field": "region", + "value": "682" + }, + "to": { + "field": "region", + "value": "sa" + } + }, + { + "from": { + "field": "region", + "value": "090" + }, + "to": { + "field": "region", + "value": "sb" + } + }, + { + "from": { + "field": "region", + "value": "690" + }, + "to": { + "field": "region", + "value": "sc" + } + }, + { + "from": { + "field": "region", + "value": "729" + }, + "to": { + "field": "region", + "value": "sd" + } + }, + { + "from": { + "field": "region", + "value": "752" + }, + "to": { + "field": "region", + "value": "se" + } + }, + { + "from": { + "field": "region", + "value": "702" + }, + "to": { + "field": "region", + "value": "sg" + } + }, + { + "from": { + "field": "region", + "value": "654" + }, + "to": { + "field": "region", + "value": "sh" + } + }, + { + "from": { + "field": "region", + "value": "705" + }, + "to": { + "field": "region", + "value": "si" + } + }, + { + "from": { + "field": "region", + "value": "744" + }, + "to": { + "field": "region", + "value": "sj" + } + }, + { + "from": { + "field": "region", + "value": "703" + }, + "to": { + "field": "region", + "value": "sk" + } + }, + { + "from": { + "field": "region", + "value": "694" + }, + "to": { + "field": "region", + "value": "sl" + } + }, + { + "from": { + "field": "region", + "value": "674" + }, + "to": { + "field": "region", + "value": "sm" + } + }, + { + "from": { + "field": "region", + "value": "686" + }, + "to": { + "field": "region", + "value": "sn" + } + }, + { + "from": { + "field": "region", + "value": "706" + }, + "to": { + "field": "region", + "value": "so" + } + }, + { + "from": { + "field": "region", + "value": "740" + }, + "to": { + "field": "region", + "value": "sr" + } + }, + { + "from": { + "field": "region", + "value": "728" + }, + "to": { + "field": "region", + "value": "ss" + } + }, + { + "from": { + "field": "region", + "value": "678" + }, + "to": { + "field": "region", + "value": "st" + } + }, + { + "from": { + "field": "region", + "value": "222" + }, + "to": { + "field": "region", + "value": "sv" + } + }, + { + "from": { + "field": "region", + "value": "534" + }, + "to": { + "field": "region", + "value": "sx" + } + }, + { + "from": { + "field": "region", + "value": "760" + }, + "to": { + "field": "region", + "value": "sy" + } + }, + { + "from": { + "field": "region", + "value": "748" + }, + "to": { + "field": "region", + "value": "sz" + } + }, + { + "from": { + "field": "region", + "value": "796" + }, + "to": { + "field": "region", + "value": "tc" + } + }, + { + "from": { + "field": "region", + "value": "148" + }, + "to": { + "field": "region", + "value": "td" + } + }, + { + "from": { + "field": "region", + "value": "260" + }, + "to": { + "field": "region", + "value": "tf" + } + }, + { + "from": { + "field": "region", + "value": "768" + }, + "to": { + "field": "region", + "value": "tg" + } + }, + { + "from": { + "field": "region", + "value": "764" + }, + "to": { + "field": "region", + "value": "th" + } + }, + { + "from": { + "field": "region", + "value": "762" + }, + "to": { + "field": "region", + "value": "tj" + } + }, + { + "from": { + "field": "region", + "value": "772" + }, + "to": { + "field": "region", + "value": "tk" + } + }, + { + "from": { + "field": "region", + "value": "626" + }, + "to": { + "field": "region", + "value": "tl" + } + }, + { + "from": { + "field": "region", + "value": "795" + }, + "to": { + "field": "region", + "value": "tm" + } + }, + { + "from": { + "field": "region", + "value": "788" + }, + "to": { + "field": "region", + "value": "tn" + } + }, + { + "from": { + "field": "region", + "value": "776" + }, + "to": { + "field": "region", + "value": "to" + } + }, + { + "from": { + "field": "region", + "value": "792" + }, + "to": { + "field": "region", + "value": "tr" + } + }, + { + "from": { + "field": "region", + "value": "780" + }, + "to": { + "field": "region", + "value": "tt" + } + }, + { + "from": { + "field": "region", + "value": "798" + }, + "to": { + "field": "region", + "value": "tv" + } + }, + { + "from": { + "field": "region", + "value": "158" + }, + "to": { + "field": "region", + "value": "tw" + } + }, + { + "from": { + "field": "region", + "value": "834" + }, + "to": { + "field": "region", + "value": "tz" + } + }, + { + "from": { + "field": "region", + "value": "804" + }, + "to": { + "field": "region", + "value": "ua" + } + }, + { + "from": { + "field": "region", + "value": "800" + }, + "to": { + "field": "region", + "value": "ug" + } + }, + { + "from": { + "field": "region", + "value": "581" + }, + "to": { + "field": "region", + "value": "um" + } + }, + { + "from": { + "field": "region", + "value": "840" + }, + "to": { + "field": "region", + "value": "us" + } + }, + { + "from": { + "field": "region", + "value": "858" + }, + "to": { + "field": "region", + "value": "uy" + } + }, + { + "from": { + "field": "region", + "value": "860" + }, + "to": { + "field": "region", + "value": "uz" + } + }, + { + "from": { + "field": "region", + "value": "336" + }, + "to": { + "field": "region", + "value": "va" + } + }, + { + "from": { + "field": "region", + "value": "670" + }, + "to": { + "field": "region", + "value": "vc" + } + }, + { + "from": { + "field": "region", + "value": "862" + }, + "to": { + "field": "region", + "value": "ve" + } + }, + { + "from": { + "field": "region", + "value": "092" + }, + "to": { + "field": "region", + "value": "vg" + } + }, + { + "from": { + "field": "region", + "value": "850" + }, + "to": { + "field": "region", + "value": "vi" + } + }, + { + "from": { + "field": "region", + "value": "704" + }, + "to": { + "field": "region", + "value": "vn" + } + }, + { + "from": { + "field": "region", + "value": "548" + }, + "to": { + "field": "region", + "value": "vu" + } + }, + { + "from": { + "field": "region", + "value": "876" + }, + "to": { + "field": "region", + "value": "wf" + } + }, + { + "from": { + "field": "region", + "value": "882" + }, + "to": { + "field": "region", + "value": "ws" + } + }, + { + "from": { + "field": "region", + "value": "973" + }, + "to": { + "field": "region", + "value": "xa" + } + }, + { + "from": { + "field": "region", + "value": "974" + }, + "to": { + "field": "region", + "value": "xb" + } + }, + { + "from": { + "field": "region", + "value": "975" + }, + "to": { + "field": "region", + "value": "xc" + } + }, + { + "from": { + "field": "region", + "value": "976" + }, + "to": { + "field": "region", + "value": "xd" + } + }, + { + "from": { + "field": "region", + "value": "977" + }, + "to": { + "field": "region", + "value": "xe" + } + }, + { + "from": { + "field": "region", + "value": "978" + }, + "to": { + "field": "region", + "value": "xf" + } + }, + { + "from": { + "field": "region", + "value": "979" + }, + "to": { + "field": "region", + "value": "xg" + } + }, + { + "from": { + "field": "region", + "value": "980" + }, + "to": { + "field": "region", + "value": "xh" + } + }, + { + "from": { + "field": "region", + "value": "981" + }, + "to": { + "field": "region", + "value": "xi" + } + }, + { + "from": { + "field": "region", + "value": "982" + }, + "to": { + "field": "region", + "value": "xj" + } + }, + { + "from": { + "field": "region", + "value": "983" + }, + "to": { + "field": "region", + "value": "xk" + } + }, + { + "from": { + "field": "region", + "value": "984" + }, + "to": { + "field": "region", + "value": "xl" + } + }, + { + "from": { + "field": "region", + "value": "985" + }, + "to": { + "field": "region", + "value": "xm" + } + }, + { + "from": { + "field": "region", + "value": "986" + }, + "to": { + "field": "region", + "value": "xn" + } + }, + { + "from": { + "field": "region", + "value": "987" + }, + "to": { + "field": "region", + "value": "xo" + } + }, + { + "from": { + "field": "region", + "value": "988" + }, + "to": { + "field": "region", + "value": "xp" + } + }, + { + "from": { + "field": "region", + "value": "989" + }, + "to": { + "field": "region", + "value": "xq" + } + }, + { + "from": { + "field": "region", + "value": "990" + }, + "to": { + "field": "region", + "value": "xr" + } + }, + { + "from": { + "field": "region", + "value": "991" + }, + "to": { + "field": "region", + "value": "xs" + } + }, + { + "from": { + "field": "region", + "value": "992" + }, + "to": { + "field": "region", + "value": "xt" + } + }, + { + "from": { + "field": "region", + "value": "993" + }, + "to": { + "field": "region", + "value": "xu" + } + }, + { + "from": { + "field": "region", + "value": "994" + }, + "to": { + "field": "region", + "value": "xv" + } + }, + { + "from": { + "field": "region", + "value": "995" + }, + "to": { + "field": "region", + "value": "xw" + } + }, + { + "from": { + "field": "region", + "value": "996" + }, + "to": { + "field": "region", + "value": "xx" + } + }, + { + "from": { + "field": "region", + "value": "997" + }, + "to": { + "field": "region", + "value": "xy" + } + }, + { + "from": { + "field": "region", + "value": "998" + }, + "to": { + "field": "region", + "value": "xz" + } + }, + { + "from": { + "field": "region", + "value": "720" + }, + "to": { + "field": "region", + "value": "ye" + } + }, + { + "from": { + "field": "region", + "value": "887" + }, + "to": { + "field": "region", + "value": "ye" + } + }, + { + "from": { + "field": "region", + "value": "175" + }, + "to": { + "field": "region", + "value": "yt" + } + }, + { + "from": { + "field": "region", + "value": "710" + }, + "to": { + "field": "region", + "value": "za" + } + }, + { + "from": { + "field": "region", + "value": "894" + }, + "to": { + "field": "region", + "value": "zm" + } + }, + { + "from": { + "field": "region", + "value": "716" + }, + "to": { + "field": "region", + "value": "zw" + } + }, + { + "from": { + "field": "region", + "value": "999" + }, + "to": { + "field": "region", + "value": "zz" + } + }, + { + "from": { + "field": "variants", + "value": "aaland" + }, + "to": { + "field": "region", + "value": "ax" + } + }, + { + "from": { + "field": "variants", + "value": "polytoni" + }, + "to": { + "field": "variants", + "value": "polyton" + } + }, + { + "from": { + "field": "variants", + "value": "heploc" + }, + "to": { + "field": "variants", + "value": "alalc97" + } + }, + { + "from": { + "field": "variants", + "value": "arevela" + }, + "to": { + "field": "language", + "value": "hy" + } + }, + { + "from": { + "field": "variants", + "value": "arevmda" + }, + "to": { + "field": "language", + "value": "hyw" + } + } +] diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..a1065b5 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,223 @@ +'use strict' + +var bcp47 = require('bcp-47') +var match = require('bcp-47-match') +var matches = require('./matches.json') +var fields = require('./fields.json') +var defaults = require('./defaults.json') +var many = require('./many.json') + +module.exports = normalize + +var own = {}.hasOwnProperty + +var parse = bcp47.parse +var stringify = bcp47.stringify + +var collator = new Intl.Collator() + +function normalize(value, options) { + var opts = options || {} + var forgiving = opts.forgiving + var warning = opts.warning + // 1. normalize and lowercase the tag (`sgn-be-fr` -> `sfb`). + var schema = parse(String(value || '').toLowerCase(), {forgiving, warning}) + var tag = stringify(schema) + // 2. Do fancy, expensive replaces (`ha-latn-gh` -> `ha-gh`). + var length = matches.length + var index = -1 + var d + + if (tag === '') { + return tag + } + + while (++index < length) { + d = matches[index] + if (match.extendedFilter(tag, d.from).length !== 0) { + replace(schema, d.from, d.to) + tag = stringify(schema) + } + } + + // 3. Do basic field replaces (`en-840` -> `en-us`). + length = fields.length + index = -1 + + while (++index < length) { + d = fields[index] + + if (remove(schema, d.from.field, d.from.value)) { + add(schema, d.to.field, d.to.value) + } + } + + // 4. Remove defaults (`nl-nl` -> `nl`). + tag = stringify(schema) + length = defaults.length + index = -1 + + while (++index < length) { + d = defaults[index] + + if ( + d === tag || + (tag.charAt(d.length) === '-' && tag.slice(0, d.length) === d) + ) { + tag = d.slice(0, d.lastIndexOf('-')) + tag.slice(d.length) + } + } + + // 5. Add proper casing back. + schema = parse(tag) + + // 6. Sort extensions on singleton. + schema.extensions.sort(compareSingleton) + + // 7. Warn if fields (currently only regions) should be updated but have + // multiple choices. + if (warning) { + for (d in many) { + if (own.call(many[d], schema[d])) { + warning( + 'Deprecated ' + + d + + ' `' + + schema[d] + + '`, expected one of `' + + many[d][schema[d]].join('`, `') + + '`', + null, + 7 + ) + } + } + } + + // Format script (ISO 15924) as titlecase (example: `Latn`): + if (schema.script !== null) { + schema.script = + schema.script.charAt(0).toUpperCase() + schema.script.slice(1) + } + + // Format region (ISO 3166) as uppercase (note: this doesn’t affect numeric + // codes, which is fine): + if (schema.region !== null) { + schema.region = schema.region.toUpperCase() + } + + return stringify(schema) +} + +function replace(schema, from, to) { + var left = parse(from) + var right = parse(to) + var removed = [] + var key + + // Remove values from `from`: + for (key in left) { + if (defined(left[key]) && remove(schema, key, left[key])) { + removed.push(key) + } + } + + // Add values from `to`: + for (key in right) { + // Only add values that are defined on `to`, and that were either removed by + // `from` or are currently empty. + if ( + defined(right[key]) && + (removed.indexOf(key) !== -1 || !defined(schema[key])) + ) { + add(schema, key, right[key]) + } + } +} + +function remove(obj, key, value) { + var removed = false + var current + var result + var length + var index + var val + + /* istanbul ignore else - this is currently done by wrapping code, so else is + * never reached. + * However, that could change in the future, so leave this guard here. */ + if (value) { + current = obj[key] + result = current + + if (array(current)) { + result = [] + length = current.length + index = -1 + while (++index < length) { + val = current[index] + + if (value.indexOf(val) === -1) { + result.push(val) + } else { + removed = true + } + } + } else if (current === value) { + result = null + removed = true + } + + obj[key] = result + } + + return removed +} + +function add(obj, key, value) { + var current = obj[key] + var list + var length + var index + var val + + if (array(current)) { + list = [].concat(value) + length = list.length + index = -1 + + while (++index < length) { + val = list[index] + + /* istanbul ignore else - this currently can’t happen, but guard for the + * future. */ + if (current.indexOf(val) === -1) { + current.push(val) + } + } + } else { + obj[key] = value + } +} + +function defined(value) { + return value !== null && value.length !== 0 +} + +function array(value) { + return value !== null && typeof value === 'object' +} + +function compareSingleton(left, right) { + return compare(singleton(left), singleton(right)) +} + +function compare(left, right) { + /* istanbul ignore next - equality can’t happen in BCP 47 tags, but let’s keep + * it in as a guard. */ + return left === right ? 0 : collator.compare(left, right) +} + +function singleton(value) { + return value.singleton +} diff --git a/lib/many.json b/lib/many.json new file mode 100644 index 0000000..51b741c --- /dev/null +++ b/lib/many.json @@ -0,0 +1,157 @@ +{ + "region": { + "172": [ + "ru", + "am", + "az", + "by", + "ge", + "kg", + "kz", + "md", + "tj", + "tm", + "ua", + "uz" + ], + "200": [ + "cz", + "sk" + ], + "530": [ + "cw", + "sx", + "bq" + ], + "532": [ + "cw", + "sx", + "bq" + ], + "536": [ + "sa", + "iq" + ], + "582": [ + "fm", + "mh", + "mp", + "pw" + ], + "810": [ + "ru", + "am", + "az", + "by", + "ee", + "ge", + "kz", + "kg", + "lv", + "lt", + "md", + "tj", + "tm", + "ua", + "uz" + ], + "830": [ + "je", + "gg" + ], + "890": [ + "rs", + "me", + "si", + "hr", + "mk", + "ba" + ], + "891": [ + "rs", + "me" + ], + "an": [ + "cw", + "sx", + "bq" + ], + "cs": [ + "rs", + "me" + ], + "fq": [ + "aq", + "tf" + ], + "nt": [ + "sa", + "iq" + ], + "pc": [ + "fm", + "mh", + "mp", + "pw" + ], + "su": [ + "ru", + "am", + "az", + "by", + "ee", + "ge", + "kz", + "kg", + "lv", + "lt", + "md", + "tj", + "tm", + "ua", + "uz" + ], + "yu": [ + "rs", + "me" + ], + "062": [ + "034", + "143" + ], + "ant": [ + "cw", + "sx", + "bq" + ], + "scg": [ + "rs", + "me" + ], + "ntz": [ + "sa", + "iq" + ], + "sun": [ + "ru", + "am", + "az", + "by", + "ee", + "ge", + "kz", + "kg", + "lv", + "lt", + "md", + "tj", + "tm", + "ua", + "uz" + ], + "yug": [ + "rs", + "me" + ] + } +} diff --git a/lib/matches.json b/lib/matches.json new file mode 100644 index 0000000..8d69800 --- /dev/null +++ b/lib/matches.json @@ -0,0 +1,1582 @@ +[ + { + "from": "in", + "to": "id" + }, + { + "from": "iw", + "to": "he" + }, + { + "from": "ji", + "to": "yi" + }, + { + "from": "jw", + "to": "jv" + }, + { + "from": "mo", + "to": "ro" + }, + { + "from": "scc", + "to": "sr" + }, + { + "from": "scr", + "to": "hr" + }, + { + "from": "aam", + "to": "aas" + }, + { + "from": "adp", + "to": "dz" + }, + { + "from": "aue", + "to": "ktz" + }, + { + "from": "ayx", + "to": "nun" + }, + { + "from": "bgm", + "to": "bcg" + }, + { + "from": "bjd", + "to": "drl" + }, + { + "from": "ccq", + "to": "rki" + }, + { + "from": "cjr", + "to": "mom" + }, + { + "from": "cka", + "to": "cmr" + }, + { + "from": "cmk", + "to": "xch" + }, + { + "from": "coy", + "to": "pij" + }, + { + "from": "cqu", + "to": "quh" + }, + { + "from": "drh", + "to": "mn" + }, + { + "from": "drw", + "to": "fa-af" + }, + { + "from": "gav", + "to": "dev" + }, + { + "from": "gfx", + "to": "vaj" + }, + { + "from": "ggn", + "to": "gvr" + }, + { + "from": "gti", + "to": "nyc" + }, + { + "from": "guv", + "to": "duz" + }, + { + "from": "hrr", + "to": "jal" + }, + { + "from": "ibi", + "to": "opa" + }, + { + "from": "ilw", + "to": "gal" + }, + { + "from": "jeg", + "to": "oyb" + }, + { + "from": "kgc", + "to": "tdf" + }, + { + "from": "kgh", + "to": "kml" + }, + { + "from": "koj", + "to": "kwv" + }, + { + "from": "krm", + "to": "bmf" + }, + { + "from": "ktr", + "to": "dtp" + }, + { + "from": "kvs", + "to": "gdj" + }, + { + "from": "kwq", + "to": "yam" + }, + { + "from": "kxe", + "to": "tvd" + }, + { + "from": "kzj", + "to": "dtp" + }, + { + "from": "kzt", + "to": "dtp" + }, + { + "from": "lii", + "to": "raq" + }, + { + "from": "lmm", + "to": "rmx" + }, + { + "from": "meg", + "to": "cir" + }, + { + "from": "mst", + "to": "mry" + }, + { + "from": "mwj", + "to": "vaj" + }, + { + "from": "myt", + "to": "mry" + }, + { + "from": "nad", + "to": "xny" + }, + { + "from": "ncp", + "to": "kdz" + }, + { + "from": "nnx", + "to": "ngv" + }, + { + "from": "nts", + "to": "pij" + }, + { + "from": "oun", + "to": "vaj" + }, + { + "from": "pcr", + "to": "adx" + }, + { + "from": "pmc", + "to": "huw" + }, + { + "from": "pmu", + "to": "phr" + }, + { + "from": "ppa", + "to": "bfy" + }, + { + "from": "ppr", + "to": "lcq" + }, + { + "from": "pry", + "to": "prt" + }, + { + "from": "puz", + "to": "pub" + }, + { + "from": "sca", + "to": "hle" + }, + { + "from": "skk", + "to": "oyb" + }, + { + "from": "tdu", + "to": "dtp" + }, + { + "from": "thc", + "to": "tpo" + }, + { + "from": "thx", + "to": "oyb" + }, + { + "from": "tie", + "to": "ras" + }, + { + "from": "tkk", + "to": "twm" + }, + { + "from": "tlw", + "to": "weo" + }, + { + "from": "tmp", + "to": "tyj" + }, + { + "from": "tne", + "to": "kak" + }, + { + "from": "tnf", + "to": "fa-af" + }, + { + "from": "tsf", + "to": "taj" + }, + { + "from": "uok", + "to": "ema" + }, + { + "from": "xba", + "to": "cax" + }, + { + "from": "xia", + "to": "acn" + }, + { + "from": "xkh", + "to": "waw" + }, + { + "from": "xsj", + "to": "suj" + }, + { + "from": "ybd", + "to": "rki" + }, + { + "from": "yma", + "to": "lrr" + }, + { + "from": "ymt", + "to": "mtm" + }, + { + "from": "yos", + "to": "zom" + }, + { + "from": "yuu", + "to": "yug" + }, + { + "from": "asd", + "to": "snz" + }, + { + "from": "dit", + "to": "dif" + }, + { + "from": "llo", + "to": "ngt" + }, + { + "from": "myd", + "to": "aog" + }, + { + "from": "nns", + "to": "nbr" + }, + { + "from": "no-bokmal", + "to": "nb" + }, + { + "from": "no-nynorsk", + "to": "nn" + }, + { + "from": "aa-saaho", + "to": "ssy" + }, + { + "from": "sh", + "to": "sr-latn" + }, + { + "from": "cnr", + "to": "sr-me" + }, + { + "from": "no", + "to": "nb" + }, + { + "from": "tl", + "to": "fil" + }, + { + "from": "az-az", + "to": "az-latn-az" + }, + { + "from": "bs-ba", + "to": "bs-latn-ba" + }, + { + "from": "ha-latn-gh", + "to": "ha-gh" + }, + { + "from": "ha-latn-ne", + "to": "ha-ne" + }, + { + "from": "ha-latn-ng", + "to": "ha-ng" + }, + { + "from": "kk-cyrl-kz", + "to": "kk-kz" + }, + { + "from": "ky-cyrl-kg", + "to": "ky-kg" + }, + { + "from": "ks-arab-in", + "to": "ks-in" + }, + { + "from": "mn-cyrl-mn", + "to": "mn-mn" + }, + { + "from": "ms-latn-bn", + "to": "ms-bn" + }, + { + "from": "ms-latn-my", + "to": "ms-my" + }, + { + "from": "ms-latn-sg", + "to": "ms-sg" + }, + { + "from": "pa-in", + "to": "pa-guru-in" + }, + { + "from": "pa-pk", + "to": "pa-arab-pk" + }, + { + "from": "shi-ma", + "to": "shi-tfng-ma" + }, + { + "from": "sr-ba", + "to": "sr-cyrl-ba" + }, + { + "from": "sr-me", + "to": "sr-latn-me" + }, + { + "from": "sr-rs", + "to": "sr-cyrl-rs" + }, + { + "from": "sr-xk", + "to": "sr-cyrl-xk" + }, + { + "from": "tzm-latn-ma", + "to": "tzm-ma" + }, + { + "from": "ug-arab-cn", + "to": "ug-cn" + }, + { + "from": "uz-af", + "to": "uz-arab-af" + }, + { + "from": "uz-uz", + "to": "uz-latn-uz" + }, + { + "from": "vai-lr", + "to": "vai-vaii-lr" + }, + { + "from": "yue-cn", + "to": "yue-hans-cn" + }, + { + "from": "yue-hk", + "to": "yue-hant-hk" + }, + { + "from": "zh-cn", + "to": "zh-hans-cn" + }, + { + "from": "zh-hk", + "to": "zh-hant-hk" + }, + { + "from": "zh-mo", + "to": "zh-hant-mo" + }, + { + "from": "zh-sg", + "to": "zh-hans-sg" + }, + { + "from": "zh-tw", + "to": "zh-hant-tw" + }, + { + "from": "aju", + "to": "jrb" + }, + { + "from": "als", + "to": "sq" + }, + { + "from": "arb", + "to": "ar" + }, + { + "from": "ayr", + "to": "ay" + }, + { + "from": "azj", + "to": "az" + }, + { + "from": "bcc", + "to": "bal" + }, + { + "from": "bcl", + "to": "bik" + }, + { + "from": "bxk", + "to": "luy" + }, + { + "from": "bxr", + "to": "bua" + }, + { + "from": "cld", + "to": "syr" + }, + { + "from": "cmn", + "to": "zh" + }, + { + "from": "cwd", + "to": "cr" + }, + { + "from": "dgo", + "to": "doi" + }, + { + "from": "dhd", + "to": "mwr" + }, + { + "from": "dik", + "to": "din" + }, + { + "from": "diq", + "to": "zza" + }, + { + "from": "lbk", + "to": "bnc" + }, + { + "from": "ekk", + "to": "et" + }, + { + "from": "emk", + "to": "man" + }, + { + "from": "esk", + "to": "ik" + }, + { + "from": "fat", + "to": "ak" + }, + { + "from": "fuc", + "to": "ff" + }, + { + "from": "gaz", + "to": "om" + }, + { + "from": "gbo", + "to": "grb" + }, + { + "from": "gno", + "to": "gon" + }, + { + "from": "gug", + "to": "gn" + }, + { + "from": "gya", + "to": "gba" + }, + { + "from": "hdn", + "to": "hai" + }, + { + "from": "hea", + "to": "hmn" + }, + { + "from": "ike", + "to": "iu" + }, + { + "from": "kmr", + "to": "ku" + }, + { + "from": "knc", + "to": "kr" + }, + { + "from": "kng", + "to": "kg" + }, + { + "from": "knn", + "to": "kok" + }, + { + "from": "kpv", + "to": "kv" + }, + { + "from": "lvs", + "to": "lv" + }, + { + "from": "mhr", + "to": "chm" + }, + { + "from": "mup", + "to": "raj" + }, + { + "from": "khk", + "to": "mn" + }, + { + "from": "npi", + "to": "ne" + }, + { + "from": "ojg", + "to": "oj" + }, + { + "from": "ory", + "to": "or" + }, + { + "from": "pbu", + "to": "ps" + }, + { + "from": "pes", + "to": "fa" + }, + { + "from": "plt", + "to": "mg" + }, + { + "from": "pnb", + "to": "lah" + }, + { + "from": "quz", + "to": "qu" + }, + { + "from": "rmy", + "to": "rom" + }, + { + "from": "spy", + "to": "kln" + }, + { + "from": "src", + "to": "sc" + }, + { + "from": "swh", + "to": "sw" + }, + { + "from": "ttq", + "to": "tmh" + }, + { + "from": "tw", + "to": "ak" + }, + { + "from": "umu", + "to": "del" + }, + { + "from": "uzn", + "to": "uz" + }, + { + "from": "xpe", + "to": "kpe" + }, + { + "from": "xsl", + "to": "den" + }, + { + "from": "ydd", + "to": "yi" + }, + { + "from": "zai", + "to": "zap" + }, + { + "from": "zsm", + "to": "ms" + }, + { + "from": "zyb", + "to": "za" + }, + { + "from": "him", + "to": "srx" + }, + { + "from": "mnk", + "to": "man" + }, + { + "from": "bh", + "to": "bho" + }, + { + "from": "prs", + "to": "fa-af" + }, + { + "from": "swc", + "to": "sw-cd" + }, + { + "from": "aar", + "to": "aa" + }, + { + "from": "abk", + "to": "ab" + }, + { + "from": "ave", + "to": "ae" + }, + { + "from": "afr", + "to": "af" + }, + { + "from": "aka", + "to": "ak" + }, + { + "from": "amh", + "to": "am" + }, + { + "from": "arg", + "to": "an" + }, + { + "from": "ara", + "to": "ar" + }, + { + "from": "asm", + "to": "as" + }, + { + "from": "ava", + "to": "av" + }, + { + "from": "aym", + "to": "ay" + }, + { + "from": "aze", + "to": "az" + }, + { + "from": "bak", + "to": "ba" + }, + { + "from": "bel", + "to": "be" + }, + { + "from": "bul", + "to": "bg" + }, + { + "from": "bih", + "to": "bho" + }, + { + "from": "bis", + "to": "bi" + }, + { + "from": "bam", + "to": "bm" + }, + { + "from": "ben", + "to": "bn" + }, + { + "from": "bod", + "to": "bo" + }, + { + "from": "bre", + "to": "br" + }, + { + "from": "bos", + "to": "bs" + }, + { + "from": "cat", + "to": "ca" + }, + { + "from": "che", + "to": "ce" + }, + { + "from": "cha", + "to": "ch" + }, + { + "from": "cos", + "to": "co" + }, + { + "from": "cre", + "to": "cr" + }, + { + "from": "ces", + "to": "cs" + }, + { + "from": "chu", + "to": "cu" + }, + { + "from": "chv", + "to": "cv" + }, + { + "from": "cym", + "to": "cy" + }, + { + "from": "dan", + "to": "da" + }, + { + "from": "deu", + "to": "de" + }, + { + "from": "div", + "to": "dv" + }, + { + "from": "dzo", + "to": "dz" + }, + { + "from": "ewe", + "to": "ee" + }, + { + "from": "ell", + "to": "el" + }, + { + "from": "eng", + "to": "en" + }, + { + "from": "epo", + "to": "eo" + }, + { + "from": "spa", + "to": "es" + }, + { + "from": "est", + "to": "et" + }, + { + "from": "eus", + "to": "eu" + }, + { + "from": "fas", + "to": "fa" + }, + { + "from": "ful", + "to": "ff" + }, + { + "from": "fin", + "to": "fi" + }, + { + "from": "fij", + "to": "fj" + }, + { + "from": "fao", + "to": "fo" + }, + { + "from": "fra", + "to": "fr" + }, + { + "from": "fry", + "to": "fy" + }, + { + "from": "gle", + "to": "ga" + }, + { + "from": "gla", + "to": "gd" + }, + { + "from": "glg", + "to": "gl" + }, + { + "from": "grn", + "to": "gn" + }, + { + "from": "guj", + "to": "gu" + }, + { + "from": "glv", + "to": "gv" + }, + { + "from": "hau", + "to": "ha" + }, + { + "from": "heb", + "to": "he" + }, + { + "from": "hin", + "to": "hi" + }, + { + "from": "hmo", + "to": "ho" + }, + { + "from": "hrv", + "to": "hr" + }, + { + "from": "hat", + "to": "ht" + }, + { + "from": "hun", + "to": "hu" + }, + { + "from": "hye", + "to": "hy" + }, + { + "from": "her", + "to": "hz" + }, + { + "from": "ina", + "to": "ia" + }, + { + "from": "ind", + "to": "id" + }, + { + "from": "ile", + "to": "ie" + }, + { + "from": "ibo", + "to": "ig" + }, + { + "from": "iii", + "to": "ii" + }, + { + "from": "ipk", + "to": "ik" + }, + { + "from": "ido", + "to": "io" + }, + { + "from": "isl", + "to": "is" + }, + { + "from": "ita", + "to": "it" + }, + { + "from": "iku", + "to": "iu" + }, + { + "from": "jpn", + "to": "ja" + }, + { + "from": "jav", + "to": "jv" + }, + { + "from": "kat", + "to": "ka" + }, + { + "from": "kon", + "to": "kg" + }, + { + "from": "kik", + "to": "ki" + }, + { + "from": "kua", + "to": "kj" + }, + { + "from": "kaz", + "to": "kk" + }, + { + "from": "kal", + "to": "kl" + }, + { + "from": "khm", + "to": "km" + }, + { + "from": "kan", + "to": "kn" + }, + { + "from": "kor", + "to": "ko" + }, + { + "from": "kau", + "to": "kr" + }, + { + "from": "kas", + "to": "ks" + }, + { + "from": "kur", + "to": "ku" + }, + { + "from": "kom", + "to": "kv" + }, + { + "from": "cor", + "to": "kw" + }, + { + "from": "kir", + "to": "ky" + }, + { + "from": "lat", + "to": "la" + }, + { + "from": "ltz", + "to": "lb" + }, + { + "from": "lug", + "to": "lg" + }, + { + "from": "lim", + "to": "li" + }, + { + "from": "lin", + "to": "ln" + }, + { + "from": "lao", + "to": "lo" + }, + { + "from": "lit", + "to": "lt" + }, + { + "from": "lub", + "to": "lu" + }, + { + "from": "lav", + "to": "lv" + }, + { + "from": "mlg", + "to": "mg" + }, + { + "from": "mah", + "to": "mh" + }, + { + "from": "mri", + "to": "mi" + }, + { + "from": "mkd", + "to": "mk" + }, + { + "from": "mal", + "to": "ml" + }, + { + "from": "mon", + "to": "mn" + }, + { + "from": "mol", + "to": "ro" + }, + { + "from": "mar", + "to": "mr" + }, + { + "from": "msa", + "to": "ms" + }, + { + "from": "mlt", + "to": "mt" + }, + { + "from": "mya", + "to": "my" + }, + { + "from": "nau", + "to": "na" + }, + { + "from": "nob", + "to": "nb" + }, + { + "from": "nde", + "to": "nd" + }, + { + "from": "nep", + "to": "ne" + }, + { + "from": "ndo", + "to": "ng" + }, + { + "from": "nld", + "to": "nl" + }, + { + "from": "nno", + "to": "nn" + }, + { + "from": "nor", + "to": "nb" + }, + { + "from": "nbl", + "to": "nr" + }, + { + "from": "nav", + "to": "nv" + }, + { + "from": "nya", + "to": "ny" + }, + { + "from": "oci", + "to": "oc" + }, + { + "from": "oji", + "to": "oj" + }, + { + "from": "orm", + "to": "om" + }, + { + "from": "ori", + "to": "or" + }, + { + "from": "oss", + "to": "os" + }, + { + "from": "pan", + "to": "pa" + }, + { + "from": "pli", + "to": "pi" + }, + { + "from": "pol", + "to": "pl" + }, + { + "from": "pus", + "to": "ps" + }, + { + "from": "por", + "to": "pt" + }, + { + "from": "que", + "to": "qu" + }, + { + "from": "roh", + "to": "rm" + }, + { + "from": "run", + "to": "rn" + }, + { + "from": "ron", + "to": "ro" + }, + { + "from": "rus", + "to": "ru" + }, + { + "from": "kin", + "to": "rw" + }, + { + "from": "san", + "to": "sa" + }, + { + "from": "srd", + "to": "sc" + }, + { + "from": "snd", + "to": "sd" + }, + { + "from": "sme", + "to": "se" + }, + { + "from": "sag", + "to": "sg" + }, + { + "from": "hbs", + "to": "sr-latn" + }, + { + "from": "sin", + "to": "si" + }, + { + "from": "slk", + "to": "sk" + }, + { + "from": "slv", + "to": "sl" + }, + { + "from": "smo", + "to": "sm" + }, + { + "from": "sna", + "to": "sn" + }, + { + "from": "som", + "to": "so" + }, + { + "from": "sqi", + "to": "sq" + }, + { + "from": "srp", + "to": "sr" + }, + { + "from": "ssw", + "to": "ss" + }, + { + "from": "sot", + "to": "st" + }, + { + "from": "sun", + "to": "su" + }, + { + "from": "swe", + "to": "sv" + }, + { + "from": "swa", + "to": "sw" + }, + { + "from": "tam", + "to": "ta" + }, + { + "from": "tel", + "to": "te" + }, + { + "from": "tgk", + "to": "tg" + }, + { + "from": "tha", + "to": "th" + }, + { + "from": "tir", + "to": "ti" + }, + { + "from": "tuk", + "to": "tk" + }, + { + "from": "tgl", + "to": "fil" + }, + { + "from": "tsn", + "to": "tn" + }, + { + "from": "ton", + "to": "to" + }, + { + "from": "tur", + "to": "tr" + }, + { + "from": "tso", + "to": "ts" + }, + { + "from": "tat", + "to": "tt" + }, + { + "from": "twi", + "to": "ak" + }, + { + "from": "tah", + "to": "ty" + }, + { + "from": "uig", + "to": "ug" + }, + { + "from": "ukr", + "to": "uk" + }, + { + "from": "urd", + "to": "ur" + }, + { + "from": "uzb", + "to": "uz" + }, + { + "from": "ven", + "to": "ve" + }, + { + "from": "vie", + "to": "vi" + }, + { + "from": "vol", + "to": "vo" + }, + { + "from": "wln", + "to": "wa" + }, + { + "from": "wol", + "to": "wo" + }, + { + "from": "xho", + "to": "xh" + }, + { + "from": "yid", + "to": "yi" + }, + { + "from": "yor", + "to": "yo" + }, + { + "from": "zha", + "to": "za" + }, + { + "from": "zho", + "to": "zh" + }, + { + "from": "zul", + "to": "zu" + }, + { + "from": "alb", + "to": "sq" + }, + { + "from": "arm", + "to": "hy" + }, + { + "from": "baq", + "to": "eu" + }, + { + "from": "bur", + "to": "my" + }, + { + "from": "chi", + "to": "zh" + }, + { + "from": "cze", + "to": "cs" + }, + { + "from": "dut", + "to": "nl" + }, + { + "from": "fre", + "to": "fr" + }, + { + "from": "geo", + "to": "ka" + }, + { + "from": "ger", + "to": "de" + }, + { + "from": "gre", + "to": "el" + }, + { + "from": "ice", + "to": "is" + }, + { + "from": "mac", + "to": "mk" + }, + { + "from": "mao", + "to": "mi" + }, + { + "from": "may", + "to": "ms" + }, + { + "from": "per", + "to": "fa" + }, + { + "from": "rum", + "to": "ro" + }, + { + "from": "slo", + "to": "sk" + }, + { + "from": "tib", + "to": "bo" + }, + { + "from": "wel", + "to": "cy" + } +] diff --git a/license b/license new file mode 100644 index 0000000..3937235 --- /dev/null +++ b/license @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright (c) 2020 Titus Wormer + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/package.json b/package.json new file mode 100644 index 0000000..8569ea8 --- /dev/null +++ b/package.json @@ -0,0 +1,82 @@ +{ + "name": "bcp-47-normalize", + "version": "0.0.0", + "description": "Normalize, canonicalize, and format BCP 47 tags", + "license": "MIT", + "keywords": [ + "bcp", + "47", + "bcp47", + "bcp-47", + "language", + "region", + "script", + "tag", + "subtag", + "format", + "pretty", + "normal", + "canonical" + ], + "repository": "wooorm/bcp-47-normalize", + "bugs": "https://github.com/wooorm/bcp-47-normalize/issues", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + }, + "author": "Titus Wormer (https://wooorm.com)", + "contributors": [ + "Titus Wormer (https://wooorm.com)" + ], + "files": [ + "index.js", + "lib/" + ], + "dependencies": { + "bcp-47": "^1.0.0", + "bcp-47-match": "^1.0.0" + }, + "devDependencies": { + "node-fetch": "^2.0.0", + "nyc": "^15.0.0", + "prettier": "^1.0.0", + "remark-cli": "^7.0.0", + "remark-preset-wooorm": "^6.0.0", + "tape": "^4.0.0", + "unist-util-visit": "^2.0.0", + "xast-util-from-xml": "^1.0.0", + "xo": "^0.26.0" + }, + "scripts": { + "format": "remark . -qfo && prettier --write \"**/*.js\" && xo --fix", + "test-api": "node test", + "test-coverage": "nyc --reporter lcov tape test.js", + "test": "npm run format && npm run test-coverage" + }, + "prettier": { + "tabWidth": 2, + "useTabs": false, + "singleQuote": true, + "bracketSpacing": false, + "semi": false, + "trailingComma": "none" + }, + "xo": { + "prettier": true, + "esnext": false, + "rules": { + "unicorn/prefer-includes": "off" + } + }, + "nyc": { + "check-coverage": true, + "lines": 100, + "functions": 100, + "branches": 100 + }, + "remarkConfig": { + "plugins": [ + "preset-wooorm" + ] + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..bdcbf80 --- /dev/null +++ b/readme.md @@ -0,0 +1,132 @@ +# bcp-47-normalize + +[![Build][build-badge]][build] +[![Coverage][coverage-badge]][coverage] +[![Downloads][downloads-badge]][downloads] +[![Size][size-badge]][size] + +Normalize, canonicalize, and format [BCP 47][spec] tags. + +## Install + +[npm][]: + +```sh +npm install bcp-47-normalize +``` + +## Use + +```js +var normalize = require('bcp-47-normalize') + +var tags = [ + 'de-de-1901', + 'en-gb', + 'en-us', + 'en-bu', + 'hy-arevmda', + 'nld-nl', + 'no-nyn', + 'pt-br', + 'pt-pt', + 'zh-hans-cn' +] + +tags.forEach(function(tag) { + console.log('%s -> %s', tag, normalize(tag)) +}) +``` + +Yields: + +```txt +de-de-1901 -> de-1901 +en-gb -> en-GB +en-us -> en +en-bu -> en-MM +hy-arevmda -> hyw +nld-nl -> nl +no-nyn -> nn +pt-br -> pt +pt-pt -> pt-PT +zh-hans-cn -> zh-CN +``` + +## API + +### `normalize(tag[, options])` + +Normalize the given BCP 47 tag according to [Unicode CLDR suggestions][alias]. + +###### `options.forgiving` + +Passed to `bcp-47` as [`options.forgiving`][forgiving]. + +###### `options.warning` + +Passed to `bcp-47` as [`options.warning`][warning]. + +One additional warning is given: + +| code | reason | +| :--- | :--------------------------------------------------------- | +| 7 | Deprecated region `CURRENT`, expected one of `SUGGESTIONS` | + +This warning is only given if the region cannot be automatically fixed (when +regions split into multiple regions). + +###### Returns + +`string` — Normal, canonical, and pretty [BCP 47][spec] tag. + +## Related + +* [`bcp-47`](https://github.com/wooorm/bcp-47-match) + — Parse and stringify BCP 47 language tags +* [`bcp-47-match`](https://github.com/wooorm/bcp-47-match) + — Match BCP 47 language tags with language ranges per RFC 4647 +* [`iso-639-2`](https://github.com/wooorm/iso-639-2) + — ISO 639-2 codes +* [`iso-639-3`](https://github.com/wooorm/iso-639-3) + — ISO 639-3 codes +* [`iso-15924`](https://github.com/wooorm/iso-15924) + — ISO 15924 codes +* [`un-m49`](https://github.com/wooorm/un-m49) + — UN M49 codes + +## License + +[MIT][license] © [Titus Wormer][author] + + + +[build-badge]: https://img.shields.io/travis/wooorm/bcp-47-normalize.svg + +[build]: https://travis-ci.org/wooorm/bcp-47-normalize + +[coverage-badge]: https://img.shields.io/codecov/c/github/wooorm/bcp-47-normalize.svg + +[coverage]: https://codecov.io/github/wooorm/bcp-47-normalize + +[downloads-badge]: https://img.shields.io/npm/dm/bcp-47-normalize.svg + +[downloads]: https://www.npmjs.com/package/bcp-47-normalize + +[size-badge]: https://img.shields.io/bundlephobia/minzip/bcp-47-normalize.svg + +[size]: https://bundlephobia.com/result?p=bcp-47-normalize + +[npm]: https://docs.npmjs.com/cli/install + +[license]: license + +[author]: https://wooorm.com + +[spec]: https://tools.ietf.org/html/bcp47 + +[alias]: https://github.com/unicode-org/cldr/blob/4b1225ead2ca9bc7a969a271b9931f137040d2bf/common/supplemental/supplementalMetadata.xml#L32 + +[forgiving]: https://github.com/wooorm/bcp-47#optionsforgiving + +[warning]: https://github.com/wooorm/bcp-47#optionswarning diff --git a/test.js b/test.js new file mode 100644 index 0000000..d5dc7f4 --- /dev/null +++ b/test.js @@ -0,0 +1,268 @@ +'use strict' + +var test = require('tape') +var normalize = require('.') + +test('bcp-47-normalize', function(t) { + t.test('basic', function(t) { + t.equal(normalize(), '', 'should not fail on without a value') + t.equal(normalize(''), '', 'should not fail on an empty string') + t.equal(normalize('en-us'), 'en', 'should normalize') + t.equal(normalize('EN-CYRL-gb'), 'en-Cyrl-GB', 'should fix casing') + + t.equal( + normalize('en-gb-abcdefghi'), + '', + 'should return empty if the tag is invalid by default' + ) + + t.equal( + normalize('en-gb-abcdefghi', {forgiving: true}), + 'en-GB', + 'should ignore trailing stuff in forgiving mode' + ) + + t.test('should emit when given a warning function', function(t) { + t.plan(1) + + normalize('en-aaa-bbb-ccc-ddd', {warning: warning}) + + function warning(reason, code, offset) { + t.deepEqual( + [reason, code, offset], + [ + 'Too many extended language subtags, expected at most 3 subtags', + 3, + 14 + ], + 'warning' + ) + } + }) + + t.test('should emit if deprecated tags can’t be fixed', function(t) { + t.plan(1) + + normalize('pap-an', {warning: warning}) + + function warning(reason, code, offset) { + t.deepEqual( + [reason, code, offset], + ['Deprecated region `an`, expected one of `cw`, `sx`, `bq`', null, 7], + 'warning' + ) + } + }) + + t.test('should not emit if there are no deprecated tags', function(t) { + var called = false + + t.plan(1) + + normalize('pap-cw', {warning: warning}) + + t.equal(called, false, 'warning') + + function warning() { + called = true + } + }) + + t.end() + }) + + t.test('normalize', function(t) { + t.equal( + normalize('art-lojban'), + 'jbo', + 'should normalize a deprecated regular language' + ) + + t.equal( + normalize('i-klingon'), + 'tlh', + 'should normalize a deprecated irregular language' + ) + + t.equal( + normalize('in'), + 'id', + 'should normalize a deprecated language (two-letter)' + ) + + t.equal( + normalize('krm'), + 'bmf', + 'should normalize a deprecated language (three-letter)' + ) + + t.equal( + normalize('no-bokmal'), + 'nb', + 'should normalize a deprecated variant' + ) + + t.equal(normalize('pa-pk'), 'pa-Arab', 'should normalize to add a script') + + t.equal( + normalize('ha-latn-gh'), + 'ha-GH', + 'should normalize to remove a script' + ) + + t.equal(normalize('nld'), 'nl', 'should normalize overlong language codes') + + t.equal(normalize('aju'), 'jrb', 'should normalize macrolanguages codes') + + t.equal(normalize('alb'), 'sq', 'should normalize bibliographic codes') + + t.equal(normalize('en-qaai'), 'en-Zinh', 'should normalize scripts') + + t.equal(normalize('en-bu'), 'en-MM', 'should normalize deprecated regions') + + t.equal(normalize('en-784'), 'en-AE', 'should normalize overlong regions') + + t.equal( + normalize('sv-aaland'), + 'sv-AX', + 'should normalize legacy variants (1)' + ) + + t.equal( + normalize('el-polytoni'), + 'el-polyton', + 'should normalize legacy variants (2)' + ) + + t.equal( + normalize('hy-arevela'), + 'hy', + 'should normalize legacy variants (3)' + ) + + t.equal( + normalize('hy-arevmda'), + 'hyw', + 'should normalize legacy variants (4)' + ) + + t.equal( + normalize('el-polytoni-polyton'), + 'el-polyton', + 'should not duplicate new variants (1)' + ) + t.equal( + normalize('el-polyton-polytoni'), + 'el-polyton', + 'should not duplicate new variants (2)' + ) + + t.equal(normalize('nl-nl'), 'nl', 'should normalize default regions') + + t.equal( + normalize('en-b-warble-a-warble'), + 'en-a-warble-b-warble', + 'should canonicalize extension sequences' + ) + + t.end() + }) + + t.test('fixtures', function(t) { + var fixtures = { + afb: 'afb', + 'ar-afb': 'ar-afb', + 'art-lojban': 'jbo', + ast: 'ast', + 'az-arab-x-aze-derbend': 'az-Arab-x-aze-derbend', + 'az-latn': 'az', + 'cel-gaulish': 'cel-gaulish', + 'cmn-hans-cn': 'zh-CN', + 'de-de-1901': 'de-1901', + 'de-de-x-goethe': 'de-x-goethe', + 'de-deva': 'de-Deva', + 'de-deva-de': 'de-Deva-DE', + 'de-latf-de': 'de-Latf-DE', + 'de-ch-x-phonebk': 'de-CH-x-phonebk', + 'de-de-u-co-phonebk': 'de-u-co-phonebk', + 'de-de': 'de', + 'de-qaaa': 'de-Qaaa', + de: 'de', + 'eng-840': 'en', + 'en-840': 'en', + 'pt-pt': 'pt-PT', + 'pt-br': 'pt', + 'az-arab-ir': 'az-Arab', + 'sl-cyrl-yu-rozaj-solba-1994-b-1234-a-Foobar-x-b-1234-a-Foobar': + 'sl-Cyrl-YU-rozaj-solba-1994-a-foobar-b-1234-x-b-1234-a-foobar', + 'en-gb-oed': 'en-GB-oxendict', + 'en-us-u-islamcal': 'en-u-islamcal', + 'en-us': 'en', + 'en-gb': 'en-GB', + 'en-us-x-twain': 'en-x-twain', + 'en-a-myext-b-another': 'en-a-myext-b-another', + 'en-bu': 'en-MM', + en: 'en', + 'es-005': 'es-005', + 'es-419': 'es-419', + 'fr-ca': 'fr-CA', + fr: 'fr', + hak: 'hak', + 'hy-latn-it-arevela': 'hy-Latn-IT', + 'i-ami': 'ami', + 'i-bnn': 'bnn', + 'i-default': 'i-default', + 'i-enochian': 'i-enochian', + 'i-hak': 'hak', + 'i-klingon': 'tlh', + 'i-lux': 'lb', + 'i-mingo': 'i-mingo', + 'i-navajo': 'nv', + 'i-pwn': 'pwn', + 'i-tao': 'tao', + 'i-tay': 'tay', + 'i-tsu': 'tsu', + ja: 'ja', + mas: 'mas', + 'no-bok': 'nb', + 'no-nyn': 'nn', + 'qaa-qaaa-qm-x-southern': 'qaa-Qaaa-QM-x-southern', + 'sgn-be-fr': 'sfb', + 'sgn-be-nl': 'vgt', + 'sgn-ch-de': 'sgg', + 'sl-it-nedis': 'sl-IT-nedis', + 'sl-nedis': 'sl-nedis', + 'sl-rozaj-biske': 'sl-rozaj-biske', + 'sl-rozaj': 'sl-rozaj', + 'sr-cyrl': 'sr', + 'sr-latn-qm': 'sr-Latn-QM', + 'sr-latn-rs': 'sr-Latn', + 'sr-latn': 'sr-Latn', + 'sr-qaaa-rs': 'sr-Qaaa-RS', + 'x-whatever': 'x-whatever', + 'yue-hk': 'yue-HK', + 'zh-cn-a-myext-x-private': 'zh-CN-a-myext-x-private', + 'zh-hans-cn': 'zh-CN', + 'zh-hans': 'zh', + 'zh-hant-hk': 'zh-Hant-HK', + 'zh-hant': 'zh-Hant', + 'zh-cmn-hans-cn': 'zh-cmn-Hans-CN', + 'zh-guoyu': 'zh', + 'zh-hakka': 'hak', + 'zh-min-nan': 'nan', + 'zh-min': 'zh-min', + 'zh-xiang': 'hsn', + 'zh-yue-hk': 'zh-yue-Hant-HK', + 'zh-yue': 'zh-yue' + } + + Object.keys(fixtures).forEach(from => { + var to = fixtures[from] + t.equal(normalize(from), to, '`' + from + '` -> `' + to + '`') + }) + + t.end() + }) + + t.end() +})