From cc1ba03078102f83e0503a96f1a081489bb865d3 Mon Sep 17 00:00:00 2001 From: Francisco Date: Thu, 21 Mar 2024 12:22:10 +0900 Subject: [PATCH] Made the usage options much simpler so that there's no chance of writing runtime vulnerabilities. Favored instances. Fixed types --- README.md | 80 +++++++++++++++++++++++---------------- index.d.ts | 61 ++++++++++++++++++++---------- index.min.js | 2 +- src/index.js | 1 + test/index.types.ts | 92 ++++++++++++++++++++++++++++++++++++++++----- 5 files changed, 173 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index ea021cd..76409a9 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Alternatively for the browser you can use [Jsdelivr **CDN**](https://www.jsdeliv ```html ``` @@ -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 +translate(text: string, { to: string, from: string }): Promise +``` + +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 diff --git a/index.d.ts b/index.d.ts index 1a025bb..377a9c4 100644 --- a/index.d.ts +++ b/index.d.ts @@ -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; +type Options = string | { from?: string; to?: string }; + +declare const translate: { + (text: string, opts?: Options): Promise; + + 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 }; diff --git a/index.min.js b/index.min.js index b9af809..e94ab38 100644 --- a/index.min.js +++ b/index.min.js @@ -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())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())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 diff --git a/src/index.js b/src/index.js index 5070b27..a6c526e 100644 --- a/src/index.js +++ b/src/index.js @@ -84,3 +84,4 @@ const Translate = function (options = {}) { const exp = new Translate(); exp.Translate = Translate; export default exp; +export { Translate }; diff --git a/test/index.types.ts b/test/index.types.ts index 2729886..8e9828f 100644 --- a/test/index.types.ts +++ b/test/index.types.ts @@ -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