diff --git a/chrome/content/zotero/xpcom/dictionaries.js b/chrome/content/zotero/xpcom/dictionaries.js
new file mode 100644
index 0000000000..8b96ae6f0d
--- /dev/null
+++ b/chrome/content/zotero/xpcom/dictionaries.js
@@ -0,0 +1,248 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2021 Corporation for Digital Scholarship
+ Vienna, Virginia, USA
+ http://digitalscholar.org/
+
+ This file is part of Zotero.
+
+ Zotero is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Zotero is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Zotero. If not, see .
+
+ ***** END LICENSE BLOCK *****
+*/
+
+Zotero.Dictionaries = new function () {
+ let _dictionaries = [];
+ let _spellChecker = Cc['@mozilla.org/spellchecker/engine;1']
+ .getService(Ci.mozISpellCheckingEngine);
+ _spellChecker.QueryInterface(Ci.mozISpellCheckingEngine);
+
+ Zotero.defineProperty(this, 'baseURL', {
+ get: () => {
+ let url = ZOTERO_CONFIG.DICTIONARIES_URL;
+ if (!url.endsWith('/')) {
+ url += '/';
+ }
+ return url;
+ }
+ });
+
+ Zotero.defineProperty(this, 'dictionaries', {
+ get: () => {
+ return _dictionaries;
+ }
+ });
+
+ /**
+ * Load all dictionaries
+ *
+ * @return {Promise}
+ */
+ this.init = async function () {
+ let dictionariesDir = OS.Path.join(Zotero.Profile.dir, 'dictionaries');
+ if (!(await OS.File.exists(dictionariesDir))) {
+ return;
+ }
+ let iterator = new OS.File.DirectoryIterator(dictionariesDir);
+ try {
+ await iterator.forEach(async function (entry) {
+ if (entry.name.startsWith('.')) {
+ return;
+ }
+ try {
+ let dir = OS.Path.join(dictionariesDir, entry.name);
+ await _loadDictionary(dir);
+ }
+ catch (e) {
+ Zotero.logError(e);
+ }
+ });
+ }
+ finally {
+ iterator.close();
+ }
+ };
+
+ /**
+ * Get available dictionaries from server
+ *
+ * @return {Promise