Repository: manusa/electronim
Pull request #2 (2020-01-01 17:03:24)

Spell-checker with multiple dictionaries and non-blocking UI


Patch:
diff --git a/README.md b/README.md
index 94c6088..96eca49 100644
--- a/README.md
+++ b/README.md
@@ -48,8 +48,9 @@ spell checking for free.
 ## Acknowledgements
 
 - [Electron](https://electronjs.org/)
+- [Bulma](https://github.com/jgthms/bulma)
 - [Chrome tabs](https://github.com/adamschwartz/chrome-tabs#readme)
 - [Draggabilly](https://github.com/desandro/draggabilly)
-- [Simple spell checker](https://github.com/jfmdev/simple-spellchecker)
-- [Bulma](https://github.com/jgthms/bulma)
 - [Font Awesome Free](https://github.com/FortAwesome/Font-Awesome)
+- [Nodehun](https://github.com/Wulf/nodehun/)
+- [Woorm's dictionary repo](https://github.com/wooorm/dictionaries)
diff --git a/package-lock.json b/package-lock.json
index 0ef928d..de9d523 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -581,11 +581,6 @@
       "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==",
       "dev": true
     },
-    "adm-zip": {
-      "version": "0.4.13",
-      "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.13.tgz",
-      "integrity": "sha512-fERNJX8sOXfel6qCBCMPvZLzENBEhZTzKqg6vrOW5pvoEaQuJhRU4ndTAh6lHOxn1I6jnz2NHra56ZODM751uw=="
-    },
     "ajv": {
       "version": "6.10.2",
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
@@ -806,7 +801,8 @@
     "balanced-match": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
-      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+      "dev": true
     },
     "base": {
       "version": "0.11.2",
@@ -878,11 +874,6 @@
         "tweetnacl": "^0.14.3"
       }
     },
-    "binarysearch": {
-      "version": "0.2.4",
-      "resolved": "https://registry.npmjs.org/binarysearch/-/binarysearch-0.2.4.tgz",
-      "integrity": "sha1-Ru8+A/1FKekyhmLmjkAyjnoL8qw="
-    },
     "bindings": {
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
@@ -909,6 +900,7 @@
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
       "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
       "requires": {
         "balanced-match": "^1.0.0",
         "concat-map": "0.0.1"
@@ -1268,7 +1260,8 @@
     "concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
-      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+      "dev": true
     },
     "concat-stream": {
       "version": "1.6.2",
@@ -1379,11 +1372,6 @@
       "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=",
       "dev": true
     },
-    "damerau-levenshtein": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.5.tgz",
-      "integrity": "sha512-CBCRqFnpu715iPmw1KrdOrzRqbdFwQTwAWyyyYS42+iAgHCuXZ+/TdMgQkUENPomxEz9z1BEzuQU2Xw0kUuAgA=="
-    },
     "dashdash": {
       "version": "1.14.1",
       "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -1523,6 +1511,101 @@
       "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==",
       "optional": true
     },
+    "dictionary-ca": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/dictionary-ca/-/dictionary-ca-2.1.1.tgz",
+      "integrity": "sha512-rSIwQqMrPAV1VgzG+tHQ7KrGRPf3qj/yuzBnkf1u8YnC7L6A8s8CRBqxiCQc/oH3QXg+cwhhRkxh4t0Kcu6K7A=="
+    },
+    "dictionary-ca-valencia": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/dictionary-ca-valencia/-/dictionary-ca-valencia-2.1.1.tgz",
+      "integrity": "sha512-k0YIKKyXAVo8ijCQwOq9Sd0DzffZTcC/xm5uYiWgBxUVDeSSecLGl8XAdDtZFUiAwellX8ZEY1oBFWVZ5CRdTQ=="
+    },
+    "dictionary-de": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/dictionary-de/-/dictionary-de-2.0.2.tgz",
+      "integrity": "sha512-NIhhHUw2pAk/zkHt9kKe1CDTwJNTGcPYAMQuyEGdHSrShAGSx52egqc4n/xG2Xfy77DI1Yt7b1gUbeBR6mQqlA=="
+    },
+    "dictionary-en-gb": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/dictionary-en-gb/-/dictionary-en-gb-2.1.1.tgz",
+      "integrity": "sha512-G9jAkpXtSpkdllzojfcVAWy0KBCR/MzyYHA9p2So+fJXAN2yrR0LXacdcRweswiV3aNnaf+krTk0M4I+d5RKMw=="
+    },
+    "dictionary-en-us": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/dictionary-en-us/-/dictionary-en-us-2.1.1.tgz",
+      "integrity": "sha512-3182Q3ede8TEm6FthZ2u8FppaayB+D43wy5cnOSweZwg9vSmmUCjCA3zxmMpZLw0nIAILGLR0j+06o2l9aaCzA=="
+    },
+    "dictionary-es": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/dictionary-es/-/dictionary-es-1.2.3.tgz",
+      "integrity": "sha512-G4Tj+xHXWY0P+J3i2LTvrZMpmA9teYrtsDPYbZIsWN1JEFK9f66kT+5PwLlEkkGby7kTf5d+g1Sc7hX74IkyCw=="
+    },
+    "dictionary-eu": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/dictionary-eu/-/dictionary-eu-3.1.1.tgz",
+      "integrity": "sha512-KB3rJdU7cpJoYysqLyQVl6LyNJS6MsjDIW8NfIS1mIyBbQPttaV2cV2CJshxXlD8ZzvzBtF7Iv1jr2Ea93NnLQ=="
+    },
+    "dictionary-fr": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/dictionary-fr/-/dictionary-fr-2.3.2.tgz",
+      "integrity": "sha512-j1e3qIkoLsGnsV/Vn74ILh/1U7+tO1K6z70ZVk/rrGatYMRmlvT3HgSQ6W/jEyXKkSE2EeeDwPZpT6U6WiTJJw=="
+    },
+    "dictionary-it": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/dictionary-it/-/dictionary-it-1.3.1.tgz",
+      "integrity": "sha512-EE6jy0F7hJewKWFQ0ITx9afHjIscP52+uKWtxH6yfRI3aCFzZJC1dhC1F1uplVTrTgSb6sbGmM7jzydY1gIDxg=="
+    },
+    "dictionary-ka": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/dictionary-ka/-/dictionary-ka-1.0.0.tgz",
+      "integrity": "sha512-NLftElBRlPPpVbF7aqQRf91qOOEuniwJvWuO3oR1qq4Wuxu6WAcdnld+xYCeAJjZlIkHGctkqTVzsTmu10UOpw=="
+    },
+    "dictionary-lt": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/dictionary-lt/-/dictionary-lt-1.0.1.tgz",
+      "integrity": "sha512-iW1n/f7i7culXxKM0XASyKp4VW0lRIu2fdcHjIGUaydXIwHT4OxqWruQSfYabqh4e4MKV/2F6bUvOek9nIeO2w=="
+    },
+    "dictionary-nl": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/dictionary-nl/-/dictionary-nl-1.3.2.tgz",
+      "integrity": "sha512-UpQ/S2UNvNcXqcgKtltyJIoWeZouUap80fQqsp250JVOmvadxXM1/UG2pvglU//F9PZCSAynFKjYh1JnbDjUNA=="
+    },
+    "dictionary-pl": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/dictionary-pl/-/dictionary-pl-1.2.2.tgz",
+      "integrity": "sha512-qvYuL1I2Mtw69JRWu+FvBvb3fKZ6bXfOn9TiO2nDlogJqjTMmhVxB4KX0UQjQtKOA47nCPNdEzXuvFjzgGIHoA=="
+    },
+    "dictionary-pt": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/dictionary-pt/-/dictionary-pt-1.3.1.tgz",
+      "integrity": "sha512-mczX359Cg9ywhjdgcRbRkCZq0QYynsaH47+6uix0YZEpxWeMAmVsavb0M4gv7VEP33SFBXWYsBV6DFViLUKUMw=="
+    },
+    "dictionary-pt-br": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/dictionary-pt-br/-/dictionary-pt-br-1.2.2.tgz",
+      "integrity": "sha512-cnyjBtybWGyKeh7RTNlpohqVrGfrquSvIRS+Ntn0+xwigD/mdalekIeqlDbbhGn6Y++wOrAAGw6L9mlruxNGgQ=="
+    },
+    "dictionary-ru": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/dictionary-ru/-/dictionary-ru-1.2.2.tgz",
+      "integrity": "sha512-IBAH6Qi/xjiVMViCiwdvmx05vRz/Y7I/UyScGkhzqeqwX3O5NdZtyM2jXru1AjF8uhAR5fhrpj87oVvkWGgziQ=="
+    },
+    "dictionary-sv": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/dictionary-sv/-/dictionary-sv-2.0.2.tgz",
+      "integrity": "sha512-rvilg7iJGpLzfoH4AToVGhKBJflFYKcbJKKd4qMWDuZPH22L7rXcidcJ/kuREkzvL7Va56drARRgEOVhWjSbRw=="
+    },
+    "dictionary-tr": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/dictionary-tr/-/dictionary-tr-1.3.1.tgz",
+      "integrity": "sha512-6cWBXpkUfMOSqi0XtwdfECLeRNdA9B/3IhPsVvWLPZngGcL2Ms/yGkovEi1iPKmrmUN/fVZTRdIo60g75tLmkw=="
+    },
+    "dictionary-uk": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/dictionary-uk/-/dictionary-uk-2.1.1.tgz",
+      "integrity": "sha512-GK7W8ZF3FRwqwpRy/mhwbVarR7/EJyaUI1/dlLNYXdeKlhR/Ead0gZxz8KN/8QQQZZrhI6OWTAkTASnStKDKKw=="
+    },
     "diff-sequences": {
       "version": "24.9.0",
       "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz",
@@ -2313,7 +2396,8 @@
     "fs.realpath": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+      "dev": true
     },
     "fsevents": {
       "version": "1.2.11",
@@ -3085,6 +3169,7 @@
       "version": "7.1.6",
       "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
       "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+      "dev": true,
       "requires": {
         "fs.realpath": "^1.0.0",
         "inflight": "^1.0.4",
@@ -3334,6 +3419,7 @@
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
       "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "dev": true,
       "requires": {
         "once": "^1.3.0",
         "wrappy": "1"
@@ -3570,11 +3656,6 @@
       "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
       "dev": true
     },
-    "is-utf8": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
-      "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI="
-    },
     "is-windows": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
@@ -4463,6 +4544,7 @@
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
       "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "dev": true,
       "requires": {
         "brace-expansion": "^1.1.7"
       }
@@ -4556,6 +4638,11 @@
       "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
       "dev": true
     },
