Introduces in-place phrase replacement.
Since we have implemented the functions to add and exlcude phrases, the commit allows users to use a table to change the output of a phrase without changing its BPMF reading and score, when the "phrase replacement" mode is on. It could help users to switch a specific input scenario and the ordinary one. For example, if a user wants to work on financial Chinese numbers like 壹、貳、參, he or she may want the characters to have higher score as the normal numbers like 一、二、三. The commit can let the users to temporarily replace 一、二、三 to 壹、貳、參 by just turn on "phrase replacement" mode and prepare a custom table. The conversion is not done on the output phase like how we do Traditional/Simplified Chinese conversion. What the phrase replacement table does is to slightly modify the language model. The replacement takes place on walking the nodes and candidates list. A user can enable the mode and edit the table from the input menu. Since the function is quite advanced, the menu items are hidden until the user holds the option key. The table is a plain text file. Each line contains a "from" and "to". For example ``` 一 壹 ``` However, if the user also want all other phrase contain 一 to become 壹, all of the phrases have to be built into the table ``` 一百 壹佰 一千 壹仟 一萬 壹萬 一百萬 壹百萬 ```
This commit is contained in:
parent
825ed4f122
commit
136ac34f22
|
@ -50,6 +50,7 @@
|
||||||
D44FB74527915565003C80A6 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74427915555003C80A6 /* Preferences.swift */; };
|
D44FB74527915565003C80A6 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74427915555003C80A6 /* Preferences.swift */; };
|
||||||
D44FB74727919D35003C80A6 /* EmacsKeyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74627919C83003C80A6 /* EmacsKeyHelper.swift */; };
|
D44FB74727919D35003C80A6 /* EmacsKeyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74627919C83003C80A6 /* EmacsKeyHelper.swift */; };
|
||||||
D44FB74A2791B829003C80A6 /* VXHanConvert in Frameworks */ = {isa = PBXBuildFile; productRef = D44FB7492791B829003C80A6 /* VXHanConvert */; };
|
D44FB74A2791B829003C80A6 /* VXHanConvert in Frameworks */ = {isa = PBXBuildFile; productRef = D44FB7492791B829003C80A6 /* VXHanConvert */; };
|
||||||
|
D44FB74D2792189A003C80A6 /* PhraseReplacementMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74B2792189A003C80A6 /* PhraseReplacementMap.cpp */; };
|
||||||
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */; };
|
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */; };
|
||||||
D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */; };
|
D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */; };
|
||||||
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */; };
|
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */; };
|
||||||
|
@ -181,6 +182,8 @@
|
||||||
D44FB74427915555003C80A6 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
|
D44FB74427915555003C80A6 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
|
||||||
D44FB74627919C83003C80A6 /* EmacsKeyHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmacsKeyHelper.swift; sourceTree = "<group>"; };
|
D44FB74627919C83003C80A6 /* EmacsKeyHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmacsKeyHelper.swift; sourceTree = "<group>"; };
|
||||||
D44FB7482791B346003C80A6 /* VXHanConvert */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = VXHanConvert; path = Packages/VXHanConvert; sourceTree = "<group>"; };
|
D44FB7482791B346003C80A6 /* VXHanConvert */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = VXHanConvert; path = Packages/VXHanConvert; sourceTree = "<group>"; };
|
||||||
|
D44FB74B2792189A003C80A6 /* PhraseReplacementMap.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PhraseReplacementMap.cpp; sourceTree = "<group>"; };
|
||||||
|
D44FB74C2792189A003C80A6 /* PhraseReplacementMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PhraseReplacementMap.h; sourceTree = "<group>"; };
|
||||||
D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
|
D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
|
||||||
D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = "<group>"; };
|
D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = "<group>"; };
|
||||||
D47F7DD1278C1263002F9DD7 /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = "<group>"; };
|
D47F7DD1278C1263002F9DD7 /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = "<group>"; };
|
||||||
|
@ -292,6 +295,8 @@
|
||||||
6ACC3D3C27914AAB00F1B140 /* KeyValueBlobReader.h */,
|
6ACC3D3C27914AAB00F1B140 /* KeyValueBlobReader.h */,
|
||||||
D41355DC278EA3ED005E5CBD /* UserPhrasesLM.cpp */,
|
D41355DC278EA3ED005E5CBD /* UserPhrasesLM.cpp */,
|
||||||
D41355DD278EA3ED005E5CBD /* UserPhrasesLM.h */,
|
D41355DD278EA3ED005E5CBD /* UserPhrasesLM.h */,
|
||||||
|
D44FB74B2792189A003C80A6 /* PhraseReplacementMap.cpp */,
|
||||||
|
D44FB74C2792189A003C80A6 /* PhraseReplacementMap.h */,
|
||||||
D41355D9278E6D17005E5CBD /* McBopomofoLM.cpp */,
|
D41355D9278E6D17005E5CBD /* McBopomofoLM.cpp */,
|
||||||
D41355DA278E6D17005E5CBD /* McBopomofoLM.h */,
|
D41355DA278E6D17005E5CBD /* McBopomofoLM.h */,
|
||||||
D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */,
|
D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */,
|
||||||
|
@ -589,6 +594,7 @@
|
||||||
D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */,
|
D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */,
|
||||||
D44FB74727919D35003C80A6 /* EmacsKeyHelper.swift in Sources */,
|
D44FB74727919D35003C80A6 /* EmacsKeyHelper.swift in Sources */,
|
||||||
6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */,
|
6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */,
|
||||||
|
D44FB74D2792189A003C80A6 /* PhraseReplacementMap.cpp in Sources */,
|
||||||
D44FB74527915565003C80A6 /* Preferences.swift in Sources */,
|
D44FB74527915565003C80A6 /* Preferences.swift in Sources */,
|
||||||
D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */,
|
D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */,
|
||||||
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */,
|
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */,
|
||||||
|
|
|
@ -52,7 +52,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle
|
||||||
|
|
||||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||||
LanguageModelManager.loadDataModels()
|
LanguageModelManager.loadDataModels()
|
||||||
LanguageModelManager.loadUserPhrasesModel()
|
LanguageModelManager.loadUserPhrases()
|
||||||
|
LanguageModelManager.loadUserPhraseReplacement()
|
||||||
|
|
||||||
if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil {
|
if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil {
|
||||||
UserDefaults.standard.set(true, forKey: kCheckUpdateAutomatically)
|
UserDefaults.standard.set(true, forKey: kCheckUpdateAutomatically)
|
||||||
|
|
|
@ -37,6 +37,7 @@ McBopomofoLM::~McBopomofoLM()
|
||||||
m_languageModel.close();
|
m_languageModel.close();
|
||||||
m_userPhrases.close();
|
m_userPhrases.close();
|
||||||
m_excludedPhrases.close();
|
m_excludedPhrases.close();
|
||||||
|
m_phraseReplacement.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void McBopomofoLM::loadLanguageModel(const char* languageModelDataPath)
|
void McBopomofoLM::loadLanguageModel(const char* languageModelDataPath)
|
||||||
|
@ -60,6 +61,13 @@ void McBopomofoLM::loadUserPhrases(const char* userPhrasesDataPath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void McBopomofoLM::loadPhraseReplacementMap(const char* phraseReplacementPath) {
|
||||||
|
if (phraseReplacementPath) {
|
||||||
|
m_phraseReplacement.close();
|
||||||
|
m_phraseReplacement.open(phraseReplacementPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const vector<Bigram> McBopomofoLM::bigramsForKeys(const string& preceedingKey, const string& key)
|
const vector<Bigram> McBopomofoLM::bigramsForKeys(const string& preceedingKey, const string& key)
|
||||||
{
|
{
|
||||||
return vector<Bigram>();
|
return vector<Bigram>();
|
||||||
|
@ -83,24 +91,45 @@ const vector<Unigram> McBopomofoLM::unigramsForKey(const string& key)
|
||||||
|
|
||||||
if (m_userPhrases.hasUnigramsForKey(key)) {
|
if (m_userPhrases.hasUnigramsForKey(key)) {
|
||||||
vector<Unigram> rawUserUnigrams = m_userPhrases.unigramsForKey(key);
|
vector<Unigram> rawUserUnigrams = m_userPhrases.unigramsForKey(key);
|
||||||
|
vector<Unigram> filterredUserUnigrams = m_userPhrases.unigramsForKey(key);
|
||||||
|
|
||||||
for (auto&& unigram : rawUserUnigrams) {
|
for (auto&& unigram : rawUserUnigrams) {
|
||||||
if (excludedValues.find(unigram.keyValue.value) == excludedValues.end()) {
|
if (excludedValues.find(unigram.keyValue.value) == excludedValues.end()) {
|
||||||
userUnigrams.push_back(unigram);
|
filterredUserUnigrams.push_back(unigram);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transform(userUnigrams.begin(), userUnigrams.end(),
|
transform(filterredUserUnigrams.begin(), filterredUserUnigrams.end(),
|
||||||
inserter(userValues, userValues.end()),
|
inserter(userValues, userValues.end()),
|
||||||
[](const Unigram &u) { return u.keyValue.value; });
|
[](const Unigram &u) { return u.keyValue.value; });
|
||||||
|
|
||||||
|
if (m_phraseReplacementEnabled) {
|
||||||
|
for (auto&& unigram : filterredUserUnigrams) {
|
||||||
|
string value = unigram.keyValue.value;
|
||||||
|
string replacement = m_phraseReplacement.valueForKey(value);
|
||||||
|
if (replacement != "") {
|
||||||
|
unigram.keyValue.value = replacement;
|
||||||
|
}
|
||||||
|
unigrams.push_back(unigram);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unigrams = filterredUserUnigrams;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_languageModel.hasUnigramsForKey(key)) {
|
if (m_languageModel.hasUnigramsForKey(key)) {
|
||||||
vector<Unigram> globalUnigrams = m_languageModel.unigramsForKey(key);
|
vector<Unigram> globalUnigrams = m_languageModel.unigramsForKey(key);
|
||||||
|
|
||||||
for (auto&& unigram : globalUnigrams) {
|
for (auto&& unigram : globalUnigrams) {
|
||||||
if (excludedValues.find(unigram.keyValue.value) == excludedValues.end() &&
|
string value = unigram.keyValue.value;
|
||||||
userValues.find(unigram.keyValue.value) == userValues.end()) {
|
if (excludedValues.find(value) == excludedValues.end() &&
|
||||||
|
userValues.find(value) == userValues.end()) {
|
||||||
|
if (m_phraseReplacementEnabled) {
|
||||||
|
string replacement = m_phraseReplacement.valueForKey(value);
|
||||||
|
if (replacement != "") {
|
||||||
|
unigram.keyValue.value = replacement;
|
||||||
|
}
|
||||||
|
}
|
||||||
unigrams.push_back(unigram);
|
unigrams.push_back(unigram);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,3 +148,14 @@ bool McBopomofoLM::hasUnigramsForKey(const string& key)
|
||||||
|
|
||||||
return unigramsForKey(key).size() > 0;
|
return unigramsForKey(key).size() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void McBopomofoLM::setPhraseReplacementEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
m_phraseReplacementEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool McBopomofoLM::phraseReplacementEnabled()
|
||||||
|
{
|
||||||
|
return m_phraseReplacementEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "FastLM.h"
|
#include "FastLM.h"
|
||||||
#include "UserPhrasesLM.h"
|
#include "UserPhrasesLM.h"
|
||||||
|
#include "PhraseReplacementMap.h"
|
||||||
|
|
||||||
namespace McBopomofo {
|
namespace McBopomofo {
|
||||||
|
|
||||||
|
@ -38,17 +39,23 @@ public:
|
||||||
~McBopomofoLM();
|
~McBopomofoLM();
|
||||||
|
|
||||||
void loadLanguageModel(const char* languageModelDataPath);
|
void loadLanguageModel(const char* languageModelDataPath);
|
||||||
void loadUserPhrases(const char* m_userPhrasesDataPath,
|
void loadUserPhrases(const char* userPhrasesDataPath,
|
||||||
const char* m_excludedPhrasesDataPath);
|
const char* excludedPhrasesDataPath);
|
||||||
|
void loadPhraseReplacementMap(const char* phraseReplacementPath);
|
||||||
|
|
||||||
const vector<Bigram> bigramsForKeys(const string& preceedingKey, const string& key);
|
const vector<Bigram> bigramsForKeys(const string& preceedingKey, const string& key);
|
||||||
const vector<Unigram> unigramsForKey(const string& key);
|
const vector<Unigram> unigramsForKey(const string& key);
|
||||||
bool hasUnigramsForKey(const string& key);
|
bool hasUnigramsForKey(const string& key);
|
||||||
|
|
||||||
|
void setPhraseReplacementEnabled(bool enabled);
|
||||||
|
bool phraseReplacementEnabled();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
FastLM m_languageModel;
|
FastLM m_languageModel;
|
||||||
UserPhrasesLM m_userPhrases;
|
UserPhrasesLM m_userPhrases;
|
||||||
UserPhrasesLM m_excludedPhrases;
|
UserPhrasesLM m_excludedPhrases;
|
||||||
|
PhraseReplacementMap m_phraseReplacement;
|
||||||
|
bool m_phraseReplacementEnabled;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
#include "PhraseReplacementMap.h"
|
||||||
|
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "KeyValueBlobReader.h"
|
||||||
|
|
||||||
|
namespace McBopomofo {
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
|
||||||
|
PhraseReplacementMap::PhraseReplacementMap()
|
||||||
|
: fd(-1)
|
||||||
|
, data(0)
|
||||||
|
, length(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
PhraseReplacementMap::~PhraseReplacementMap()
|
||||||
|
{
|
||||||
|
if (data) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PhraseReplacementMap::open(const char *path)
|
||||||
|
{
|
||||||
|
if (data) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fd = ::open(path, O_RDONLY);
|
||||||
|
if (fd == -1) {
|
||||||
|
printf("open:: file not exist");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct stat sb;
|
||||||
|
if (fstat(fd, &sb) == -1) {
|
||||||
|
printf("open:: cannot open file");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
length = (size_t)sb.st_size;
|
||||||
|
|
||||||
|
data = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, 0);
|
||||||
|
if (!data) {
|
||||||
|
::close(fd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyValueBlobReader reader(static_cast<char*>(data), length);
|
||||||
|
KeyValueBlobReader::KeyValue keyValue;
|
||||||
|
KeyValueBlobReader::State state;
|
||||||
|
while ((state = reader.Next(&keyValue)) == KeyValueBlobReader::State::HAS_PAIR) {
|
||||||
|
keyValueMap[keyValue.key] = keyValue.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state == KeyValueBlobReader::State::ERROR) {
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhraseReplacementMap::close()
|
||||||
|
{
|
||||||
|
if (data) {
|
||||||
|
munmap(data, length);
|
||||||
|
::close(fd);
|
||||||
|
data = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
keyValueMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string PhraseReplacementMap::valueForKey(const std::string& key)
|
||||||
|
{
|
||||||
|
auto iter = keyValueMap.find(key);
|
||||||
|
if (iter != keyValueMap.end()) {
|
||||||
|
const std::string_view v = iter->second;
|
||||||
|
return {v.data(), v.size()};
|
||||||
|
}
|
||||||
|
return string("");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
#ifndef PHRASEREPLACEMENTMAP_H
|
||||||
|
#define PHRASEREPLACEMENTMAP_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace McBopomofo {
|
||||||
|
|
||||||
|
class PhraseReplacementMap
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PhraseReplacementMap();
|
||||||
|
~PhraseReplacementMap();
|
||||||
|
|
||||||
|
bool open(const char *path);
|
||||||
|
void close();
|
||||||
|
const std::string valueForKey(const std::string& key);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::map<std::string_view, std::string_view> keyValueMap;
|
||||||
|
int fd;
|
||||||
|
void *data;
|
||||||
|
size_t length;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -142,6 +142,7 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
||||||
|
|
||||||
// create the lattice builder
|
// create the lattice builder
|
||||||
_languageModel = [LanguageModelManager languageModelMcBopomofo];
|
_languageModel = [LanguageModelManager languageModelMcBopomofo];
|
||||||
|
_languageModel->setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled);
|
||||||
_userOverrideModel = [LanguageModelManager userOverrideModel];
|
_userOverrideModel = [LanguageModelManager userOverrideModel];
|
||||||
|
|
||||||
_builder = new BlockReadingBuilder(_languageModel);
|
_builder = new BlockReadingBuilder(_languageModel);
|
||||||
|
@ -165,14 +166,19 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
||||||
|
|
||||||
[menu addItemWithTitle:NSLocalizedString(@"McBopomofo Preferences", @"") action:@selector(showPreferences:) keyEquivalent:@""];
|
[menu addItemWithTitle:NSLocalizedString(@"McBopomofo Preferences", @"") action:@selector(showPreferences:) keyEquivalent:@""];
|
||||||
|
|
||||||
NSMenuItem *chineseConversionMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Chinese Conversion", @"") action:@selector(toggleChineseConverter:) keyEquivalent:@"g"];
|
NSMenuItem *chineseConversionMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Chinese Conversion", @"") action:@selector(toggleChineseConverter:) keyEquivalent:@"g"];
|
||||||
chineseConversionMenuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl;
|
chineseConversionMenuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl;
|
||||||
chineseConversionMenuItem.state = Preferences.chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff;
|
chineseConversionMenuItem.state = Preferences.chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff;
|
||||||
[menu addItem:chineseConversionMenuItem];
|
|
||||||
|
|
||||||
NSMenuItem *halfWidthPunctuationMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Use Half-Width Punctuations", @"") action:@selector(toggleHalfWidthPunctuation:) keyEquivalent:@""];
|
NSMenuItem *halfWidthPunctuationMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Use Half-Width Punctuations", @"") action:@selector(toggleHalfWidthPunctuation:) keyEquivalent:@""];
|
||||||
halfWidthPunctuationMenuItem.state = Preferences.halfWidthPunctuationEnabled ? NSControlStateValueOn : NSControlStateValueOff;
|
halfWidthPunctuationMenuItem.state = Preferences.halfWidthPunctuationEnabled ? NSControlStateValueOn : NSControlStateValueOff;
|
||||||
[menu addItem:halfWidthPunctuationMenuItem];
|
|
||||||
|
BOOL optionKeyPressed = [[NSEvent class] respondsToSelector:@selector(modifierFlags)] && ([NSEvent modifierFlags] & NSAlternateKeyMask);
|
||||||
|
|
||||||
|
if (_inputMode == kBopomofoModeIdentifier && optionKeyPressed) {
|
||||||
|
NSMenuItem *phaseReplacementMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Use Phrase Replacement", @"") action:@selector(togglePhraseReplacementEnabled:) keyEquivalent:@""];
|
||||||
|
phaseReplacementMenuItem.state = Preferences.phraseReplacementEnabled ? NSControlStateValueOn : NSControlStateValueOff;
|
||||||
|
}
|
||||||
|
|
||||||
[menu addItem:[NSMenuItem separatorItem]];
|
[menu addItem:[NSMenuItem separatorItem]];
|
||||||
[menu addItemWithTitle:NSLocalizedString(@"User Phrases", @"") action:NULL keyEquivalent:@""];
|
[menu addItemWithTitle:NSLocalizedString(@"User Phrases", @"") action:NULL keyEquivalent:@""];
|
||||||
|
@ -183,6 +189,9 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
||||||
else {
|
else {
|
||||||
[menu addItemWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""];
|
[menu addItemWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""];
|
||||||
[menu addItemWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesMcBopomofo:) keyEquivalent:@""];
|
[menu addItemWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesMcBopomofo:) keyEquivalent:@""];
|
||||||
|
if (optionKeyPressed) {
|
||||||
|
[menu addItemWithTitle:NSLocalizedString(@"Edit Phrase Replacement Table", @"") action:@selector(openPhraseReplacementMcBopomofo:) keyEquivalent:@""];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
[menu addItemWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""];
|
[menu addItemWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""];
|
||||||
[menu addItem:[NSMenuItem separatorItem]];
|
[menu addItem:[NSMenuItem separatorItem]];
|
||||||
|
@ -270,6 +279,7 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
||||||
else {
|
else {
|
||||||
newInputMode = kBopomofoModeIdentifier;
|
newInputMode = kBopomofoModeIdentifier;
|
||||||
newLanguageModel = [LanguageModelManager languageModelMcBopomofo];
|
newLanguageModel = [LanguageModelManager languageModelMcBopomofo];
|
||||||
|
newLanguageModel->setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only apply the changes if the value is changed
|
// Only apply the changes if the value is changed
|
||||||
|
@ -1481,6 +1491,30 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
||||||
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)toggleChineseConverter:(id)sender
|
||||||
|
{
|
||||||
|
BOOL chineseConversionEnabled = [Preferences toggleChineseConversionEnabled];
|
||||||
|
[NotifierController notifyWithMessage:
|
||||||
|
chineseConversionEnabled ?
|
||||||
|
NSLocalizedString(@"Chinese conversion on", @"") :
|
||||||
|
NSLocalizedString(@"Chinese conversion off", @"") stay:NO];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)toggleHalfWidthPunctuation:(id)sender
|
||||||
|
{
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wunused-result"
|
||||||
|
[Preferences tooglePhraseReplacementEnabled];
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)togglePhraseReplacementEnabled:(id)sender
|
||||||
|
{
|
||||||
|
BOOL enabled = [Preferences tooglePhraseReplacementEnabled];
|
||||||
|
McBopomofoLM *lm = [LanguageModelManager languageModelMcBopomofo];
|
||||||
|
lm->setPhraseReplacementEnabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
- (void)checkForUpdate:(id)sender
|
- (void)checkForUpdate:(id)sender
|
||||||
{
|
{
|
||||||
[(AppDelegate *)[[NSApplication sharedApplication] delegate] checkForUpdateForced:YES];
|
[(AppDelegate *)[[NSApplication sharedApplication] delegate] checkForUpdateForced:YES];
|
||||||
|
@ -1521,9 +1555,15 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
||||||
[self _openUserFile:[LanguageModelManager excludedPhrasesDataPathMcBopomofo]];
|
[self _openUserFile:[LanguageModelManager excludedPhrasesDataPathMcBopomofo]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)openPhraseReplacementMcBopomofo:(id)sender
|
||||||
|
{
|
||||||
|
[self _openUserFile:[LanguageModelManager phraseReplacementDataPathMcBopomofo]];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)reloadUserPhrases:(id)sender
|
- (void)reloadUserPhrases:(id)sender
|
||||||
{
|
{
|
||||||
[LanguageModelManager loadUserPhrasesModel];
|
[LanguageModelManager loadUserPhrases];
|
||||||
|
[LanguageModelManager loadUserPhraseReplacement];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)showAbout:(id)sender
|
- (void)showAbout:(id)sender
|
||||||
|
@ -1532,22 +1572,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
||||||
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)toggleChineseConverter:(id)sender
|
|
||||||
{
|
|
||||||
BOOL chineseConversionEnabled = [Preferences toggleChineseConversionEnabled];
|
|
||||||
[NotifierController notifyWithMessage:
|
|
||||||
chineseConversionEnabled ?
|
|
||||||
NSLocalizedString(@"Chinese conversion on", @"") :
|
|
||||||
NSLocalizedString(@"Chinese conversion off", @"") stay:NO];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)toggleHalfWidthPunctuation:(id)sender
|
|
||||||
{
|
|
||||||
#pragma GCC diagnostic push
|
|
||||||
#pragma GCC diagnostic ignored "-Wunused-result"
|
|
||||||
[Preferences toogleHalfWidthPunctuationEnabled];
|
|
||||||
#pragma GCC diagnostic pop
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
@interface LanguageModelManager : NSObject
|
@interface LanguageModelManager : NSObject
|
||||||
|
|
||||||
+ (void)loadDataModels;
|
+ (void)loadDataModels;
|
||||||
+ (void)loadUserPhrasesModel;
|
+ (void)loadUserPhrases;
|
||||||
|
+ (void)loadUserPhraseReplacement;
|
||||||
+ (BOOL)checkIfUserLanguageModelFilesExist;
|
+ (BOOL)checkIfUserLanguageModelFilesExist;
|
||||||
+ (BOOL)writeUserPhrase:(NSString *)userPhrase;
|
+ (BOOL)writeUserPhrase:(NSString *)userPhrase;
|
||||||
|
|
||||||
|
@ -16,6 +17,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
@property (class, readonly, nonatomic) NSString *userPhrasesDataPathMcBopomofo;
|
@property (class, readonly, nonatomic) NSString *userPhrasesDataPathMcBopomofo;
|
||||||
@property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathMcBopomofo;
|
@property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathMcBopomofo;
|
||||||
@property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathPlainBopomofo;
|
@property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathPlainBopomofo;
|
||||||
|
@property (class, readonly, nonatomic) NSString *phraseReplacementDataPathMcBopomofo;
|
||||||
@property (class, readonly, nonatomic) McBopomofo::McBopomofoLM *languageModelMcBopomofo;
|
@property (class, readonly, nonatomic) McBopomofo::McBopomofoLM *languageModelMcBopomofo;
|
||||||
@property (class, readonly, nonatomic) McBopomofo::McBopomofoLM *languageModelPlainBopomofo;
|
@property (class, readonly, nonatomic) McBopomofo::McBopomofoLM *languageModelPlainBopomofo;
|
||||||
@property (class, readonly, nonatomic) McBopomofo::UserOverrideModel *userOverrideModel;
|
@property (class, readonly, nonatomic) McBopomofo::UserOverrideModel *userOverrideModel;
|
||||||
|
|
|
@ -32,12 +32,17 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, McBopomo
|
||||||
LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelPlainBopomofo);
|
LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelPlainBopomofo);
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (void)loadUserPhrasesModel
|
+ (void)loadUserPhrases
|
||||||
{
|
{
|
||||||
gLanguageModelMcBopomofo.loadUserPhrases([[self userPhrasesDataPathMcBopomofo] UTF8String], [[self excludedPhrasesDataPathMcBopomofo] UTF8String]);
|
gLanguageModelMcBopomofo.loadUserPhrases([[self userPhrasesDataPathMcBopomofo] UTF8String], [[self excludedPhrasesDataPathMcBopomofo] UTF8String]);
|
||||||
gLanguageModelPlainBopomofo.loadUserPhrases(NULL, [[self excludedPhrasesDataPathPlainBopomofo] UTF8String]);
|
gLanguageModelPlainBopomofo.loadUserPhrases(NULL, [[self excludedPhrasesDataPathPlainBopomofo] UTF8String]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ (void)loadUserPhraseReplacement
|
||||||
|
{
|
||||||
|
gLanguageModelMcBopomofo.loadPhraseReplacementMap([[self phraseReplacementDataPathMcBopomofo] UTF8String]);
|
||||||
|
}
|
||||||
|
|
||||||
+ (BOOL)checkIfUserDataFolderExists
|
+ (BOOL)checkIfUserDataFolderExists
|
||||||
{
|
{
|
||||||
NSString *folderPath = [self dataFolderPath];
|
NSString *folderPath = [self dataFolderPath];
|
||||||
|
@ -89,6 +94,9 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, McBopomo
|
||||||
if (![self checkIfFileExist:[self excludedPhrasesDataPathPlainBopomofo]]) {
|
if (![self checkIfFileExist:[self excludedPhrasesDataPathPlainBopomofo]]) {
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
if (![self checkIfFileExist:[self phraseReplacementDataPathMcBopomofo]]) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +143,7 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, McBopomo
|
||||||
[writeFile writeData:data];
|
[writeFile writeData:data];
|
||||||
[writeFile closeFile];
|
[writeFile closeFile];
|
||||||
|
|
||||||
[self loadUserPhrasesModel];
|
[self loadUserPhrases];
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,6 +170,11 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, McBopomo
|
||||||
return [[self dataFolderPath] stringByAppendingPathComponent:@"exclude-phrases-plain-bpmf.txt"];
|
return [[self dataFolderPath] stringByAppendingPathComponent:@"exclude-phrases-plain-bpmf.txt"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ (NSString *)phraseReplacementDataPathMcBopomofo
|
||||||
|
{
|
||||||
|
return [[self dataFolderPath] stringByAppendingPathComponent:@"phrases-replacement.txt"];
|
||||||
|
}
|
||||||
|
|
||||||
+ (McBopomofoLM *)languageModelMcBopomofo
|
+ (McBopomofoLM *)languageModelMcBopomofo
|
||||||
{
|
{
|
||||||
return &gLanguageModelMcBopomofo;
|
return &gLanguageModelMcBopomofo;
|
||||||
|
|
|
@ -6,5 +6,6 @@
|
||||||
|
|
||||||
@interface LanguageModelManager : NSObject
|
@interface LanguageModelManager : NSObject
|
||||||
+ (void)loadDataModels;
|
+ (void)loadDataModels;
|
||||||
+ (void)loadUserPhrasesModel;
|
+ (void)loadUserPhrases;
|
||||||
|
+ (void)loadUserPhraseReplacement;
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -51,6 +51,7 @@ private let kCandidateTextFontName = "CandidateTextFontName"
|
||||||
private let kCandidateKeyLabelFontName = "CandidateKeyLabelFontName"
|
private let kCandidateKeyLabelFontName = "CandidateKeyLabelFontName"
|
||||||
private let kCandidateKeys = "CandidateKeys"
|
private let kCandidateKeys = "CandidateKeys"
|
||||||
private let kChineseConversionEngineKey = "ChineseConversionEngine"
|
private let kChineseConversionEngineKey = "ChineseConversionEngine"
|
||||||
|
private let kPhraseReplacementEnabledKey = "PhraseReplacementEnabled"
|
||||||
|
|
||||||
private let kDefaultCandidateListTextSize: CGFloat = 16
|
private let kDefaultCandidateListTextSize: CGFloat = 16
|
||||||
private let kMinKeyLabelSize: CGFloat = 10
|
private let kMinKeyLabelSize: CGFloat = 10
|
||||||
|
@ -291,4 +292,12 @@ class Preferences: NSObject {
|
||||||
return ChineseConversionEngine(rawValue: chineneConversionEngine)?.name
|
return ChineseConversionEngine(rawValue: chineneConversionEngine)?.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UserDefault(key: kPhraseReplacementEnabledKey, defaultValue: false)
|
||||||
|
@objc static var phraseReplacementEnabled: Bool
|
||||||
|
|
||||||
|
@objc static func tooglePhraseReplacementEnabled() -> Bool {
|
||||||
|
phraseReplacementEnabled = !phraseReplacementEnabled
|
||||||
|
return phraseReplacementEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,3 +72,7 @@
|
||||||
"Chinese conversion on" = "Chinese conversion on";
|
"Chinese conversion on" = "Chinese conversion on";
|
||||||
|
|
||||||
"Chinese conversion off" = "Chinese conversion off";
|
"Chinese conversion off" = "Chinese conversion off";
|
||||||
|
|
||||||
|
"Edit Phrase Replacement Table" = "Edit Phrase Replacement Table";
|
||||||
|
|
||||||
|
"Use Phrase Replacement" = "Use Phrase Replacement";
|
||||||
|
|
|
@ -72,3 +72,7 @@
|
||||||
"Chinese conversion on" = "已經切換到簡體中文模式";
|
"Chinese conversion on" = "已經切換到簡體中文模式";
|
||||||
|
|
||||||
"Chinese conversion off" = "已經切換到繁體中文模式";
|
"Chinese conversion off" = "已經切換到繁體中文模式";
|
||||||
|
|
||||||
|
"Edit Phrase Replacement Table" = "編輯詞彙替換表格";
|
||||||
|
|
||||||
|
"Use Phrase Replacement" = "使用詞彙替換";
|
||||||
|
|
Loading…
Reference in New Issue