Merge pull request #268 from zonble/master

Various bug fixes for the inputting and marking states
This commit is contained in:
Weizhong Yang a.k.a zonble 2022-02-02 06:04:36 +08:00 committed by GitHub
commit 063164d0ab
11 changed files with 365 additions and 108 deletions

View File

@ -11,8 +11,6 @@
6A0D4F0915FC0DA600ABF4B3 /* Bopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EF015FC0DA600ABF4B3 /* Bopomofo@2x.tiff */; };
6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F2015FC0EB100ABF4B3 /* Mandarin.cpp */; };
6A0D4F5315FC0EE100ABF4B3 /* preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4F4E15FC0EE100ABF4B3 /* preferences.xib */; };
6A0D4F5715FC0EF900ABF4B3 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4F4815FC0EE100ABF4B3 /* InfoPlist.strings */; };
6A0D4F5815FC0EF900ABF4B3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4F4A15FC0EE100ABF4B3 /* Localizable.strings */; };
6A187E2616004C5900466B2E /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6A187E2816004C5900466B2E /* MainMenu.xib */; };
6A225A1F23679F2600F685C6 /* NotarizedArchives in Resources */ = {isa = PBXBuildFile; fileRef = 6A225A1E23679F2600F685C6 /* NotarizedArchives */; };
6A2E40F6253A69DA00D1AE1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; };
@ -48,19 +46,22 @@
D44FB74A2791B829003C80A6 /* VXHanConvert in Frameworks */ = {isa = PBXBuildFile; productRef = D44FB7492791B829003C80A6 /* VXHanConvert */; };
D44FB74D2792189A003C80A6 /* PhraseReplacementMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74B2792189A003C80A6 /* PhraseReplacementMap.cpp */; };
D456576E279E4F7B00DF6BC9 /* KeyHandlerInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = D456576D279E4F7B00DF6BC9 /* KeyHandlerInput.swift */; };
D45EB5C127A9894C00E28B17 /* StringUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D45EB5BF27A9890C00E28B17 /* StringUtils.swift */; };
D461B792279DAC010070E734 /* InputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D461B791279DAC010070E734 /* InputState.swift */; };
D47B92C027972AD100458394 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47B92BF27972AC800458394 /* main.swift */; };
D47D73A427A5D43900255A50 /* KeyHandlerBopomofoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47D73A327A5D43900255A50 /* KeyHandlerBopomofoTests.swift */; };
D47D73C327A7200500255A50 /* FSEventStreamHelper in Frameworks */ = {isa = PBXBuildFile; productRef = D47D73C227A7200500255A50 /* FSEventStreamHelper */; };
D47D73A827A6C84F00255A50 /* associated-phrases.cin in Resources */ = {isa = PBXBuildFile; fileRef = D47D73A727A6C84F00255A50 /* associated-phrases.cin */; };
D47D73A927A6C84F00255A50 /* associated-phrases.cin in Resources */ = {isa = PBXBuildFile; fileRef = D47D73A727A6C84F00255A50 /* associated-phrases.cin */; };
D47D73AC27A6CAE600255A50 /* AssociatedPhrases.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D47D73AA27A6CAE600255A50 /* AssociatedPhrases.cpp */; };
D47D73C327A7200500255A50 /* FSEventStreamHelper in Frameworks */ = {isa = PBXBuildFile; productRef = D47D73C227A7200500255A50 /* FSEventStreamHelper */; };
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 */; };
D485D3B92796A8A000657FF3 /* PreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D485D3B82796A8A000657FF3 /* PreferencesTests.swift */; };
D485D3C02796CE3200657FF3 /* VersionUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */; };
D4A13D5A27A59F0B003BE359 /* InputMethodController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A13D5927A59D5C003BE359 /* InputMethodController.swift */; };
D4E33D8A27A838CF006DB1CF /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D4E33D8827A838CF006DB1CF /* Localizable.strings */; };
D4E33D8F27A838F0006DB1CF /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D4E33D8D27A838F0006DB1CF /* InfoPlist.strings */; };
D4E569DC27A34D0E00AC2CEF /* KeyHandler.mm in Sources */ = {isa = PBXBuildFile; fileRef = D4E569DB27A34CC100AC2CEF /* KeyHandler.mm */; };
D4E569E427A414CB00AC2CEF /* data-plain-bpmf.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6AD7CBC715FE555000691B5B /* data-plain-bpmf.txt */; };
D4E569E527A414CB00AC2CEF /* data.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A38BBF615FC117A00A8A51F /* data.txt */; };
@ -145,10 +146,6 @@
6A0D4F4015FC0EB100ABF4B3 /* OVTextBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVTextBuffer.h; sourceTree = "<group>"; };
6A0D4F4115FC0EB100ABF4B3 /* OVUTF8Helper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVUTF8Helper.h; sourceTree = "<group>"; };
6A0D4F4215FC0EB100ABF4B3 /* OVWildcard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVWildcard.h; sourceTree = "<group>"; };
6A0D4F4915FC0EE100ABF4B3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Source/en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
6A0D4F4B15FC0EE100ABF4B3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Source/en.lproj/Localizable.strings; sourceTree = "<group>"; };
6A0D4F5415FC0EF900ABF4B3 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "Source/zh-Hant.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
6A0D4F5515FC0EF900ABF4B3 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "Source/zh-Hant.lproj/Localizable.strings"; sourceTree = "<group>"; };
6A0D4F5615FC0EF900ABF4B3 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = "zh-Hant"; path = "Source/zh-Hant.lproj/preferences.xib"; sourceTree = "<group>"; };
6A15B32421A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
6A15B32521A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
@ -204,13 +201,14 @@
D44FB74B2792189A003C80A6 /* PhraseReplacementMap.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PhraseReplacementMap.cpp; sourceTree = "<group>"; };
D44FB74C2792189A003C80A6 /* PhraseReplacementMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PhraseReplacementMap.h; sourceTree = "<group>"; };
D456576D279E4F7B00DF6BC9 /* KeyHandlerInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyHandlerInput.swift; sourceTree = "<group>"; };
D45EB5BF27A9890C00E28B17 /* StringUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringUtils.swift; sourceTree = "<group>"; };
D461B791279DAC010070E734 /* InputState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputState.swift; sourceTree = "<group>"; };
D47B92BF27972AC800458394 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
D47D73A327A5D43900255A50 /* KeyHandlerBopomofoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyHandlerBopomofoTests.swift; sourceTree = "<group>"; };
D47D73C027A71FFA00255A50 /* FSEventStreamHelper */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FSEventStreamHelper; path = Packages/FSEventStreamHelper; sourceTree = "<group>"; };
D47D73A727A6C84F00255A50 /* associated-phrases.cin */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "associated-phrases.cin"; sourceTree = "<group>"; };
D47D73AA27A6CAE600255A50 /* AssociatedPhrases.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AssociatedPhrases.cpp; sourceTree = "<group>"; };
D47D73AB27A6CAE600255A50 /* AssociatedPhrases.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AssociatedPhrases.h; sourceTree = "<group>"; };
D47D73C027A71FFA00255A50 /* FSEventStreamHelper */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FSEventStreamHelper; path = Packages/FSEventStreamHelper; sourceTree = "<group>"; };
D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = "<group>"; };
D47F7DD1278C1263002F9DD7 /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = "<group>"; };
@ -220,6 +218,12 @@
D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionUpdateTests.swift; sourceTree = "<group>"; };
D495583A27A5C6C4006ADE1C /* LanguageModelManager+Privates.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "LanguageModelManager+Privates.h"; sourceTree = "<group>"; };
D4A13D5927A59D5C003BE359 /* InputMethodController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputMethodController.swift; sourceTree = "<group>"; };
D4E33D8927A838CF006DB1CF /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = "<group>"; };
D4E33D8B27A838D5006DB1CF /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
D4E33D8C27A838D8006DB1CF /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = "<group>"; };
D4E33D8E27A838F0006DB1CF /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = "<group>"; };
D4E33D9027A838F4006DB1CF /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
D4E33D9127A838F7006DB1CF /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
D4E569DA27A34CC100AC2CEF /* KeyHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyHandler.h; sourceTree = "<group>"; };
D4E569DB27A34CC100AC2CEF /* KeyHandler.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyHandler.mm; sourceTree = "<group>"; };
D4F0BBDE279AF1AF0071253C /* ArchiveUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveUtil.swift; sourceTree = "<group>"; };
@ -300,6 +304,7 @@
D4E569DB27A34CC100AC2CEF /* KeyHandler.mm */,
D456576D279E4F7B00DF6BC9 /* KeyHandlerInput.swift */,
D461B791279DAC010070E734 /* InputState.swift */,
D45EB5BF27A9890C00E28B17 /* StringUtils.swift */,
D427F76B278CA1BA004A2160 /* AppDelegate.swift */,
D44FB74427915555003C80A6 /* Preferences.swift */,
D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */,
@ -423,8 +428,8 @@
6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */,
6A0D4EEE15FC0DA600ABF4B3 /* Images */,
6A0D4EF515FC0DA600ABF4B3 /* McBopomofo-Info.plist */,
6A0D4F4815FC0EE100ABF4B3 /* InfoPlist.strings */,
6A0D4F4A15FC0EE100ABF4B3 /* Localizable.strings */,
D4E33D8D27A838F0006DB1CF /* InfoPlist.strings */,
D4E33D8827A838CF006DB1CF /* Localizable.strings */,
6A187E2816004C5900466B2E /* MainMenu.xib */,
6A0D4F4E15FC0EE100ABF4B3 /* preferences.xib */,
);
@ -640,14 +645,14 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D4E33D8A27A838CF006DB1CF /* Localizable.strings in Resources */,
6A6ED16E2797650A0012872E /* template-exclude-phrases.txt in Resources */,
6A0D4F0815FC0DA600ABF4B3 /* Bopomofo.tiff in Resources */,
6A0D4F0915FC0DA600ABF4B3 /* Bopomofo@2x.tiff in Resources */,
6A6ED16D2797650A0012872E /* template-exclude-phrases-plain-bpmf.txt in Resources */,
6A0D4F5315FC0EE100ABF4B3 /* preferences.xib in Resources */,
6A0D4F5715FC0EF900ABF4B3 /* InfoPlist.strings in Resources */,
6A0D4F5815FC0EF900ABF4B3 /* Localizable.strings in Resources */,
6A2E40F6253A69DA00D1AE1D /* Images.xcassets in Resources */,
D4E33D8F27A838F0006DB1CF /* InfoPlist.strings in Resources */,
6A38BC1515FC117A00A8A51F /* data.txt in Resources */,
6A6ED16B2797650A0012872E /* template-phrases-replacement.txt in Resources */,
6AFF97F2253B299E007F1C49 /* NonModalAlertWindowController.xib in Resources */,
@ -728,6 +733,7 @@
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */,
6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */,
6ACC3D452793701600F1B140 /* ParselessLM.cpp in Sources */,
D45EB5C127A9894C00E28B17 /* StringUtils.swift in Sources */,
D41355DE278EA3ED005E5CBD /* UserPhrasesLM.cpp in Sources */,
6ACC3D3F27914F2400F1B140 /* KeyValueBlobReader.cpp in Sources */,
D41355D8278D74B5005E5CBD /* LanguageModelManager.mm in Sources */,
@ -775,26 +781,6 @@
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
6A0D4F4815FC0EE100ABF4B3 /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
6A0D4F4915FC0EE100ABF4B3 /* en */,
6A0D4F5415FC0EF900ABF4B3 /* zh-Hant */,
);
name = InfoPlist.strings;
path = ..;
sourceTree = "<group>";
};
6A0D4F4A15FC0EE100ABF4B3 /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
6A0D4F4B15FC0EE100ABF4B3 /* en */,
6A0D4F5515FC0EF900ABF4B3 /* zh-Hant */,
);
name = Localizable.strings;
path = ..;
sourceTree = "<group>";
};
6A0D4F4E15FC0EE100ABF4B3 /* preferences.xib */ = {
isa = PBXVariantGroup;
children = (
@ -890,6 +876,26 @@
path = Source/Installer;
sourceTree = SOURCE_ROOT;
};
D4E33D8827A838CF006DB1CF /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
D4E33D8927A838CF006DB1CF /* Base */,
D4E33D8B27A838D5006DB1CF /* en */,
D4E33D8C27A838D8006DB1CF /* zh-Hant */,
);
name = Localizable.strings;
sourceTree = "<group>";
};
D4E33D8D27A838F0006DB1CF /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
D4E33D8E27A838F0006DB1CF /* Base */,
D4E33D9027A838F4006DB1CF /* en */,
D4E33D9127A838F7006DB1CF /* zh-Hant */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
};
D4F0BBE9279B14C20071253C /* Credits.rtf */ = {
isa = PBXVariantGroup;
children = (

View File

@ -0,0 +1,5 @@
CFBundleName = "McBopomofo";
CFBundleDisplayName = "McBopomofo";
NSHumanReadableCopyright = "Copyright © 2011-2022 Mengjuei Hsieh et al.\nAll Rights Reserved.";
"org.openvanilla.inputmethod.McBopomofo.Bopomofo" = "Bopomofo";
"org.openvanilla.inputmethod.McBopomofo.PlainBopomofo" = "Plain Bopomofo";

View File

@ -0,0 +1,92 @@
/* No comment provided by engineer. */
"About McBopomofo…" = "About McBopomofo…";
/* No comment provided by engineer. */
"McBopomofo Preferences" = "McBopomofo Preferences";
/* No comment provided by engineer. */
"Check for Updates…" = "Check for Updates…";
/* No comment provided by engineer. */
"Update Check Failed" = "Update Check Failed";
/* No comment provided by engineer. */
"There may be no internet connection or the server failed to respond.\n\nError message: %@" = "There may be no internet connection or the server failed to respond.\n\nError message: %@";
/* No comment provided by engineer. */
"OK" = "OK";
/* No comment provided by engineer. */
"Dismiss" = "Dismiss";
/* No comment provided by engineer. */
"New Version Available" = "New Version Available";
/* No comment provided by engineer. */
"Not Now" = "Not Now";
/* No comment provided by engineer. */
"Visit Website" = "Visit Website";
/* No comment provided by engineer. */
"You're currently using McBopomofo %@ (%@), a new version %@ (%@) is now available. Do you want to visit McBopomofo's website to download the version?%@" = "You're currently using McBopomofo %@ (%@), a new version %@ (%@) is now available. Do you want to visit McBopomofo's website to download the version?%@";
"Chinese Conversion" = "Convert to Simplified Chinese";
"User Phrases" = "User Phrases";
"Edit User Phrases" = "Edit User Phrases";
"Reload User Phrases" = "Reload User Phrases";
"Unable to create the user phrase file." = "Unable to create the user phrase file.";
"Please check the permission of at \"%@\"." = "Please check the permission of at \"%@\".";
"Edit Excluded Phrases" = "Edit Excluded Phrases";
"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.";
"You are now selecting \"%@\". A phrase cannot be longer than %d characters." = "You are now selecting \"%@\". A phrase cannot be longer than %d characters.";
"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";
"Candidates keys cannot be empty." = "Candidates keys cannot be empty.";
"Candidate keys can only contain latin characters and numbers." = "Candidate keys can only contain latin characters and numbers.";
"Candidate keys cannot contain space." = "Candidate keys cannot contain space.";
"There should not be duplicated keys." = "There should not be duplicated keys.";
"The length of your candidate keys can not be less than 4 characters." = "The length of your candidate keys can not be less than 4 characters.";
"The length of your candidate keys can not be larger than 15 characters." = "The length of your candidate keys can not be larger than 15 characters.";
"Phrase replacement mode is on. Not suggested to add phrase in the mode." = "Phrase replacement mode is on. Not suggested to add phrase in the mode.";
"Model based Chinese conversion is on. Not suggested to add phrase in the mode." = "Model based Chinese conversion is on. Not suggested to add phrase in the mode.";
"Half-width punctuation on" = "Half-width punctuation on";
"Half-width punctuation off" = "Half-width punctuation off";
"Associated Phrases" = "Associated Phrases";
"There are special phrases in your text. We don't support adding new phrases in this case." = "There are special phrases in your text. We don't support adding new phrases in this case.";
"Cursor is before \"%@\"." = "Cursor is before \"%@\".";
"Cursor is after \"%@\"." = "Cursor is after \"%@\".";
"Cursor is between \"%@\" and \"%@\"." = "Cursor is between \"%@\" and \"%@\".";

View File

@ -180,6 +180,7 @@ class McBopomofoInputMethodController: IMKInputController {
}
let input = KeyHandlerInput(event: event, isVerticalMode: useVerticalMode)
let result = keyHandler.handle(input, state: state) { newState in
self.handle(state: newState, client: client)
} candidateSelectionCallback: {
@ -388,6 +389,9 @@ extension McBopomofoInputMethodController {
// the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound,
// i.e. the client app needs to take care of where to put this composing buffer
client.setMarkedText(state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound))
if !state.tooltip.isEmpty {
show(tooltip: state.tooltip, composingBuffer: state.composingBuffer, cursorIndex: state.cursorIndex, client: client)
}
}
private func handle(state: InputState.Marking, previous: InputState, client: Any?) {
@ -482,16 +486,19 @@ extension McBopomofoInputMethodController {
gCurrentCandidateController?.visible = true
var lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0)
var cursor: UInt = 0
var cursor: Int = 0
if let state = state as? InputState.ChoosingCandidate {
cursor = state.cursorIndex
cursor = Int(state.cursorIndex)
if cursor == state.composingBuffer.count && cursor != 0 {
cursor -= 1
}
}
(client as? IMKTextInput)?.attributes(forCharacterIndex: Int(cursor), lineHeightRectangle: &lineHeightRect)
while lineHeightRect.origin.x == 0 && lineHeightRect.origin.y == 0 && cursor >= 0 {
(client as? IMKTextInput)?.attributes(forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect)
cursor -= 1
}
if useVerticalMode {
gCurrentCandidateController?.set(windowTopLeftPoint: NSMakePoint(lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, lineHeightRect.origin.y - 4.0), bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0)
@ -502,11 +509,14 @@ extension McBopomofoInputMethodController {
private func show(tooltip: String, composingBuffer: String, cursorIndex: UInt, client: Any!) {
var lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0)
var cursor = cursorIndex
var cursor: Int = Int(cursorIndex)
if cursor == composingBuffer.count && cursor != 0 {
cursor -= 1
}
(client as? IMKTextInput)?.attributes(forCharacterIndex: Int(cursor), lineHeightRectangle: &lineHeightRect)
while lineHeightRect.origin.x == 0 && lineHeightRect.origin.y == 0 && cursor >= 0 {
(client as? IMKTextInput)?.attributes(forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect)
cursor -= 1
}
McBopomofoInputMethodController.tooltipController.show(tooltip: tooltip, at: lineHeightRect.origin)
}

View File

@ -64,6 +64,8 @@ class InputState: NSObject {
}
}
// MARK: -
/// Represents that the composing buffer is empty.
@objc (InputStateEmpty)
class Empty: InputState {
@ -76,6 +78,8 @@ class InputState: NSObject {
}
}
// MARK: -
/// Represents that the composing buffer is empty.
@objc (InputStateEmptyIgnoringPreviousState)
class EmptyIgnoringPreviousState: InputState {
@ -87,6 +91,8 @@ class InputState: NSObject {
}
}
// MARK: -
/// Represents that the input controller is committing text into client app.
@objc (InputStateCommitting)
class Committing: InputState {
@ -101,11 +107,14 @@ class InputState: NSObject {
"<InputState.Committing poppedText:\(poppedText)>"
}
}
// MARK: -
/// Represents that the composing buffer is not empty.
@objc (InputStateNotEmpty)
class NotEmpty: InputState {
@objc private(set) var composingBuffer: String = ""
@objc private(set) var cursorIndex: UInt = 0
@objc private(set) var composingBuffer: String
@objc private(set) var cursorIndex: UInt
@objc init(composingBuffer: String, cursorIndex: UInt) {
self.composingBuffer = composingBuffer
@ -117,12 +126,13 @@ class InputState: NSObject {
}
}
// MARK: -
/// Represents that the user is inputting text.
@objc (InputStateInputting)
class Inputting: NotEmpty {
@objc var bpmfReading: String = ""
@objc var bpmfReadingCursorIndex: UInt8 = 0
@objc var poppedText: String = ""
@objc var tooltip: String = ""
@objc override init(composingBuffer: String, cursorIndex: UInt) {
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
@ -137,20 +147,27 @@ class InputState: NSObject {
}
override var description: String {
"<InputState.Inputting, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex), poppedText:\(poppedText)>"
"<InputState.Inputting, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>, poppedText:\(poppedText)>"
}
}
// MARK: -
private let kMinMarkRangeLength = 2
private let kMaxMarkRangeLength = 6
/// Represents that the user is marking a range in the composing buffer.
@objc (InputStateMarking)
class Marking: NotEmpty {
@objc private(set) var markerIndex: UInt
@objc private(set) var markedRange: NSRange
@objc var tooltip: String {
if composingBuffer.count != readings.count {
return NSLocalizedString("There are special phrases in your text. We don't support adding new phrases in this case.", comment: "")
}
if Preferences.phraseReplacementEnabled {
return NSLocalizedString("Phrase replacement mode is on. Not suggested to add phrase in the mode.", comment: "")
}
@ -170,6 +187,7 @@ class InputState: NSObject {
return String(format: NSLocalizedString("You are now selecting \"%@\". Press enter to add a new phrase.", comment: ""), text)
}
@objc var tooltipForInputting: String = ""
@objc private(set) var readings: [String]
@objc init(composingBuffer: String, cursorIndex: UInt, markerIndex: UInt, readings: [String]) {
@ -197,32 +215,45 @@ class InputState: NSObject {
.underlineStyle: NSUnderlineStyle.single.rawValue,
.markedClauseSegment: 2
], range: NSRange(location: end,
length: composingBuffer.count - end))
length: (composingBuffer as NSString).length - end))
return attributedSting
}
override var description: String {
"<InputState.Marking, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex), markedRange:\(markedRange), readings:\(readings)>"
"<InputState.Marking, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex), markedRange:\(markedRange)>"
}
@objc func convertToInputting() -> Inputting {
let state = Inputting(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
state.tooltip = tooltipForInputting
return state
}
@objc var validToWrite: Bool {
markedRange.length >= kMinMarkRangeLength && markedRange.length <= kMaxMarkRangeLength
/// McBopomofo allows users to input a string whose length differs
/// from the amount of Bopomofo readings. In this case, the range
/// in the composing buffer and the readings could not match, so
/// we disable the function to write user phrases in this case.
if composingBuffer.count != readings.count {
return false
}
return markedRange.length >= kMinMarkRangeLength &&
markedRange.length <= kMaxMarkRangeLength
}
@objc var userPhrase: String {
let text = (composingBuffer as NSString).substring(with: markedRange)
let end = markedRange.location + markedRange.length
let readings = readings[markedRange.location..<end]
let exactBegin = StringUtils.convertToCharIndex(from: markedRange.location, in: composingBuffer)
let exactEnd = StringUtils.convertToCharIndex(from: markedRange.location + markedRange.length, in: composingBuffer)
let readings = readings[exactBegin..<exactEnd]
let joined = readings.joined(separator: "-")
return "\(text) \(joined)"
}
}
// MARK: -
/// Represents that the user is choosing in a candidates list.
@objc (InputStateChoosingCandidate)
class ChoosingCandidate: NotEmpty {
@ -248,6 +279,8 @@ class InputState: NSObject {
}
}
// MARK: -
/// Represents that the user is choosing in a candidates list
/// in the associated phrases mode.
@objc (InputStateAssociatedPhrases)
@ -264,5 +297,4 @@ class InputState: NSObject {
"<InputState.AssociatedPhrases, candidates:\(candidates), useVerticalMode:\(useVerticalMode)>"
}
}
}

View File

@ -226,12 +226,12 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
McBopomofoEmacsKey emacsKey = input.emacsKey;
// if the inputText is empty, it's a function key combination, we ignore it
if (![input.inputText length]) {
if (!input.inputText.length) {
return NO;
}
// if the composing buffer is empty and there's no reading, and there is some function key combination, we ignore it
BOOL isFunctionKey = ([input isCommandHold] || [input isControlHold] || [input isOptionHold] || [input isNumericPad]);
BOOL isFunctionKey = ([input isCommandHold] || [input isOptionHold] || [input isNumericPad]) || [input isControlHotKey];
if (![state isKindOfClass:[InputStateNotEmpty class]] &&
![state isKindOfClass:[InputStateAssociatedPhrases class]] &&
isFunctionKey) {
@ -302,10 +302,12 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
}
bool composeReading = false;
BOOL skipBpmfHandling = [input isReservedKey] || [input isControlHold];
// MARK: Handle BPMF Keys
// see if it's valid BPMF reading
if (_bpmfReadingBuffer->isValidKey((char) charCode)) {
if (!skipBpmfHandling && _bpmfReadingBuffer->isValidKey((char) charCode)) {
_bpmfReadingBuffer->combineKey((char) charCode);
// if we have a tone marker, we have to insert the reading to the
@ -348,7 +350,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
size_t cursorIndex = [self _actualCandidateCursorIndex];
vector<NodeAnchor> nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex);
double highestScore = FindHighestScore(nodes, kEpsilon);
_builder->grid().overrideNodeScoreForSelectedCandidate(cursorIndex, overrideValue, highestScore);
_builder->grid().overrideNodeScoreForSelectedCandidate(cursorIndex, overrideValue, static_cast<float>(highestScore));
}
// then update the text
@ -397,13 +399,11 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
// if the spacebar is NOT set to be a selection key
if ([input isShiftHold] || !Preferences.chooseCandidateUsingSpace) {
if (_builder->cursorIndex() >= _builder->length()) {
if ([state isKindOfClass:[InputStateNotEmpty class]]) {
NSString *composingBuffer = [(InputStateNotEmpty*) state composingBuffer];
if ([composingBuffer length]) {
if (composingBuffer.length) {
InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:composingBuffer];
stateCallback (committing);
}
}
[self clear];
InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:@" "];
stateCallback(committing);
@ -490,8 +490,16 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
// MARK: Punctuation
// if nothing is matched, see if it's a punctuation key for current layout.
string punctuationNamePrefix;
if ([input isControlHold]) {
punctuationNamePrefix = string("_ctrl_punctuation_");
} else if (Preferences.halfWidthPunctuationEnabled) {
punctuationNamePrefix = string("_half_punctuation_");
} else {
punctuationNamePrefix = string("_punctuation_");
}
string layout = [self _currentLayout];
string punctuationNamePrefix = Preferences.halfWidthPunctuationEnabled ? string("_half_punctuation_") : string("_punctuation_");
string customPunctuation = punctuationNamePrefix + layout + string(1, (char) charCode);
if ([self _handlePunctuation:customPunctuation state:state usingVerticalMode:input.useVerticalMode stateCallback:stateCallback errorCallback:errorCallback]) {
return YES;
@ -540,7 +548,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
// other platforms
if (_bpmfReadingBuffer->isEmpty()) {
// no nee to beep since the event is deliberately triggered by user
// no need to beep since the event is deliberately triggered by user
if (![state isKindOfClass:[InputStateInputting class]]) {
return NO;
}
@ -569,8 +577,10 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
if ([input isShiftHold]) {
// Shift + left
if (_builder->cursorIndex() > 0) {
InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:currentState.cursorIndex - 1 readings: [self _currentReadings]];
if (currentState.cursorIndex > 0) {
NSInteger previousPosition = [StringUtils previousUtf16PositionForIndex:currentState.cursorIndex in:currentState.composingBuffer];
InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:previousPosition readings:[self _currentReadings]];
marking.tooltipForInputting = currentState.tooltip;
stateCallback(marking);
} else {
errorCallback();
@ -605,8 +615,10 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
if ([input isShiftHold]) {
// Shift + Right
if (_builder->cursorIndex() < _builder->length()) {
InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:currentState.cursorIndex + 1 readings: [self _currentReadings]];
if (currentState.cursorIndex < currentState.composingBuffer.length) {
NSInteger nextPosition = [StringUtils nextUtf16PositionForIndex:currentState.cursorIndex in:currentState.composingBuffer];
InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:nextPosition readings:[self _currentReadings]];
marking.tooltipForInputting = currentState.tooltip;
stateCallback(marking);
} else {
errorCallback();
@ -832,8 +844,9 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
&& ([input isShiftHold])) {
NSUInteger index = state.markerIndex;
if (index > 0) {
index -= 1;
index = [StringUtils previousUtf16PositionForIndex:index in:state.composingBuffer];
InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:state.composingBuffer cursorIndex:state.cursorIndex markerIndex:index readings:state.readings];
marking.tooltipForInputting = state.tooltipForInputting;
stateCallback(marking);
} else {
errorCallback();
@ -847,8 +860,9 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
&& ([input isShiftHold])) {
NSUInteger index = state.markerIndex;
if (index < state.composingBuffer.length) {
index += 1;
index = [StringUtils nextUtf16PositionForIndex:index in:state.composingBuffer];
InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:state.composingBuffer cursorIndex:state.cursorIndex markerIndex:index readings:state.readings];
marking.tooltipForInputting = state.tooltipForInputting;
stateCallback(marking);
} else {
errorCallback();
@ -1070,7 +1084,14 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
if (_inputMode == InputModePlainBopomofo) {
string layout = [self _currentLayout];
string punctuationNamePrefix = Preferences.halfWidthPunctuationEnabled ? string("_half_punctuation_") : string("_punctuation_");
string punctuationNamePrefix;
if ([input isControlHold]) {
punctuationNamePrefix = string("_ctrl_punctuation_");
} else if (Preferences.halfWidthPunctuationEnabled) {
punctuationNamePrefix = string("_half_punctuation_");
} else {
punctuationNamePrefix = string("_punctuation_");
}
string customPunctuation = punctuationNamePrefix + layout + string(1, (char) charCode);
string punctuation = punctuationNamePrefix + string(1, (char) charCode);
@ -1114,6 +1135,8 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
size_t readingCursorIndex = 0;
size_t builderCursorIndex = _builder->cursorIndex();
NSString *tooltip = @"";
// we must do some Unicode codepoint counting to find the actual cursor location for the client
// i.e. we need to take UTF-16 into consideration, for which a surrogate pair takes 2 UniChars
// locations
@ -1137,10 +1160,31 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
composedStringCursorIndex += [valueString length];
readingCursorIndex += spanningLength;
} else {
if (codepointCount == spanningLength) {
for (size_t i = 0; i < codepointCount && readingCursorIndex < builderCursorIndex; i++) {
composedStringCursorIndex += [[NSString stringWithUTF8String:codepoints[i].c_str()] length];
readingCursorIndex++;
}
} else {
if (readingCursorIndex < builderCursorIndex) {
composedStringCursorIndex += [valueString length];
readingCursorIndex += spanningLength;
if (readingCursorIndex > builderCursorIndex) {
readingCursorIndex = builderCursorIndex;
}
if (builderCursorIndex == 0) {
tooltip = [NSString stringWithFormat:NSLocalizedString(@"Cursor is before \"%@\".", @""),
[NSString stringWithUTF8String:_builder->readings()[builderCursorIndex].c_str()]];
} else if (builderCursorIndex >= _builder->readings().size()) {
tooltip = [NSString stringWithFormat:NSLocalizedString(@"Cursor is after \"%@\".", @""),
[NSString stringWithUTF8String:_builder->readings()[_builder->readings().size() - 1].c_str()]];
} else {
tooltip = [NSString stringWithFormat:NSLocalizedString(@"Cursor is between \"%@\" and \"%@\".", @""),
[NSString stringWithUTF8String:_builder->readings()[builderCursorIndex - 1].c_str()],
[NSString stringWithUTF8String:_builder->readings()[builderCursorIndex].c_str()]];
}
}
}
}
}
}
@ -1155,6 +1199,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
NSInteger cursorIndex = composedStringCursorIndex + [reading length];
InputStateInputting *newState = [[InputStateInputting alloc] initWithComposingBuffer:composedText cursorIndex:cursorIndex];
newState.tooltip = tooltip;
return newState;
}

View File

@ -90,6 +90,10 @@ class KeyHandlerInput: NSObject {
super.init()
}
override var description: String {
return "<\(super.description) inputText:\(String(describing: inputText)), inputTextIgnoringModifiers:\(String(describing: inputTextIgnoringModifiers)) charCode:\(charCode), keyCode:\(keyCode), flags:\(flags), cursorForwardKey:\(cursorForwardKey), cursorBackwardKey:\(cursorBackwardKey), extraChooseCandidateKey:\(extraChooseCandidateKey), absorbedArrowKey:\(absorbedArrowKey), verticalModeOnlyChooseCandidateKey:\(verticalModeOnlyChooseCandidateKey), emacsKey:\(emacsKey), useVerticalMode:\(useVerticalMode)>"
}
@objc var isShiftHold: Bool {
flags.contains([.shift])
}
@ -102,6 +106,10 @@ class KeyHandlerInput: NSObject {
flags.contains([.control])
}
@objc var isControlHotKey: Bool {
flags.contains([.control]) && inputText?.first?.isLetter ?? false
}
@objc var isOptionHold: Bool {
flags.contains([.option])
}
@ -114,6 +122,13 @@ class KeyHandlerInput: NSObject {
flags.contains([.numericPad])
}
@objc var isReservedKey: Bool {
guard let code = KeyCode(rawValue: keyCode) else {
return false
}
return code.rawValue != KeyCode.none.rawValue
}
@objc var isEnter: Bool {
KeyCode(rawValue: keyCode) == KeyCode.enter
}

View File

@ -219,7 +219,6 @@ class Preferences: NSObject {
defaults.removeObject(forKey: kChineseConversionEngineKey)
defaults.removeObject(forKey: kChineseConversionStyle)
defaults.removeObject(forKey: kAssociatedPhrasesEnabledKey)
// defaults.removeObject(forKey: kAssociatedPhrasesKeys)
}
@UserDefault(key: kKeyboardLayoutPreferenceKey, defaultValue: 0)

73
Source/StringUtils.swift Normal file
View File

@ -0,0 +1,73 @@
// Copyright (c) 2022 and onwards The McBopomofo Authors.
//
// 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.
import Foundation
/// Utilities to convert the length of an NSString and a Swift string.
class StringUtils: NSObject {
/// Converts the index in an NSString to the index in a Swift string.
///
/// An Emoji might be compose by more than one UTF-16 code points, however
/// the length of an NSString is only the sum of the UTF-16 code points. It
/// causes that the NSString and Swift string representation of the same
/// string have different lengths once the string contains such Emoji. The
/// method helps to find the index in a Swift string by passing the index
/// in an NSString.
static func convertToCharIndex(from utf16Index: Int, in string: String) -> Int {
var length = 0
for (i, character) in string.enumerated() {
if length >= utf16Index {
return i
}
length += character.utf16.count
}
return string.count
}
@objc (nextUtf16PositionForIndex:in:)
static func nextUtf16Position(for index: Int, in string: String) -> Int {
var index = convertToCharIndex(from: index, in: string)
if index < string.count {
index += 1
}
let count = string[..<string.index(string.startIndex, offsetBy: index)].utf16.count
return count
}
@objc (previousUtf16PositionForIndex:in:)
static func previousUtf16Position(for index: Int, in string: String) -> Int {
var index = convertToCharIndex(from: index, in: string)
if index > 0 {
index -= 1
}
let count = string[..<string.index(string.startIndex, offsetBy: index)].utf16.count
return count
}
}
extension NSString {
@objc var count: Int {
(self as String).count
}
}

View File

@ -1,30 +1,12 @@
/* No comment provided by engineer. */
"About McBopomofo…" = "About McBopomofo…";
/* No comment provided by engineer. */
"Clear Learning Dictionary (%ju Items)" = "Clear Learning Dictionary (%ju Items)";
/* No comment provided by engineer. */
"Dump Learning Data to Console" = "Dump Learning Data to Console";
/* No comment provided by engineer. */
"Enable Selection Learning" = "Enable Selection Learning";
/* No comment provided by engineer. */
"McBopomofo Preferences" = "McBopomofo Preferences";
/* No comment provided by engineer. */
"Check Later" = "Check Later";
/* No comment provided by engineer. */
"Check for Updates…" = "Check for Updates…";
/* No comment provided by engineer. */
"Check for Update Completed" = "Check for Update Completed";
/* No comment provided by engineer. */
"You are already using the latest version of McBopomofo." = "You are already using the latest version of McBopomofo.";
/* No comment provided by engineer. */
"Update Check Failed" = "Update Check Failed";
@ -100,3 +82,11 @@
"Half-width punctuation off" = "Half-width punctuation off";
"Associated Phrases" = "Associated Phrases";
"There are special phrases in your text. We don't support adding new phrases in this case." = "There are special phrases in your text. We don't support adding new phrases in this case.";
"Cursor is before \"%@\"." = "Cursor is before \"%@\".";
"Cursor is after \"%@\"." = "Cursor is after \"%@\".";
"Cursor is between \"%@\" and \"%@\"." = "Cursor is between \"%@\" and \"%@\".";

View File

@ -1,30 +1,12 @@
/* No comment provided by engineer. */
"About McBopomofo…" = "關於小麥注音…";
/* No comment provided by engineer. */
"Clear Learning Dictionary (%ju Items)" = "清除學習辭典 (%ju 個項目)";
/* No comment provided by engineer. */
"Dump Learning Data to Console" = "將學習辭典內容輸出到 Console 上";
/* No comment provided by engineer. */
"Enable Selection Learning" = "使用自動學習功能";
/* No comment provided by engineer. */
"McBopomofo Preferences" = "小麥注音偏好設定";
/* No comment provided by engineer. */
"Check Later" = "晚點再通知我";
/* No comment provided by engineer. */
"Check for Updates…" = "檢查是否有新版…";
/* No comment provided by engineer. */
"Check for Update Completed" = "新版檢查完畢";
/* No comment provided by engineer. */
"You are already using the latest version of McBopomofo." = "目前使用的已經是最新版本。";
/* No comment provided by engineer. */
"Update Check Failed" = "無法檢查新版";
@ -100,3 +82,11 @@
"Half-width punctuation off" = "已經切回到全型標點模式";
"Associated Phrases" = "聯想詞";
"There are special phrases in your text. We don't support adding new phrases in this case." = "您輸入了特殊符號,我們還無法支援在這種狀況下手動加詞。";
"Cursor is before \"%@\"." = "游標正在「%@」前方";
"Cursor is after \"%@\"." = "游標正在「%@」後方";
"Cursor is between \"%@\" and \"%@\"." = "游標正在「%@」與「%@」之間";