+    "node-addon-api": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz",
+      "integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA=="
+    },
     "node-int64": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@@ -4589,6 +4676,14 @@
         }
       }
     },
+    "nodehun": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/nodehun/-/nodehun-3.0.1.tgz",
+      "integrity": "sha512-JiS+6ky3uPAQ9xDzb1nq54shrTzGZJmB1J6sZoevzrp2ENy2tXqMpfV+9axNq5KsDTnX5s7rG+Jeyt81/CputA==",
+      "requires": {
+        "node-addon-api": "*"
+      }
+    },
     "normalize-package-data": {
       "version": "2.5.0",
       "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
@@ -4642,6 +4737,21 @@
         "path-key": "^2.0.0"
       }
     },
+    "nspell": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/nspell/-/nspell-2.1.2.tgz",
+      "integrity": "sha512-j79L4A5aJSiswMUJ/6BW8+7ytgBUVce5BJX6xq8LtvKQpU96CMB340/yMeREH9U+gB/8dk4ctTn62DLiPMAXsA==",
+      "requires": {
+        "is-buffer": "^2.0.0"
+      },
+      "dependencies": {
+        "is-buffer": {
+          "version": "2.0.4",
+          "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
+          "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A=="
+        }
+      }
+    },
     "nwsapi": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz",
