init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "spellcheck/platform/linux/language_linux.h"
namespace Platform::Language {
Id Recognize(QStringView text) {
return {};
}
} // namespace Platform::Language

View File

@@ -0,0 +1,13 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "spellcheck/platform/platform_language.h"
namespace Platform::Language {
} // namespace Platform::Language

View File

@@ -0,0 +1,251 @@
/* enchant
* Copyright (C) 2003 Dom Lachowicz
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, Dom Lachowicz
* gives permission to link the code of this program with
* non-LGPL Spelling Provider libraries (eg: a MSFT Office
* spell checker backend) and distribute linked combinations including
* the two. You must obey the GNU Lesser General Public License in all
* respects for all of the code used other than said providers. If you modify
* this file, you may extend this exception to your version of the
* file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Nicholas Guriev (email: guriev-ns@ya.ru) split the full <enchant++.h> header
* into two files, linux_enchant.h and linux_enchant.cpp, to use within Desktop
* App Toolkit. He also implemented explicit linking with dlopen/dlsym to avoid
* rigid dependency on the Enchant library at runtime.
*/
#include <enchant.h>
#include "base/platform/linux/base_linux_library.h"
#include "spellcheck/platform/linux/linux_enchant.h"
namespace {
struct {
//decltype (enchant_broker_describe) * broker_describe;
//decltype (enchant_broker_dict_exists) * broker_dict_exists;
decltype (enchant_broker_free) * broker_free;
decltype (enchant_broker_free_dict) * broker_free_dict;
decltype (enchant_broker_get_error) * broker_get_error;
decltype (enchant_broker_init) * broker_init;
decltype (enchant_broker_list_dicts) * broker_list_dicts;
decltype (enchant_broker_request_dict) * broker_request_dict;
//decltype (enchant_broker_request_pwl_dict) * broker_request_pwl_dict;
decltype (enchant_broker_set_ordering) * broker_set_ordering;
decltype (enchant_dict_add) * dict_add;
decltype (enchant_dict_add_to_session) * dict_add_to_session;
decltype (enchant_dict_check) * dict_check;
decltype (enchant_dict_describe) * dict_describe;
decltype (enchant_dict_free_string_list) * dict_free_string_list;
decltype (enchant_dict_get_error) * dict_get_error;
decltype (enchant_dict_is_added) * dict_is_added;
//decltype (enchant_dict_is_removed) * dict_is_removed;
decltype (enchant_dict_remove) * dict_remove;
decltype (enchant_dict_remove_from_session) * dict_remove_from_session;
//decltype (enchant_dict_store_replacement) * dict_store_replacement;
decltype (enchant_dict_suggest) * dict_suggest;
} f_enchant;
} // anonymous namespace
enchant::Exception::Exception (const char * ex)
: std::exception (), m_ex ("") {
if (ex)
m_ex = ex;
}
enchant::Exception::~Exception () = default;
const char * enchant::Exception::what () const noexcept {
return m_ex.c_str();
}
enchant::Dict::Dict (EnchantDict * dict, EnchantBroker * broker)
: m_dict (dict), m_broker (broker) {
f_enchant.dict_describe (m_dict, s_describe_fn, this);
}
enchant::Dict::~Dict () {
f_enchant.broker_free_dict (m_broker, m_dict);
}
bool enchant::Dict::check (const std::string & utf8word) {
int val;
val = f_enchant.dict_check (m_dict, utf8word.c_str(), utf8word.size());
if (val == 0)
return true;
else if (val > 0)
return false;
else {
throw enchant::Exception (f_enchant.dict_get_error (m_dict));
}
return false; // never reached
}
void enchant::Dict::suggest (const std::string & utf8word,
std::vector<std::string> & out_suggestions) {
size_t n_suggs;
char ** suggs;
out_suggestions.clear ();
suggs = f_enchant.dict_suggest (m_dict, utf8word.c_str(),
utf8word.size(), &n_suggs);
if (suggs && n_suggs) {
out_suggestions.reserve(n_suggs);
for (size_t i = 0; i < n_suggs; i++) {
out_suggestions.push_back (suggs[i]);
}
f_enchant.dict_free_string_list (m_dict, suggs);
}
}
void enchant::Dict::add (const std::string & utf8word) {
f_enchant.dict_add (m_dict, utf8word.c_str(), utf8word.size());
}
void enchant::Dict::add_to_session (const std::string & utf8word) {
f_enchant.dict_add_to_session (m_dict, utf8word.c_str(), utf8word.size());
}
bool enchant::Dict::is_added (const std::string & utf8word) {
return f_enchant.dict_is_added (m_dict, utf8word.c_str(),
utf8word.size());
}
void enchant::Dict::remove (const std::string & utf8word) {
f_enchant.dict_remove (m_dict, utf8word.c_str(), utf8word.size());
}
void enchant::Dict::remove_from_session (const std::string & utf8word) {
f_enchant.dict_remove_from_session (m_dict, utf8word.c_str(),
utf8word.size());
}
//bool enchant::Dict::is_removed (const std::string & utf8word) {
// return f_enchant.dict_is_removed (m_dict, utf8word.c_str(),
// utf8word.size());
//}
//void enchant::Dict::store_replacement (const std::string & utf8bad,
// const std::string & utf8good) {
// f_enchant.dict_store_replacement (m_dict,
// utf8bad.c_str(), utf8bad.size(),
// utf8good.c_str(), utf8good.size());
//}
enchant::Broker::Broker ()
: m_broker (f_enchant.broker_init ())
{
}
enchant::Broker::~Broker () {
f_enchant.broker_free (m_broker);
}
enchant::Dict * enchant::Broker::request_dict (const std::string & lang) {
EnchantDict * dict = f_enchant.broker_request_dict (m_broker, lang.c_str());
if (!dict) {
throw enchant::Exception (f_enchant.broker_get_error (m_broker));
return 0; // never reached
}
return new Dict (dict, m_broker);
}
//enchant::Dict * enchant::Broker::request_pwl_dict (const std::string & pwl) {
// EnchantDict * dict = f_enchant.broker_request_pwl_dict (m_broker, pwl.c_str());
//
// if (!dict) {
// throw enchant::Exception (f_enchant.broker_get_error (m_broker));
// return 0; // never reached
// }
//
// return new Dict (dict, m_broker);
//}
//bool enchant::Broker::dict_exists (const std::string & lang) {
// if (f_enchant.broker_dict_exists (m_broker, lang.c_str()))
// return true;
// return false;
//}
void enchant::Broker::set_ordering (const std::string & tag, const std::string & ordering) {
f_enchant.broker_set_ordering (m_broker, tag.c_str(), ordering.c_str());
}
//void enchant::Broker::describe (EnchantBrokerDescribeFn fn, void * user_data) {
// f_enchant.broker_describe (m_broker, fn, user_data);
//}
void enchant::Broker::list_dicts (EnchantDictDescribeFn fn, void * user_data) {
f_enchant.broker_list_dicts (m_broker, fn, user_data);
}
#define GET_SYMBOL_enchant(func_name) \
if (!base::Platform::LoadSymbol (handle, "enchant_" # func_name, f_enchant.func_name)) { \
return false; \
}
bool enchant::loader::do_explicit_linking () {
static enum { NotLoadedYet, LoadSuccessful, LoadFailed = -1 } load_status;
if (load_status == NotLoadedYet) {
load_status = LoadFailed;
const auto handle = base::Platform::LoadLibrary ("libenchant.so.1", RTLD_NODELETE)
?: base::Platform::LoadLibrary ("libenchant-2.so.2", RTLD_NODELETE)
?: base::Platform::LoadLibrary ("libenchant.so.2", RTLD_NODELETE);
if (!handle) {
// logs ?
return false;
}
//GET_SYMBOL_enchant (broker_describe);
//GET_SYMBOL_enchant (broker_dict_exists);
GET_SYMBOL_enchant (broker_free);
GET_SYMBOL_enchant (broker_free_dict);
GET_SYMBOL_enchant (broker_get_error);
GET_SYMBOL_enchant (broker_init);
GET_SYMBOL_enchant (broker_list_dicts);
GET_SYMBOL_enchant (broker_request_dict);
//GET_SYMBOL_enchant (broker_request_pwl_dict);
GET_SYMBOL_enchant (broker_set_ordering);
GET_SYMBOL_enchant (dict_add);
GET_SYMBOL_enchant (dict_add_to_session);
GET_SYMBOL_enchant (dict_check);
GET_SYMBOL_enchant (dict_describe);
GET_SYMBOL_enchant (dict_free_string_list);
GET_SYMBOL_enchant (dict_get_error);
GET_SYMBOL_enchant (dict_is_added);
//GET_SYMBOL_enchant (dict_is_removed);
GET_SYMBOL_enchant (dict_remove);
GET_SYMBOL_enchant (dict_remove_from_session);
//GET_SYMBOL_enchant (dict_store_replacement);
GET_SYMBOL_enchant (dict_suggest);
load_status = LoadSuccessful;
}
return load_status == LoadSuccessful;
}
// vi: ts=8 sw=8

View File

@@ -0,0 +1,201 @@
/* enchant
* Copyright (C) 2003 Dom Lachowicz
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, Dom Lachowicz
* gives permission to link the code of this program with
* non-LGPL Spelling Provider libraries (eg: a MSFT Office
* spell checker backend) and distribute linked combinations including
* the two. You must obey the GNU Lesser General Public License in all
* respects for all of the code used other than said providers. If you modify
* this file, you may extend this exception to your version of the
* file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Nicholas Guriev (email: guriev-ns@ya.ru) split the full <enchant++.h> header
* into two files, linux_enchant.h and linux_enchant.cpp, to use within Desktop
* App Toolkit. He also implemented explicit linking with dlopen/dlsym to avoid
* rigid dependency on the Enchant library at runtime.
*/
#pragma once
#include <string>
#include <vector>
#include <exception>
#ifndef ENCHANT_H
typedef struct str_enchant_broker EnchantBroker;
typedef struct str_enchant_dict EnchantDict;
/**
* EnchantBrokerDescribeFn
* @provider_name: The provider's identifier, such as "ispell" or "aspell" in UTF8 encoding
* @provider_desc: A description of the provider, such as "Aspell 0.53" in UTF8 encoding
* @provider_dll_file: The provider's DLL filename in Glib file encoding (UTF8 on Windows)
* @user_data: Supplied user data, or %null if you don't care
*
* Callback used to enumerate and describe Enchant's various providers
*/
typedef void (*EnchantBrokerDescribeFn) (const char * const provider_name,
const char * const provider_desc,
const char * const provider_dll_file,
void * user_data);
/**
* EnchantDictDescribeFn
* @lang_tag: The dictionary's language tag (eg: en_US, de_AT, ...)
* @provider_name: The provider's name (eg: Aspell) in UTF8 encoding
* @provider_desc: The provider's description (eg: Aspell 0.50.3) in UTF8 encoding
* @provider_file: The DLL/SO where this dict's provider was loaded from in Glib file encoding (UTF8 on Windows)
* @user_data: Supplied user data, or %null if you don't care
*
* Callback used to describe an individual dictionary
*/
typedef void (*EnchantDictDescribeFn) (const char * const lang_tag,
const char * const provider_name,
const char * const provider_desc,
const char * const provider_file,
void * user_data);
#endif // !ENCHANT_H
namespace enchant
{
class Broker;
class Exception : public std::exception
{
public:
explicit Exception (const char * ex);
virtual ~Exception () noexcept;
virtual const char * what () const noexcept;
private:
std::string m_ex;
}; // class enchant::Exception
class Dict
{
friend class enchant::Broker;
public:
~Dict ();
bool check (const std::string & utf8word);
void suggest (const std::string & utf8word,
std::vector<std::string> & out_suggestions);
std::vector<std::string> suggest (const std::string & utf8word) {
std::vector<std::string> result;
suggest (utf8word, result);
return result;
}
void add (const std::string & utf8word);
void add_to_session (const std::string & utf8word);
bool is_added (const std::string & utf8word);
void remove (const std::string & utf8word);
void remove_from_session (const std::string & utf8word);
bool is_removed (const std::string & utf8word);
void store_replacement (const std::string & utf8bad,
const std::string & utf8good);
const std::string & get_lang () const {
return m_lang;
}
const std::string & get_provider_name () const {
return m_provider_name;
}
const std::string & get_provider_desc () const {
return m_provider_desc;
}
const std::string & get_provider_file () const {
return m_provider_file;
}
private:
// space reserved for API/ABI expansion
void * _private[5];
static void s_describe_fn (const char * const lang,
const char * const provider_name,
const char * const provider_desc,
const char * const provider_file,
void * user_data) {
enchant::Dict * dict = static_cast<enchant::Dict *> (user_data);
dict->m_lang = lang;
dict->m_provider_name = provider_name;
dict->m_provider_desc = provider_desc;
dict->m_provider_file = provider_file;
}
Dict (EnchantDict * dict, EnchantBroker * broker);
// private, unimplemented
Dict () = delete;
Dict (const Dict & rhs) = delete;
Dict& operator=(const Dict & rhs) = delete;
EnchantDict * m_dict;
EnchantBroker * m_broker;
std::string m_lang;
std::string m_provider_name;
std::string m_provider_desc;
std::string m_provider_file;
}; // class enchant::Dict
class Broker
{
public:
Broker ();
~Broker ();
Dict * request_dict (const std::string & lang);
Dict * request_pwl_dict (const std::string & pwl);
bool dict_exists (const std::string & lang);
void set_ordering (const std::string & tag, const std::string & ordering);
void describe (EnchantBrokerDescribeFn fn, void * user_data = nullptr);
void list_dicts (EnchantDictDescribeFn fn, void * user_data = nullptr);
private:
// space reserved for API/ABI expansion
void * _private[5];
// not implemented
Broker (const Broker & rhs) = delete;
Broker& operator=(const Broker & rhs) = delete;
EnchantBroker * m_broker;
}; // class enchant::Broker
namespace loader {
bool do_explicit_linking ();
} // loader subnamespace
} // enchant namespace
// vi: ts=8 sw=8

View File

@@ -0,0 +1,289 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// Author: Nicholas Guriev <guriev-ns@ya.ru>, public domain, 2019
// License: CC0, https://creativecommons.org/publicdomain/zero/1.0/legalcode
#include <set>
#include <QLocale>
#include "spellcheck/platform/linux/linux_enchant.h"
#include "spellcheck/platform/linux/spellcheck_linux.h"
#include "base/debug_log.h"
namespace Platform::Spellchecker {
namespace {
constexpr auto kHspell = "hspell";
constexpr auto kMySpell = "myspell";
constexpr auto kHunspell = "hunspell";
constexpr auto kOrdering = "hspell,aspell,hunspell,myspell";
constexpr auto kMaxValidators = 10;
constexpr auto kMaxMySpellCount = 3;
constexpr auto kMaxWordLength = 15;
using DictPtr = std::unique_ptr<enchant::Dict>;
auto CheckProvider(DictPtr &validator, const std::string &provider) {
auto p = validator->get_provider_name();
std::transform(begin(p), end(p), begin(p), ::tolower);
return (p.find(provider) == 0); // startsWith.
}
auto IsHebrew(const QString &word) {
// Words with mixed scripts will be automatically ignored,
// so this check should be fine.
return ::Spellchecker::WordScript(word) == QChar::Script_Hebrew;
}
class EnchantSpellChecker {
public:
auto knownLanguages();
bool checkSpelling(const QString &word);
auto findSuggestions(const QString &word);
void addWord(const QString &wordToAdd);
void ignoreWord(const QString &word);
void removeWord(const QString &word);
bool isWordInDictionary(const QString &word);
static EnchantSpellChecker *instance();
private:
EnchantSpellChecker();
EnchantSpellChecker(const EnchantSpellChecker&) = delete;
EnchantSpellChecker& operator =(const EnchantSpellChecker&) = delete;
std::unique_ptr<enchant::Broker> _brokerHandle;
std::vector<DictPtr> _validators;
std::vector<not_null<enchant::Dict*>> _hspells;
};
EnchantSpellChecker::EnchantSpellChecker() {
if (!enchant::loader::do_explicit_linking()) return;
std::set<std::string> langs;
_brokerHandle = std::make_unique<enchant::Broker>();
_brokerHandle->list_dicts([](
const char *language,
const char *provider,
const char *description,
const char *filename,
void *our_payload) {
static_cast<decltype(langs)*>(our_payload)->insert(language);
}, &langs);
_validators.reserve(langs.size());
try {
std::string langTag = QLocale::system().name().toStdString();
_brokerHandle->set_ordering(langTag, kOrdering);
_validators.push_back(DictPtr(_brokerHandle->request_dict(langTag)));
langs.erase(langTag);
} catch (const enchant::Exception &e) {
// no first dictionary found
}
auto mySpellCount = 0;
for (const std::string &language : langs) {
try {
_brokerHandle->set_ordering(language, kOrdering);
auto validator = DictPtr(_brokerHandle->request_dict(language));
if (!validator) {
continue;
}
if (CheckProvider(validator, kHspell)) {
_hspells.push_back(validator.get());
}
if (CheckProvider(validator, kMySpell)
|| CheckProvider(validator, kHunspell)) {
if (mySpellCount > kMaxMySpellCount) {
continue;
} else {
mySpellCount++;
}
}
_validators.push_back(std::move(validator));
if (_validators.size() > kMaxValidators) {
break;
}
} catch (const enchant::Exception &e) {
DEBUG_LOG(("Catch after request_dict: %1").arg(e.what()));
}
}
}
EnchantSpellChecker *EnchantSpellChecker::instance() {
static EnchantSpellChecker capsule;
return &capsule;
}
auto EnchantSpellChecker::knownLanguages() {
return _validators | ranges::views::transform([](const auto &validator) {
return QString(validator->get_lang().c_str());
}) | ranges::to_vector;
}
bool EnchantSpellChecker::checkSpelling(const QString &word) {
auto w = word.toStdString();
const auto checkWord = [&](const auto &validator, auto w) {
try {
return validator->check(w);
} catch (const enchant::Exception &e) {
DEBUG_LOG(("Catch after check '%1': %2").arg(word, e.what()));
return true;
}
};
if (IsHebrew(word) && _hspells.size()) {
return ranges::any_of(_hspells, [&](const auto &validator) {
return checkWord(validator, w);
});
}
return ranges::any_of(_validators, [&](const auto &validator) {
// Hspell is the spell checker that only checks words in Hebrew.
// It returns 'true' for any non-Hebrew word,
// so we should skip Hspell if a word is not in Hebrew.
if (ranges::any_of(_hspells, [&](auto &v) {
return v == validator.get();
})) {
return false;
}
if (validator->get_lang().find("uk") == 0) {
return false;
}
return checkWord(validator, w);
}) || _validators.empty();
}
auto EnchantSpellChecker::findSuggestions(const QString &word) {
const auto wordScript = ::Spellchecker::WordScript(word);
auto w = word.toStdString();
std::vector<QString> result;
if (!_validators.size()) {
return result;
}
const auto convertSuggestions = [&](auto suggestions) {
for (const auto &replacement : suggestions) {
if (result.size() >= kMaxSuggestions) {
break;
}
if (!replacement.empty()) {
result.push_back(replacement.c_str());
}
}
};
if (word.size() >= kMaxWordLength) {
// The first element is the validator of the system language.
auto *v = _validators[0].get();
const auto lang = QString::fromStdString(v->get_lang());
if (wordScript == ::Spellchecker::LocaleToScriptCode(lang)) {
convertSuggestions(v->suggest(w));
}
return result;
}
if (IsHebrew(word) && _hspells.size()) {
for (const auto &h : _hspells) {
convertSuggestions(h->suggest(w));
if (result.size()) {
return result;
}
}
}
for (const auto &validator : _validators) {
const auto lang = QString::fromStdString(validator->get_lang());
if (wordScript != ::Spellchecker::LocaleToScriptCode(lang)) {
continue;
}
convertSuggestions(validator->suggest(w));
if (!result.empty()) {
break;
}
}
return result;
}
void EnchantSpellChecker::addWord(const QString &wordToAdd) {
auto word = wordToAdd.toStdString();
auto &&first = _validators.at(0);
first->add(word);
first->add_to_session(word);
}
void EnchantSpellChecker::ignoreWord(const QString &word) {
_validators.at(0)->add_to_session(word.toStdString());
}
void EnchantSpellChecker::removeWord(const QString &word) {
auto w = word.toStdString();
for (const auto &validator : _validators) {
validator->remove_from_session(w);
validator->remove(w);
}
}
bool EnchantSpellChecker::isWordInDictionary(const QString &word) {
auto w = word.toStdString();
return ranges::any_of(_validators, [&w](const auto &validator) {
return validator->is_added(w);
});
}
} // namespace
void Init() {
}
std::vector<QString> ActiveLanguages() {
return EnchantSpellChecker::instance()->knownLanguages();
}
void UpdateLanguages(std::vector<int> languages) {
::Spellchecker::UpdateSupportedScripts(ActiveLanguages());
crl::async([=] {
const auto result = ActiveLanguages();
crl::on_main([=] {
::Spellchecker::UpdateSupportedScripts(result);
});
});
}
bool CheckSpelling(const QString &wordToCheck) {
return EnchantSpellChecker::instance()->checkSpelling(wordToCheck);
}
void FillSuggestionList(
const QString &wrongWord,
std::vector<QString> *variants) {
*variants = EnchantSpellChecker::instance()->findSuggestions(wrongWord);
}
void AddWord(const QString &word) {
EnchantSpellChecker::instance()->addWord(word);
}
void RemoveWord(const QString &word) {
EnchantSpellChecker::instance()->removeWord(word);
}
void IgnoreWord(const QString &word) {
EnchantSpellChecker::instance()->ignoreWord(word);
}
bool IsWordInDictionary(const QString &wordToCheck) {
return EnchantSpellChecker::instance()->isWordInDictionary(wordToCheck);
}
void CheckSpellingText(
const QString &text,
MisspelledWords *misspelledWords) {
*misspelledWords = ::Spellchecker::RangesFromText(
text,
::Spellchecker::CheckSkipAndSpell);
}
bool IsSystemSpellchecker() {
return true;
}
} // namespace Platform::Spellchecker

View File

@@ -0,0 +1,13 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "spellcheck/platform/platform_spellcheck.h"
namespace Platform::Spellchecker {
} // namespace Platform::Spellchecker