Zonble: Add in-place phrase replacement system.

- Also write the settings to the user plist (OOBE).
This commit is contained in:
ShikiSuen 2022-01-17 01:06:15 +08:00
parent 70b22f9864
commit 707fb9ac99
15 changed files with 335 additions and 41 deletions

View File

@ -66,7 +66,8 @@ class AppDelegate: NSObject, NSApplicationDelegate,
func applicationDidFinishLaunching(_ notification: Notification) {
LanguageModelManager.loadDataModels()
LanguageModelManager.loadUserPhrasesModel()
LanguageModelManager.loadUserPhrases()
LanguageModelManager.loadUserPhraseReplacement()
OOBE.setMissingDefaults()

View File

@ -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 <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <fstream>
#include <unistd.h>
#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<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("");
}
}

View File

@ -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 <string>
#include <map>
#include <iostream>
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<std::string_view, std::string_view> keyValueMap;
int fd;
void *data;
size_t length;
};
}
#endif

View File

@ -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<Bigram> vChewingLM::bigramsForKeys(const string& preceedingKey, const string& key)
{
return vector<Bigram>();
@ -96,24 +104,45 @@ const vector<Unigram> vChewingLM::unigramsForKey(const string& key)
if (m_userPhrases.hasUnigramsForKey(key)) {
vector<Unigram> rawUserUnigrams = m_userPhrases.unigramsForKey(key);
vector<Unigram> 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<Unigram> 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;
}

View File

@ -40,6 +40,7 @@
#include <stdio.h>
#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<Bigram> bigramsForKeys(const string& preceedingKey, const string& key);
const vector<Unigram> 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;
};
};

View File

@ -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 {
@ -77,6 +78,11 @@ private let kChineseConversionEnabledKey = "ChineseConversionEnabled"
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 {
UserDefaults.standard.set(Preferences.shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep)

View File

@ -140,6 +140,7 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
// create the lattice builder
_languageModel = [LanguageModelManager languageModelBopomofo];
_languageModel->setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled);
_userOverrideModel = [LanguageModelManager userOverrideModel];
_builder = new BlockReadingBuilder(_languageModel);
@ -158,19 +159,25 @@ static double FindHighestScore(const vector<NodeAnchor>& 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]]; // ------------------------------
@ -181,6 +188,9 @@ static double FindHighestScore(const vector<NodeAnchor>& 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:@""];
@ -273,6 +283,7 @@ static double FindHighestScore(const vector<NodeAnchor>& 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)

View File

@ -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;

View File

@ -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

View File

@ -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;
}
}

View File

@ -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";

View File

@ -6,5 +6,6 @@
#import <Foundation/Foundation.h> // @import Foundation;
@interface LanguageModelManager : NSObject
+ (void)loadDataModels;
+ (void)loadUserPhrasesModel;
+ (void)loadUserPhrases;
+ (void)loadUserPhraseReplacement;
@end

View File

@ -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" = "使用语汇置换";

View File

@ -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" = "使用語彙置換";

View File

@ -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 = "<group>"; };
5B5F4F952792A4EA00922DC2 /* UserPhrasesLM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UserPhrasesLM.h; sourceTree = "<group>"; };
5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = UserPhrasesLM.cpp; sourceTree = "<group>"; };
5B6797B32794822C004AC7CE /* PhraseReplacementMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PhraseReplacementMap.h; sourceTree = "<group>"; };
5B6797B42794822C004AC7CE /* PhraseReplacementMap.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PhraseReplacementMap.cpp; sourceTree = "<group>"; };
5B9781D32763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
5B9781D52763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
5B9781D72763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
@ -270,6 +274,8 @@
5B42B63F27876FDC00BB9B9F /* UserOverrideModel.h */,
5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.cpp */,
5B5F4F952792A4EA00922DC2 /* UserPhrasesLM.h */,
5B6797B42794822C004AC7CE /* PhraseReplacementMap.cpp */,
5B6797B32794822C004AC7CE /* PhraseReplacementMap.h */,
);
path = LanguageModel;
sourceTree = "<group>";
@ -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 */,