@@ -4882,7 +4992,8 @@
     "path-is-absolute": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
-      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+      "dev": true
     },
     "path-key": {
       "version": "2.0.1",
@@ -5281,6 +5392,7 @@
       "version": "2.6.3",
       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
       "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+      "dev": true,
       "requires": {
         "glob": "^7.1.3"
       }
@@ -5458,36 +5570,6 @@
       "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
       "dev": true
     },
-    "simple-spellchecker": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/simple-spellchecker/-/simple-spellchecker-1.0.0.tgz",
-      "integrity": "sha512-gOQdOyXV/SoQJ2MfNj2eEbumnDIMWa6ET01xvK+fbHfZMjMC08ZNGNqa6WZmtoLxzK0l3IRkxALRaOo9Pg0jPg==",
-      "requires": {
-        "adm-zip": "^0.4.13",
-        "binarysearch": "^0.2.4",
-        "damerau-levenshtein": "^1.0.5",
-        "strip-bom": "^2.0.0",
-        "tmp": "^0.1.0"
-      },
-      "dependencies": {
-        "strip-bom": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
-          "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
-          "requires": {
-            "is-utf8": "^0.2.0"
-          }
-        },
-        "tmp": {
-          "version": "0.1.0",
-          "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz",
-          "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==",
-          "requires": {
-            "rimraf": "^2.6.3"
-          }
-        }
-      }
-    },
     "sisteransi": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.4.tgz",
