CVE-2024-29042 (published 2024-03-22T17:15Z)

Translate is a package that allows users to convert text to different languages on Node.js and the browser. Prior to version 3.0.0, an attacker controlling the second variable of the `translate` function is able to perform a cache poisoning attack. They can change the outcome of translation requests made by subsequent users. The `opt.id` parameter allows the overwriting of the cache key. If an attacker sets the `id` variable to the cache key that would be generated by another user, they can choose the response that user gets served. Version 3.0.0 fixes this issue.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/franciscop/translate
commit_details: repo_url: https://github.com/franciscop/translate
author: Francisco
author_date: 2024-03-21 11:45:03+09:00
msg: Made the usage options much simpler so that there's no chance of writing runtime vulnerabilities
repository: repo_url: https://github.com/franciscop/translate
repo_name: franciscop/translate
description: :sa: Translate text on node.js and the browser with promises
repo_language: JavaScript
owner: franciscop
file_changes: filename: README.md
change_type: ModificationType.MODIFY
diff: @@ -61,14 +61,16 @@ console.log(text);
 
 The first parameter is the **string** that you want to translate. Right now only a single string of text is accepted.
 
-The second parameter is the options. It accepts either a `String` of the language to translate **to** or a simple `Object` with these options:
+The second parameter is the options. It accepts either a `String` of the language to translate **to** or a simple `Object` with the keys `to` and `from`. However, in total there are more options, so here is a list of all of them:
 
 - **`to`**: the string of the language to translate to. It can be in any of the two ISO 639 (1 or 2) or the full name in English like `Spanish`. Defaults to **en**.
 - **`from`**: the string of the language to translate to. It can be in any of the two ISO 639 (1 or 2) or the full name in English like `Spanish`. Also defaults to **en**.
