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" = "使用詞彙替換";