diff --git a/package.json b/package.json
index 81fc2b4..52ce586 100644
--- a/package.json
+++ b/package.json
@@ -52,8 +52,27 @@
     "@fortawesome/fontawesome-free": "^5.12.0",
     "bulma": "^0.8.0",
     "chrome-tabs": "^5.4.0",
+    "dictionary-ca": "^2.1.1",
+    "dictionary-ca-valencia": "^2.1.1",
+    "dictionary-de": "^2.0.2",
+    "dictionary-en-gb": "^2.1.1",
+    "dictionary-en-us": "^2.1.1",
+    "dictionary-es": "^1.2.3",
+    "dictionary-eu": "^3.1.1",
+    "dictionary-fr": "^2.3.2",
+    "dictionary-it": "^1.3.1",
+    "dictionary-ka": "^1.0.0",
+    "dictionary-lt": "^1.0.1",
+    "dictionary-nl": "^1.3.2",
+    "dictionary-pl": "^1.2.2",
+    "dictionary-pt": "^1.3.1",
+    "dictionary-pt-br": "^1.2.2",
+    "dictionary-ru": "^1.2.2",
+    "dictionary-sv": "^2.0.2",
+    "dictionary-tr": "^1.3.1",
+    "dictionary-uk": "^2.1.1",
     "draggabilly": "^2.2.0",
     "electron": "^7.1.6",
-    "simple-spellchecker": "^1.0.0"
+    "nodehun": "^3.0.1"
   }
 }