-- **`cache`**: a `Number` with the milliseconds that each translation should be cached. Leave it undefined to cache it indefinitely (until a server/browser restart).
-- **`engine`**: a `String` containing the name of the engine to use for translation. Right now it defaults to `google`. Read more [in the engine section](#engines).
-- **`key`**: the API Key for the engine of your choice. Read more [in the engine section](#engines).
-- **`url`**: only available for those engines that you can install on your own server (like Libretranslate), allows you to specify a custom endpoint for the translations. [See this issue](https://github.com/franciscop/translate/issues/26#issuecomment-845038821) for more info.
+- **`cache`** [instance]: a `Number` with the milliseconds that each translation should be cached. Leave it undefined to cache it indefinitely (until a server/browser restart).
+- **`engine`** [instance]: a `String` containing the name of the engine to use for translation. Right now it defaults to `google`. Read more [in the engine section](#engines).
+- **`key`** [instance]: the API Key for the engine of your choice. Read more [in the engine section](#engines).
+- **`url`** [instance]: only available for those engines that you can install on your own server (like Libretranslate), allows you to specify a custom endpoint for the translations. [See this issue](https://github.com/franciscop/translate/issues/26#issuecomment-845038821) for more info.
+
+> The options marked as [instance] can only be set to the root `translate.cache = 1000` or when creating a new instance `const myDeepL = translate.Translate()`
 
 Examples:
 
@@ -78,6 +80,9 @@ const foo = await translate("Hello world", "es");
 
 // Same as this:
 const bar = await translate("Hello world", { to: "es" });
+
+// INVALID:
+const bar = await translate("Hello world", { to: "es", engine: "google" });
 ```
 
 > On both `to` and `from` defaulting to `en`: while I _am_ Spanish and was quite tempted to set this as one of those, English is the main language of the Internet and the main secondary language for those who have a different native language. This is why most of the translations will happen either to or from English.
@@ -89,9 +94,15 @@ You can change the default options for anything by calling the root library and
 ```js
 translate.from = "es";
 translate.engine = "deepl";
+await translate("Hola mundo", "ja");
 ```
 
-This can be applied to any of the options enumerated above.
+You can also create a new instance with different default options:
+
+```js
+const myLib = translate.Translate({ engine: 'deepl', from: 'es', ... });
+await myLib("Hola mundo", "ja" );
+```
 
 ## Engines
 
@@ -126,10 +137,21 @@ translate.key = process.env.TRANSLATE_KEY;
 // ... use translate()
 ```
 
-To pass it per-translation, you can add it to your arguments:
+You can create different instances if you want to combine different engines:
 
 ```js
-translate("Hello world", { to: "en", engine: "deepl", key: "YOUR-KEY-HERE" });
+const gTranslate = translate.Translate({
+  engine: "google",
+  key: "YOUR-KEY-HERE",
+});
+const dTranslate = translate.Translate({
+  engine: "deepl",
+  key: "YOUR-KEY-HERE",
+});
+const lTranslate = translate.Translate({
+  engine: "libre",
+  key: "YOUR-KEY-HERE",
+});
 ```
 
 Specifically in Libretranslate, you can also add a `url` parameter if you install it on your own server:
@@ -137,6 +159,14 @@ Specifically in Libretranslate, you can also add a `url` parameter if you instal
 ```js
 translate.url = "https://example.com/";
 translate.key = process.env.TRANSLATE_KEY;
+
+// or
+
+const lTranslate = translate.Translate({
+  engine: "libre",
+  url: "...",
+  key: "YOUR-KEY-HERE",
+});
 ```
 
 ## Promises

programming_language: Markdown
filename: index.min.js
change_type: ModificationType.MODIFY
diff: @@ -1 +1 @@
-var iso={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"},names={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"};const isoKeys=Object.values(iso).sort();var languages=e=>{if("string"!=typeof e)throw new Error('The "language" must be a string, received '+typeof e);if(e.length>100)throw new Error(`The "language" is too long at ${e.length} characters`);if(e=e.toLowerCase(),e=names[e]||iso[e]||e,!isoKeys.includes(e))throw new Error(`The language "${e}" is not part of the ISO 639-1`);return e};function Cache(){var e=Object.create(null);function a(a){delete e[a]}this.set=function(n,i,r){if(void 0!==r&&("number"!=typeof r||isNaN(r)||r<=0))throw new Error("Cache timeout must be a positive number");var t=e[n];t&&clearTimeout(t.timeout);var o={value:i,expire:r+Date.now()};return isNaN(o.expire)||(o.timeout=setTimeout((()=>a(n)),r)),e[n]=o,i},this.del=function(n){var i=!0,r=e[n];return r?(clearTimeout(r.timeout),!isNaN(r.expire)&&r.expire<Date.now()&&(i=!1)):i=!1,i&&a(n),i},this.clear=function(){for(var a in e)clearTimeout(e[a].timeout);e=Object.create(null)},this.get=function(a){var n=e[a];if(void 0!==n){if(isNaN(n.expire)||n.expire>=Date.now())return n.value;delete e[a]}return null}}const exp$1=new Cache;exp$1.Cache=Cache;const base="https://translate.googleapis.com/translate_a/single";var google={fetch:({key:e,from:a,to:n,text:i})=>[`${base}?client=gtx&sl=${a}&tl=${n}&dt=t&q=${encodeURI(i)}`],parse:e=>e.json().then((e=>{if(!(e=e&&e[0]&&e[0][0]&&e[0].map((e=>e[0])).join("")))throw new Error("Translation not found");return e}))},yandex={needkey:!0,fetch:({key:e,from:a,to:n,text:i})=>[`https://translate.yandex.net/api/v1.5/tr.json/translate?key=${e}&lang=${a}-${n}&text=${encodeURIComponent(i)}`,{method:"POST",body:""}],parse:e=>e.json().then((e=>{if(200!==e.code)throw new Error(e.message);return e.text[0]}))};const libreUrl="https://libretranslate.com/translate";var libre={needkey:!1,fetch:({url:e=libreUrl,key:a,from:n,to:i,text:r})=>[e,{method:"POST",body:JSON.stringify({q:r,source:n,target:i,api_key:a}),headers:{"Content-Type":"application/json"}}],parse:e=>e.json().then((e=>{if(!e)throw new Error("No response found");if(e.error)throw new Error(e.error);if(!e.translatedText)throw new Error("No response found");return e.translatedText}))},deepl={needkey:!0,fetch:({key:e,from:a,to:n,text:i})=>[`https://api${/:fx$/.test(e)?"-free":""}.deepl.com/v2/translate?auth_key=${e}&source_lang=${a}&target_lang=${n}&text=${i=encodeURIComponent(i)}`,{method:"POST",body:""}],parse:async e=>{if(!e.ok){if(403===e.status)throw new Error("Auth Error, please review the key for DeepL");throw new Error(`Error ${e.status}`)}return e.json().then((e=>e.translations[0].text))}},engines={google:google,yandex:yandex,libre:libre,deepl:deepl};const Translate=function(e={}){if(!(this instanceof Translate))return new Translate(e);const a={from:"en",to:"en",cache:void 0,languages:languages,engines:engines,engine:"google",keys:{}},n=async(e,a={})=>{"string"==typeof a&&(a={to:a}),a.text=e,a.from=languages(a.from||n.from),a.to=languages(a.to||n.to),a.cache=a.cache||n.cache,a.engines=a.engines||{},a.engine=a.engine||n.engine,a.url=a.url||n.url,a.id=a.id||`${a.url}:${a.from}:${a.to}:${a.engine}:${a.text}`,a.keys=a.keys||n.keys||{};for(let e in n.keys)a.keys[e]=a.keys[e]||n.keys[e];a.key=a.key||n.key||a.keys[a.engine];const i=a.engines[a.engine]||n.engines[a.engine],r=exp$1.get(a.id);if(r)return Promise.resolve(r);if(a.to===a.from)return Promise.resolve(a.text);if(i.needkey&&!a.key)throw new Error(`The engine "${a.engine}" needs a key, please provide it`);const t=i.fetch(a);return fetch(...t).then(i.parse).then((e=>exp$1.set(a.id,e,a.cache)))};for(let i in a)n[i]=void 0===e[i]?a[i]:e[i];return n},exp=new Translate;exp.Translate=Translate;export{exp as default};
\ No newline at end of file
+function Cache(){var e=Object.create(null);function a(a){delete e[a]}this.set=function(n,i,r){if(void 0!==r&&("number"!=typeof r||isNaN(r)||r<=0))throw new Error("Cache timeout must be a positive number");var t=e[n];t&&clearTimeout(t.timeout);var o={value:i,expire:r+Date.now()};return isNaN(o.expire)||(o.timeout=setTimeout((()=>a(n)),r)),e[n]=o,i},this.del=function(n){var i=!0,r=e[n];return r?(clearTimeout(r.timeout),!isNaN(r.expire)&&r.expire<Date.now()&&(i=!1)):i=!1,i&&a(n),i},this.clear=function(){for(var a in e)clearTimeout(e[a].timeout);e=Object.create(null)},this.get=function(a){var n=e[a];if(void 0!==n){if(isNaN(n.expire)||n.expire>=Date.now())return n.value;delete e[a]}return null}}const exp$1=new Cache;exp$1.Cache=Cache;const base="https://translate.googleapis.com/translate_a/single";var google={fetch:({key:e,from:a,to:n,text:i})=>[`${base}?client=gtx&sl=${a}&tl=${n}&dt=t&q=${encodeURI(i)}`],parse:e=>e.json().then((e=>{if(!(e=e&&e[0]&&e[0][0]&&e[0].map((e=>e[0])).join("")))throw new Error("Translation not found");return e}))},yandex={needkey:!0,fetch:({key:e,from:a,to:n,text:i})=>[`https://translate.yandex.net/api/v1.5/tr.json/translate?key=${e}&lang=${a}-${n}&text=${encodeURIComponent(i)}`,{method:"POST",body:""}],parse:e=>e.json().then((e=>{if(200!==e.code)throw new Error(e.message);return e.text[0]}))};const libreUrl="https://libretranslate.com/translate";var libre={needkey:!1,fetch:({url:e=libreUrl,key:a,from:n,to:i,text:r})=>[e,{method:"POST",body:JSON.stringify({q:r,source:n,target:i,api_key:a}),headers:{"Content-Type":"application/json"}}],parse:e=>e.json().then((e=>{if(!e)throw new Error("No response found");if(e.error)throw new Error(e.error);if(!e.translatedText)throw new Error("No response found");return e.translatedText}))},deepl={needkey:!0,fetch:({key:e,from:a,to:n,text:i})=>[`https://api${e.endsWith(":fx")?"-free":""}.deepl.com/v2/translate?auth_key=${e}&source_lang=${a}&target_lang=${n}&text=${i=encodeURIComponent(i)}`,{method:"POST",body:""}],parse:async e=>{if(!e.ok){if(403===e.status)throw new Error("Auth Error, please review the key for DeepL");throw new Error(`Error ${e.status}`)}return e.json().then((e=>e.translations[0].text))}},engines={google:google,yandex:yandex,libre:libre,deepl:deepl},iso={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"},names={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"};const isoKeys=Object.values(iso).sort();var languages=e=>{if("string"!=typeof e)throw new Error('The "language" must be a string, received '+typeof e);if(e.length>100)throw new Error(`The "language" is too long at ${e.length} characters`);if(e=e.toLowerCase(),e=names[e]||iso[e]||e,!isoKeys.includes(e))throw new Error(`The language "${e}" is not part of the ISO 639-1`);return e};const Translate=function(e={}){if(!(this instanceof Translate))return new Translate(e);const a={from:"en",to:"en",cache:void 0,engine:"google",key:void 0,url:void 0,languages:languages,engines:engines,keys:{}},n=async(e,a={})=>{"string"==typeof a&&(a={to:a});const i=Object.keys(a).find((e=>"from"!==e&&"to"!==e));if(i)throw new Error(`Invalid option with the name '${i}'`);a.text=e,a.from=languages(a.from||n.from),a.to=languages(a.to||n.to),a.cache=n.cache,a.engine=n.engine,a.url=n.url,a.id=`${a.url}:${a.from}:${a.to}:${a.engine}:${a.text}`,a.keys=n.keys||{};for(let e in n.keys)a.keys[e]=a.keys[e]||n.keys[e];a.key=a.key||n.key||a.keys[a.engine];const r=n.engines[a.engine],t=exp$1.get(a.id);if(t)return Promise.resolve(t);if(a.to===a.from)return Promise.resolve(a.text);if(r.needkey&&!a.key)throw new Error(`The engine "${a.engine}" needs a key, please provide it`);const o=r.fetch(a);return fetch(...o).then(r.parse).then((e=>exp$1.set(a.id,e,a.cache)))};for(let i in a)n[i]=void 0===e[i]?a[i]:e[i];return n},exp=new Translate;exp.Translate=Translate;export{exp as default};
\ No newline at end of file

programming_language: JavaScript
filename: package.json
change_type: ModificationType.MODIFY
diff: @@ -2,14 +2,14 @@
   "name": "translate",
   "version": "2.0.2",
   "description": "Translate text to different languages on node.js and the browser",
+  "author": "Francisco Presencia <public@francisco.io> (https://francisco.io/)",
   "homepage": "https://github.com/franciscop/translate#readme",
   "repository": "https://github.com/franciscop/translate.git",
   "bugs": "https://github.com/franciscop/translate/issues",
   "funding": "https://www.paypal.me/franciscopresencia/19",
-  "author": "Francisco Presencia <public@francisco.io> (https://francisco.io/)",
   "license": "MIT",
   "scripts": {
-    "start": "npm run watch # Start ~= Start dev",
+    "start": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
     "build": "rollup src/index.js --name translate --output.format esm | terser --compress --mangle -o index.min.js",
     "size": "gzip -c index.min.js | wc -c && echo 'bytes' # Only for Unix",
     "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage --collectCoverageFrom=src/**/*.js && npx check-dts",

programming_language: CoffeeScript
filename: cache.test.js
change_type: ModificationType.MODIFY
diff: @@ -35,12 +35,13 @@ describe("cache", () => {
   });
 
   it("removes cache after the time is out", async () => {
+    const t = translate.Translate({ cache: 1000 });
     const before = new Date();
-    await translate("Is this also cached?", { to: "es", cache: 1000 });
+    await t("Is this also cached?", { to: "es" });
     const mid = new Date();
     await delay(1100);
     mock(/googleapis.*tl=es/, [[["Hola mundo"]]]);
-    await translate("Is this also cached?", { to: "es" });
+    await t("Is this also cached?", { to: "es" });
     const after = new Date();
     expect(mid - before).toBeLessThan(10000);
     expect(mid - before).toBeGreaterThan(100);

programming_language: TypeScript
filename: deepl.js
change_type: ModificationType.MODIFY
diff: @@ -1,7 +1,7 @@
 export default {
   needkey: true,
   fetch: ({ key, from, to, text }) => {
-    const suffix = /:fx$/.test(key) ? "-free" : "";
+    const suffix = key.endsWith(":fx") ? "-free" : "";
     text = encodeURIComponent(text);
     return [
       `https://api${suffix}.deepl.com/v2/translate?auth_key=${key}&source_lang=${from}&target_lang=${to}&text=${text}`,

programming_language: TypeScript
filename: google.test.js
change_type: ModificationType.MODIFY
diff: @@ -1,7 +1,10 @@
 import "dotenv/config";
-import translate from "../../src";
+
+import t from "../../src";
 import mock from "../../test/mock.js";
 
+const translate = t.Translate({ engine: "google" });
+
 describe("google mocked responses", () => {
   afterEach(() => mock.end());
 
@@ -39,17 +42,17 @@ describe("google full requests", () => {
   });
 
   it("calls Google to translate to Japanese", async () => {
-    const opts = { to: "ja", engine: "google" };
+    const opts = { to: "ja" };
     expect(await translate("Hello world", opts)).toBe("こんにちは世界");
   });
 
   it("calls Google to translate to Spanish", async () => {
-    const opts = { to: "es", engine: "google" };
+    const opts = { to: "es" };
     expect(await translate("Hello world", opts)).toMatch(/Hola mundo/i);
   });
 
   it("works with punctuation", async () => {
-    const opts = { to: "pt", engine: "google" };
+    const opts = { to: "pt" };
     const text = await translate(
       "What do you call a pig that knows karate? A pork chop!",
       opts

programming_language: TypeScript
filename: libre.test.js
change_type: ModificationType.MODIFY
diff: @@ -1,7 +1,9 @@
 import "dotenv/config";
-import translate from "../../src";
+
+import t from "../../src";
 import mock from "../../test/mock";
 
+const translate = t.Translate({ engine: "libre" });
 translate.keys.libre = process.env.LIBRE_KEY || "xxx";
 
 describe("Libre mocked requests", () => {
@@ -10,12 +12,12 @@ describe("Libre mocked requests", () => {
 
   it("works with libretranslate", async () => {
     mock.libre("Hola mundo");
-    const text = await translate("Hello world", { to: "es", engine: "libre" });
+    const text = await translate("Hello world", { to: "es" });
     expect(text).toMatch(/Hola mundo/i);
   });
 
   it("will throw with a wrong language", async () => {
-    const opts = { to: "adgdfnj", engine: "libre" };
+    const opts = { to: "adgdfnj" };
     await expect(translate("Hello world", opts)).rejects.toMatchObject({
       message: `The language "adgdfnj" is not part of the ISO 639-1`,
     });
@@ -23,14 +25,14 @@ describe("Libre mocked requests", () => {
 
   it("will throw with an empty result", async () => {
     mock.libre("");
-    const opts = { to: "es", engine: "libre" };
+    const opts = { to: "es" };
     await expect(translate("What's going on?", opts)).rejects.toMatchObject({
       message: "No response found",
     });
   });
 
   it("requires an API key", async () => {
-    const opts = { to: "es", engine: "libre" };
+    const opts = { to: "es" };
     await expect(translate("What's going on?", opts)).rejects.toMatchObject({
       message: "Visit https://portal.libretranslate.com to get an API key",
     });
@@ -40,7 +42,7 @@ describe("Libre mocked requests", () => {
     mock(/example\.*/, new Error("no domain"), { throws: true });
 
     translate.url = "https://example.com/";
-    const opts = { to: "es", engine: "libre" };
+    const opts = { to: "es" };
     await expect(translate("Hello world", opts)).rejects.toMatchObject({
       message: "no domain",
     });
@@ -59,23 +61,23 @@ describe("libre full requests", () => {
   }
 
   it("calls Libre to translate to Japanese", async () => {
-    const opts = { to: "ja", engine: "libre" };
+    const opts = { to: "ja" };
     expect(await translate("Hello world", opts)).toBe("ハローワールド");
   });
 
   it("calls Libre to translate to Spanish", async () => {
-    const opts = { to: "es", engine: "libre" };
+    const opts = { to: "es" };
     expect(await translate("Hello world", opts)).toBe("Hola mundo");
   });
 
   it("requires a valid key", async () => {
-    const opts = { to: "ru", engine: "libre", key: "abc" };
+    const opts = { to: "ru", key: "abc" };
     await expect(translate("Hello world", opts)).rejects.toThrow();
   });
 
   it("can set a custom URL", async () => {
     translate.url = "https://example.com/";
-    const opts = { to: "es", engine: "libre" };
+    const opts = { to: "es" };
     const text = await translate("libre custom url", opts);
     delete translate.url;
     expect(text).toBe("Url personalizada de libre");

programming_language: TypeScript
filename: yandex.test.js
change_type: ModificationType.MODIFY
diff: @@ -1,7 +1,9 @@
 import "dotenv/config";
-import translate from "../../src";
+
+import t from "../../src";
 import mock from "../../test/mock.js";
 
+const translate = t.Translate({ engine: "yandex" });
 translate.keys.yandex = process.env.YANDEX_KEY || "xxx";
 
 describe("yandex mocked requests", () => {
@@ -18,20 +20,17 @@ describe("yandex mocked requests", () => {
 
   it("works with a simple request", async () => {
     mock.yandex("Hola de Yandex");
-    const spanish = await translate("Hello from Yandex", {
-      to: "es",
-      engine: "yandex",
-    });
+    const spanish = await translate("Hello from Yandex", { to: "es" });
     expect(spanish).toMatch(/Hola de Yandex/i);
   });
 
   it("can handle errors from the API", async () => {
-    const prom = translate("error", { to: "es", engine: "yandex" });
+    const prom = translate("error", { to: "es" });
     await expect(prom).rejects.toHaveProperty("message", "it fails");
   });
 
   it("can handle errors thrown by fetch()", async () => {
-    const prom = translate("throw", { to: "es", engine: "yandex" });
+    const prom = translate("throw", { to: "es" });
     await expect(prom).rejects.toHaveProperty("message", "also fails harder");
   });
 });
@@ -46,17 +45,17 @@ describe("yandex full requests", () => {
   }
 
   it("calls Yandex to translate to Japanese", async () => {
-    const opts = { to: "ja", engine: "yandex" };
+    const opts = { to: "ja" };
     expect(await translate("Hello world", opts)).toBe("こんにちは世界");
   });
 
   it("calls Yandex to translate to Spanish", async () => {
-    const opts = { to: "es", engine: "yandex" };
+    const opts = { to: "es" };
     expect(await translate("Hello world", opts)).toBe("Hola mundo");
   });
 
   it("requires a valid key", async () => {
-    const opts = { to: "ru", engine: "yandex", key: "abc" };
+    const opts = { to: "ru", key: "abc" };
     await expect(translate("Hello world", opts)).rejects.toThrow();
   });
 });

programming_language: TypeScript
filename: index.js
change_type: ModificationType.MODIFY
diff: @@ -1,13 +1,11 @@
 // translate.js
 // Translate text into different languages;
 
-// Load a language parser to allow for more flexibility in the language choice
-import languages from "./languages/index.js";
-
 // Cache the different translations to avoid resending requests
 import cache from "./cache.js";
-
 import engines from "./engines/index.js";
+// Load a language parser to allow for more flexibility in the language choice
+import languages from "./languages/index.js";
 
 // Main function
 const Translate = function (options = {}) {
@@ -19,9 +17,11 @@ const Translate = function (options = {}) {
     from: "en",
     to: "en",
     cache: undefined,
+    engine: "google",
+    key: undefined,
+    url: undefined,
     languages: languages,
     engines: engines,
-    engine: "google",
     keys: {},
   };
 
@@ -29,17 +29,20 @@ const Translate = function (options = {}) {
     // 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 };
+    const invalidKey = Object.keys(opts).find(
+      (k) => k !== "from" && k !== "to"
+    );
+    if (invalidKey) {
+      throw new Error(`Invalid option with the name '${invalidKey}'`);
+    }
     opts.text = text;
     opts.from = languages(opts.from || translate.from);
     opts.to = languages(opts.to || translate.to);
-    opts.cache = opts.cache || translate.cache;
-    opts.engines = opts.engines || {};
-    opts.engine = opts.engine || translate.engine;
-    opts.url = opts.url || translate.url;
-    opts.id =
-      opts.id ||
-      `${opts.url}:${opts.from}:${opts.to}:${opts.engine}:${opts.text}`;
-    opts.keys = opts.keys || translate.keys || {};
+    opts.cache = translate.cache;
+    opts.engine = translate.engine;
+    opts.url = translate.url;
+    opts.id = `${opts.url}:${opts.from}:${opts.to}:${opts.engine}:${opts.text}`;
+    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];
@@ -47,7 +50,7 @@ const Translate = function (options = {}) {
     opts.key = opts.key || translate.key || opts.keys[opts.engine];
 
     // Use the desired engine
-    const engine = opts.engines[opts.engine] || translate.engines[opts.engine];
+    const engine = translate.engines[opts.engine];
 
     // If it is cached return ASAP
     const cached = cache.get(opts.id);

programming_language: TypeScript
filename: translate.test.js
change_type: ModificationType.MODIFY
diff: @@ -1,8 +1,10 @@
 import "dotenv/config";
-import translate from "./";
-import mock from "../test/mock";
+
 import fs from "fs";
 
+import mock from "../test/mock";
+import translate from "./";
+
 describe("translate.js", () => {
   afterEach(() => mock.end());
 
@@ -47,12 +49,17 @@ describe("translate.js", () => {
     expect(jp).toBe("こんにちは世界");
   });
 
-  it("requires the key", async () => {
+  it("does not accept other keys", async () => {
     await expect(() =>
-      translate("hello", { engine: "yandex", key: false, to: "es" })
-    ).rejects.toMatchObject({
-      message: 'The engine "yandex" needs a key, please provide it',
-    });
+      translate("hello", { to: "es", blabla: "blu" })
+    ).rejects.toThrow("Invalid option with the name 'blabla'");
+  });
+
+  it("requires the key", async () => {
+    const yandex = translate.Translate({ engine: "yandex", key: false });
+    await expect(() => yandex("hello", "es")).rejects.toThrow(
+      'The engine "yandex" needs a key, please provide it'
+    );
   });
 });
 
@@ -72,11 +79,14 @@ describe("Independent", () => {
     expect(inst.from).toBe("en");
   });
 
-  it.skip("fixed the bug #5", async () => {
-    // translate.keys = { google: 'abc' };
-    const options = { to: "ja", keys: { yandex: "def" }, engine: "google" };
-
-    // This will wrongly ignore the key for "google"
-    expect(await translate("Hello world", options)).toBe("こんにちは世界");
+  it("cannot poison the cache", async () => {
+    const options = {
+      id: "undefined:en:nl:google:I love you",
+      from: "en",
+      to: "ja",
+    };
+    await expect(() => translate("hello", options)).rejects.toThrow(
+      "Invalid option with the name 'id'"
+    );
   });
 });

programming_language: TypeScript
filename: index.types.ts
change_type: ModificationType.MODIFY
diff: @@ -1,4 +1,4 @@
-import translate from "../";
+import translate from "..";
 
 // Don't even need to run it to test the types!
 async function tests() {

programming_language: JavaScript
filename: mock.js
change_type: ModificationType.MODIFY
diff: @@ -23,7 +23,7 @@ mock.libre = (content, opts) => {
 };
 
 mock.yandex = (content, opts) => {
-  mock(/yandex.*&lang=[a-z]*\-es/, { code: 200, text: [content] }, opts);
+  mock(/yandex.*&lang=[a-z]*-es/, { code: 200, text: [content] }, opts);
 };
 
 mock.end = () => {

programming_language: TypeScript
repo_url: https://github.com/franciscop/translate
commit_details: repo_url: https://github.com/franciscop/translate
author: Francisco
author_date: 2024-03-21 12:22:10+09:00
msg: Made the usage options much simpler so that there's no chance of writing runtime vulnerabilities. Favored instances. Fixed types
repository: repo_url: https://github.com/franciscop/translate
repo_name: franciscop/translate
description: :sa: Translate text on node.js and the browser with promises
repo_language: JavaScript
owner: franciscop
file_changes: filename: README.md
change_type: ModificationType.MODIFY
diff: @@ -32,7 +32,7 @@ Alternatively for the browser you can use [Jsdelivr **CDN**](https://www.jsdeliv
 ```html
 <script
   type="module"
-  src="https://cdn.jsdelivr.net/npm/translate@2/index.min.js"
+  src="https://cdn.jsdelivr.net/npm/translate/index.min.js"
 ></script>
 ```
 
@@ -43,7 +43,7 @@ translate.engine = "deepl"; // "google", "yandex", "libre", "deepl"
 translate.key = process.env.DEEPL_KEY;
 ```
 
-Then you can finally use it. Putting it all together:
+Then you can use it. Putting it all together:
 
 ```js
 // Omit this line if loading form a CDN
@@ -59,18 +59,23 @@ console.log(text);
 
 ## Options
 
-The first parameter is the **string** that you want to translate. Right now only a single string of text is accepted.
+Here is a list of all the options. They all can be set on the root instance like `translate[key] = value` and when creating a new instance `const t = Translate({ ... })`. The function has this signature:
 
-The second parameter is the options. It accepts either a `String` of the language to translate **to** or a simple `Object` with the keys `to` and `from`. However, in total there are more options, so here is a list of all of them:
+```js
+translate(text: string, to: string): Promise<string>
+translate(text: string, { to: string, from: string }): Promise<string>
+```
+
+These are all of the options:
 
 - **`to`**: the string of the language to translate to. It can be in any of the two ISO 639 (1 or 2) or the full name in English like `Spanish`. Defaults to **en**.
 - **`from`**: the string of the language to translate to. It can be in any of the two ISO 639 (1 or 2) or the full name in English like `Spanish`. Also defaults to **en**.
-- **`cache`** [instance]: a `Number` with the milliseconds that each translation should be cached. Leave it undefined to cache it indefinitely (until a server/browser restart).
-- **`engine`** [instance]: a `String` containing the name of the engine to use for translation. Right now it defaults to `google`. Read more [in the engine section](#engines).
-- **`key`** [instance]: the API Key for the engine of your choice. Read more [in the engine section](#engines).
-- **`url`** [instance]: only available for those engines that you can install on your own server (like Libretranslate), allows you to specify a custom endpoint for the translations. [See this issue](https://github.com/franciscop/translate/issues/26#issuecomment-845038821) for more info.
+- **`cache`** \*: a `Number` with the milliseconds that each translation should be cached. Leave it undefined to cache it indefinitely (until a server/browser restart).
+- **`engine`** \* (& `engines`): a `String` containing the name of the engine to use for translation. Right now it defaults to `google`. Read more [in the engine section](#engines).
+- **`key`** \* (& `keys`): the API Key for the engine of your choice. Read more [in the engine section](#engines).
+- **`url`** \*: only available for those engines that you can install on your own server (like Libretranslate), allows you to specify a custom endpoint for the translations. [See this issue](https://github.com/franciscop/translate/issues/26#issuecomment-845038821) for more info.
 
-> The options marked as [instance] can only be set to the root `translate.cache = 1000` or when creating a new instance `const myDeepL = translate.Translate()`
+> \* The options marked as can only be set to the root `translate.cache = 1000` or when creating a new instance `const myDeepL = translate.Translate()`, but not as the second parameter.
 
 Examples:
 
@@ -80,27 +85,43 @@ const foo = await translate("Hello world", "es");
 
 // Same as this:
 const bar = await translate("Hello world", { to: "es" });
-
-// INVALID:
-const bar = await translate("Hello world", { to: "es", engine: "google" });
 ```
 
-> On both `to` and `from` defaulting to `en`: while I _am_ Spanish and was quite tempted to set this as one of those, English is the main language of the Internet and the main secondary language for those who have a different native language. This is why most of the translations will happen either to or from English.
-
 ### Default options
 
-You can change the default options for anything by calling the root library and the option name:
+You can change the options in 3 ways. First, for the simple `from` and `to` options, set them as the second argument:
+
+```js
+import translate from "translate";
+
+translate("Hello world", "es");
+translate("Hola mundo", { from: "en", to: "es" });
+```
+
+You can also change the default options straight on the base instance, which should be common for the shared options such as the API key, engine, etc:
 
 ```js
+import translate from "translate";
+
+// Configure it, setting the "from" to "Spanish" as default!
 translate.from = "es";
 translate.engine = "deepl";
+translate.key = process.env.DEEPL_KEY;
+
+// Use it
 await translate("Hola mundo", "ja");
 ```
 
-You can also create a new instance with different default options:
+Finally, you can combine multiple instances of Translate, which is useful to have different caches, or use different engines at the same time:
 
 ```js
-const myLib = translate.Translate({ engine: 'deepl', from: 'es', ... });
+// Import the constructor
+import { Translate } from 'translate';
+
+// Create an instance
+const myLib = Translate({ engine: 'deepl', from: 'es', ... });
+
+// Use it
 await myLib("Hola mundo", "ja" );
 ```
 
@@ -118,7 +139,7 @@ Several translating engines are available to translate your text:
 Once you get the API key and if you are only going to be using one engine (very likely), we recommend setting this globally for your whole project:
 
 ```js
-// ... include translate
+import translate from "translate";
 
 translate.engine = "deepl";
 translate.key = "YOUR-KEY-HERE";
@@ -129,7 +150,7 @@ translate.key = "YOUR-KEY-HERE";
 If you are in Node.js, this likely comes from an environment variable:
 
 ```js
-// ... include translate
+import translate from "translate";
 
 translate.engine = "deepl";
 translate.key = process.env.TRANSLATE_KEY;
@@ -140,18 +161,11 @@ translate.key = process.env.TRANSLATE_KEY;
 You can create different instances if you want to combine different engines:
 
 ```js
-const gTranslate = translate.Translate({
-  engine: "google",
-  key: "YOUR-KEY-HERE",
-});
-const dTranslate = translate.Translate({
-  engine: "deepl",
-  key: "YOUR-KEY-HERE",
-});
-const lTranslate = translate.Translate({
-  engine: "libre",
-  key: "YOUR-KEY-HERE",
-});
+import { Translate } from "translate";
+
+const gTranslate = Translate({ engine: "google", key: "..." });
+const dTranslate = Translate({ engine: "deepl", key: "..." });
+const lTranslate = Translate({ engine: "libre", key: "..." });
 ```
 
 Specifically in Libretranslate, you can also add a `url` parameter if you install it on your own server:
@@ -162,7 +176,7 @@ translate.key = process.env.TRANSLATE_KEY;
 
 // or
 
-const lTranslate = translate.Translate({
+const lTranslate = Translate({
   engine: "libre",
   url: "...",
   key: "YOUR-KEY-HERE",
@@ -171,7 +185,7 @@ const lTranslate = translate.Translate({
 
 ## Promises
 
-Working with Promises and specially with [async/await](https://ponyfoo.com/articles/understanding-javascript-async-await) reduces [Callback Hell](http://callbackhell.com/). To see it in action, first you'll need an `async` function. Then put your `await` calls inside:
+This library uses promises which need to be awaited for the translated result:
 
 ```js
 // Browser; jQuery for demonstration purposes

programming_language: Markdown
filename: index.d.ts
change_type: ModificationType.MODIFY
diff: @@ -9,22 +9,45 @@ type Engine = {
   parse: (res: { [key: string]: any }) => string;
 };
 
-type Options =
-  | string
-  | {
-      // Basic types expected for everyday use
-      from?: string;
-      to?: string;
-      key?: string;
-      engine?: "google" | "deepl" | "libre" | "yandex";
-
-      // More advanced types when tweaking the library
-      keys?: { [name: string]: string };
-      cache?: number;
-      engines?: { [name: string]: Engine };
-    };
-
-export default function Translate(
-  text: string,
-  options?: Options
-): Promise<string>;
+type Options = string | { from?: string; to?: string };
+
+declare const translate: {
+  (text: string, opts?: Options): Promise<string>;
+
+  key?: string;
+  engine?: "google" | "deepl" | "libre" | "yandex";
+
+  // More advanced types when tweaking the library
+  keys?: { [name: string]: string };
+  cache?: number;
+  engines?: { [name: string]: Engine };
+
+  Translate({}: {
+    from?: string;
+    to?: string;
+
+    key?: string;
+    engine?: "google" | "deepl" | "libre" | "yandex";
+
+    // More advanced types when tweaking the library
+    keys?: { [name: string]: string };
+    cache?: number;
+    engines?: { [name: string]: Engine };
+  }): typeof translate;
+};
+
+declare const Translate: ({}: {
+  from?: string;
+  to?: string;
+
+  key?: string;
+  engine?: "google" | "deepl" | "libre" | "yandex";
+
+  // More advanced types when tweaking the library
+  keys?: { [name: string]: string };
+  cache?: number;
+  engines?: { [name: string]: Engine };
+}) => typeof translate;
+
+export default translate;
+export { Translate };

programming_language: TypeScript
filename: index.min.js
change_type: ModificationType.MODIFY
diff: @@ -1 +1 @@
-function Cache(){var e=Object.create(null);function a(a){delete e[a]}this.set=function(n,i,r){if(void 0!==r&&("number"!=typeof r||isNaN(r)||r<=0))throw new Error("Cache timeout must be a positive number");var t=e[n];t&&clearTimeout(t.timeout);var o={value:i,expire:r+Date.now()};return isNaN(o.expire)||(o.timeout=setTimeout((()=>a(n)),r)),e[n]=o,i},this.del=function(n){var i=!0,r=e[n];return r?(clearTimeout(r.timeout),!isNaN(r.expire)&&r.expire<Date.now()&&(i=!1)):i=!1,i&&a(n),i},this.clear=function(){for(var a in e)clearTimeout(e[a].timeout);e=Object.create(null)},this.get=function(a){var n=e[a];if(void 0!==n){if(isNaN(n.expire)||n.expire>=Date.now())return n.value;delete e[a]}return null}}const exp$1=new Cache;exp$1.Cache=Cache;const base="https://translate.googleapis.com/translate_a/single";var google={fetch:({key:e,from:a,to:n,text:i})=>[`${base}?client=gtx&sl=${a}&tl=${n}&dt=t&q=${encodeURI(i)}`],parse:e=>e.json().then((e=>{if(!(e=e&&e[0]&&e[0][0]&&e[0].map((e=>e[0])).join("")))throw new Error("Translation not found");return e}))},yandex={needkey:!0,fetch:({key:e,from:a,to:n,text:i})=>[`https://translate.yandex.net/api/v1.5/tr.json/translate?key=${e}&lang=${a}-${n}&text=${encodeURIComponent(i)}`,{method:"POST",body:""}],parse:e=>e.json().then((e=>{if(200!==e.code)throw new Error(e.message);return e.text[0]}))};const libreUrl="https://libretranslate.com/translate";var libre={needkey:!1,fetch:({url:e=libreUrl,key:a,from:n,to:i,text:r})=>[e,{method:"POST",body:JSON.stringify({q:r,source:n,target:i,api_key:a}),headers:{"Content-Type":"application/json"}}],parse:e=>e.json().then((e=>{if(!e)throw new Error("No response found");if(e.error)throw new Error(e.error);if(!e.translatedText)throw new Error("No response found");return e.translatedText}))},deepl={needkey:!0,fetch:({key:e,from:a,to:n,text:i})=>[`https://api${e.endsWith(":fx")?"-free":""}.deepl.com/v2/translate?auth_key=${e}&source_lang=${a}&target_lang=${n}&text=${i=encodeURIComponent(i)}`,{method:"POST",body:""}],parse:async e=>{if(!e.ok){if(403===e.status)throw new Error("Auth Error, please review the key for DeepL");throw new Error(`Error ${e.status}`)}return e.json().then((e=>e.translations[0].text))}},engines={google:google,yandex:yandex,libre:libre,deepl:deepl},iso={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"},names={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"};const isoKeys=Object.values(iso).sort();var languages=e=>{if("string"!=typeof e)throw new Error('The "language" must be a string, received '+typeof e);if(e.length>100)throw new Error(`The "language" is too long at ${e.length} characters`);if(e=e.toLowerCase(),e=names[e]||iso[e]||e,!isoKeys.includes(e))throw new Error(`The language "${e}" is not part of the ISO 639-1`);return e};const Translate=function(e={}){if(!(this instanceof Translate))return new Translate(e);const a={from:"en",to:"en",cache:void 0,engine:"google",key:void 0,url:void 0,languages:languages,engines:engines,keys:{}},n=async(e,a={})=>{"string"==typeof a&&(a={to:a});const i=Object.keys(a).find((e=>"from"!==e&&"to"!==e));if(i)throw new Error(`Invalid option with the name '${i}'`);a.text=e,a.from=languages(a.from||n.from),a.to=languages(a.to||n.to),a.cache=n.cache,a.engine=n.engine,a.url=n.url,a.id=`${a.url}:${a.from}:${a.to}:${a.engine}:${a.text}`,a.keys=n.keys||{};for(let e in n.keys)a.keys[e]=a.keys[e]||n.keys[e];a.key=a.key||n.key||a.keys[a.engine];const r=n.engines[a.engine],t=exp$1.get(a.id);if(t)return Promise.resolve(t);if(a.to===a.from)return Promise.resolve(a.text);if(r.needkey&&!a.key)throw new Error(`The engine "${a.engine}" needs a key, please provide it`);const o=r.fetch(a);return fetch(...o).then(r.parse).then((e=>exp$1.set(a.id,e,a.cache)))};for(let i in a)n[i]=void 0===e[i]?a[i]:e[i];return n},exp=new Translate;exp.Translate=Translate;export{exp as default};
\ No newline at end of file
+function Cache(){var e=Object.create(null);function a(a){delete e[a]}this.set=function(n,i,r){if(void 0!==r&&("number"!=typeof r||isNaN(r)||r<=0))throw new Error("Cache timeout must be a positive number");var t=e[n];t&&clearTimeout(t.timeout);var o={value:i,expire:r+Date.now()};return isNaN(o.expire)||(o.timeout=setTimeout((()=>a(n)),r)),e[n]=o,i},this.del=function(n){var i=!0,r=e[n];return r?(clearTimeout(r.timeout),!isNaN(r.expire)&&r.expire<Date.now()&&(i=!1)):i=!1,i&&a(n),i},this.clear=function(){for(var a in e)clearTimeout(e[a].timeout);e=Object.create(null)},this.get=function(a){var n=e[a];if(void 0!==n){if(isNaN(n.expire)||n.expire>=Date.now())return n.value;delete e[a]}return null}}const exp$1=new Cache;exp$1.Cache=Cache;const base="https://translate.googleapis.com/translate_a/single";var google={fetch:({key:e,from:a,to:n,text:i})=>[`${base}?client=gtx&sl=${a}&tl=${n}&dt=t&q=${encodeURI(i)}`],parse:e=>e.json().then((e=>{if(!(e=e&&e[0]&&e[0][0]&&e[0].map((e=>e[0])).join("")))throw new Error("Translation not found");return e}))},yandex={needkey:!0,fetch:({key:e,from:a,to:n,text:i})=>[`https://translate.yandex.net/api/v1.5/tr.json/translate?key=${e}&lang=${a}-${n}&text=${encodeURIComponent(i)}`,{method:"POST",body:""}],parse:e=>e.json().then((e=>{if(200!==e.code)throw new Error(e.message);return e.text[0]}))};const libreUrl="https://libretranslate.com/translate";var libre={needkey:!1,fetch:({url:e=libreUrl,key:a,from:n,to:i,text:r})=>[e,{method:"POST",body:JSON.stringify({q:r,source:n,target:i,api_key:a}),headers:{"Content-Type":"application/json"}}],parse:e=>e.json().then((e=>{if(!e)throw new Error("No response found");if(e.error)throw new Error(e.error);if(!e.translatedText)throw new Error("No response found");return e.translatedText}))},deepl={needkey:!0,fetch:({key:e,from:a,to:n,text:i})=>[`https://api${e.endsWith(":fx")?"-free":""}.deepl.com/v2/translate?auth_key=${e}&source_lang=${a}&target_lang=${n}&text=${i=encodeURIComponent(i)}`,{method:"POST",body:""}],parse:async e=>{if(!e.ok){if(403===e.status)throw new Error("Auth Error, please review the key for DeepL");throw new Error(`Error ${e.status}`)}return e.json().then((e=>e.translations[0].text))}},engines={google:google,yandex:yandex,libre:libre,deepl:deepl},iso={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"},names={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"};const isoKeys=Object.values(iso).sort();var languages=e=>{if("string"!=typeof e)throw new Error('The "language" must be a string, received '+typeof e);if(e.length>100)throw new Error(`The "language" is too long at ${e.length} characters`);if(e=e.toLowerCase(),e=names[e]||iso[e]||e,!isoKeys.includes(e))throw new Error(`The language "${e}" is not part of the ISO 639-1`);return e};const Translate=function(e={}){if(!(this instanceof Translate))return new Translate(e);const a={from:"en",to:"en",cache:void 0,engine:"google",key:void 0,url:void 0,languages:languages,engines:engines,keys:{}},n=async(e,a={})=>{"string"==typeof a&&(a={to:a});const i=Object.keys(a).find((e=>"from"!==e&&"to"!==e));if(i)throw new Error(`Invalid option with the name '${i}'`);a.text=e,a.from=languages(a.from||n.from),a.to=languages(a.to||n.to),a.cache=n.cache,a.engine=n.engine,a.url=n.url,a.id=`${a.url}:${a.from}:${a.to}:${a.engine}:${a.text}`,a.keys=n.keys||{};for(let e in n.keys)a.keys[e]=a.keys[e]||n.keys[e];a.key=a.key||n.key||a.keys[a.engine];const r=n.engines[a.engine],t=exp$1.get(a.id);if(t)return Promise.resolve(t);if(a.to===a.from)return Promise.resolve(a.text);if(r.needkey&&!a.key)throw new Error(`The engine "${a.engine}" needs a key, please provide it`);const o=r.fetch(a);return fetch(...o).then(r.parse).then((e=>exp$1.set(a.id,e,a.cache)))};for(let i in a)n[i]=void 0===e[i]?a[i]:e[i];return n},exp=new Translate;exp.Translate=Translate;export{Translate,exp as default};
\ No newline at end of file

programming_language: JavaScript
filename: index.js
change_type: ModificationType.MODIFY
diff: @@ -84,3 +84,4 @@ const Translate = function (options = {}) {
 const exp = new Translate();
 exp.Translate = Translate;
 export default exp;
+export { Translate };

programming_language: TypeScript
filename: index.types.ts
change_type: ModificationType.MODIFY
diff: @@ -1,18 +1,37 @@
-import translate from "..";
+import translate, { Translate } from "..";
 
 // Don't even need to run it to test the types!
 async function tests() {
   translate("hello world", "es");
-  // translate("hello world", { abc: "def" });
   translate("hello world", { from: "en" });
   translate("hello world", { to: "es" });
-  translate("hello world", { key: "abc" }); // <= not a real key
-  translate("hello world", { engine: "google" });
 
-  translate("hello world", { keys: { google: "abc" } }); // <= not a real key
-  translate("hello world", { cache: undefined });
-  translate("hello world", { cache: 1000 });
-  translate("hello world", {
+  // New stuff
+  translate.key = "abc"; // <= not a real key
+  translate.engine = "google";
+
+  translate.keys = { google: "abc" }; // <= not a real key
+  translate.cache = undefined;
+  translate.cache = 1000;
+  translate.engines = {
+    google: {
+      needkey: false,
+      fetch: () => ["abc", {}],
+      parse: (req) => "hello",
+    },
+  };
+
+  // Instance
+  const t = translate.Translate({
+    from: "en",
+    to: "es",
+
+    // New stuff
+    key: "abc", // <= not a real key
+    engine: "google",
+
+    keys: { google: "abc" }, // <= not a real key
+    cache: 1000,
     engines: {
       google: {
         needkey: false,
@@ -21,9 +40,62 @@ async function tests() {
       },
     },
   });
+  t("hello world", "es");
+  t("hello world", { from: "en" });
+  t("hello world", { to: "es" });
+
+  // New stuff
+  t.key = "abc"; // <= not a real key
+  t.engine = "google";
 
-  const text: string = await translate("hello");
-  console.log(text);
+  t.keys = { google: "abc" }; // <= not a real key
+  t.cache = undefined;
+  t.cache = 1000;
+  t.engines = {
+    google: {
+      needkey: false,
+      fetch: () => ["abc", {}],
+      parse: (req) => "hello",
+    },
+  };
+
+  // Another instance
+  const t2 = Translate({
+    from: "en",
+    to: "es",
+
+    // New stuff
+    key: "abc", // <= not a real key
+    engine: "google",
+
+    keys: { google: "abc" }, // <= not a real key
+    cache: 1000,
+    engines: {
+      google: {
+        needkey: false,
+        fetch: () => ["abc", {}],
+        parse: (req) => "hello",
+      },
+    },
+  });
+  t2("hello world", "es");
+  t2("hello world", { from: "en" });
+  t2("hello world", { to: "es" });
+
+  // New stuff
+  t2.key = "abc"; // <= not a real key
+  t2.engine = "google";
+
+  t2.keys = { google: "abc" }; // <= not a real key
+  t2.cache = undefined;
+  t2.cache = 1000;
+  t2.engines = {
+    google: {
+      needkey: false,
+      fetch: () => ["abc", {}],
+      parse: (req) => "hello",
+    },
+  };
 }
 
 // To make TS happy that we "use" the function

programming_language: JavaScript