(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.translate = factory()); }(this, (function () { 'use strict'; var iso = [ "aa", "ab", "ae", "af", "ak", "am", "an", "ar", "as", "av", "ay", "az", "ba", "be", "bg", "bh", "bi", "bm", "bn", "bo", "br", "bs", "ca", "ce", "ch", "co", "cr", "cs", "cu", "cv", "cy", "da", "de", "dv", "dz", "ee", "el", "en", "eo", "es", "et", "eu", "fa", "ff", "fi", "fj", "fo", "fr", "fy", "ga", "gd", "gl", "gn", "gu", "gv", "ha", "he", "hi", "ho", "hr", "ht", "hu", "hy", "hz", "ia", "id", "ie", "ig", "ii", "ik", "io", "is", "it", "iu", "ja", "jv", "ka", "kg", "ki", "kj", "kk", "kl", "km", "kn", "ko", "kr", "ks", "ku", "kv", "kw", "ky", "la", "lb", "lg", "li", "ln", "lo", "lt", "lu", "lv", "mg", "mh", "mi", "mk", "ml", "mn", "mr", "ms", "mt", "my", "na", "nb", "nd", "ne", "ng", "nl", "nn", "no", "nr", "nv", "ny", "oc", "oj", "om", "or", "os", "pa", "pi", "pl", "ps", "pt", "qu", "rm", "rn", "ro", "ru", "rw", "sa", "sc", "sd", "se", "sg", "si", "sk", "sl", "sm", "sn", "so", "sq", "sr", "ss", "st", "su", "sv", "sw", "ta", "te", "tg", "th", "ti", "tk", "tl", "tn", "to", "tr", "ts", "tt", "tw", "ty", "ug", "uk", "ur", "uz", "ve", "vi", "vo", "wa", "wo", "xh", "yi", "yo", "za", "zh", "zu" ]; var iso2 = { "aar": "aa", "abk": "ab", "afr": "af", "aka": "ak", "alb": "sq", "amh": "am", "ara": "ar", "arg": "an", "arm": "hy", "asm": "as", "ava": "av", "ave": "ae", "aym": "ay", "aze": "az", "bak": "ba", "bam": "bm", "baq": "eu", "bel": "be", "ben": "bn", "bih": "bh", "bis": "bi", "bos": "bs", "bre": "br", "bul": "bg", "bur": "my", "cat": "ca", "cha": "ch", "che": "ce", "chi": "zh", "chu": "cu", "chv": "cv", "cor": "kw", "cos": "co", "cre": "cr", "cze": "cs", "dan": "da", "div": "dv", "dut": "nl", "dzo": "dz", "eng": "en", "epo": "eo", "est": "et", "ewe": "ee", "fao": "fo", "fij": "fj", "fin": "fi", "fre": "fr", "fry": "fy", "ful": "ff", "geo": "ka", "ger": "de", "gla": "gd", "gle": "ga", "glg": "gl", "glv": "gv", "gre": "el", "grn": "gn", "guj": "gu", "hat": "ht", "hau": "ha", "heb": "he", "her": "hz", "hin": "hi", "hmo": "ho", "hrv": "hr", "hun": "hu", "ibo": "ig", "ice": "is", "ido": "io", "iii": "ii", "iku": "iu", "ile": "ie", "ina": "ia", "ind": "id", "ipk": "ik", "ita": "it", "jav": "jv", "jpn": "ja", "kal": "kl", "kan": "kn", "kas": "ks", "kau": "kr", "kaz": "kk", "khm": "km", "kik": "ki", "kin": "rw", "kir": "ky", "kom": "kv", "kon": "kg", "kor": "ko", "kua": "kj", "kur": "ku", "lao": "lo", "lat": "la", "lav": "lv", "lim": "li", "lin": "ln", "lit": "lt", "ltz": "lb", "lub": "lu", "lug": "lg", "mac": "mk", "mah": "mh", "mal": "ml", "mao": "mi", "mar": "mr", "may": "ms", "mlg": "mg", "mlt": "mt", "mon": "mn", "nau": "na", "nav": "nv", "nbl": "nr", "nde": "nd", "ndo": "ng", "nep": "ne", "nno": "nn", "nob": "nb", "nor": "no", "nya": "ny", "oci": "oc", "oji": "oj", "ori": "or", "orm": "om", "oss": "os", "pan": "pa", "per": "fa", "pli": "pi", "pol": "pl", "por": "pt", "pus": "ps", "que": "qu", "roh": "rm", "rum": "ro", "run": "rn", "rus": "ru", "sag": "sg", "san": "sa", "sin": "si", "slo": "sk", "slv": "sl", "sme": "se", "smo": "sm", "sna": "sn", "snd": "sd", "som": "so", "sot": "st", "spa": "es", "srd": "sc", "srp": "sr", "ssw": "ss", "sun": "su", "swa": "sw", "swe": "sv", "tah": "ty", "tam": "ta", "tat": "tt", "tel": "te", "tgk": "tg", "tgl": "tl", "tha": "th", "tib": "bo", "tir": "ti", "ton": "to", "tsn": "tn", "tso": "ts", "tuk": "tk", "tur": "tr", "twi": "tw", "uig": "ug", "ukr": "uk", "urd": "ur", "uzb": "uz", "ven": "ve", "vie": "vi", "vol": "vo", "wel": "cy", "wln": "wa", "wol": "wo", "xho": "xh", "yid": "yi", "yor": "yo", "zha": "za", "zul": "zu" }; var map = { "afar": "aa", "abkhazian": "ab", "afrikaans": "af", "akan": "ak", "albanian": "sq", "amharic": "am", "arabic": "ar", "aragonese": "an", "armenian": "hy", "assamese": "as", "avaric": "av", "avestan": "ae", "aymara": "ay", "azerbaijani": "az", "bashkir": "ba", "bambara": "bm", "basque": "eu", "belarusian": "be", "bengali": "bn", "bihari languages": "bh", "bislama": "bi", "tibetan": "bo", "bosnian": "bs", "breton": "br", "bulgarian": "bg", "burmese": "my", "catalan": "ca", "valencian": "ca", "czech": "cs", "chamorro": "ch", "chechen": "ce", "chinese": "zh", "church slavic": "cu", "old slavonic": "cu", "church slavonic": "cu", "old bulgarian": "cu", "old church slavonic": "cu", "chuvash": "cv", "cornish": "kw", "corsican": "co", "cree": "cr", "welsh": "cy", "danish": "da", "german": "de", "divehi": "dv", "dhivehi": "dv", "maldivian": "dv", "dutch": "nl", "flemish": "nl", "dzongkha": "dz", "greek": "el", "english": "en", "esperanto": "eo", "estonian": "et", "ewe": "ee", "faroese": "fo", "persian": "fa", "fijian": "fj", "finnish": "fi", "french": "fr", "western frisian": "fy", "fulah": "ff", "georgian": "ka", "gaelic": "gd", "scottish gaelic": "gd", "irish": "ga", "galician": "gl", "manx": "gv", "guarani": "gn", "gujarati": "gu", "haitian": "ht", "haitian creole": "ht", "hausa": "ha", "hebrew": "he", "herero": "hz", "hindi": "hi", "hiri motu": "ho", "croatian": "hr", "hungarian": "hu", "igbo": "ig", "icelandic": "is", "ido": "io", "sichuan yi": "ii", "nuosu": "ii", "inuktitut": "iu", "interlingue": "ie", "occidental": "ie", "interlingua": "ia", "indonesian": "id", "inupiaq": "ik", "italian": "it", "javanese": "jv", "japanese": "ja", "kalaallisut": "kl", "greenlandic": "kl", "kannada": "kn", "kashmiri": "ks", "kanuri": "kr", "kazakh": "kk", "central khmer": "km", "kikuyu": "ki", "gikuyu": "ki", "kinyarwanda": "rw", "kirghiz": "ky", "kyrgyz": "ky", "komi": "kv", "kongo": "kg", "korean": "ko", "kuanyama": "kj", "kwanyama": "kj", "kurdish": "ku", "lao": "lo", "latin": "la", "latvian": "lv", "limburgan": "li", "limburger": "li", "limburgish": "li", "lingala": "ln", "lithuanian": "lt", "luxembourgish": "lb", "letzeburgesch": "lb", "luba-katanga": "lu", "ganda": "lg", "macedonian": "mk", "marshallese": "mh", "malayalam": "ml", "maori": "mi", "marathi": "mr", "malay": "ms", "malagasy": "mg", "maltese": "mt", "mongolian": "mn", "nauru": "na", "navajo": "nv", "navaho": "nv", "ndebele, south": "nr", "south ndebele": "nr", "ndebele, north": "nd", "north ndebele": "nd", "ndonga": "ng", "nepali": "ne", "norwegian nynorsk": "nn", "nynorsk, norwegian": "nn", "norwegian bokmål": "nb", "bokmål, norwegian": "nb", "norwegian": "no", "chichewa": "ny", "chewa": "ny", "nyanja": "ny", "occitan": "oc", "ojibwa": "oj", "oriya": "or", "oromo": "om", "ossetian": "os", "ossetic": "os", "panjabi": "pa", "punjabi": "pa", "pali": "pi", "polish": "pl", "portuguese": "pt", "pushto": "ps", "pashto": "ps", "quechua": "qu", "romansh": "rm", "romanian": "ro", "moldavian": "ro", "moldovan": "ro", "rundi": "rn", "russian": "ru", "sango": "sg", "sanskrit": "sa", "sinhala": "si", "sinhalese": "si", "slovak": "sk", "slovenian": "sl", "northern sami": "se", "samoan": "sm", "shona": "sn", "sindhi": "sd", "somali": "so", "sotho, southern": "st", "spanish": "es", "castilian": "es", "sardinian": "sc", "serbian": "sr", "swati": "ss", "sundanese": "su", "swahili": "sw", "swedish": "sv", "tahitian": "ty", "tamil": "ta", "tatar": "tt", "telugu": "te", "tajik": "tg", "tagalog": "tl", "thai": "th", "tigrinya": "ti", "tonga": "to", "tswana": "tn", "tsonga": "ts", "turkmen": "tk", "turkish": "tr", "twi": "tw", "uighur": "ug", "uyghur": "ug", "ukrainian": "uk", "urdu": "ur", "uzbek": "uz", "venda": "ve", "vietnamese": "vi", "volapük": "vo", "walloon": "wa", "wolof": "wo", "xhosa": "xh", "yiddish": "yi", "yoruba": "yo", "zhuang": "za", "chuang": "za", "zulu": "zu" }; // Relevant ISO: ISO 639-1 & ISO 639-2. Google uses the ISO 639-1. // Valid ISO 639-1 codes // https://www.loc.gov/standards/iso639-2/php/code_list.php // Extract these with this code (after loading https://superdom.site/ ) // [...dom.table[1].querySelectorAll('tbody tr')].slice(1).filter(row => !/^\s*$/.test(row.querySelector('td:nth-child(2)').textContent)).map((row, i) => `"${row.querySelector('td:nth-child(2)').textContent}", ${i % 12 === 11 ? '\n' : ''}`).join(''); // Parsed from here: https://github.com/wooorm/iso-639-2/blob/master/index.json // Extract these with this code (after loading https://superdom.site/ ) + a lot of manual clean up // [...dom.table[1].querySelectorAll('tbody tr')].slice(1).filter(row => !/^\s*$/.test(row.querySelector('td:nth-child(2)').textContent)).map(row => // ` "${row.querySelector('td:nth-child(3)').textContent.toLowerCase()}": "${row.querySelector('td:nth-child(2)').textContent.toLowerCase()}",` // ).join('\n'); // Language parser // @name: a string to be parsed // @output: the normalized language string var language = name => { // Validate the name string if (typeof name !== 'string') { throw new Error(`The language must be a string, received ${typeof name}`); } // Possible overflow errors if (name.length > 100) { throw new Error(`The language must be a string under 100 characters, received ${name.length}`); } // Let's work with lowercase for everything name = name.toLowerCase(); // Pass it through several possible maps to try to find the right one name = map[name] || iso2[name] || name; // Make sure we have the correct name or throw if (!iso.includes(name)) { throw new Error(`The name "${name}" is not part of the ISO 639-1 and we couldn't automatically parse it :(`); } return name; }; // Translation engines // Handle different translations differently to allow for extra flexibility const google = { needkey: true, fetch: ({ key, from, to, text }) => [ `https://translation.googleapis.com/language/translate/v2?key=${key}&format=text&source=${from}&target=${to}&q=${encodeURIComponent(text)}`, { method: 'POST' } ], parse: res => res.json().then(body => { if (body.error) { throw new Error(body.error.errors[0].message); } body = body.data.translations[0]; if (!body) { throw new Error('Translation not found'); } return body.translatedText; }) }; const yandex = { needkey: true, fetch: ({ key, from, to, text }) => [ `https://translate.yandex.net/api/v1.5/tr.json/translate?key=${key}&lang=${from}-${to}&text=${encodeURIComponent(text)}`, { method: 'POST', body: '' } ], parse: res => res.json().then(body => { if (body.code !== 200) { throw new Error(body.message); } return body.text[0]; }) }; var engines = { google, yandex }; // From https://www.npmjs.com/package/memory-cache (Rollup didn't want to bundle it otherwise) 'use strict'; function Cache () { var _cache = Object.create(null); var _hitCount = 0; var _missCount = 0; var _size = 0; var _debug = false; this.put = function(key, value, time, timeoutCallback) { if (_debug) { console.log('caching: %s = %j (@%s)', key, value, time); } if (typeof time !== 'undefined' && (typeof time !== 'number' || isNaN(time) || time <= 0)) { throw new Error('Cache timeout must be a positive number'); } else if (typeof timeoutCallback !== 'undefined' && typeof timeoutCallback !== 'function') { throw new Error('Cache timeout callback must be a function'); } var oldRecord = _cache[key]; if (oldRecord) { clearTimeout(oldRecord.timeout); } else { _size++; } var record = { value: value, expire: time + Date.now() }; if (!isNaN(record.expire)) { record.timeout = setTimeout(function() { _del(key); if (timeoutCallback) { timeoutCallback(key, value); } }.bind(this), time); } _cache[key] = record; return value; }; this.del = function(key) { var canDelete = true; var oldRecord = _cache[key]; if (oldRecord) { clearTimeout(oldRecord.timeout); if (!isNaN(oldRecord.expire) && oldRecord.expire < Date.now()) { canDelete = false; } } else { canDelete = false; } if (canDelete) { _del(key); } return canDelete; }; function _del(key){ _size--; delete _cache[key]; } this.clear = function() { for (var key in _cache) { clearTimeout(_cache[key].timeout); } _size = 0; _cache = Object.create(null); if (_debug) { _hitCount = 0; _missCount = 0; } }; this.get = function(key) { var data = _cache[key]; if (typeof data != "undefined") { if (isNaN(data.expire) || data.expire >= Date.now()) { if (_debug) _hitCount++; return data.value; } else { // free some space if (_debug) _missCount++; _size--; delete _cache[key]; } } else if (_debug) { _missCount++; } return null; }; this.size = function() { return _size; }; this.memsize = function() { var size = 0, key; for (key in _cache) { size++; } return size; }; this.debug = function(bool) { _debug = bool; }; this.hits = function() { return _hitCount; }; this.misses = function() { return _missCount; }; this.keys = function() { return Object.keys(_cache); }; this.exportJson = function() { var plainJsCache = {}; // Discard the `timeout` property. // Note: JSON doesn't support `NaN`, so convert it to `'NaN'`. for (var key in _cache) { var record = _cache[key]; plainJsCache[key] = { value: record.value, expire: record.expire || 'NaN', }; } return JSON.stringify(plainJsCache); }; this.importJson = function(jsonToImport, options) { var cacheToImport = JSON.parse(jsonToImport); var currTime = Date.now(); var skipDuplicates = options && options.skipDuplicates; for (var key in cacheToImport) { if (cacheToImport.hasOwnProperty(key)) { if (skipDuplicates) { var existingRecord = _cache[key]; if (existingRecord) { if (_debug) { console.log('Skipping duplicate imported key \'%s\'', key); } continue; } } var record = cacheToImport[key]; // record.expire could be `'NaN'` if no expiry was set. // Try to subtract from it; a string minus a number is `NaN`, which is perfectly fine here. var remainingTime = record.expire - currTime; if (remainingTime <= 0) { // Delete any record that might exist with the same key, since this key is expired. this.del(key); continue; } // Remaining time must now be either positive or `NaN`, // but `put` will throw an error if we try to give it `NaN`. remainingTime = remainingTime > 0 ? remainingTime : undefined; this.put(key, record.value, remainingTime); } } return this.size(); }; } const exp$2 = new Cache(); exp$2.Cache = Cache; // translate.js // Translate text into different languages; // Load a language parser to allow for more flexibility in the language choice // Load the default engines for translation // Cache the different translations to avoid resending requests // Main function const Translate = function(options = {}) { if (!(this instanceof Translate)) { return new Translate(options); } const defaults = { from: "en", to: "en", cache: undefined, language: language, engines: engines, engine: "google", keys: {} }; const translate = (text, opts = {}) => { // Load all of the appropriate options (verbose but fast) // Note: not all of those *should* be documented since some are internal only if (typeof opts === "string") opts = { to: opts }; opts.text = text; opts.from = language(opts.from || translate.from); opts.to = language(opts.to || translate.to); opts.cache = opts.cache || translate.cache; opts.engines = opts.engines || {}; opts.engine = opts.engine || translate.engine; opts.id = opts.id || `${opts.from}:${opts.to}:${opts.engine}:${opts.text}`; opts.keys = opts.keys || translate.keys || {}; for (let name in translate.keys) { // The options has stronger preference than the global value opts.keys[name] = opts.keys[name] || translate.keys[name]; } opts.key = opts.key || translate.key || opts.keys[opts.engine]; // TODO: validation for few of those // Use the desired engine const engine = opts.engines[opts.engine] || translate.engines[opts.engine]; // If it is cached return ASAP const cached = exp$2.get(opts.id); if (cached) return Promise.resolve(cached); // Target is the same as origin, just return the string if (opts.to === opts.from) { return Promise.resolve(opts.text); } // Will load only for Node.js and use the native function on the browser if (typeof fetch === "undefined") { try { global.fetch = require("node-fetch"); } catch (error) { console.warn("Please make sure node-fetch is available"); } } if (engine.needkey && !opts.key) { throw new Error( `The engine "${opts.engine}" needs a key, please provide it` ); } const fetchOpts = engine.fetch(opts); return fetch(...fetchOpts) .then(engine.parse) .then(translated => exp$2.put(opts.id, translated, opts.cache)); }; for (let key in defaults) { translate[key] = typeof options[key] === "undefined" ? defaults[key] : options[key]; } return translate; }; // Small hack needed for Webpack/Babel: https://github.com/webpack/webpack/issues/706 const exp = new Translate(); exp.Translate = Translate; return exp; })));