diff --git a/src/constants/index.js b/src/constants/index.js
index 902177b..78a99e8 100644
--- a/src/constants/index.js
+++ b/src/constants/index.js
@@ -1,6 +1,7 @@
 const APP_EVENTS = {
   activateTab: 'activateTab',
   addTabs: 'addTabs',
+  dictionaryGetMisspelled: 'dictionaryGetMisspelled',
   settingsCancel: 'settingsCancel',
   settingsOpenDialog: 'settingsOpenDialog',
   settingsSave: 'settingsSave',
diff --git a/src/main/index.js b/src/main/index.js
index 65db6de..68722f7 100644
--- a/src/main/index.js
+++ b/src/main/index.js
@@ -1,7 +1,8 @@
-const {BrowserWindow, ipcMain: ipc} = require('electron');
+const {BrowserWindow, app, ipcMain: ipc} = require('electron');
 const {APP_EVENTS} = require('../constants');
 const {TABS_CONTAINER_HEIGHT, initTabContainer} = require('../chrome-tabs');
 const {loadSettings, updateSettings, updateTabs, openSettingsDialog} = require('../settings');
+const {loadDictionaries} = require('../spell-check');
 const tabManager = require('../tab-manager');
 
 const webPreferences = {
@@ -57,6 +58,7 @@ const closeSettings = () => {
 const saveSettings = (event, {tabs, dictionaries}) => {
   updateSettings({enabledDictionaries: [...dictionaries]});
   updateTabs(tabs);
+  loadDictionaries();
   const currentBrowserView = mainWindow.getBrowserView();
   mainWindow.removeBrowserView(currentBrowserView);
   tabManager.removeAll();
@@ -71,6 +73,7 @@ const initSettingsListener = () => {
 };
 
 const init = () => {
+  loadDictionaries();
   const {width = 800, height = 600} = loadSettings();
   mainWindow = new BrowserWindow({
     width, height, resizable: true, maximizable: false, webPreferences
@@ -80,6 +83,7 @@ const init = () => {
     const [currentWidth, currentHeight] = mainWindow.getSize();
     updateSettings({width: currentWidth, height: currentHeight});
   });
+  mainWindow.on('closed', () => app.quit());
   initTabListener();
   initSettingsListener();
   tabContainer = initTabContainer(mainWindow);
diff --git a/src/spell-check/browser-spell-check.js b/src/spell-check/browser-spell-check.js
deleted file mode 100644
index 02d0179..0000000
--- a/src/spell-check/browser-spell-check.js
+++ /dev/null
@@ -1,17 +0,0 @@
-const activeDictionaries = require('./').getActiveDictionariesSnapshot();
-
-const isMisspelled = word =>
-  activeDictionaries.every(dictionary => !dictionary.spellCheck(word));
-
-const initSpellChecker = webFrame => {
-  webFrame.setSpellCheckProvider(navigator.language, {
-    spellCheck (words, callback) {
-      setTimeout(() => {
-        const misspelled = words.filter(isMisspelled);
-        callback(misspelled);
-      }, 0);
-    }
-  });
-};
-
-module.exports = {initSpellChecker};
diff --git a/src/spell-check/dictionary.renderer/README.md b/src/spell-check/dictionary.renderer/README.md
new file mode 100644
index 0000000..2cfd253
--- /dev/null
+++ b/src/spell-check/dictionary.renderer/README.md
@@ -0,0 +1,4 @@
+Fake BrowserWindow that runs long processes (such as loading dictionaries)
+in an Electron renderer thread.
+
+Communication with the thread happens via events and remote JavaScript execution.
diff --git a/src/spell-check/dictionary.renderer/dictionary.worker.js b/src/spell-check/dictionary.renderer/dictionary.worker.js
new file mode 100644
index 0000000..6e0d12d
--- /dev/null
+++ b/src/spell-check/dictionary.renderer/dictionary.worker.js
@@ -0,0 +1,42 @@
+const Nodehun = require('nodehun');
+const {loadSettings} = require('../../settings');
+
+const dictionaries = [];
+
+const isMisspelled = word =>
+  dictionaries.every(dictionary => !dictionary.spellSync(word));
+
+window.getMisspelled = words => {
+  if (dictionaries.length === 0) {
+    return [];
+  }
+  return words.filter(isMisspelled);
+};
+
+window.getSuggestions = word => {
+  const ret = new Set();
+  dictionaries.map(dictionary => dictionary.suggestSync(word))
+    .flatMap(suggestions => suggestions)
+    .forEach(suggestion => ret.add(suggestion));
+  return Array.from(ret.values()).sort().slice(0, 10);
+};
+
+window.reloadDictionaries = () => {
+  dictionaries.length = 0;
+  const {enabledDictionaries} = loadSettings();
+  enabledDictionaries
+    .forEach(dictionaryKey => {
+      let dictionary;
+      try {
+        dictionary = require(`dictionary-${dictionaryKey}`);
+      } catch (error) {
+        // Error is ignored
+      }
+      if (dictionary) {
+        dictionary((err, {aff, dic}) => {
+          dictionaries.push(new Nodehun(aff, dic));
+        });
+      }
+    });
+};
+
diff --git a/src/spell-check/dictionary.renderer/index.html b/src/spell-check/dictionary.renderer/index.html
new file mode 100644
index 0000000..97f6706
--- /dev/null
+++ b/src/spell-check/dictionary.renderer/index.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>This is a Fake Renderer to perform background tasks</title>
+</head>
+<body>
+    <script>
+        require('./dictionary.worker');
+        reloadDictionaries();
+    </script>
+</body>
+</html>
diff --git a/src/spell-check/index.js b/src/spell-check/index.js
index 6e20e77..e9a4c69 100644
--- a/src/spell-check/index.js
+++ b/src/spell-check/index.js
@@ -1,46 +1,53 @@
-const simpleSpellChecker = require('simple-spellchecker');
+const {BrowserWindow, ipcMain} = require('electron');
+const {APP_EVENTS} = require('../constants');
 const {loadSettings} = require('../settings');
 
 const AVAILABLE_DICTIONARIES = [
-  'de-DE',
-  'en-GB',
-  'en-US',
-  'es-ES',
-  'es-MX',
-  'fr-FR',
-  'it-IT',
-  'lt-LT',
-  'nl-NL',
-  'pl-PL',
-  'pt-BR',
-  'sv-SE'
+  'ca',
+  'ca-valencia',
+  'de',
+  'en-gb',
+  'en-us',
+  'es',
+  'eu',
+  'fr',
+  'it',
+  'ka',
+  'lt',
+  'nl',
+  'pl',
+  'pt',
+  'pt-br',
+  'ru',
+  'sv',
+  'tr',
+  'uk'
 ];
 
-const activeDictionaries = [];
+let fakeRendererWorker;
+
+const handleGetMisspelled = async (event, words) =>
+  await fakeRendererWorker.webContents.executeJavaScript(`getMisspelled(${JSON.stringify(words)})`);
 
 const getEnabledDictionaries = () => loadSettings().enabledDictionaries;
 
 const loadDictionaries = () => {
-  getEnabledDictionaries().forEach(dictionaryKey =>
-    simpleSpellChecker.getDictionary(dictionaryKey, (err, loadedDictionary) => {
-      activeDictionaries.push(loadedDictionary);
-    })
-  );
-};
-
-const getSuggestions = word => {
-  const ret = new Set();
-  activeDictionaries.map(dictionary => dictionary.checkAndSuggest(word))
-    .flatMap(({misspelled, suggestions}) => (misspelled ? suggestions : []))
-    .forEach(suggestion => ret.add(suggestion));
-  return Array.from(ret.values());
+  if (!fakeRendererWorker) {
+    fakeRendererWorker = new BrowserWindow({
+      show: false,
+      webPreferences: {nodeIntegration: true}
+    });
+    ipcMain.handle(APP_EVENTS.dictionaryGetMisspelled, handleGetMisspelled);
+  }
+  fakeRendererWorker.loadURL(`file://${__dirname}/dictionary.renderer/index.html`);
 };
 
-const contextMenuHandler = (event, {misspelledWord}, webContents) => {
+const contextMenuHandler = async (event, {misspelledWord}, webContents) => {
   const {MenuItem} = require('electron');
   const ret = [];
   if (misspelledWord && misspelledWord.length > 0) {
-    getSuggestions(misspelledWord).forEach(suggestion =>
+    const suggestions = await fakeRendererWorker.webContents.executeJavaScript(`getSuggestions('${misspelledWord}')`);
+    suggestions.forEach(suggestion =>
       ret.push(new MenuItem({
         label: suggestion,
         click: () => {
@@ -52,9 +59,7 @@ const contextMenuHandler = (event, {misspelledWord}, webContents) => {
   return ret;
 };
 
-const getActiveDictionariesSnapshot = () => activeDictionaries;
 
-loadDictionaries();
 module.exports = {
-  AVAILABLE_DICTIONARIES, contextMenuHandler, getEnabledDictionaries, getActiveDictionariesSnapshot
+  AVAILABLE_DICTIONARIES, contextMenuHandler, getEnabledDictionaries, loadDictionaries
 };
diff --git a/src/tab-manager/browser-spell-check.js b/src/tab-manager/browser-spell-check.js
new file mode 100644
index 0000000..cf2ee2a
--- /dev/null
+++ b/src/tab-manager/browser-spell-check.js
@@ -0,0 +1,13 @@
+/* eslint-disable no-undef */
+const {ipcRenderer} = require('electron');
+
+const initSpellChecker = webFrame => {
+  webFrame.setSpellCheckProvider(navigator.language, {
+    async spellCheck (words, callback) {
+      const misspelled = await ipcRenderer.invoke(APP_EVENTS.dictionaryGetMisspelled, words);
+      callback(misspelled);
+    }
+  });
+};
+
+module.exports = {initSpellChecker};
diff --git a/src/tab-manager/index.js b/src/tab-manager/index.js
index e20dc88..001c327 100644
--- a/src/tab-manager/index.js
+++ b/src/tab-manager/index.js
@@ -34,10 +34,11 @@ const handlePageFaviconUpdated = (browserView, ipcSender, tabId) => async (e, fa
   }
 };
 
-const handleContextMenu = browserView => (event, params) => {
+const handleContextMenu = browserView => async (event, params) => {
   const {webContents} = browserView;
   const menu = new Menu();
-  const spellingSuggestions = contextMenuHandler(event, params, webContents);
+
+  const spellingSuggestions = await contextMenuHandler(event, params, webContents);
   if (spellingSuggestions.length > 0) {
     spellingSuggestions.forEach(mi => menu.append(mi));
     menu.append(new MenuItem({type: 'separator'}));
diff --git a/src/tab-manager/preload.js b/src/tab-manager/preload.js
index 93d4a2d..d2711d3 100644
--- a/src/tab-manager/preload.js
+++ b/src/tab-manager/preload.js
@@ -1,5 +1,5 @@
 require('../main/preload');
 const {webFrame} = require('electron');
-const {initSpellChecker} = require('../spell-check/browser-spell-check');
+const {initSpellChecker} = require('./browser-spell-check');
 
 initSpellChecker(webFrame);


Test patch:
diff --git a/src/main/__tests__/index.test.js b/src/main/__tests__/index.test.js
index 2ddbbec..278d43a 100644
--- a/src/main/__tests__/index.test.js
+++ b/src/main/__tests__/index.test.js
@@ -30,6 +30,7 @@ describe('Main module test suite', () => {
       updateSettings: jest.fn()
     }));
     settingsModule = require('../../settings');
+    jest.mock('../../spell-check');
     jest.mock('../../tab-manager', () => ({}));
     main = require('../');
   });
diff --git a/src/spell-check/__tests__/index.test.js b/src/spell-check/__tests__/index.test.js
index efa8642..0a71c82 100644
--- a/src/spell-check/__tests__/index.test.js
+++ b/src/spell-check/__tests__/index.test.js
@@ -1,11 +1,23 @@
 describe('Spell-check module test suite', () => {
+  let mockBrowserWindow;
+  let mockIpc;
   let mockSettings;
   let spellCheck;
   beforeEach(() => {
+    mockBrowserWindow = {
+      loadURL: jest.fn()
+    };
+    mockIpc = {
+      handle: jest.fn()
+    };
     mockSettings = {
       enabledDictionaries: []
     };
     jest.resetModules();
+    jest.mock('electron', () => ({
+      BrowserWindow: jest.fn(() => mockBrowserWindow),
+      ipcMain: mockIpc
+    }));
     jest.mock('../../settings', () => ({
       loadSettings: jest.fn(() => mockSettings)
     }));
@@ -19,4 +31,11 @@ describe('Spell-check module test suite', () => {
     // Then
     expect(result).toEqual(['13-37']);
   });
+  test('loadDictionaries', () => {
+    // When
+    spellCheck.loadDictionaries();
+    // Then
+    expect(mockBrowserWindow.loadURL).toHaveBeenCalledWith(expect.stringMatching(/\/dictionary.renderer\/index.html$/));
+    expect(mockIpc.handle).toHaveBeenCalledWith('dictionaryGetMisspelled', expect.any(Function));
+  });
 });