From 707fb9ac994033090cffb1b02955bc8117005a43 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 17 Jan 2022 01:06:15 +0800 Subject: [PATCH] Zonble: Add in-place phrase replacement system. - Also write the settings to the user plist (OOBE). --- Source/AppDelegate.swift | 3 +- .../LanguageModel/PhraseReplacementMap.cpp | 119 ++++++++++++++++++ .../LanguageModel/PhraseReplacementMap.h | 59 +++++++++ Source/Engine/LanguageModel/vChewingLM.cpp | 55 ++++++-- Source/Engine/LanguageModel/vChewingLM.h | 11 +- Source/Engine/vChewing/clsOOBEDefaults.swift | 6 + Source/InputMethodController.mm | 68 ++++++---- Source/LanguageModelManager.h | 4 +- Source/LanguageModelManager.mm | 25 +++- Source/PreferencesModule.swift | 9 ++ Source/en.lproj/Localizable.strings | 2 + Source/vChewing-Bridging-Header.h | 3 +- Source/zh-Hans.lproj/Localizable.strings | 2 + Source/zh-Hant.lproj/Localizable.strings | 2 + vChewing.xcodeproj/project.pbxproj | 8 ++ 15 files changed, 335 insertions(+), 41 deletions(-) create mode 100644 Source/Engine/LanguageModel/PhraseReplacementMap.cpp create mode 100644 Source/Engine/LanguageModel/PhraseReplacementMap.h diff --git a/Source/AppDelegate.swift b/Source/AppDelegate.swift index 04af4771..49a539ea 100644 --- a/Source/AppDelegate.swift +++ b/Source/AppDelegate.swift @@ -66,7 +66,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, func applicationDidFinishLaunching(_ notification: Notification) { LanguageModelManager.loadDataModels() - LanguageModelManager.loadUserPhrasesModel() + LanguageModelManager.loadUserPhrases() + LanguageModelManager.loadUserPhraseReplacement() OOBE.setMissingDefaults() diff --git a/Source/Engine/LanguageModel/PhraseReplacementMap.cpp b/Source/Engine/LanguageModel/PhraseReplacementMap.cpp new file mode 100644 index 00000000..06c8bf88 --- /dev/null +++ b/Source/Engine/LanguageModel/PhraseReplacementMap.cpp @@ -0,0 +1,119 @@ +// +// PhraseReplacementMap.cpp +// +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Weizhong Yang (@zonble) @ OpenVanilla +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#include +#include +#include +#include +#include +#include "KeyValueBlobReader.h" +#include "PhraseReplacementMap.h" + +namespace vChewing { + +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/LanguageModel/PhraseReplacementMap.h b/Source/Engine/LanguageModel/PhraseReplacementMap.h new file mode 100644 index 00000000..b775fc50 --- /dev/null +++ b/Source/Engine/LanguageModel/PhraseReplacementMap.h @@ -0,0 +1,59 @@ +// +// PhraseReplacementMap.h +// +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Weizhong Yang (@zonble) @ OpenVanilla +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#ifndef PHRASEREPLACEMENTMAP_H +#define PHRASEREPLACEMENTMAP_H + +#include +#include +#include + +namespace vChewing { + +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/Engine/LanguageModel/vChewingLM.cpp b/Source/Engine/LanguageModel/vChewingLM.cpp index 45b11683..3cdbf94e 100644 --- a/Source/Engine/LanguageModel/vChewingLM.cpp +++ b/Source/Engine/LanguageModel/vChewingLM.cpp @@ -50,6 +50,7 @@ vChewingLM::~vChewingLM() m_languageModel.close(); m_userPhrases.close(); m_excludedPhrases.close(); + m_phraseReplacement.close(); } void vChewingLM::loadLanguageModel(const char* languageModelDataPath) @@ -73,6 +74,13 @@ void vChewingLM::loadUserPhrases(const char* userPhrasesDataPath, } } +void vChewingLM::loadPhraseReplacementMap(const char* phraseReplacementPath) { + if (phraseReplacementPath) { + m_phraseReplacement.close(); + m_phraseReplacement.open(phraseReplacementPath); + } +} + const vector vChewingLM::bigramsForKeys(const string& preceedingKey, const string& key) { return vector(); @@ -96,24 +104,45 @@ const vector vChewingLM::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); } } @@ -132,3 +161,13 @@ bool vChewingLM::hasUnigramsForKey(const string& key) return unigramsForKey(key).size() > 0; } + +void vChewingLM::setPhraseReplacementEnabled(bool enabled) +{ + m_phraseReplacementEnabled = enabled; +} + +bool vChewingLM::phraseReplacementEnabled() +{ + return m_phraseReplacementEnabled; +} diff --git a/Source/Engine/LanguageModel/vChewingLM.h b/Source/Engine/LanguageModel/vChewingLM.h index 0d57fcea..cb42249a 100644 --- a/Source/Engine/LanguageModel/vChewingLM.h +++ b/Source/Engine/LanguageModel/vChewingLM.h @@ -40,6 +40,7 @@ #include #include "FastLM.h" #include "UserPhrasesLM.h" +#include "PhraseReplacementMap.h" namespace vChewing { @@ -51,17 +52,23 @@ public: ~vChewingLM(); 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/vChewing/clsOOBEDefaults.swift b/Source/Engine/vChewing/clsOOBEDefaults.swift index 56975c8b..8bc8fc95 100644 --- a/Source/Engine/vChewing/clsOOBEDefaults.swift +++ b/Source/Engine/vChewing/clsOOBEDefaults.swift @@ -40,6 +40,7 @@ private let kChooseCandidateUsingSpaceKey = "ChooseCandidateUsingSpaceKey" private let kSelectPhraseAfterCursorAsCandidate = "SelectPhraseAfterCursorAsCandidate" private let kUseHorizontalCandidateList = "UseHorizontalCandidateList" private let kChineseConversionEnabledKey = "ChineseConversionEnabled" +private let kPhraseReplacementEnabledKey = "PhraseReplacementEnabled" @objc public class OOBE : NSObject { @@ -76,6 +77,11 @@ private let kChineseConversionEnabledKey = "ChineseConversionEnabled" if UserDefaults.standard.object(forKey: kChineseConversionEnabledKey) == nil { UserDefaults.standard.set(Preferences.chineseConversionEnabled, forKey: kChineseConversionEnabledKey) } + + // 預設停用自訂語彙置換 + if UserDefaults.standard.object(forKey: kPhraseReplacementEnabledKey) == nil { + UserDefaults.standard.set(Preferences.phraseReplacementEnabled, forKey: kPhraseReplacementEnabledKey) + } // 預設沒事不要在那裡放屁 if UserDefaults.standard.object(forKey: kShouldNotFartInLieuOfBeep) == nil { diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 7582fdcf..3a2b9017 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -140,6 +140,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) // create the lattice builder _languageModel = [LanguageModelManager languageModelBopomofo]; + _languageModel->setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled); _userOverrideModel = [LanguageModelManager userOverrideModel]; _builder = new BlockReadingBuilder(_languageModel); @@ -158,22 +159,28 @@ static double FindHighestScore(const vector& nodes, double epsilon) - (NSMenu *)menu { + // Define the case which ALT / Option key is pressed. + BOOL optionKeyPressed = [[NSEvent class] respondsToSelector:@selector(modifierFlags)] && ([NSEvent modifierFlags] & NSAlternateKeyMask); + // a menu instance (autoreleased) is requested every time the user click on the input menu NSMenu *menu = [[NSMenu alloc] initWithTitle:LocalizationNotNeeded(@"Input Method Menu")]; [menu addItemWithTitle:NSLocalizedString(@"vChewing Preferences", @"") action:@selector(showPreferences:) keyEquivalent:@""]; - NSMenuItem *chineseConversionMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Chinese Conversion", @"") action:@selector(toggleChineseConverter:) keyEquivalent:@"K"]; + NSMenuItem *chineseConversionMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Chinese Conversion", @"") action:@selector(toggleChineseConverter:) keyEquivalent:@"K"]; 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]; + + if (optionKeyPressed) { + NSMenuItem *phaseReplacementMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Use Phrase Replacement", @"") action:@selector(togglePhraseReplacementEnabled:) keyEquivalent:@""]; + phaseReplacementMenuItem.state = Preferences.phraseReplacementEnabled ? NSControlStateValueOn : NSControlStateValueOff; + } [menu addItem:[NSMenuItem separatorItem]]; // ------------------------------ - + if (_inputMode == kSimpBopomofoModeIdentifier) { NSMenuItem *editExcludedPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesSimpBopomofo:) keyEquivalent:@""]; [menu addItem:editExcludedPhrasesItem]; @@ -181,9 +188,12 @@ 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(openExcludedPhrasesvChewing:) keyEquivalent:@""]; + if (optionKeyPressed) { + [menu addItemWithTitle:NSLocalizedString(@"Edit Phrase Replacement Table", @"") action:@selector(openPhraseReplacementvChewing:) keyEquivalent:@""]; + } } [menu addItemWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""]; - + [menu addItem:[NSMenuItem separatorItem]]; // ------------------------------ [menu addItemWithTitle:NSLocalizedString(@"Check for Updates…", @"") action:@selector(checkForUpdate:) keyEquivalent:@""]; @@ -273,6 +283,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) else { newInputMode = kBopomofoModeIdentifier; newLanguageModel = [LanguageModelManager languageModelBopomofo]; + newLanguageModel->setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled); } // Only apply the changes if the value is changed @@ -1486,6 +1497,27 @@ 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:[NSString stringWithFormat:@"%@%@%@", NSLocalizedString(@"Chinese Conversion", @""), @"\n", chineseConversionEnabled ? NSLocalizedString(@"NotificationSwitchON", @"") : NSLocalizedString(@"NotificationSwitchOFF", @"")] stay:NO]; +} + +- (void)toggleHalfWidthPunctuation:(id)sender +{ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-result" + [Preferences toogleHalfWidthPunctuationEnabled]; +#pragma GCC diagnostic pop +} + +- (void)togglePhraseReplacementEnabled:(id)sender +{ + BOOL enabled = [Preferences tooglePhraseReplacementEnabled]; + vChewingLM *lm = [LanguageModelManager languageModelBopomofo]; + lm->setPhraseReplacementEnabled(enabled); +} + - (void)checkForUpdate:(id)sender { [(AppDelegate *)[[NSApplication sharedApplication] delegate] checkForUpdateForced:YES]; @@ -1526,9 +1558,15 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self _openUserFile:[LanguageModelManager excludedPhrasesDataPathBopomofo]]; } +- (void)openPhraseReplacementvChewing:(id)sender +{ + [self _openUserFile:[LanguageModelManager phraseReplacementDataPathBopomofo]]; +} + - (void)reloadUserPhrases:(id)sender { - [LanguageModelManager loadUserPhrasesModel]; + [LanguageModelManager loadUserPhrases]; + [LanguageModelManager loadUserPhraseReplacement]; } - (void)showAbout:(id)sender @@ -1538,23 +1576,9 @@ 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:[NSString stringWithFormat:@"%@%@%@", NSLocalizedString(@"Chinese Conversion", @""), @"\n", chineseConversionEnabled ? NSLocalizedString(@"NotificationSwitchON", @"") : NSLocalizedString(@"NotificationSwitchOFF", @"")] stay:NO]; -} - -- (void)toggleHalfWidthPunctuation:(id)sender -{ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-result" - [Preferences toogleHalfWidthPunctuationEnabled]; -#pragma GCC diagnostic pop -} - @end -#pragma mark - +#pragma mark - Voltaire @implementation vChewingInputMethodController (VTCandidateController) diff --git a/Source/LanguageModelManager.h b/Source/LanguageModelManager.h index be4476a3..4fa02a92 100644 --- a/Source/LanguageModelManager.h +++ b/Source/LanguageModelManager.h @@ -44,7 +44,8 @@ NS_ASSUME_NONNULL_BEGIN @interface LanguageModelManager : NSObject + (void)loadDataModels; -+ (void)loadUserPhrasesModel; ++ (void)loadUserPhrases; ++ (void)loadUserPhraseReplacement; + (BOOL)checkIfUserLanguageModelFilesExist; + (BOOL)writeUserPhrase:(NSString *)userPhrase; @@ -52,6 +53,7 @@ NS_ASSUME_NONNULL_BEGIN @property (class, readonly, nonatomic) NSString *userPhrasesDataPathBopomofo; @property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathBopomofo; @property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathSimpBopomofo; +@property (class, readonly, nonatomic) NSString *phraseReplacementDataPathBopomofo; @property (class, readonly, nonatomic) vChewing::vChewingLM *languageModelBopomofo; @property (class, readonly, nonatomic) vChewing::vChewingLM *languageModelSimpBopomofo; @property (class, readonly, nonatomic) vChewing::UserOverrideModel *userOverrideModel; diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index 9ceed6c1..7e2ecd1d 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -49,7 +49,7 @@ using namespace OpenVanilla; static const int kUserOverrideModelCapacity = 500; static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr. -vChewingLM glanguageModelBopomofo; +vChewingLM gLanguageModelBopomofo; vChewingLM gLanguageModelSimpBopomofo; UserOverrideModel gUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife); @@ -64,16 +64,21 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing + (void)loadDataModels { - LTLoadLanguageModelFile(@"data", glanguageModelBopomofo); + LTLoadLanguageModelFile(@"data", gLanguageModelBopomofo); LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelSimpBopomofo); } -+ (void)loadUserPhrasesModel ++ (void)loadUserPhrases { - glanguageModelBopomofo.loadUserPhrases([[self userPhrasesDataPathBopomofo] UTF8String], [[self excludedPhrasesDataPathBopomofo] UTF8String]); + gLanguageModelBopomofo.loadUserPhrases([[self userPhrasesDataPathBopomofo] UTF8String], [[self excludedPhrasesDataPathBopomofo] UTF8String]); gLanguageModelSimpBopomofo.loadUserPhrases(NULL, [[self excludedPhrasesDataPathSimpBopomofo] UTF8String]); } ++ (void)loadUserPhraseReplacement +{ + gLanguageModelBopomofo.loadPhraseReplacementMap([[self phraseReplacementDataPathBopomofo] UTF8String]); +} + + (BOOL)checkIfUserDataFolderExists { NSString *folderPath = [self dataFolderPath]; @@ -125,6 +130,9 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing if (![self checkIfFileExist:[self excludedPhrasesDataPathSimpBopomofo]]) { return NO; } + if (![self checkIfFileExist:[self phraseReplacementDataPathBopomofo]]) { + return NO; + } return YES; } @@ -171,7 +179,7 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing [writeFile writeData:data]; [writeFile closeFile]; - [self loadUserPhrasesModel]; + [self loadUserPhrases]; return YES; } @@ -198,9 +206,14 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing return [[self dataFolderPath] stringByAppendingPathComponent:@"exclude-phrases-plain-bpmf.txt"]; } ++ (NSString *)phraseReplacementDataPathBopomofo +{ + return [[self dataFolderPath] stringByAppendingPathComponent:@"phrases-replacement.txt"]; +} + + (vChewingLM *)languageModelBopomofo { - return &glanguageModelBopomofo; + return &gLanguageModelBopomofo; } + (vChewingLM *)languageModelSimpBopomofo diff --git a/Source/PreferencesModule.swift b/Source/PreferencesModule.swift index 1413218b..39ea0b4d 100644 --- a/Source/PreferencesModule.swift +++ b/Source/PreferencesModule.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 = 18 private let kMinKeyLabelSize: CGFloat = 10 @@ -297,4 +298,12 @@ struct ComposingKeys { return ChineseConversionEngine(rawValue: chineseConversionEngine)?.name } + @UserDefault(key: kPhraseReplacementEnabledKey, defaultValue: false) + @objc static var phraseReplacementEnabled: Bool + + @objc static func tooglePhraseReplacementEnabled() -> Bool { + phraseReplacementEnabled = !phraseReplacementEnabled + UserDefaults.standard.set(phraseReplacementEnabled, forKey: kPhraseReplacementEnabledKey) + return phraseReplacementEnabled; + } } diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index c4f74b43..426b919f 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -27,3 +27,5 @@ "Use Half-Width Punctuations" = "Use Half-Width Punctuations"; "You are now selecting \"%@\". You can add a phrase with two or more characters." = "You are now selecting \"%@\". You can add a phrase with two or more characters."; "You are now selecting \"%@\". Press enter to add a new phrase." = "You are now selecting \"%@\". Press enter to add a new phrase."; +"Edit Phrase Replacement Table" = "Edit Phrase Replacement Table"; +"Use Phrase Replacement" = "Use Phrase Replacement"; diff --git a/Source/vChewing-Bridging-Header.h b/Source/vChewing-Bridging-Header.h index e4c62d3f..18ec5050 100644 --- a/Source/vChewing-Bridging-Header.h +++ b/Source/vChewing-Bridging-Header.h @@ -6,5 +6,6 @@ #import // @import Foundation; @interface LanguageModelManager : NSObject + (void)loadDataModels; -+ (void)loadUserPhrasesModel; ++ (void)loadUserPhrases; ++ (void)loadUserPhraseReplacement; @end diff --git a/Source/zh-Hans.lproj/Localizable.strings b/Source/zh-Hans.lproj/Localizable.strings index 8f7bae55..a3710edb 100644 --- a/Source/zh-Hans.lproj/Localizable.strings +++ b/Source/zh-Hans.lproj/Localizable.strings @@ -27,3 +27,5 @@ "Use Half-Width Punctuations" = "啟用半角標點輸出"; "You are now selecting \"%@\". You can add a phrase with two or more characters." = "您目前选择了「%@」。请选择至少两个字,才能将其加入自订语汇。"; "You are now selecting \"%@\". Press enter to add a new phrase." = "您目前选择了「%@」。按下 Enter 就可以加入到自订语汇中。"; +"Edit Phrase Replacement Table" = "编辑语汇置换表"; +"Use Phrase Replacement" = "使用语汇置换"; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index bdc29ed1..eec7b514 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -27,3 +27,5 @@ "Use Half-Width Punctuations" = "啟用半形標點輸出"; "You are now selecting \"%@\". You can add a phrase with two or more characters." = "您目前選擇了「%@」。請選擇至少兩個字,才能將其加入自訂語彙。"; "You are now selecting \"%@\". Press enter to add a new phrase." = "您目前選擇了「%@」。按下 Enter 就可以加入到自訂語彙中。"; +"Edit Phrase Replacement Table" = "編輯語彙置換表"; +"Use Phrase Replacement" = "使用語彙置換"; diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 34dcd2cf..21f1f779 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 5B5F4F8E27928F9300922DC2 /* vChewingLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F8D27928F9300922DC2 /* vChewingLM.cpp */; }; 5B5F4F93279294A300922DC2 /* LanguageModelManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F92279294A300922DC2 /* LanguageModelManager.mm */; }; 5B5F4F972792A4EA00922DC2 /* UserPhrasesLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.cpp */; }; + 5B6797B52794822C004AC7CE /* PhraseReplacementMap.h in Sources */ = {isa = PBXBuildFile; fileRef = 5B6797B32794822C004AC7CE /* PhraseReplacementMap.h */; }; 5BC2D2872793B434002C0BEC /* CMakeLists.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BC2D2852793B434002C0BEC /* CMakeLists.txt */; }; 5BC2D2882793B434002C0BEC /* KeyValueBlobReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */; }; 5BC2D28B2793B8FB002C0BEC /* EmacsKeyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D28A2793B8FB002C0BEC /* EmacsKeyHelper.swift */; }; @@ -27,6 +28,7 @@ 5BD0D19A27943D390008F48E /* clsSFX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD0D19927943D390008F48E /* clsSFX.swift */; }; 5BD0D19F279454F60008F48E /* Beep.aif in Resources */ = {isa = PBXBuildFile; fileRef = 5BD0D19E279454F60008F48E /* Beep.aif */; }; 5BD0D1A4279463510008F48E /* clsOOBEDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD0D1A3279463510008F48E /* clsOOBEDefaults.swift */; }; + 5BD13F482794F0A6000E429F /* PhraseReplacementMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B6797B42794822C004AC7CE /* PhraseReplacementMap.cpp */; }; 5BDF2CFE2791BE4400838ADB /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */; }; 5BDF2CFF2791BECC00838ADB /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */; }; 5BDF2D012791C03B00838ADB /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */; }; @@ -102,6 +104,8 @@ 5B5F4F92279294A300922DC2 /* LanguageModelManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = LanguageModelManager.mm; sourceTree = ""; }; 5B5F4F952792A4EA00922DC2 /* UserPhrasesLM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UserPhrasesLM.h; sourceTree = ""; }; 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = UserPhrasesLM.cpp; sourceTree = ""; }; + 5B6797B32794822C004AC7CE /* PhraseReplacementMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PhraseReplacementMap.h; sourceTree = ""; }; + 5B6797B42794822C004AC7CE /* PhraseReplacementMap.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PhraseReplacementMap.cpp; sourceTree = ""; }; 5B9781D32763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 5B9781D52763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 5B9781D72763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; @@ -270,6 +274,8 @@ 5B42B63F27876FDC00BB9B9F /* UserOverrideModel.h */, 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.cpp */, 5B5F4F952792A4EA00922DC2 /* UserPhrasesLM.h */, + 5B6797B42794822C004AC7CE /* PhraseReplacementMap.cpp */, + 5B6797B32794822C004AC7CE /* PhraseReplacementMap.h */, ); path = LanguageModel; sourceTree = ""; @@ -741,9 +747,11 @@ 5BD0D1A4279463510008F48E /* clsOOBEDefaults.swift in Sources */, D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */, 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */, + 5B6797B52794822C004AC7CE /* PhraseReplacementMap.h in Sources */, 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */, 5BDF2D012791C03B00838ADB /* PreferencesWindowController.swift in Sources */, 5BC2D28D2793B98F002C0BEC /* PreferencesModule.swift in Sources */, + 5BD13F482794F0A6000E429F /* PhraseReplacementMap.cpp in Sources */, 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */, 5BDF2D032791C71200838ADB /* NonModalAlertWindowController.swift in Sources */, 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */,