From 136ac34f22b20fce02a6d803e3b583978318f96b Mon Sep 17 00:00:00 2001 From: zonble Date: Sat, 15 Jan 2022 06:23:09 +0800 Subject: [PATCH] Introduces in-place phrase replacement. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 ``` 一百 壹佰 一千 壹仟 一萬 壹萬 一百萬 壹百萬 ``` --- McBopomofo.xcodeproj/project.pbxproj | 6 ++ Source/AppDelegate.swift | 3 +- Source/Engine/McBopomofoLM.cpp | 48 +++++++++++-- Source/Engine/McBopomofoLM.h | 11 ++- Source/Engine/PhraseReplacementMap.cpp | 91 ++++++++++++++++++++++++ Source/Engine/PhraseReplacementMap.h | 29 ++++++++ Source/InputMethodController.mm | 65 +++++++++++------ Source/LanguageModelManager.h | 4 +- Source/LanguageModelManager.mm | 17 ++++- Source/McBopomofo-Bridging-Header.h | 3 +- Source/Preferences.swift | 9 +++ Source/en.lproj/Localizable.strings | 4 ++ Source/zh-Hant.lproj/Localizable.strings | 4 ++ 13 files changed, 263 insertions(+), 31 deletions(-) create mode 100644 Source/Engine/PhraseReplacementMap.cpp create mode 100644 Source/Engine/PhraseReplacementMap.h diff --git a/McBopomofo.xcodeproj/project.pbxproj b/McBopomofo.xcodeproj/project.pbxproj index 1a16216f..abeca958 100644 --- a/McBopomofo.xcodeproj/project.pbxproj +++ b/McBopomofo.xcodeproj/project.pbxproj @@ -50,6 +50,7 @@ D44FB74527915565003C80A6 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74427915555003C80A6 /* Preferences.swift */; }; D44FB74727919D35003C80A6 /* EmacsKeyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74627919C83003C80A6 /* EmacsKeyHelper.swift */; }; 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 */; }; D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */; }; 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 = ""; }; D44FB74627919C83003C80A6 /* EmacsKeyHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmacsKeyHelper.swift; sourceTree = ""; }; D44FB7482791B346003C80A6 /* VXHanConvert */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = VXHanConvert; path = Packages/VXHanConvert; sourceTree = ""; }; + D44FB74B2792189A003C80A6 /* PhraseReplacementMap.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PhraseReplacementMap.cpp; sourceTree = ""; }; + D44FB74C2792189A003C80A6 /* PhraseReplacementMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PhraseReplacementMap.h; sourceTree = ""; }; D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = ""; }; D47F7DD1278C1263002F9DD7 /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = ""; }; @@ -292,6 +295,8 @@ 6ACC3D3C27914AAB00F1B140 /* KeyValueBlobReader.h */, D41355DC278EA3ED005E5CBD /* UserPhrasesLM.cpp */, D41355DD278EA3ED005E5CBD /* UserPhrasesLM.h */, + D44FB74B2792189A003C80A6 /* PhraseReplacementMap.cpp */, + D44FB74C2792189A003C80A6 /* PhraseReplacementMap.h */, D41355D9278E6D17005E5CBD /* McBopomofoLM.cpp */, D41355DA278E6D17005E5CBD /* McBopomofoLM.h */, D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */, @@ -589,6 +594,7 @@ D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */, D44FB74727919D35003C80A6 /* EmacsKeyHelper.swift in Sources */, 6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */, + D44FB74D2792189A003C80A6 /* PhraseReplacementMap.cpp in Sources */, D44FB74527915565003C80A6 /* Preferences.swift in Sources */, D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */, D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */, diff --git a/Source/AppDelegate.swift b/Source/AppDelegate.swift index 94e553ec..f93bf8b2 100644 --- a/Source/AppDelegate.swift +++ b/Source/AppDelegate.swift @@ -52,7 +52,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle func applicationDidFinishLaunching(_ notification: Notification) { LanguageModelManager.loadDataModels() - LanguageModelManager.loadUserPhrasesModel() + LanguageModelManager.loadUserPhrases() + LanguageModelManager.loadUserPhraseReplacement() if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil { UserDefaults.standard.set(true, forKey: kCheckUpdateAutomatically) diff --git a/Source/Engine/McBopomofoLM.cpp b/Source/Engine/McBopomofoLM.cpp index 3d577fd2..ea85c2dc 100644 --- a/Source/Engine/McBopomofoLM.cpp +++ b/Source/Engine/McBopomofoLM.cpp @@ -37,6 +37,7 @@ McBopomofoLM::~McBopomofoLM() m_languageModel.close(); m_userPhrases.close(); m_excludedPhrases.close(); + m_phraseReplacement.close(); } 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 McBopomofoLM::bigramsForKeys(const string& preceedingKey, const string& key) { return vector(); @@ -83,24 +91,45 @@ const vector McBopomofoLM::unigramsForKey(const string& key) if (m_userPhrases.hasUnigramsForKey(key)) { vector rawUserUnigrams = m_userPhrases.unigramsForKey(key); + vector filterredUserUnigrams = m_userPhrases.unigramsForKey(key); for (auto&& unigram : rawUserUnigrams) { 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()), [](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)) { vector globalUnigrams = m_languageModel.unigramsForKey(key); for (auto&& unigram : globalUnigrams) { - if (excludedValues.find(unigram.keyValue.value) == excludedValues.end() && - userValues.find(unigram.keyValue.value) == userValues.end()) { + string value = unigram.keyValue.value; + 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); } } @@ -119,3 +148,14 @@ bool McBopomofoLM::hasUnigramsForKey(const string& key) return unigramsForKey(key).size() > 0; } + +void McBopomofoLM::setPhraseReplacementEnabled(bool enabled) +{ + m_phraseReplacementEnabled = enabled; +} + +bool McBopomofoLM::phraseReplacementEnabled() +{ + return m_phraseReplacementEnabled; +} + diff --git a/Source/Engine/McBopomofoLM.h b/Source/Engine/McBopomofoLM.h index 82b02f0d..46e28e2a 100644 --- a/Source/Engine/McBopomofoLM.h +++ b/Source/Engine/McBopomofoLM.h @@ -27,6 +27,7 @@ #include #include "FastLM.h" #include "UserPhrasesLM.h" +#include "PhraseReplacementMap.h" namespace McBopomofo { @@ -38,17 +39,23 @@ public: ~McBopomofoLM(); void loadLanguageModel(const char* languageModelDataPath); - void loadUserPhrases(const char* m_userPhrasesDataPath, - const char* m_excludedPhrasesDataPath); + void loadUserPhrases(const char* userPhrasesDataPath, + const char* excludedPhrasesDataPath); + void loadPhraseReplacementMap(const char* phraseReplacementPath); const vector bigramsForKeys(const string& preceedingKey, const string& key); const vector unigramsForKey(const string& key); bool hasUnigramsForKey(const string& key); + void setPhraseReplacementEnabled(bool enabled); + bool phraseReplacementEnabled(); + protected: FastLM m_languageModel; UserPhrasesLM m_userPhrases; UserPhrasesLM m_excludedPhrases; + PhraseReplacementMap m_phraseReplacement; + bool m_phraseReplacementEnabled; }; }; diff --git a/Source/Engine/PhraseReplacementMap.cpp b/Source/Engine/PhraseReplacementMap.cpp new file mode 100644 index 00000000..c0c3abbe --- /dev/null +++ b/Source/Engine/PhraseReplacementMap.cpp @@ -0,0 +1,91 @@ +#include "PhraseReplacementMap.h" + +#include +#include +#include +#include +#include + +#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(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(""); +} + + +} diff --git a/Source/Engine/PhraseReplacementMap.h b/Source/Engine/PhraseReplacementMap.h new file mode 100644 index 00000000..d58d7c17 --- /dev/null +++ b/Source/Engine/PhraseReplacementMap.h @@ -0,0 +1,29 @@ +#ifndef PHRASEREPLACEMENTMAP_H +#define PHRASEREPLACEMENTMAP_H + +#include +#include +#include + +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 keyValueMap; + int fd; + void *data; + size_t length; +}; + +} + +#endif diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 1b6ce782..762e7665 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -142,6 +142,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) // create the lattice builder _languageModel = [LanguageModelManager languageModelMcBopomofo]; + _languageModel->setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled); _userOverrideModel = [LanguageModelManager userOverrideModel]; _builder = new BlockReadingBuilder(_languageModel); @@ -165,14 +166,19 @@ static double FindHighestScore(const vector& nodes, double epsilon) [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.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; - [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 addItemWithTitle:NSLocalizedString(@"User Phrases", @"") action:NULL keyEquivalent:@""]; @@ -183,6 +189,9 @@ static double FindHighestScore(const vector& nodes, double epsilon) else { [menu addItemWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) 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 addItem:[NSMenuItem separatorItem]]; @@ -270,6 +279,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) else { newInputMode = kBopomofoModeIdentifier; newLanguageModel = [LanguageModelManager languageModelMcBopomofo]; + newLanguageModel->setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled); } // 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]; } +- (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 { [(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]]; } +- (void)openPhraseReplacementMcBopomofo:(id)sender +{ + [self _openUserFile:[LanguageModelManager phraseReplacementDataPathMcBopomofo]]; +} + - (void)reloadUserPhrases:(id)sender { - [LanguageModelManager loadUserPhrasesModel]; + [LanguageModelManager loadUserPhrases]; + [LanguageModelManager loadUserPhraseReplacement]; } - (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]; } -- (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 diff --git a/Source/LanguageModelManager.h b/Source/LanguageModelManager.h index d819e067..455d9b18 100644 --- a/Source/LanguageModelManager.h +++ b/Source/LanguageModelManager.h @@ -8,7 +8,8 @@ NS_ASSUME_NONNULL_BEGIN @interface LanguageModelManager : NSObject + (void)loadDataModels; -+ (void)loadUserPhrasesModel; ++ (void)loadUserPhrases; ++ (void)loadUserPhraseReplacement; + (BOOL)checkIfUserLanguageModelFilesExist; + (BOOL)writeUserPhrase:(NSString *)userPhrase; @@ -16,6 +17,7 @@ NS_ASSUME_NONNULL_BEGIN @property (class, readonly, nonatomic) NSString *userPhrasesDataPathMcBopomofo; @property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathMcBopomofo; @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 *languageModelPlainBopomofo; @property (class, readonly, nonatomic) McBopomofo::UserOverrideModel *userOverrideModel; diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index 51951041..189e2eb6 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -32,12 +32,17 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, McBopomo LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelPlainBopomofo); } -+ (void)loadUserPhrasesModel ++ (void)loadUserPhrases { gLanguageModelMcBopomofo.loadUserPhrases([[self userPhrasesDataPathMcBopomofo] UTF8String], [[self excludedPhrasesDataPathMcBopomofo] UTF8String]); gLanguageModelPlainBopomofo.loadUserPhrases(NULL, [[self excludedPhrasesDataPathPlainBopomofo] UTF8String]); } ++ (void)loadUserPhraseReplacement +{ + gLanguageModelMcBopomofo.loadPhraseReplacementMap([[self phraseReplacementDataPathMcBopomofo] UTF8String]); +} + + (BOOL)checkIfUserDataFolderExists { NSString *folderPath = [self dataFolderPath]; @@ -89,6 +94,9 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, McBopomo if (![self checkIfFileExist:[self excludedPhrasesDataPathPlainBopomofo]]) { return NO; } + if (![self checkIfFileExist:[self phraseReplacementDataPathMcBopomofo]]) { + return NO; + } return YES; } @@ -135,7 +143,7 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, McBopomo [writeFile writeData:data]; [writeFile closeFile]; - [self loadUserPhrasesModel]; + [self loadUserPhrases]; return YES; } @@ -162,6 +170,11 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, McBopomo return [[self dataFolderPath] stringByAppendingPathComponent:@"exclude-phrases-plain-bpmf.txt"]; } ++ (NSString *)phraseReplacementDataPathMcBopomofo +{ + return [[self dataFolderPath] stringByAppendingPathComponent:@"phrases-replacement.txt"]; +} + + (McBopomofoLM *)languageModelMcBopomofo { return &gLanguageModelMcBopomofo; diff --git a/Source/McBopomofo-Bridging-Header.h b/Source/McBopomofo-Bridging-Header.h index 084274e7..8310cc67 100644 --- a/Source/McBopomofo-Bridging-Header.h +++ b/Source/McBopomofo-Bridging-Header.h @@ -6,5 +6,6 @@ @interface LanguageModelManager : NSObject + (void)loadDataModels; -+ (void)loadUserPhrasesModel; ++ (void)loadUserPhrases; ++ (void)loadUserPhraseReplacement; @end diff --git a/Source/Preferences.swift b/Source/Preferences.swift index ea5e4021..e1f2303c 100644 --- a/Source/Preferences.swift +++ b/Source/Preferences.swift @@ -51,6 +51,7 @@ private let kCandidateTextFontName = "CandidateTextFontName" private let kCandidateKeyLabelFontName = "CandidateKeyLabelFontName" private let kCandidateKeys = "CandidateKeys" private let kChineseConversionEngineKey = "ChineseConversionEngine" +private let kPhraseReplacementEnabledKey = "PhraseReplacementEnabled" private let kDefaultCandidateListTextSize: CGFloat = 16 private let kMinKeyLabelSize: CGFloat = 10 @@ -291,4 +292,12 @@ class Preferences: NSObject { return ChineseConversionEngine(rawValue: chineneConversionEngine)?.name } + @UserDefault(key: kPhraseReplacementEnabledKey, defaultValue: false) + @objc static var phraseReplacementEnabled: Bool + + @objc static func tooglePhraseReplacementEnabled() -> Bool { + phraseReplacementEnabled = !phraseReplacementEnabled + return phraseReplacementEnabled; + } + } diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index 7aa8d586..2f07e92d 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -72,3 +72,7 @@ "Chinese conversion on" = "Chinese conversion on"; "Chinese conversion off" = "Chinese conversion off"; + +"Edit Phrase Replacement Table" = "Edit Phrase Replacement Table"; + +"Use Phrase Replacement" = "Use Phrase Replacement"; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index 98664234..c68a3e42 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -72,3 +72,7 @@ "Chinese conversion on" = "已經切換到簡體中文模式"; "Chinese conversion off" = "已經切換到繁體中文模式"; + +"Edit Phrase Replacement Table" = "編輯詞彙替換表格"; + +"Use Phrase Replacement" = "使用詞彙替換";