Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.

Commit f9e8503

Browse files
committed
Merge pull request #48 from nightuser/master
Add multilingual support (multiple dictionaries)
2 parents aed067e + 66bf643 commit f9e8503

10 files changed

Lines changed: 235 additions & 97 deletions

binding.gyp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,16 @@
5050
'src/spellchecker_linux.cc',
5151
'src/transcoder_posix.cc',
5252
],
53+
'cflags!': [ '-fno-exceptions' ],
54+
'cflags_cc!': [ '-fno-exceptions' ],
5355
}],
5456
['OS=="mac"', {
5557
'sources': [
5658
'src/spellchecker_mac.mm',
5759
'src/transcoder_posix.cc',
5860
],
61+
'cflags!': [ '-fno-exceptions' ],
62+
'cflags_cc!': [ '-fno-exceptions' ],
5963
'link_settings': {
6064
'libraries': [
6165
'$(SDKROOT)/System/Library/Frameworks/AppKit.framework',

lib/spellchecker.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ var ensureDefaultSpellCheck = function() {
1717
setDictionary(lang, getDictionaryPath());
1818
};
1919

20+
var addDictionary = function(lang, dictPath) {
21+
ensureDefaultSpellCheck();
22+
return defaultSpellcheck.addDictionary(lang, dictPath);
23+
};
24+
2025
var setDictionary = function(lang, dictPath) {
2126
ensureDefaultSpellCheck();
2227
return defaultSpellcheck.setDictionary(lang, dictPath);
@@ -72,6 +77,7 @@ var getDictionaryPath = function() {
7277
}
7378

7479
module.exports = {
80+
addDictionary: addDictionary,
7581
setDictionary: setDictionary,
7682
add: add,
7783
remove: remove,

src/main.cc

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,25 @@ class Spellchecker : public Nan::ObjectWrap {
1919
info.GetReturnValue().Set(info.This());
2020
}
2121

22+
static NAN_METHOD(AddDictionary) {
23+
Nan::HandleScope scope;
24+
25+
if (info.Length() < 1) {
26+
return Nan::ThrowError("Bad argument");
27+
}
28+
29+
Spellchecker* that = Nan::ObjectWrap::Unwrap<Spellchecker>(info.Holder());
30+
31+
std::string language = *String::Utf8Value(info[0]);
32+
std::string directory = ".";
33+
if (info.Length() > 1) {
34+
directory = *String::Utf8Value(info[1]);
35+
}
36+
37+
bool result = that->impl->AddDictionary(language, directory);
38+
info.GetReturnValue().Set(Nan::New(result));
39+
}
40+
2241
static NAN_METHOD(SetDictionary) {
2342
Nan::HandleScope scope;
2443

@@ -98,7 +117,7 @@ class Spellchecker : public Nan::ObjectWrap {
98117
that->impl->Add(word);
99118
return;
100119
}
101-
120+
102121
static NAN_METHOD(Remove) {
103122
Nan::HandleScope scope;
104123
if (info.Length() < 1) {
@@ -174,6 +193,7 @@ class Spellchecker : public Nan::ObjectWrap {
174193
tpl->SetClassName(Nan::New<String>("Spellchecker").ToLocalChecked());
175194
tpl->InstanceTemplate()->SetInternalFieldCount(1);
176195

196+
Nan::SetMethod(tpl->InstanceTemplate(), "addDictionary", Spellchecker::AddDictionary);
177197
Nan::SetMethod(tpl->InstanceTemplate(), "setDictionary", Spellchecker::SetDictionary);
178198
Nan::SetMethod(tpl->InstanceTemplate(), "getAvailableDictionaries", Spellchecker::GetAvailableDictionaries);
179199
Nan::SetMethod(tpl->InstanceTemplate(), "getCorrectionsForMisspelling", Spellchecker::GetCorrectionsForMisspelling);

src/spellchecker.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#ifndef SRC_SPELLCHECKER_H_
22
#define SRC_SPELLCHECKER_H_
33

4+
#include <locale>
45
#include <string>
56
#include <vector>
67
#include <stdint.h>
@@ -14,6 +15,7 @@ struct MisspelledRange {
1415

1516
class SpellcheckerImplementation {
1617
public:
18+
virtual bool AddDictionary(const std::string& language, const std::string& path) = 0;
1719
virtual bool SetDictionary(const std::string& language, const std::string& path) = 0;
1820
virtual std::vector<std::string> GetAvailableDictionaries(const std::string& path) = 0;
1921

@@ -29,7 +31,7 @@ class SpellcheckerImplementation {
2931
// NB: When using Hunspell, this will not modify the .dic file; custom words must be added each
3032
// time the spellchecker is created. Use a custom dictionary file.
3133
virtual void Add(const std::string& word) = 0;
32-
34+
3335
// Removes a word from the custom dictionary added by Add.
3436
// NB: When using Hunspell, this will not modify the .dic file; custom words must be added each
3537
// time the spellchecker is created. Use a custom dictionary file.

src/spellchecker_hunspell.cc

Lines changed: 69 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,25 @@
11
#include <cstdio>
22
#include <cwctype>
33
#include <algorithm>
4+
#include <stdexcept>
45
#include "../vendor/hunspell/src/hunspell/hunspell.hxx"
56
#include "spellchecker_hunspell.h"
67

78
namespace spellchecker {
89

9-
HunspellSpellchecker::HunspellSpellchecker() : hunspell(NULL), transcoder(NewTranscoder()) { }
10+
HunspellSpellchecker::HunspellSpellchecker() : transcoder(NewTranscoder()) { }
1011

1112
HunspellSpellchecker::~HunspellSpellchecker() {
12-
if (hunspell) {
13-
delete hunspell;
13+
for (size_t i = 0; i < hunspells.size(); ++i) {
14+
delete hunspells[i].second;
1415
}
1516

1617
if (transcoder) {
1718
FreeTranscoder(transcoder);
1819
}
1920
}
2021

21-
bool HunspellSpellchecker::SetDictionary(const std::string& language, const std::string& dirname) {
22-
if (hunspell) {
23-
delete hunspell;
24-
hunspell = NULL;
25-
}
26-
22+
bool HunspellSpellchecker::AddDictionary(const std::string& language, const std::string& dirname) {
2723
// NB: Hunspell uses underscore to separate language and locale, and Win8 uses
2824
// dash - if they use the wrong one, just silently replace it for them
2925
std::string lang = language;
@@ -39,25 +35,51 @@ bool HunspellSpellchecker::SetDictionary(const std::string& language, const std:
3935
}
4036
fclose(handle);
4137

42-
hunspell = new Hunspell(affixpath.c_str(), dpath.c_str());
38+
std::locale loc;
39+
try {
40+
// On Linux locale requires "UTF-8" suffix; e.g., "en_US.UTF-8"
41+
loc = std::locale((lang + ".UTF-8").c_str());
42+
} catch (std::runtime_error & e) {
43+
// On Windows locale names are different; e.g. en-US
44+
std::string langDashed = language;
45+
std::replace(langDashed.begin(), langDashed.end(), '_', '-');
46+
std::locale loc(langDashed.c_str());
47+
}
48+
49+
Hunspell* hunspell = new Hunspell(affixpath.c_str(), dpath.c_str());
50+
hunspells.push_back(std::make_pair(loc, hunspell));
4351
return true;
4452
}
4553

54+
bool HunspellSpellchecker::SetDictionary(const std::string& language, const std::string& dirname) {
55+
for (size_t i = 0; i < hunspells.size(); ++i) {
56+
delete hunspells[i].second;
57+
}
58+
hunspells.clear();
59+
60+
return AddDictionary(language, dirname);
61+
}
62+
4663
std::vector<std::string> HunspellSpellchecker::GetAvailableDictionaries(const std::string& path) {
4764
return std::vector<std::string>();
4865
}
4966

5067
bool HunspellSpellchecker::IsMisspelled(const std::string& word) {
51-
if (!hunspell) {
52-
return false;
68+
for (size_t i = 0; i < hunspells.size(); ++i) {
69+
Hunspell* hunspell = hunspells[i].second;
70+
bool misspelled = hunspell->spell(word.c_str()) == 0;
71+
if (!misspelled) {
72+
return false;
73+
}
5374
}
54-
return hunspell->spell(word.c_str()) == 0;
75+
76+
return true;
5577
}
5678

5779
std::vector<MisspelledRange> HunspellSpellchecker::CheckSpelling(const uint16_t *utf16_text, size_t utf16_length) {
5880
std::vector<MisspelledRange> result;
5981

60-
if (!hunspell || !transcoder) {
82+
if (hunspells.empty() || !transcoder) {
6183
return result;
6284
}
6385

@@ -80,7 +102,7 @@ std::vector<MisspelledRange> HunspellSpellchecker::CheckSpelling(const uint16_t
80102
break;
81103

82104
case in_separator:
83-
if (iswalpha(c)) {
105+
if (isAlpha(c)) {
84106
word_start = i;
85107
state = in_word;
86108
} else if (!iswpunct(c) && !iswspace(c)) {
@@ -89,20 +111,29 @@ std::vector<MisspelledRange> HunspellSpellchecker::CheckSpelling(const uint16_t
89111
break;
90112

91113
case in_word:
92-
if (c == '\'' && iswalpha(utf16_text[i + 1])) {
114+
if (c == '\'' && isAlpha(utf16_text[i + 1])) {
93115
i++;
94116
} else if (c == 0 || iswpunct(c) || iswspace(c)) {
95117
state = in_separator;
96118
bool converted = TranscodeUTF16ToUTF8(transcoder, (char *)utf8_buffer.data(), utf8_buffer.size(), utf16_text + word_start, i - word_start);
97119
if (converted) {
98-
if (hunspell->spell(utf8_buffer.data()) == 0) {
120+
bool all_misspelled = true;
121+
for (size_t i = 0; i < hunspells.size(); ++i) {
122+
Hunspell* hunspell = hunspells[i].second;
123+
bool misspelled = hunspell->spell(utf8_buffer.data()) == 0;
124+
if (!misspelled) {
125+
all_misspelled = false;
126+
break;
127+
}
128+
}
129+
if (all_misspelled) {
99130
MisspelledRange range;
100131
range.start = word_start;
101132
range.end = i;
102133
result.push_back(range);
103134
}
104135
}
105-
} else if (!iswalpha(c)) {
136+
} else if (!isAlpha(c)) {
106137
state = unknown;
107138
}
108139
break;
@@ -113,21 +144,25 @@ std::vector<MisspelledRange> HunspellSpellchecker::CheckSpelling(const uint16_t
113144
}
114145

115146
void HunspellSpellchecker::Add(const std::string& word) {
116-
if (hunspell) {
147+
if (!hunspells.empty()) {
148+
Hunspell* hunspell = hunspells[0].second;
117149
hunspell->add(word.c_str());
118150
}
119151
}
120152

121153
void HunspellSpellchecker::Remove(const std::string& word) {
122-
if (hunspell) {
154+
if (!hunspells.empty()) {
155+
Hunspell* hunspell = hunspells[0].second;
123156
hunspell->remove(word.c_str());
124157
}
125158
}
126159

127160
std::vector<std::string> HunspellSpellchecker::GetCorrectionsForMisspelling(const std::string& word) {
128161
std::vector<std::string> corrections;
129162

130-
if (hunspell) {
163+
for (size_t i = 0; i < hunspells.size(); ++i) {
164+
Hunspell* hunspell = hunspells[i].second;
165+
131166
char** slist;
132167
int size = hunspell->suggest(&slist, word.c_str());
133168

@@ -141,4 +176,17 @@ std::vector<std::string> HunspellSpellchecker::GetCorrectionsForMisspelling(cons
141176
return corrections;
142177
}
143178

179+
bool HunspellSpellchecker::isAlpha(std::wint_t c) const {
180+
if (iswalpha(c)) {
181+
return true;
182+
}
183+
for (size_t i = 0; i < hunspells.size(); ++i) {
184+
std::locale loc = hunspells[i].first;
185+
if (std::isalpha((wchar_t)c, loc)) {
186+
return true;
187+
}
188+
}
189+
return false;
190+
}
191+
144192
} // namespace spellchecker

src/spellchecker_hunspell.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class HunspellSpellchecker : public SpellcheckerImplementation {
1313
HunspellSpellchecker();
1414
~HunspellSpellchecker();
1515

16+
bool AddDictionary(const std::string& language, const std::string& path);
1617
bool SetDictionary(const std::string& language, const std::string& path);
1718
std::vector<std::string> GetAvailableDictionaries(const std::string& path);
1819
std::vector<std::string> GetCorrectionsForMisspelling(const std::string& word);
@@ -22,7 +23,9 @@ class HunspellSpellchecker : public SpellcheckerImplementation {
2223
void Remove(const std::string& word);
2324

2425
private:
25-
Hunspell* hunspell;
26+
bool isAlpha(std::wint_t c) const;
27+
28+
std::vector<std::pair<std::locale, Hunspell*>> hunspells;
2629
Transcoder *transcoder;
2730
};
2831

src/spellchecker_mac.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@ class MacSpellchecker : public SpellcheckerImplementation {
1313
MacSpellchecker();
1414
~MacSpellchecker();
1515

16+
bool AddDictionary(const std::string& language, const std::string& path);
1617
bool SetDictionary(const std::string& language, const std::string& path);
1718
std::vector<std::string> GetAvailableDictionaries(const std::string& path);
1819
std::vector<std::string> GetCorrectionsForMisspelling(const std::string& word);
1920
bool IsMisspelled(const std::string& word);
2021
std::vector<MisspelledRange> CheckSpelling(const uint16_t *text, size_t length);
2122
void Add(const std::string& word);
2223
void Remove(const std::string& word);
23-
24+
2425
private:
2526
NSSpellChecker* spellChecker;
2627
NSString* spellCheckerLanguage;

src/spellchecker_mac.mm

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
this->spellCheckerLanguage = nil;
1919
}
2020

21+
bool MacSpellchecker::AddDictionary(const std::string& language, const std::string& path) {
22+
// TODO: write appropriate method
23+
return this->SetDictionary(language, path);
24+
}
25+
2126
bool MacSpellchecker::SetDictionary(const std::string& language, const std::string& path) {
2227
@autoreleasepool {
2328
[this->spellCheckerLanguage release];

0 commit comments

Comments
 (0)