Compare commits

...

270 Commits
3.6.0 ... main

Author SHA1 Message Date
ShikiSuen abb17e9ff6 [VersionUp] 3.8.5 GM Build 3850. 2024-04-07 00:56:09 +08:00
ShikiSuen afad2076b0 Update Data - 20240407 2024-04-07 00:26:41 +08:00
ShikiSuen 40245cf1d5 ToolTipUI // Enable round panel corners on old systems. 2024-04-06 17:07:13 +08:00
ShikiSuen 08a54c14c1 PCB // Enable round panel corners. 2024-04-06 16:38:26 +08:00
ShikiSuen 67a6bb5f87 LMMgr && Main // Allow dumping user dicts through terminal. 2024-04-02 19:12:35 +08:00
ShikiSuen 97a9e9aa5c LMAssembly // Let LMInstantiator summarize user data. 2024-04-02 19:12:35 +08:00
ShikiSuen c932083c5f LMInstantiator // Make async optional while loading user dicts. 2024-04-02 18:47:18 +08:00
ShikiSuen 25d8f7c093 LMInstantiator // Stop pinning default user weights for single reading. 2024-03-30 18:38:45 +08:00
ShikiSuen 14adf03311 LMInstantiator // Differentiate scores from factory results. 2024-03-30 18:38:45 +08:00
ShikiSuen 817df50916 LMMgr.UserPhrase // Fine-tweak suggestNextFreq(). 2024-03-30 18:31:06 +08:00
ShikiSuen e8961ff33f SessionCtl // Switch to .ofEmpty() state on toggling CapsLock. 2024-03-21 19:58:41 +08:00
ShikiSuen 2d23deb83a Tekkon // Update to v1.6.0 release. 2024-03-21 16:50:49 +08:00
ShikiSuen fc5243c97f AppDelegate // Update max RAM threshold to 1024MB. 2024-03-21 16:50:46 +08:00
ShikiSuen 9e1d130ba7 UserDef // +kMinCellWidthForHorizontalMatrix. 2024-03-10 22:00:51 +08:00
ShikiSuen 5a6aee2a25 PCB // Tweak panel opacity. 2024-03-10 22:00:51 +08:00
ShikiSuen aa4162fa9b DataCompiler // Fix SQLite random segmentation fault 11. 2024-03-10 00:03:54 +08:00
ShikiSuen 951f41461a TDKCandidates // Refactor highlightedColor(). 2024-03-09 04:14:59 +08:00
ShikiSuen f46cfda6f5 [VersionUp] 3.8.4 GM Build 3840. 2024-03-08 03:21:39 +08:00
ShikiSuen 03edccff4f Update Data - 20240308 2024-03-08 03:18:05 +08:00
ShikiSuen d8aba434d9 SecureEventInputSputnik // Patch a memory leak, etc. 2024-03-08 01:54:11 +08:00
ShikiSuen 005116c429 SPM // Consolidate dependencies. 2024-03-06 00:18:55 +08:00
ShikiSuen 93256f0095 Xcode // Add a debuggable-only target. 2024-03-04 18:34:28 +08:00
ShikiSuen 11bb5a9b66 Xcode // Stop stripping Swift symbols. 2024-03-04 17:32:25 +08:00
ShikiSuen 9dc7821708 [VersionUp] 3.8.3 GM Build 3830. 2024-03-02 23:06:53 +08:00
ShikiSuen 5d62d5b66d Update Data - 20240301 2024-03-02 23:06:53 +08:00
ShikiSuen c63c531f1b MainAssembly // Include remaining AppDelegate IBOutlets. 2024-03-02 23:06:53 +08:00
ShikiSuen 35d4426730 UserPhrase // Improve score boosting / nerfing for single kanji. 2024-03-02 23:06:53 +08:00
ShikiSuen b628ddd082 LMInstantiator // Expose factoryCoreUnigramsFor(). 2024-03-02 23:06:53 +08:00
ShikiSuen 4e00791144 LMPlainBopomofo // Fix mistakes in Eten DOS CHS Sequence Data. 2024-03-02 23:06:53 +08:00
ShikiSuen 1e098cac53 InputHandler // Prioritize the handling of the service menu. 2024-03-02 23:06:53 +08:00
ShikiSuen 0107e7cd78 ServiceMenu // Filter some services if readings are unavailable. 2024-03-02 23:06:53 +08:00
ShikiSuen 9411686d03 UserDef // +useShiftQuestionToCallServiceMenu. 2024-03-02 23:06:53 +08:00
ShikiSuen 5eec7cd604 MainAssembly // + Candidate Service (Menu & Editor). 2024-03-02 23:06:53 +08:00
ShikiSuen dc79c629a1 CandidateNode // Subclass: ServiceMenuNode. 2024-03-02 23:06:53 +08:00
ShikiSuen 040c597345 Shared // +CandidateTextService. 2024-03-02 23:06:53 +08:00
ShikiSuen 923471c8bb UserDef // +kCandidateServiceMenuContents. 2024-03-02 23:06:53 +08:00
ShikiSuen 46d4e7bdb3 MainAssembly // Refactor wherever using UniformTypeIdentifiers. 2024-03-02 23:06:53 +08:00
ShikiSuen 55dcdc8ce0 (NS)String // Add some codepoint extensions. 2024-03-02 23:06:53 +08:00
ShikiSuen bd5fdcaa26 ClientListMgr // Fix metrics and the invisible scroller. 2024-03-02 23:06:53 +08:00
ShikiSuen d5d9167b1e CocoaImpl // Fix isDarkMode(). 2024-03-02 23:06:53 +08:00
ShikiSuen c2679735c1 PrefMgr // Refactor the didSet methods. 2024-03-02 23:06:53 +08:00
ShikiSuen 4904664277 Shared // Fix a KVO Observer. 2024-03-02 23:06:53 +08:00
ShikiSuen 76dd75ce5a UserDef // +kSpecifyCmdOptCtrlEnterBehavior. 2024-03-02 23:06:53 +08:00
ShikiSuen 7be2a85b25 BrailleSputnik // Initial Implementation. 2024-03-02 23:06:53 +08:00
ShikiSuen 549c361af4 [VersionUp] 3.8.2 GM Build 3820. 2024-03-02 23:06:53 +08:00
ShikiSuen 72655119fa Update Data - 20240223 2024-02-24 03:56:21 +08:00
ShikiSuen 3ebb5f2f48 LMAssembly // Integrate EtenDOS SCPC data into the codebase. 2024-02-23 14:01:30 +08:00
ShikiSuen e44843e603 LMAssembly // Pack LMUserOverride inside LMInstantiator, etc. 2024-02-23 13:55:29 +08:00
ShikiSuen c5899152e6 InputHandler // Move some case-switch results to InputMode enum. 2024-02-21 14:58:26 +08:00
ShikiSuen 275288ea61 Hotenka // Deprecate NSJSONSerialization. 2024-02-19 01:33:47 +08:00
ShikiSuen 4184c3c1d2 DataCompiler // Post-dump SQLite database. 2024-02-18 23:58:06 +08:00
ShikiSuen a559818111 [VersionUp] 3.8.1 SP1 Build 3811. 2024-02-17 17:18:41 +08:00
ShikiSuen 9e3f0d7929 SessionCtl // Fix incorrect menu behavior. 2024-02-17 17:18:36 +08:00
ShikiSuen 3545cc1c22 AppInstaller // Add missing copyright label. 2024-02-17 16:20:59 +08:00
ShikiSuen 69d839e833 VwrAboutCocoa // Patch vertical button stack spacing for macOS 10.9. 2024-02-17 16:20:59 +08:00
ShikiSuen e887ba01a5 CtlCandidateTDK // Patch another issue with reverse lookup. 2024-02-17 16:20:59 +08:00
ShikiSuen b20dfec630 TDKCandidates // Force-refresh reverse lookup results on refresh. 2024-02-17 12:41:45 +08:00
ShikiSuen 63f7cc91fc [VersionUp] 3.8.1 GM Build 3810. 2024-02-17 00:37:24 +08:00
ShikiSuen b9595bed4f Update Data - 20240217 2024-02-17 00:09:04 +08:00
ShikiSuen 83b80fb863 AboutCocoa & ClientListMgr // Tweak metrics. 2024-02-17 00:07:46 +08:00
ShikiSuen 791256cf31 SessionCtl // Optimize the IME menu for macOS 10.9. 2024-02-17 00:07:46 +08:00
ShikiSuen 0e4651e70e UserDef // +filterNonCNSReadingsForCHTInput. 2024-02-16 16:18:34 +08:00
ShikiSuen 58815d7c54 LMAssembly // Implement CNS pronunciation filter. 2024-02-16 16:18:34 +08:00
ShikiSuen b479acf779 Repo // Add KimoDataReader using NSConnection. 2024-02-16 16:18:34 +08:00
ShikiSuen 424a736c8e TDKCandidates // Support displaying codepoints. 2024-02-15 13:44:25 +08:00
ShikiSuen ccd9b391e4 UserDef // +showCodePointInCandidateUI. 2024-02-15 13:44:25 +08:00
ShikiSuen 5090c7e6d4 CocoaExtension // Allow overriding stack spacing. 2024-02-14 22:36:54 +08:00
ShikiSuen 169902db19 SettingsCocoa // Simplify some phrases. 2024-02-14 22:15:49 +08:00
ShikiSuen 3af51f22e1 SettingsCocoa // Share metrics across panes. 2024-02-14 22:15:49 +08:00
ShikiSuen ad98484094
GitHub // Update CI to build SPM packages instead. 2024-02-14 17:54:10 +08:00
ShikiSuen 982686018b Makefile // Optimize for SPM packages. 2024-02-14 17:41:34 +08:00
ShikiSuen b013dc4d82 FileOpenMethod // Use localized app names. 2024-02-14 14:37:47 +08:00
ShikiSuen bb4729ee3f LMMgr // Fix when to reload phrase editors. 2024-02-14 13:44:22 +08:00
ShikiSuen 71e34790e8 SettingsCocoa // Add file drag receiver button for Kimo Data import. 2024-02-14 04:32:49 +08:00
ShikiSuen 54f61a28b1 Repo // Unify modal window calling methods. 2024-02-14 04:14:17 +08:00
ShikiSuen 21dcb58748 SettingsCocoa // Make narration settings effective immediately. 2024-02-13 20:53:34 +08:00
ShikiSuen 903faae51f SettingsCocoa // Lock dimensions for descriptions and titles. 2024-02-13 18:10:38 +08:00
ShikiSuen 820ee5b0f6 CocoaImpl // Fix NSView.makeSimpleConstraint(). 2024-02-13 18:10:38 +08:00
ShikiSuen 2ce79e5a05 SettingsCocoa // Also layout subtree at final step. 2024-02-13 18:10:38 +08:00
ShikiSuen 92b2ada9c5 LatinKeyboardMappings // Maintenance fix with Dvorak.QwertyCMD. 2024-02-13 18:10:38 +08:00
ShikiSuen e749e65627 Xcode // FUCK OFF USER SCRIPT SANDBOXING. 2024-02-13 00:19:42 +08:00
ShikiSuen 1b3dcb0e0e [VersionUp] 3.8.0 SP1 Build 3801. 2024-02-13 00:10:44 +08:00
ShikiSuen fcb839bb33 PkgInstaller // Fix a wrong parameter in postflight script. 2024-02-13 00:10:44 +08:00
ShikiSuen 1111a249ec SettingsCocoa // Fix an issue with menu tags in PhraseEditor. 2024-02-13 00:08:21 +08:00
ShikiSuen 5d8680c4b5 Main // Support "--import-kimo" terminal parameter, etc. 2024-02-13 00:08:21 +08:00
ShikiSuen ae73869a2d [VersionUp] 3.8.0 GM Build 3800. 2024-02-12 15:30:27 +08:00
ShikiSuen 933fdb2347 Update Data - 20240212 2024-02-12 15:28:11 +08:00
ShikiSuen 026fbd7fa4 Repo // Update CNS data timestamp to 2024-01-23. 2024-02-12 15:20:09 +08:00
ShikiSuen 177d0c87a3 PCB // Add shadow for cursor. 2024-02-12 14:26:37 +08:00
ShikiSuen 89d07b2edd Repo // Checking J / K key validity as candidate keys. 2024-02-12 04:08:27 +08:00
ShikiSuen 072d39790e UserDef // + useJKtoMoveCompositorCursorInCandidateState. 2024-02-12 04:08:27 +08:00
ShikiSuen 319a1f8d4a Repo // Introducing LocalizableFileSorter script. 2024-02-12 04:08:27 +08:00
ShikiSuen e1b7a4df9f InputHandler // +Enum: TypingMethod. 2024-02-12 04:08:27 +08:00
ShikiSuen 28e53c27ad UserDef // + dodgeInvalidEdgeCandidateCursorPosition. 2024-02-12 04:08:27 +08:00
ShikiSuen 095a0a34c5 Localizable // Tweak file structure. 2024-02-12 04:08:27 +08:00
ShikiSuen b82467dc2d MainAssembly // Organize certain source code files. 2024-02-12 04:08:27 +08:00
ShikiSuen f4a407a860 Settings // Use unique toolbar identifiers. 2024-02-12 04:08:27 +08:00
ShikiSuen b8f5077198 PrefUITabs // Update new icons for macOS 10.x. 2024-02-12 04:08:27 +08:00
ShikiSuen 4af0d515dc SettingsCocoa // Center the toolbar icons. 2024-02-12 04:08:27 +08:00
ShikiSuen 7fd6ea36da UpdateSputnik // Patch a misnotification. 2024-02-12 04:08:27 +08:00
ShikiSuen df5075972a IMKHelper // Fix a TISInputSource installation crash in macOS 10.9. 2024-02-12 04:08:27 +08:00
ShikiSuen cfad082b14 NSEventImpl // Again fix NSInternalInconsistencyException issue. 2024-02-10 13:39:30 +08:00
ShikiSuen 965008fb7f TDKCandidates // Refactor context menu items. 2024-02-10 02:35:06 +08:00
ShikiSuen bb9bc058cc SessionCtl // Refactor the menu structure. 2024-02-10 02:35:05 +08:00
ShikiSuen a219b7881f AboutUI // Reimplement without XIB for old systems. 2024-02-10 02:35:05 +08:00
ShikiSuen 80fe5ecb74 Repo // Move RevLookUpWindow to MainAssembly. 2024-02-10 02:35:05 +08:00
ShikiSuen e071ece2f3 Localizable // Maintenance. 2024-02-10 02:35:05 +08:00
ShikiSuen 962e61f6f3 ClientListMgr // Reimplement without XIB. 2024-02-10 02:35:05 +08:00
ShikiSuen 8679943b9d Repo // Deprecating CtlPrefWindow. 2024-02-10 02:35:05 +08:00
ShikiSuen 2465814e55 SettingsCocoa // First implementation, replacing CtlPrefWindow. 2024-02-10 02:35:05 +08:00
ShikiSuen b8c915dca0 Repo // + UserDefRenderableCocoa & extending AppKit. 2024-02-10 02:33:58 +08:00
ShikiSuen 368f9bb653 SwiftImpl // Add array builder and Bool.from(integer:). 2024-02-09 18:37:20 +08:00
ShikiSuen a1d9f502c1 Repo // Implementing FileOpenMethod. 2024-02-09 18:37:20 +08:00
ShikiSuen c0e1eb449d PEUI // Patch textView.isRichText. 2024-02-09 01:37:34 +08:00
ShikiSuen e3a775dfa5 PEUI // Fix i18n. 2024-02-06 21:33:19 +08:00
ShikiSuen 2fb1b22270 SettingsUI // Update a binding & Folder structure changes. 2024-02-06 03:23:42 +08:00
ShikiSuen a448c4cf7a SwiftUI // Fix the wrong term "onChange" to "didChange". 2024-02-04 00:16:34 +08:00
ShikiSuen 77f39023ab [VersionUp] 3.7.3 GM Build 3730. 2024-02-02 20:08:41 +08:00
ShikiSuen 0905d5c50d Update Data - 20240202 2024-02-02 20:07:32 +08:00
ShikiSuen 85833c40d9 SecureEventInputSputnik // Also monitor hibernation status. 2024-01-30 14:13:51 +08:00
ShikiSuen 6b0353107f CheatSheet // Add new contents to reflect recent feature changes. 2024-01-30 13:28:47 +08:00
ShikiSuen 38fcbb3e46 Repo // Update CNS data timestamp to 2024-01-15. 2024-01-29 23:12:22 +08:00
ShikiSuen aac80eba7d InputHandler // Allow moving cursor in candidate state.
- The hotkey is (Shift+)Opt+FWD/BWD.
2024-01-29 22:23:35 +08:00
ShikiSuen 24cf7e9971 InputHandler // Improve precision of handling modified flags. 2024-01-29 21:19:15 +08:00
ShikiSuen 4c45f4ddda PrefWindow // + numPadCharInputBehavior. 2024-01-29 21:18:56 +08:00
ShikiSuen 56d4f42641 SettingsUI // + numPadCharInputBehavior. 2024-01-29 21:18:56 +08:00
ShikiSuen 782f3bd21c InputHandler // + handleNumPadKeyInput(). 2024-01-29 21:18:56 +08:00
ShikiSuen ffd64bd7a8 UserDef // + numPadCharInputBehavior. 2024-01-29 21:18:56 +08:00
ShikiSuen 6763cf7889 Localizable // Fix CJK colons. 2024-01-29 21:18:56 +08:00
ShikiSuen ca3a3abf9c CtlPrefWindow // i18n fixes. 2024-01-29 21:18:56 +08:00
ShikiSuen 01924a1cf1 SettingsUI // Massive refactor using UserDefRenderable. 2024-01-29 21:18:56 +08:00
ShikiSuen 09aec2bb06 SettingsUI // Implement UserDefRenderable. 2024-01-29 21:18:56 +08:00
ShikiSuen cfe9a1ce5d LMInstantiator // Add ability for supplying NumPad results. 2024-01-29 21:18:56 +08:00
ShikiSuen 586822c981 Repo // Refactor APIs related to LM access and configs. 2024-01-29 21:18:56 +08:00
ShikiSuen 23ef3124d4 Shared // Implementing KBEvent. 2024-01-29 21:18:56 +08:00
ShikiSuen 5e1208bc5e InputHandler // Implement (BOOL)handleCandidateInput:ignoringModifiers:. 2024-01-19 11:53:17 +08:00
ShikiSuen 71dc1afc48 ShiftKeyUpChecker // Change delay interval to 0.2. 2024-01-19 11:53:17 +08:00
ShikiSuen b3ae482c70 PCB // Renovate the UI theme. 2024-01-19 11:53:17 +08:00
ShikiSuen 68d61f2311 SecureEventInputSputnik // Patch the detection method for screen savers. 2024-01-09 09:57:49 +08:00
ShikiSuen 5fb0bab91a [VersionUp] 3.7.2 GM Build 3720. 2024-01-08 01:23:38 +08:00
ShikiSuen f638de4b35 Update Data - 20240108 2024-01-08 01:22:57 +08:00
ShikiSuen 68b72112db Xcode // Re-enable build-script sandboxing. 2024-01-07 18:59:28 +08:00
ShikiSuen ad5fd50733 DataCompiler // Set SQLite journal mode == OFF, etc. 2024-01-07 18:55:28 +08:00
ShikiSuen 53effaae0a Hotenka // Set SQLite journal mode == OFF. 2024-01-07 18:49:43 +08:00
ShikiSuen 86bab5c7a8 LMInstantiator // Set SQLite journal mode == OFF. 2024-01-07 18:49:43 +08:00
ShikiSuen 3cd327eb15 vChewingLM // Fix [String].runAsSQLPreparedSteps(). 2024-01-07 18:49:43 +08:00
ShikiSuen fe34bfffa0 PrefWindow // Fix an i18n usage of colons. 2024-01-07 18:49:37 +08:00
ShikiSuen ed93a87f47 PrefWindow // Bind "ReadingNarrationCoverage" option. 2024-01-07 18:49:37 +08:00
ShikiSuen edd38d8c92 SettingsUI // Bind "ReadingNarrationCoverage" option. 2024-01-07 18:49:37 +08:00
ShikiSuen c85e7142fc Repo // Implement "ReadingNarrationCoverage" feature. 2024-01-07 18:49:37 +08:00
ShikiSuen 2a694081bd UserDef & Prefs // Add "ReadingNarrationCoverage" option. 2024-01-07 18:49:37 +08:00
ShikiSuen 2ec3214491 Repo // Introducing associated phrases in non-SCPC mode.
- Our implementation doesn't use compositor to handle associated phrases, considering that there are too many polyphonic ideographs in Mandarin Chinese.
- This implementation is NOT meant to be as competitive as the similar feature in McBopomofo PR416 (which uses compositor but has issues with polyphonic ideographs).
- This also brings related updates for CheatSheet.
- The translated terms of "Associated Phrases" are changed in this commit.
2024-01-07 18:49:37 +08:00
ShikiSuen 3e4c564248 InfoPlist // Maintenance edits. 2024-01-07 18:49:37 +08:00
ShikiSuen 95f0ff5fd8 SecureEventInputSputnik // New method for checking a locked desktop. 2024-01-07 18:49:36 +08:00
ShikiSuen cfe8f8759c SessionCtl // Fix selectionRange() for inline composition buffers. 2024-01-07 18:49:36 +08:00
ShikiSuen 622744e961 GitHub // Update CI settings. 2024-01-07 18:49:36 +08:00
ShikiSuen d7fb717030 [VersionUp] 3.7.1 SP1 Build 3711. 2024-01-01 02:05:24 +08:00
ShikiSuen 8a5d2a5f6f SessionCtl // Revert some buggy refactors. 2024-01-01 02:04:33 +08:00
ShikiSuen b449edc2a8 [VersionUp] 3.7.1 GM Build 3710. 2024-01-01 00:10:28 +08:00
ShikiSuen 7d53740f88 Update Data - 20240101 2024-01-01 00:05:33 +08:00
ShikiSuen 00b5be3784 InfoPlist // Refactor. 2023-12-31 23:51:42 +08:00
ShikiSuen 73b0da3eb4 Repo // Stop previous session from interfering current palettes. 2023-12-31 23:51:42 +08:00
ShikiSuen 1c92ab8edf LMCassette // Refactor && Fix .clear(). 2023-12-31 23:51:42 +08:00
ShikiSuen 4317c9c653 LMAssembly // Add a test against LMInstantiator with given tokens. 2023-12-29 16:50:31 +08:00
ShikiSuen b0e237e08d [VersionUp] 3.7.0 GM Build 3700. 2023-12-28 22:12:05 +08:00
ShikiSuen b1bb8a6710 Update Data - 20231228 2023-12-28 22:11:22 +08:00
ShikiSuen cd28c5d44d Assets // Make icons look more concordant on macOS 14 Sonoma.
- There are still issues need to be solved by Apple.
2023-12-28 22:11:22 +08:00
ShikiSuen a845dae62b TDKCandidates // Use Courier New for selection keys on old OS. 2023-12-28 22:11:22 +08:00
ShikiSuen d8e72674d7 InputHandler // Add support for `%keys_to_directly_commit`. 2023-12-27 14:53:37 +08:00
ShikiSuen 51580ac2fb LMCassette // Add support for `%keys_to_directly_commit`. 2023-12-27 14:53:37 +08:00
ShikiSuen 2a4b01c234 Repo // Close all panels of the previous session. 2023-12-27 14:53:37 +08:00
ShikiSuen c5ce9199bd LMAssembly // Implementing InputToken support. 2023-12-21 22:53:33 +08:00
ShikiSuen 87f7328636 Megrez // Turn KeyValuePaired and Unigram into Structs again.
- This can solve the crash issues while being deduplicated in Swift-only method.
2023-12-20 11:10:14 +08:00
ShikiSuen 1dcdc21411 SwiftImpl // Patch RangeReplaceableCollection.deduplicated(). 2023-12-20 11:10:14 +08:00
ShikiSuen 1e0ec83dda Xcode // Disable build-script sandboxing for now.
- This motherfucking feature hinders dictionary compiler from working well, bugging it with SQLite segmentation faults.
2023-12-20 11:10:14 +08:00
ShikiSuen 7339d6e6e6 SecureEventInputSputnik // Handle screen-saver and lock-screen. 2023-12-20 11:10:14 +08:00
ShikiSuen 0742af02a9 Config // Update config backup, adding GitLink repository. 2023-12-08 00:43:56 +08:00
ShikiSuen 87e367a81c Git // Switch GitLab from GL-China to GL-Global. 2023-12-04 17:25:20 +08:00
ShikiSuen 70a1b7896f [VersionUp] 3.6.3 SP1 Build 3631. 2023-12-04 17:23:46 +08:00
ShikiSuen 7b4cfb554a Update Data - 20231204 2023-12-04 17:23:46 +08:00
ShikiSuen 464838b721 SettingsUI // Ensure the visibility of scroll bars when should. 2023-12-04 17:23:46 +08:00
ShikiSuen 19339d8195 DataCompiler // Avoid segmentation faults. 2023-12-04 16:55:04 +08:00
ShikiSuen 5ef9a5b012 LMA // Fix wrong results given by hasUnigramsFor() for cassette module. 2023-12-04 15:26:10 +08:00
ShikiSuen 57a49cd245 [VersionUp] 3.6.3 GM Build 3630. 2023-12-04 15:26:10 +08:00
ShikiSuen 815e56db20 Update Data - 20231202 2023-12-04 15:26:10 +08:00
ShikiSuen 36fcff6614 LMMgr // Fix logical errors with external factory dict path. 2023-12-04 15:26:10 +08:00
ShikiSuen 8a634a9abd i18n // Fix certain texts regarding "plist". 2023-12-02 14:48:11 +08:00
ShikiSuen 3a748b6cc2 Xcode // Simplify the allowed output file list for Script Sandboxing. 2023-12-02 13:24:55 +08:00
ShikiSuen a8e72c902b Main // Allow dumping UserDefaults through terminal. 2023-12-02 13:22:41 +08:00
ShikiSuen 82dea233b6 PrefMgr // Add ability to dump pref data as a shell script. 2023-12-02 13:22:41 +08:00
ShikiSuen 238845fb1d UserDef // Let kClientsIMKTextInputIncapable dumpable. 2023-12-02 13:22:41 +08:00
ShikiSuen 33bcbff3dc DataCompiler // Only compile JSON when `--json` argument is given. 2023-12-02 13:22:41 +08:00
ShikiSuen a4e301beef DataCompiler // Correctly handle statement pointers. 2023-12-02 13:22:37 +08:00
ShikiSuen bbe1b409c5 LMAssembly // Faster query speed to check data existence. 2023-12-02 13:22:37 +08:00
ShikiSuen 161aa100cc LMAssembly // Correctly handle statement pointers. 2023-12-02 13:22:37 +08:00
ShikiSuen a66879f7bf Hotenka // Patch a memory leak. 2023-12-02 00:53:15 +08:00
ShikiSuen af8b15e170 i18n // Fix the description for .RespectClientAccentColor(). 2023-12-02 00:53:15 +08:00
ShikiSuen c45772b90c SessionCtl // Don't read client accent if the OS accent is customized. 2023-12-02 00:53:15 +08:00
ShikiSuen 274534b0f2 CocoaImpl // Check whether "AppleAccentColor" is specified. 2023-12-02 00:53:15 +08:00
ShikiSuen 2f81bd5824 Megrez // Add a safe check in Compositor.update(). 2023-12-02 00:53:15 +08:00
ShikiSuen 2880901491 BookmarkManager // Better compatibility with URL bookmarks. 2023-12-02 00:49:41 +08:00
ShikiSuen de1272f389 [VersionUp] 3.6.2 GM Build 3620. 2023-11-29 22:19:21 +08:00
ShikiSuen f95eac4ab8 Update Data - 20231129 2023-11-29 22:19:21 +08:00
ShikiSuen 1c435aefc6 Repo // Update CNS version timestamp to v2023-11-06. 2023-11-29 22:19:21 +08:00
ShikiSuen bee7b6e555 InfoPlist // Update TISIconLabels. 2023-11-29 22:16:56 +08:00
ShikiSuen 344b08c7a2 AboutUI // Fine-tune the visual style. 2023-11-29 11:24:28 +08:00
ShikiSuen 4b0ec368e7 AppInstaller // Fine-tune the visual style. 2023-11-29 11:24:28 +08:00
ShikiSuen 4c97eae53e i18n // Term fix. 2023-11-29 11:24:28 +08:00
ShikiSuen 67a2734d44 AppDelegate // Set the maximum allowed memory usage to 384MB. 2023-11-29 09:24:47 +08:00
ShikiSuen eb68454d8d SessionCtl // Show memory usage when debug mode is ON. 2023-11-29 09:24:47 +08:00
ShikiSuen 334e6e0ad5 PrefUI // Bind respectClientAccentColor(). 2023-11-29 09:24:47 +08:00
ShikiSuen d30013a552 PrefWindow // Bind respectClientAccentColor(). 2023-11-29 09:24:47 +08:00
ShikiSuen 9b1fdb9c69 TDKCandidates // Read delegate.clientAccentColor(). 2023-11-29 09:24:47 +08:00
ShikiSuen 8c70327d90 SessionCtl // Add .clientAccentColor(). 2023-11-29 09:24:47 +08:00
ShikiSuen 61dc2b991f UserDefs // +respectClientAccentColor(). 2023-11-29 09:24:47 +08:00
ShikiSuen 4d623c58a4 CocoaImpl // Add API for finding accent colors. 2023-11-29 09:24:47 +08:00
ShikiSuen e5591ef9cc CtlPrefWindow // Compatibility with vintage macOS versions. 2023-11-27 23:54:39 +08:00
ShikiSuen 133901ede2 Repo // Add SQLite support for factory database. 2023-11-27 23:54:39 +08:00
ShikiSuen 40d866714e TDKCandidates // Fix a bug with single-page matrix pools. 2023-11-27 19:06:18 +08:00
ShikiSuen 787bbd5c0f TDKCandidates // Patch the bgColor of the current candidate line. 2023-11-27 19:06:18 +08:00
ShikiSuen 93da928342 AppInstaller // i18n fix. 2023-11-18 00:15:04 +08:00
ShikiSuen ee82abc3dd AboutUI // Make close button effective on macOS 13. 2023-11-09 22:20:42 +08:00
ShikiSuen 728570a342 AppInstaller // Compatibility with Xcode 14.2. 2023-11-05 22:50:46 +08:00
ShikiSuen d2f4fe6f2f [VersionUp] 3.6.1 SP2 Build 3612. 2023-11-01 21:40:05 +08:00
ShikiSuen ad2d4fd781 PrefWindow // Add a button for importing KeyKey user data. 2023-11-01 21:40:05 +08:00
ShikiSuen f26025ffe7 PrefUI // Add a button for importing KeyKey user data. 2023-11-01 21:40:01 +08:00
ShikiSuen b0c5ba0f98 LMMgr // + importYahooKeyKeyUserDictionary(). 2023-11-01 21:03:33 +08:00
ShikiSuen d5117dc3e5 i18n // Prepare for the next new feature. 2023-11-01 20:36:24 +08:00
ShikiSuen 6417c0193e CapsLockToggler // Fix a memory-leak issue. 2023-11-01 13:46:01 +08:00
ShikiSuen 3fbb0b418f LMMgr // Fix openPhraseFile().
- This is only needed in mainstream releases.
2023-11-01 11:38:29 +08:00
ShikiSuen f9f0c21b9e [VersionUp] 3.6.1 SP1 Build 3611. 2023-10-30 12:01:48 +08:00
ShikiSuen 36fe4fced3 SessionCtl // Deprecate .isCapsLocked(). 2023-10-30 12:01:48 +08:00
ShikiSuen 65cf83d902 Repo // Fix the capslock state detection. 2023-10-30 11:01:36 +08:00
ShikiSuen c943f4ec52 SessionCtl // Remove PreCommit Handling. 2023-10-30 10:46:21 +08:00
ShikiSuen 378e32d7cd README // Update system requirements. 2023-10-29 01:39:39 +08:00
ShikiSuen 2e52a6b2d8 [VersionUp] 3.6.1 GM Build 3610. 2023-10-28 22:47:56 +08:00
ShikiSuen eb5a867a37 Update Data - 20231028 2023-10-28 22:47:56 +08:00
ShikiSuen 1b73504572 SessionCtl // Hide notifications if dedicated CpLk processing is off. 2023-10-28 22:47:56 +08:00
ShikiSuen dca874dc2a NSEvent // Omit .capslock from .keyModifierFlags(). 2023-10-28 22:47:56 +08:00
ShikiSuen 64837d699a InputHandler // Fix Zhuyin typing in CapsLock mode. 2023-10-28 22:47:56 +08:00
ShikiSuen c55c54c7e8 PkgInstaller // Patch some commands to let them always return 0. 2023-10-28 22:47:56 +08:00
ShikiSuen d66d9799c0 SessionCtl // Clear ICB display before committing things. 2023-10-28 22:47:56 +08:00
ShikiSuen 2a5cc07b5b NotifierUI // Compensate the window area with system notifications. 2023-10-28 22:47:56 +08:00
ShikiSuen ffe943dfbc PrefUI // Add two new UserDef items. 2023-10-28 22:47:56 +08:00
ShikiSuen 9c525b1d0e PrefWindow // Add two new UserDef items. 2023-10-28 22:47:56 +08:00
ShikiSuen 54c1e60d7e MainAssembly // Bind two new UserDef items. 2023-10-28 22:47:56 +08:00
ShikiSuen 1f9a9fa0c0 Prefs // Add two new UserDef items. 2023-10-28 22:47:56 +08:00
ShikiSuen ac653015c6 UserDef // Add 2 new keys regarding Alphanumerical Modes. 2023-10-28 22:47:56 +08:00
ShikiSuen 608d8970bd Repo // Remove PreferencePane.
- It became ineffective since macOS 10.15 Catalina.
2023-10-28 22:47:56 +08:00
ShikiSuen cbdc68e180 Repo // Notice that Shift-key toggle only works when CpLk is OFF. 2023-10-28 22:47:56 +08:00
ShikiSuen 7f744589af PrefWindow // +checkAbusersOfSecureEventInputAPI(). 2023-10-28 22:47:56 +08:00
ShikiSuen 6e946f5db1 PrefUI // +checkAbusersOfSecureEventInputAPI(). 2023-10-28 22:47:56 +08:00
ShikiSuen 8d89da0c2b Prefs // +checkAbusersOfSecureEventInputAPI(). 2023-10-28 22:47:56 +08:00
ShikiSuen 9a0d0dc633 Repo // Introduce SecurityAgentHelper. 2023-10-28 22:47:56 +08:00
ShikiSuen 5460217594 CocoaExtension // Add SecureEventInputSputnik. 2023-10-28 22:47:56 +08:00
ShikiSuen 90d1803cf3 SessionCtl // Turn off isASCIIMode if CapsLock is turned off. 2023-10-28 22:47:56 +08:00
ShikiSuen 6be6492470 SessionCtl // Turn off Caps Lock if Eisu key is turned off. 2023-10-28 22:47:56 +08:00
ShikiSuen 6c371b844f Repo // Add HangarRash_SwiftyCapsLockToggler. 2023-10-28 22:47:56 +08:00
ShikiSuen b7c914a611 NSEvent // Use filtered modifier flags. 2023-10-28 22:47:56 +08:00
ShikiSuen acce63d7bc SymbolMenu // Add subset for radicals. 2023-10-28 22:47:56 +08:00
ShikiSuen efe2abb03d TDKCandidates // Tweak default background color. 2023-10-28 22:47:56 +08:00
ShikiSuen ade9953756 MainAssembly // Remove PrefMgrObservable. 2023-10-28 22:47:56 +08:00
ShikiSuen ac4ed0a320 Repo // Add SCPC sequence data from Eten DOS. 2023-10-28 22:47:56 +08:00
ShikiSuen 7bab872346 Assets // +AppIconFallback. 2023-10-28 22:47:56 +08:00
ShikiSuen 1025dca837 CtlAboutUI // Refactor using SwiftUI. 2023-10-28 22:47:56 +08:00
ShikiSuen d18eb5d45b SettingsUI // Remove an unused variable. 2023-10-28 22:47:56 +08:00
ShikiSuen fac382a4ea AppInstaller // Rewrite using SwiftUI. 2023-10-28 22:47:56 +08:00
ShikiSuen 6c2a3ee8b4 PhraseEditorUI // Fine-tweaks. 2023-10-28 22:47:56 +08:00
ShikiSuen 5b11ffec44 SwiftPackages // Boost platform req. to macOS 11. 2023-10-28 22:47:56 +08:00
ShikiSuen 293582fa5d Repo // Boost minimum OS req. to macOS 12. 2023-10-28 22:47:56 +08:00
318 changed files with 24722 additions and 20428 deletions

View File

@ -18,14 +18,14 @@
[branch "upd/dev"]
remote = origin
merge = refs/heads/upd/dev
[branch "bleed/1.5.x"]
remote = origin
merge = refs/heads/bleed/1.5.x
[remote "gitlink"]
url = https://gitlink.org.cn/vChewing/vChewing-macOS.git
fetch = +refs/heads/*:refs/remotes/gitlink/*
[remote "gitcode"]
url = https://gitcode.net/vChewing/vChewing-macOS.git/
fetch = +refs/heads/*:refs/remotes/gitcode/*
[remote "gitlab"]
url = https://jihulab.com/vChewing/vChewing-macOS.git/
url = https://gitlab.com/vChewing/vChewing-macOS.git/
fetch = +refs/heads/*:refs/remotes/gitlab/*
[remote "github"]
url = https://github.com/vChewing/vChewing-macOS/
@ -34,6 +34,7 @@
url = https://gitee.com/vChewing/vChewing-macOS.git/
fetch = +refs/heads/*:refs/remotes/all/*
pushurl = https://gitee.com/vchewing/vChewing-macOS.git/
pushurl = https://gitlink.org.cn/vChewing/vChewing-macOS.git/
pushurl = https://gitcode.net/vChewing/vChewing-macOS.git/
pushurl = https://jihulab.com/vChewing/vChewing-macOS.git/
pushurl = https://gitlab.com/vChewing/vChewing-macOS.git/
pushurl = https://github.com/vChewing/vChewing-macOS/

View File

@ -1,4 +1,4 @@
name: Build-with-macOS-latest
name: debug-macOS-MainAssembly
on:
push:
branches: [ "main" ]
@ -7,16 +7,16 @@ on:
jobs:
build:
name: Build (latest)
runs-on: macOS-latest
name: Build
runs-on: macOS-13
env:
GIT_SSL_NO_VERIFY: true
steps:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '^15.0'
xcode-version: '^15.1'
- uses: actions/checkout@v1
- name: Clean
run: make clean
run: make spmClean
- name: Build
run: git pull --all && git submodule sync; make update; make
run: make spmDebug

View File

@ -9,6 +9,7 @@
// requirements defined in MIT License.
import Foundation
import SQLite3
// MARK: -
@ -27,6 +28,14 @@ fileprivate extension String {
}
}
// MARK: - String as SQL Command
fileprivate extension String {
@discardableResult func runAsSQLExec(dbPointer ptrDB: inout OpaquePointer?) -> Bool {
ptrDB != nil && sqlite3_exec(ptrDB, self, nil, nil, nil) == SQLITE_OK
}
}
// MARK: - StringView Ranges Extension (by Isaac Xen)
fileprivate extension String {
@ -117,40 +126,125 @@ func cnvPhonabetToASCII(_ incoming: String) -> String {
private let urlCurrentFolder = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
private let urlCHSRoot: String = "./components/chs/"
private let urlCHTRoot: String = "./components/cht/"
private let urlCHSRoot: String = "\(urlCurrentFolder.path)/components/chs/"
private let urlCHTRoot: String = "\(urlCurrentFolder.path)/components/cht/"
private let urlKanjiCore: String = "./components/common/char-kanji-core.txt"
private let urlMiscBPMF: String = "./components/common/char-misc-bpmf.txt"
private let urlMiscNonKanji: String = "./components/common/char-misc-nonkanji.txt"
private let urlKanjiCore: String = "\(urlCurrentFolder.path)/components/common/char-kanji-core.txt"
private let urlMiscBPMF: String = "\(urlCurrentFolder.path)/components/common/char-misc-bpmf.txt"
private let urlMiscNonKanji: String = "\(urlCurrentFolder.path)/components/common/char-misc-nonkanji.txt"
private let urlPunctuation: String = "./components/common/data-punctuations.txt"
private let urlSymbols: String = "./components/common/data-symbols.txt"
private let urlZhuyinwen: String = "./components/common/data-zhuyinwen.txt"
private let urlCNS: String = "./components/common/char-kanji-cns.txt"
private let urlPunctuation: String = "\(urlCurrentFolder.path)/components/common/data-punctuations.txt"
private let urlSymbols: String = "\(urlCurrentFolder.path)/components/common/data-symbols.txt"
private let urlZhuyinwen: String = "\(urlCurrentFolder.path)/components/common/data-zhuyinwen.txt"
private let urlCNS: String = "\(urlCurrentFolder.path)/components/common/char-kanji-cns.txt"
private let urlOutputCHS: String = "./data-chs.txt"
private let urlOutputCHT: String = "./data-cht.txt"
private let urlOutputCHS: String = "\(urlCurrentFolder.path)/data-chs.txt"
private let urlOutputCHT: String = "\(urlCurrentFolder.path)/data-cht.txt"
private let urlJSONSymbols: String = "./data-symbols.json"
private let urlJSONZhuyinwen: String = "./data-zhuyinwen.json"
private let urlJSONCNS: String = "./data-cns.json"
private let urlJSONSymbols: String = "\(urlCurrentFolder.path)/data-symbols.json"
private let urlJSONZhuyinwen: String = "\(urlCurrentFolder.path)/data-zhuyinwen.json"
private let urlJSONCNS: String = "\(urlCurrentFolder.path)/data-cns.json"
private let urlJSONCHS: String = "./data-chs.json"
private let urlJSONCHT: String = "./data-cht.json"
private let urlJSONBPMFReverseLookup: String = "./data-bpmf-reverse-lookup.json"
private let urlJSONBPMFReverseLookupCNS1: String = "./data-bpmf-reverse-lookup-CNS1.json"
private let urlJSONBPMFReverseLookupCNS2: String = "./data-bpmf-reverse-lookup-CNS2.json"
private let urlJSONBPMFReverseLookupCNS3: String = "./data-bpmf-reverse-lookup-CNS3.json"
private let urlJSONBPMFReverseLookupCNS4: String = "./data-bpmf-reverse-lookup-CNS4.json"
private let urlJSONBPMFReverseLookupCNS5: String = "./data-bpmf-reverse-lookup-CNS5.json"
private let urlJSONBPMFReverseLookupCNS6: String = "./data-bpmf-reverse-lookup-CNS6.json"
private let urlJSONCHS: String = "\(urlCurrentFolder.path)/data-chs.json"
private let urlJSONCHT: String = "\(urlCurrentFolder.path)/data-cht.json"
private let urlJSONBPMFReverseLookup: String = "\(urlCurrentFolder.path)/data-bpmf-reverse-lookup.json"
private let urlJSONBPMFReverseLookupCNS1: String = "\(urlCurrentFolder.path)/data-bpmf-reverse-lookup-CNS1.json"
private let urlJSONBPMFReverseLookupCNS2: String = "\(urlCurrentFolder.path)/data-bpmf-reverse-lookup-CNS2.json"
private let urlJSONBPMFReverseLookupCNS3: String = "\(urlCurrentFolder.path)/data-bpmf-reverse-lookup-CNS3.json"
private let urlJSONBPMFReverseLookupCNS4: String = "\(urlCurrentFolder.path)/data-bpmf-reverse-lookup-CNS4.json"
private let urlJSONBPMFReverseLookupCNS5: String = "\(urlCurrentFolder.path)/data-bpmf-reverse-lookup-CNS5.json"
private let urlJSONBPMFReverseLookupCNS6: String = "\(urlCurrentFolder.path)/data-bpmf-reverse-lookup-CNS6.json"
private var isReverseLookupDictionaryProcessed: Bool = false
private let urlSQLite: String = "\(urlCurrentFolder.path)/Build/Release/vChewingFactoryDatabase.sqlite"
private var mapReverseLookupForCheck: [String: [String]] = [:]
private var exceptedChars: Set<String> = .init()
private var ptrSQL: OpaquePointer?
var rangeMapJSONCHS: [String: [String]] = [:]
var rangeMapJSONCHT: [String: [String]] = [:]
var rangeMapSymbols: [String: [String]] = [:]
var rangeMapZhuyinwen: [String: [String]] = [:]
var rangeMapCNS: [String: [String]] = [:]
var rangeMapReverseLookup: [String: [String]] = [:]
/// Also use mapReverseLookupForCheck.
// MARK: -
func prepareDatabase() -> Bool {
let sqlMakeTableMACV = """
DROP TABLE IF EXISTS DATA_REV;
DROP TABLE IF EXISTS DATA_MAIN;
CREATE TABLE IF NOT EXISTS DATA_MAIN (
theKey TEXT NOT NULL,
theDataCHS TEXT,
theDataCHT TEXT,
theDataCNS TEXT,
theDataMISC TEXT,
theDataSYMB TEXT,
theDataCHEW TEXT,
PRIMARY KEY (theKey)
) WITHOUT ROWID;
CREATE TABLE IF NOT EXISTS DATA_REV (
theChar TEXT NOT NULL,
theReadings TEXT NOT NULL,
PRIMARY KEY (theChar)
) WITHOUT ROWID;
"""
guard sqlite3_open(":memory:", &ptrSQL) == SQLITE_OK else { return false }
guard sqlite3_exec(ptrSQL, "PRAGMA synchronous = OFF;", nil, nil, nil) == SQLITE_OK else { return false }
guard sqlite3_exec(ptrSQL, "PRAGMA journal_mode = OFF;", nil, nil, nil) == SQLITE_OK else { return false }
guard sqlMakeTableMACV.runAsSQLExec(dbPointer: &ptrSQL) else { return false }
guard "begin;".runAsSQLExec(dbPointer: &ptrSQL) else { return false }
return true
}
@discardableResult func writeMainMapToSQL(_ theMap: [String: [String]], column columnName: String) -> Bool {
for (encryptedKey, arrValues) in theMap {
// SQL 西 ASCII 退''
let safeKey = encryptedKey.replacingOccurrences(of: "'", with: "''")
let valueText = arrValues.joined(separator: "\t").replacingOccurrences(of: "'", with: "''")
let sqlStmt = "INSERT INTO DATA_MAIN (theKey, \(columnName)) VALUES ('\(safeKey)', '\(valueText)') ON CONFLICT(theKey) DO UPDATE SET \(columnName)='\(valueText)';"
guard sqlStmt.runAsSQLExec(dbPointer: &ptrSQL) else {
print("Failed: " + sqlStmt)
return false
}
}
return true
}
@discardableResult func writeRevLookupMapToSQL(_ theMap: [String: [String]]) -> Bool {
for (encryptedKey, arrValues) in theMap {
// SQL 西 ASCII 退''
let safeKey = encryptedKey.replacingOccurrences(of: "'", with: "''")
let valueText = arrValues.joined(separator: "\t").replacingOccurrences(of: "'", with: "''")
let sqlStmt = "INSERT INTO DATA_REV (theChar, theReadings) VALUES ('\(safeKey)', '\(valueText)') ON CONFLICT(theChar) DO UPDATE SET theReadings='\(valueText)';"
guard sqlStmt.runAsSQLExec(dbPointer: &ptrSQL) else {
print("Failed: " + sqlStmt)
return false
}
}
return true
}
// MARK: - Dump SQLite3 Memory Database to File.
@discardableResult func dumpSQLDB() -> Bool {
var ptrSQLTarget: OpaquePointer?
defer { sqlite3_close_v2(ptrSQLTarget) }
guard sqlite3_open(urlSQLite, &ptrSQLTarget) == SQLITE_OK else { return false }
let ptrBackupObj = sqlite3_backup_init(ptrSQLTarget, "main", ptrSQL, "main")
if ptrBackupObj != nil {
sqlite3_backup_step(ptrBackupObj, -1)
sqlite3_backup_finish(ptrBackupObj)
}
return sqlite3_errcode(ptrSQLTarget) == SQLITE_OK
}
// MARK: -
func rawDictForPhrases(isCHS: Bool) -> [Unigram] {
@ -319,8 +413,10 @@ func rawDictForKanjis(isCHS: Bool) -> [Unigram] {
if !isReverseLookupDictionaryProcessed {
do {
isReverseLookupDictionaryProcessed = true
try JSONSerialization.data(withJSONObject: mapReverseLookupJSON, options: .sortedKeys).write(
to: URL(fileURLWithPath: urlJSONBPMFReverseLookup))
if compileJSON {
try JSONSerialization.data(withJSONObject: mapReverseLookupJSON, options: .sortedKeys).write(
to: URL(fileURLWithPath: urlJSONBPMFReverseLookup))
}
mapReverseLookupForCheck = mapReverseLookupUnencrypted
} catch {
NSLog(" - Core Reverse Lookup Data Generation Failed.")
@ -459,10 +555,8 @@ func fileOutput(isCHS: Bool) {
let i18n: String = isCHS ? "簡體中文" : "繁體中文"
var strPunctuation = ""
var rangeMapJSON: [String: [String]] = [:]
let pathOutput = urlCurrentFolder.appendingPathComponent(
isCHS ? urlOutputCHS : urlOutputCHT)
let jsonURL = urlCurrentFolder.appendingPathComponent(
isCHS ? urlJSONCHS : urlJSONCHT)
let pathOutput = URL(fileURLWithPath: isCHS ? urlOutputCHS : urlOutputCHT)
let jsonURL = URL(fileURLWithPath: isCHS ? urlJSONCHS : urlJSONCHT)
var strPrintLine = ""
//
do {
@ -532,11 +626,18 @@ func fileOutput(isCHS: Bool) {
NSLog(" - \(i18n): 要寫入檔案的 txt 內容編譯完畢。")
do {
try strPrintLine.write(to: pathOutput, atomically: true, encoding: .utf8)
try JSONSerialization.data(withJSONObject: rangeMapJSON, options: .sortedKeys).write(to: jsonURL)
if compileJSON {
try JSONSerialization.data(withJSONObject: rangeMapJSON, options: .sortedKeys).write(to: jsonURL)
}
if isCHS {
rangeMapJSONCHS = rangeMapJSON
} else {
rangeMapJSONCHT = rangeMapJSON
}
} catch {
NSLog(" - \(i18n): Error on writing strings to file: \(error)")
}
NSLog(" - \(i18n): 寫入完成。")
NSLog(" - \(i18n): JSON & TXT 寫入完成。")
if !arrFoundedDuplications.isEmpty {
NSLog(" - \(i18n): 尋得下述重複項目,請務必手動排查:")
print("-------------------")
@ -576,7 +677,9 @@ func commonFileOutput() {
let theKey = String(neta[1])
let theValue = String(neta[0])
if !neta[0].isEmpty, !neta[1].isEmpty, line.first != "#" {
mapSymbols[cnvPhonabetToASCII(theKey), default: []].append(theValue)
let encryptedKey = cnvPhonabetToASCII(theKey)
mapSymbols[encryptedKey, default: []].append(theValue)
rangeMapSymbols[encryptedKey, default: []].append(theValue)
}
}
}
@ -587,7 +690,9 @@ func commonFileOutput() {
let theKey = String(neta[1])
let theValue = String(neta[0])
if !neta[0].isEmpty, !neta[1].isEmpty, line.first != "#" {
mapZhuyinwen[cnvPhonabetToASCII(theKey), default: []].append(theValue)
let encryptedKey = cnvPhonabetToASCII(theKey)
mapZhuyinwen[encryptedKey, default: []].append(theValue)
rangeMapZhuyinwen[encryptedKey, default: []].append(theValue)
}
}
}
@ -598,30 +703,33 @@ func commonFileOutput() {
let theKey = String(neta[1])
let theValue = String(neta[0])
if !neta[0].isEmpty, !neta[1].isEmpty, line.first != "#" {
mapCNS[cnvPhonabetToASCII(theKey), default: []].append(theValue)
let encryptedKey = cnvPhonabetToASCII(theKey)
mapCNS[encryptedKey, default: []].append(theValue)
rangeMapCNS[encryptedKey, default: []].append(theValue)
json: if !theKey.contains("_"), !theKey.contains("-") {
rangeMapReverseLookup[theValue, default: []].append(encryptedKey)
if mapReverseLookupCNS1.keys.count <= 16500 {
mapReverseLookupCNS1[theValue, default: []].append(cnvPhonabetToASCII(theKey))
mapReverseLookupCNS1[theValue, default: []].append(encryptedKey)
break json
}
if mapReverseLookupCNS2.keys.count <= 16500 {
mapReverseLookupCNS2[theValue, default: []].append(cnvPhonabetToASCII(theKey))
mapReverseLookupCNS2[theValue, default: []].append(encryptedKey)
break json
}
if mapReverseLookupCNS3.keys.count <= 16500 {
mapReverseLookupCNS3[theValue, default: []].append(cnvPhonabetToASCII(theKey))
mapReverseLookupCNS3[theValue, default: []].append(encryptedKey)
break json
}
if mapReverseLookupCNS4.keys.count <= 16500 {
mapReverseLookupCNS4[theValue, default: []].append(cnvPhonabetToASCII(theKey))
mapReverseLookupCNS4[theValue, default: []].append(encryptedKey)
break json
}
if mapReverseLookupCNS5.keys.count <= 16500 {
mapReverseLookupCNS5[theValue, default: []].append(cnvPhonabetToASCII(theKey))
mapReverseLookupCNS5[theValue, default: []].append(encryptedKey)
break json
}
if mapReverseLookupCNS6.keys.count <= 16500 {
mapReverseLookupCNS6[theValue, default: []].append(cnvPhonabetToASCII(theKey))
mapReverseLookupCNS6[theValue, default: []].append(encryptedKey)
break json
}
}
@ -630,62 +738,32 @@ func commonFileOutput() {
}
NSLog(" - \(i18n): 要寫入檔案的內容編譯完畢。")
do {
try JSONSerialization.data(withJSONObject: mapSymbols, options: .sortedKeys).write(
to: URL(fileURLWithPath: urlJSONSymbols))
try JSONSerialization.data(withJSONObject: mapZhuyinwen, options: .sortedKeys).write(
to: URL(fileURLWithPath: urlJSONZhuyinwen))
try JSONSerialization.data(withJSONObject: mapCNS, options: .sortedKeys).write(
to: URL(fileURLWithPath: urlJSONCNS))
try JSONSerialization.data(withJSONObject: mapReverseLookupCNS1, options: .sortedKeys).write(
to: URL(fileURLWithPath: urlJSONBPMFReverseLookupCNS1))
try JSONSerialization.data(withJSONObject: mapReverseLookupCNS2, options: .sortedKeys).write(
to: URL(fileURLWithPath: urlJSONBPMFReverseLookupCNS2))
try JSONSerialization.data(withJSONObject: mapReverseLookupCNS3, options: .sortedKeys).write(
to: URL(fileURLWithPath: urlJSONBPMFReverseLookupCNS3))
try JSONSerialization.data(withJSONObject: mapReverseLookupCNS4, options: .sortedKeys).write(
to: URL(fileURLWithPath: urlJSONBPMFReverseLookupCNS4))
try JSONSerialization.data(withJSONObject: mapReverseLookupCNS5, options: .sortedKeys).write(
to: URL(fileURLWithPath: urlJSONBPMFReverseLookupCNS5))
try JSONSerialization.data(withJSONObject: mapReverseLookupCNS6, options: .sortedKeys).write(
to: URL(fileURLWithPath: urlJSONBPMFReverseLookupCNS6))
if compileJSON {
try JSONSerialization.data(withJSONObject: mapSymbols, options: .sortedKeys).write(
to: URL(fileURLWithPath: urlJSONSymbols))
try JSONSerialization.data(withJSONObject: mapZhuyinwen, options: .sortedKeys).write(
to: URL(fileURLWithPath: urlJSONZhuyinwen))
try JSONSerialization.data(withJSONObject: mapCNS, options: .sortedKeys).write(
to: URL(fileURLWithPath: urlJSONCNS))
try JSONSerialization.data(withJSONObject: mapReverseLookupCNS1, options: .sortedKeys).write(
to: URL(fileURLWithPath: urlJSONBPMFReverseLookupCNS1))
try JSONSerialization.data(withJSONObject: mapReverseLookupCNS2, options: .sortedKeys).write(
to: URL(fileURLWithPath: urlJSONBPMFReverseLookupCNS2))
try JSONSerialization.data(withJSONObject: mapReverseLookupCNS3, options: .sortedKeys).write(
to: URL(fileURLWithPath: urlJSONBPMFReverseLookupCNS3))
try JSONSerialization.data(withJSONObject: mapReverseLookupCNS4, options: .sortedKeys).write(
to: URL(fileURLWithPath: urlJSONBPMFReverseLookupCNS4))
try JSONSerialization.data(withJSONObject: mapReverseLookupCNS5, options: .sortedKeys).write(
to: URL(fileURLWithPath: urlJSONBPMFReverseLookupCNS5))
try JSONSerialization.data(withJSONObject: mapReverseLookupCNS6, options: .sortedKeys).write(
to: URL(fileURLWithPath: urlJSONBPMFReverseLookupCNS6))
}
} catch {
NSLog(" - \(i18n): Error on writing strings to file: \(error)")
}
NSLog(" - \(i18n): 寫入完成。")
}
// MARK: -
func main() {
let globalQueue = DispatchQueue.global(qos: .default)
let group = DispatchGroup()
group.enter()
globalQueue.async {
NSLog("// 準備編譯符號表情ㄅ文語料檔案。")
commonFileOutput()
group.leave()
}
group.enter()
globalQueue.async {
NSLog("// 準備編譯繁體中文核心語料檔案。")
fileOutput(isCHS: false)
group.leave()
}
group.enter()
globalQueue.async {
NSLog("// 準備編譯簡體中文核心語料檔案。")
fileOutput(isCHS: true)
group.leave()
}
//
_ = group.wait(timeout: .distantFuture)
group.notify(queue: DispatchQueue.main) {
NSLog("// 全部辭典檔案建置完畢。")
}
}
main()
// MARK: -
func healthCheck(_ data: [Unigram]) -> String {
@ -979,3 +1057,107 @@ func healthCheck(_ data: [Unigram]) -> String {
result += "\n"
return result
}
// MARK: - Flags
struct TaskFlags: OptionSet {
public let rawValue: Int
public init(rawValue: Int) {
self.rawValue = rawValue
}
public static let common = TaskFlags(rawValue: 1 << 0)
public static let chs = TaskFlags(rawValue: 1 << 1)
public static let cht = TaskFlags(rawValue: 1 << 2)
}
// MARK: -
var compileJSON = false
var compileSQLite = true
func main() {
let arguments = CommandLine.arguments.compactMap { $0.lowercased() }
let jsonConditionMet = arguments.contains(where: { $0 == "--json" || $0 == "json" })
if jsonConditionMet {
NSLog("// 接下來準備建置 JSON 格式的原廠辭典,同時生成用來偵錯的 TXT 副產物。")
compileJSON = true
compileSQLite = false
} else {
NSLog("// 接下來準備建置 SQLite 格式的原廠辭典,同時生成用來偵錯的 TXT 副產物。")
compileJSON = false
compileSQLite = true
}
let prepared = prepareDatabase()
if compileSQLite, !prepared {
NSLog("// SQLite 資料庫初期化失敗。")
exit(-1)
}
var taskFlags: TaskFlags = [.common, .chs, .cht] {
didSet {
guard taskFlags.isEmpty else { return }
NSLog("// 全部 TXT 辭典檔案建置完畢。")
if compileJSON {
NSLog("// 全部 JSON 辭典檔案建置完畢。")
}
if compileSQLite, prepared {
NSLog("// 開始整合反查資料。")
mapReverseLookupForCheck.forEach { key, values in
values.reversed().forEach { valueLiteral in
let value = cnvPhonabetToASCII(valueLiteral)
if !rangeMapReverseLookup[key, default: []].contains(value) {
rangeMapReverseLookup[key, default: []].insert(value, at: 0)
}
}
}
NSLog("// 反查資料整合完畢。")
NSLog("// 準備建置 SQL 資料庫。")
writeMainMapToSQL(rangeMapJSONCHS, column: "theDataCHS")
writeMainMapToSQL(rangeMapJSONCHT, column: "theDataCHT")
writeMainMapToSQL(rangeMapSymbols, column: "theDataSYMB")
writeMainMapToSQL(rangeMapZhuyinwen, column: "theDataCHEW")
writeMainMapToSQL(rangeMapCNS, column: "theDataCNS")
writeRevLookupMapToSQL(rangeMapReverseLookup)
let committed = "commit;".runAsSQLExec(dbPointer: &ptrSQL)
assert(committed)
let compressed = "VACUUM;".runAsSQLExec(dbPointer: &ptrSQL)
assert(compressed)
if !dumpSQLDB() {
NSLog("// SQLite 辭典傾印失敗。")
} else {
NSLog("// 全部 SQLite 辭典檔案建置完畢。")
}
sqlite3_close_v2(ptrSQL)
}
}
}
let globalQueue = DispatchQueue.global(qos: .default)
let group = DispatchGroup()
group.enter()
globalQueue.async {
NSLog("// 準備編譯符號表情ㄅ文語料檔案。")
commonFileOutput()
taskFlags.remove(.common)
group.leave()
}
group.enter()
globalQueue.async {
NSLog("// 準備編譯繁體中文核心語料檔案。")
fileOutput(isCHS: false)
taskFlags.remove(.cht)
group.leave()
}
group.enter()
globalQueue.async {
NSLog("// 準備編譯簡體中文核心語料檔案。")
fileOutput(isCHS: true)
taskFlags.remove(.chs)
group.leave()
}
//
group.wait()
}
main()

View File

@ -1,199 +0,0 @@
// (c) 2011 and onwards The OpenVanilla Project (MIT License).
// All possible vChewing-specific modifications are of:
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import AppKit
import IMKUtils
import InputMethodKit
import SwiftExtension
public let kTargetBin = "vChewing"
public let kTargetBinPhraseEditor = "vChewingPhraseEditor"
public let kTargetType = "app"
public let kTargetBundle = "vChewing.app"
public let kTargetBundleWithComponents = "Library/Input%20Methods/vChewing.app"
public let realHomeDir = URL(
fileURLWithFileSystemRepresentation: getpwuid(getuid()).pointee.pw_dir, isDirectory: true, relativeTo: nil
)
public let urlDestinationPartial = realHomeDir.appendingPathComponent("Library/Input Methods")
public let urlTargetPartial = realHomeDir.appendingPathComponent(kTargetBundleWithComponents)
public let urlTargetFullBinPartial = urlTargetPartial.appendingPathComponent("Contents/MacOS")
.appendingPathComponent(kTargetBin)
public let kDestinationPartial = urlDestinationPartial.path
public let kTargetPartialPath = urlTargetPartial.path
public let kTargetFullBinPartialPath = urlTargetFullBinPartial.path
public let kTranslocationRemovalTickInterval: TimeInterval = 0.5
public let kTranslocationRemovalDeadline: TimeInterval = 60.0
@NSApplicationMain
@objc(AppDelegate)
class AppDelegate: NSWindowController, NSApplicationDelegate {
@IBOutlet var installButton: NSButton!
@IBOutlet var cancelButton: NSButton!
@IBOutlet var progressSheet: NSWindow!
@IBOutlet var progressIndicator: NSProgressIndicator!
@IBOutlet var appVersionLabel: NSTextField!
@IBOutlet var appCopyrightLabel: NSTextField!
@IBOutlet var appEULAContent: NSTextView!
var installingVersion = ""
var translocationRemovalStartTime: Date?
var currentVersionNumber: Int = 0
let imeURLInstalled = realHomeDir.appendingPathComponent("Library/Input Methods/vChewing.app")
var allRegisteredInstancesOfThisInputMethod: [TISInputSource] {
guard let components = Bundle(url: imeURLInstalled)?.infoDictionary?["ComponentInputModeDict"] as? [String: Any],
let tsInputModeListKey = components["tsInputModeListKey"] as? [String: Any]
else {
return []
}
return tsInputModeListKey.keys.compactMap { TISInputSource.generate(from: $0) }
}
func runAlertPanel(title: String, message: String, buttonTitle: String) {
let alert = NSAlert()
alert.alertStyle = .informational
alert.messageText = title
alert.informativeText = message
alert.addButton(withTitle: buttonTitle)
alert.runModal()
}
func applicationDidFinishLaunching(_: Notification) {
guard
let window = window,
let cell = installButton.cell as? NSButtonCell,
let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String,
let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String,
let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String,
let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String,
let eulaContentUpstream = Bundle.main.infoDictionary?["CFUpstreamEULAContent"] as? String
else {
NSSound.beep()
NSLog("The vChewing App Installer failed its initial guard-let process on appDidFinishLaunching().")
return
}
self.installingVersion = installingVersion
cancelButton.nextKeyView = installButton
installButton.nextKeyView = cancelButton
window.defaultButtonCell = cell
appCopyrightLabel.stringValue = copyrightLabel
appEULAContent.string = eulaContent + "\n" + eulaContentUpstream
appVersionLabel.stringValue = "\(versionString) Build \(installingVersion)"
window.title = "\(window.title) (v\(versionString), Build \(installingVersion))"
window.standardWindowButton(.closeButton)?.isHidden = true
window.standardWindowButton(.miniaturizeButton)?.isHidden = true
window.standardWindowButton(.zoomButton)?.isHidden = true
window.titlebarAppearsTransparent = true
if FileManager.default.fileExists(atPath: kTargetPartialPath) {
let currentBundle = Bundle(path: kTargetPartialPath)
let shortVersion = currentBundle?.infoDictionary?["CFBundleShortVersionString"] as? String
let currentVersion = currentBundle?.infoDictionary?[kCFBundleVersionKey as String] as? String
currentVersionNumber = (currentVersion as NSString?)?.integerValue ?? 0
if shortVersion != nil, let currentVersion = currentVersion,
currentVersion.compare(installingVersion, options: .numeric) == .orderedAscending
{
// Upgrading confirmed.
installButton.title = NSLocalizedString("Upgrade", comment: "")
}
}
window.center()
window.orderFront(self)
NSApp.popup()
}
@IBAction func agreeAndInstallAction(_: AnyObject) {
cancelButton.isEnabled = false
installButton.isEnabled = false
removeThenInstallInputMethod()
}
@objc func timerTick(_ timer: Timer) {
guard let window = window else { return }
let elapsed = Date().timeIntervalSince(translocationRemovalStartTime ?? Date())
if elapsed >= kTranslocationRemovalDeadline {
timer.invalidate()
window.endSheet(progressSheet, returnCode: .cancel)
} else if Reloc.isAppBundleTranslocated(atPath: kTargetPartialPath) == false {
progressIndicator.doubleValue = 1.0
timer.invalidate()
window.endSheet(progressSheet, returnCode: .continue)
}
}
func endAppWithDelay() {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {
NSApp.terminate(self)
}
}
@IBAction func cancelAction(_: AnyObject) {
NSApp.terminate(self)
}
func windowWillClose(_: Notification) {
NSApp.terminate(self)
}
func shell(_ command: String) throws -> String {
let task = Process()
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.arguments = ["-c", command]
if #available(macOS 10.13, *) {
task.executableURL = URL(fileURLWithPath: "/bin/zsh")
} else {
task.launchPath = "/bin/zsh"
}
task.standardInput = nil
if #available(macOS 10.13, *) {
try task.run()
} else {
task.launch()
}
var output = ""
do {
let data = try pipe.fileHandleForReading.readToEnd()
if let data = data, let str = String(data: data, encoding: .utf8) {
output.append(str)
}
} catch {
return ""
}
return output
}
}
// MARK: - NSApp Activation Helper
// This is to deal with changes brought by macOS 14.
private extension NSApplication {
func popup() {
#if compiler(>=5.9) && canImport(AppKit, _version: "14.0")
if #available(macOS 14.0, *) {
NSApp.activate()
} else {
NSApp.activate(ignoringOtherApps: true)
}
#else
NSApp.activate(ignoringOtherApps: true)
#endif
}
}

View File

@ -0,0 +1,171 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import AppKit
import InputMethodKit
import SwiftUI
public let kTargetBin = "vChewing"
public let kTargetBinPhraseEditor = "vChewingPhraseEditor"
public let kTargetType = "app"
public let kTargetBundle = "vChewing.app"
public let kTargetBundleWithComponents = "Library/Input%20Methods/vChewing.app"
public let kTISInputSourceID = "org.atelierInmu.inputmethod.vChewing"
let imeURLInstalled = realHomeDir.appendingPathComponent("Library/Input Methods/vChewing.app")
public let realHomeDir = URL(
fileURLWithFileSystemRepresentation: getpwuid(getuid()).pointee.pw_dir, isDirectory: true, relativeTo: nil
)
public let urlDestinationPartial = realHomeDir.appendingPathComponent("Library/Input Methods")
public let urlTargetPartial = realHomeDir.appendingPathComponent(kTargetBundleWithComponents)
public let urlTargetFullBinPartial = urlTargetPartial.appendingPathComponent("Contents/MacOS")
.appendingPathComponent(kTargetBin)
public let kDestinationPartial = urlDestinationPartial.path
public let kTargetPartialPath = urlTargetPartial.path
public let kTargetFullBinPartialPath = urlTargetFullBinPartial.path
public let kTranslocationRemovalTickInterval: TimeInterval = 0.5
public let kTranslocationRemovalDeadline: TimeInterval = 60.0
public let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String ?? "BAD_INSTALLING_VER"
public let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "BAD_VER_STR"
public let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String ?? "BAD_COPYRIGHT_LABEL"
public let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String ?? "BAD_EULA_CONTENT"
public let eulaContentUpstream = Bundle.main.infoDictionary?["CFUpstreamEULAContent"] as? String ?? "BAD_EULA_UPSTREAM"
public var mainWindowTitle: String {
"i18n:installer.INSTALLER_APP_TITLE_FULL".i18n + " (v\(versionString), Build \(installingVersion))"
}
var allRegisteredInstancesOfThisInputMethod: [TISInputSource] {
guard let components = Bundle(url: imeURLInstalled)?.infoDictionary?["ComponentInputModeDict"] as? [String: Any],
let tsInputModeListKey = components["tsInputModeListKey"] as? [String: Any]
else {
return []
}
return TISInputSource.match(modeIDs: tsInputModeListKey.keys.map(\.description))
}
// MARK: - NSApp Activation Helper
// This is to deal with changes brought by macOS 14.
public extension NSApplication {
func popup() {
#if compiler(>=5.9) && canImport(AppKit, _version: "14.0")
if #available(macOS 14.0, *) {
NSApp.activate()
} else {
NSApp.activate(ignoringOtherApps: true)
}
#else
NSApp.activate(ignoringOtherApps: true)
#endif
}
}
// MARK: - KeyWindow Finder
public extension NSApplication {
var keyWindows: [NSWindow] {
NSApp.windows.filter(\.isKeyWindow)
}
}
// MARK: - NSApp End With Delay
public extension NSApplication {
func terminateWithDelay() {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { [weak self] in
if let this = self {
this.terminate(this)
}
}
}
}
// MARK: - Alert Message & Title Structure
public struct AlertIntel {}
public enum AlertType: String, Identifiable {
public var id: String { rawValue }
case nothing, installationFailed, missingAfterRegistration, postInstallAttention, postInstallWarning, postInstallOK
var title: LocalizedStringKey {
switch self {
case .nothing: return ""
case .installationFailed: return "Install Failed"
case .missingAfterRegistration: return "Fatal Error"
case .postInstallAttention: return "Attention"
case .postInstallWarning: return "Warning"
case .postInstallOK: return "Installation Successful"
}
}
var message: String {
switch self {
case .nothing: return ""
case .installationFailed:
return "Cannot copy the file to the destination.".i18n
case .missingAfterRegistration:
return String(
format: "Cannot find input source %@ after registration.".i18n,
kTISInputSourceID
)
case .postInstallAttention:
return "vChewing is upgraded, but please log out or reboot for the new version to be fully functional.".i18n
case .postInstallWarning:
return "Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources.".i18n
case .postInstallOK:
return "vChewing is ready to use. \n\nPlease relogin if this is the first time you install it in this user account.".i18n
}
}
}
private extension StringLiteralType {
var i18n: String { NSLocalizedString(description, comment: "") }
}
// MARK: - Shell
public extension NSApplication {
func shell(_ command: String) throws -> String {
let task = Process()
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.arguments = ["-c", command]
if #available(macOS 10.13, *) {
task.executableURL = URL(fileURLWithPath: "/bin/zsh")
} else {
task.launchPath = "/bin/zsh"
}
task.standardInput = nil
if #available(macOS 10.13, *) {
try task.run()
} else {
task.launch()
}
var output = ""
do {
let data = try pipe.fileHandleForReading.readToEnd()
if let data = data, let str = String(data: data, encoding: .utf8) {
output.append(str)
}
} catch {
return ""
}
return output
}
}

161
Installer/MainView.swift Normal file
View File

@ -0,0 +1,161 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import AppKit
import SwiftUI
public struct MainView: View {
static let strCopyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String ?? "BAD_COPYRIGHT_LABEL"
@State var pendingSheetPresenting = false
@State var isShowingAlertForFailedInstallation = false
@State var isShowingAlertForMissingPostInstall = false
@State var isShowingPostInstallNotification = false
@State var currentAlertContent: AlertType = .nothing
@State var isCancelButtonEnabled = true
@State var isAgreeButtonEnabled = true
@State var isPreviousVersionNotFullyDeactivated = false
@State var isTranslocationFinished: Bool?
@State var isUpgrading: Bool = false
var translocationRemovalStartTime: Date?
@State var timeRemaining = 60
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
public init() {
if FileManager.default.fileExists(atPath: kTargetPartialPath) {
let currentBundle = Bundle(path: kTargetPartialPath)
let shortVersion = currentBundle?.infoDictionary?["CFBundleShortVersionString"] as? String
let currentVersion = currentBundle?.infoDictionary?[kCFBundleVersionKey as String] as? String
if shortVersion != nil, let currentVersion = currentVersion,
currentVersion.compare(installingVersion, options: .numeric) == .orderedAscending
{
isUpgrading = true
}
}
}
public var body: some View {
GroupBox {
VStack(alignment: .leading, spacing: 6) {
VStack(alignment: .leading) {
HStack(alignment: .center) {
if let icon = NSImage(named: "IconSansMargin") {
Image(nsImage: icon).resizable().frame(width: 90, height: 90)
}
VStack(alignment: .leading) {
HStack {
Text("i18n:installer.APP_NAME").fontWeight(.heavy).lineLimit(1)
Text("v\(versionString) Build \(installingVersion)").lineLimit(1)
}.fixedSize()
Text("i18n:installer.APP_DERIVED_FROM").font(.custom("Tahoma", size: 11))
Text(Self.strCopyrightLabel).font(.custom("Tahoma", size: 11))
Text("i18n:installer.DEV_CREW").font(.custom("Tahoma", size: 11)).padding([.vertical], 2)
}
}
GroupBox(label: Text("i18n:installer.LICENSE_TITLE")) {
ScrollView(.vertical, showsIndicators: true) {
HStack {
Text(eulaContent + "\n" + eulaContentUpstream).textSelection(.enabled)
.frame(maxWidth: 455)
.font(.custom("Tahoma", size: 11))
Spacer()
}
}.padding(4).frame(height: 128)
}
Text("i18n:installer.EULA_PROMPT_NOTICE").bold().padding(.bottom, 2)
}
Divider()
HStack(alignment: .top) {
Text("i18n:installer.DISCLAIMER_TEXT")
.font(.custom("Tahoma", size: 11))
.opacity(0.5)
.frame(maxWidth: .infinity)
VStack(spacing: 4) {
Button { installationButtonClicked() } label: {
Text(isUpgrading ? "i18n:installer.DO_APP_UPGRADE" : "i18n:installer.ACCEPT_INSTALLATION")
.bold().frame(width: 114)
}
.keyboardShortcut(.defaultAction)
.disabled(!isCancelButtonEnabled)
Button(role: .cancel) { NSApp.terminateWithDelay() } label: {
Text("i18n:installer.CANCEL_INSTALLATION").frame(width: 114)
}
.keyboardShortcut(.cancelAction)
.disabled(!isAgreeButtonEnabled)
}.fixedSize(horizontal: true, vertical: true)
}
Spacer()
}
.font(.custom("Tahoma", size: 12))
.padding(4)
}
// ALERTS
.alert(AlertType.installationFailed.title, isPresented: $isShowingAlertForFailedInstallation) {
Button(role: .cancel) { NSApp.terminateWithDelay() } label: { Text("Cancel") }
} message: {
Text(AlertType.installationFailed.message)
}
.alert(AlertType.missingAfterRegistration.title, isPresented: $isShowingAlertForMissingPostInstall) {
Button(role: .cancel) { NSApp.terminateWithDelay() } label: { Text("Abort") }
} message: {
Text(AlertType.missingAfterRegistration.message)
}
.alert(currentAlertContent.title, isPresented: $isShowingPostInstallNotification) {
Button(role: .cancel) { NSApp.terminateWithDelay() } label: {
Text(currentAlertContent == .postInstallWarning ? "Continue" : "OK")
}
} message: {
Text(currentAlertContent.message)
}
// SHEET FOR STOPPING THE OLD VERSION
.sheet(isPresented: $pendingSheetPresenting) {
// TODO: Tasks after sheet gets closed by `dismiss()`.
} content: {
Text("i18n:installer.STOPPING_THE_OLD_VERSION").frame(width: 407, height: 144)
.onReceive(timer) { _ in
if timeRemaining > 0 {
if Reloc.isAppBundleTranslocated(atPath: kTargetPartialPath) == false {
pendingSheetPresenting = false
isTranslocationFinished = true
installInputMethod(
previousExists: true,
previousVersionNotFullyDeactivatedWarning: false
)
}
timeRemaining -= 1
} else {
pendingSheetPresenting = false
isTranslocationFinished = false
installInputMethod(
previousExists: true,
previousVersionNotFullyDeactivatedWarning: true
)
}
}
}
// OTHER
.padding(12)
.frame(width: 533, alignment: .topLeading)
.navigationTitle(mainWindowTitle)
.fixedSize()
.foregroundStyle(Color(nsColor: NSColor.textColor))
.background(Color(nsColor: NSColor.windowBackgroundColor))
.clipShape(RoundedRectangle(cornerRadius: 16))
.frame(minWidth: 533, idealWidth: 533, maxWidth: 533,
minHeight: 386, idealHeight: 386, maxHeight: 386,
alignment: .top)
}
func installationButtonClicked() {
isCancelButtonEnabled = false
isAgreeButtonEnabled = false
removeThenInstallInputMethod()
}
}

View File

@ -1,5 +1,3 @@
// (c) 2011 and onwards The OpenVanilla Project (MIT License).
// All possible vChewing-specific modifications are of:
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
@ -9,22 +7,12 @@
// requirements defined in MIT License.
import AppKit
import IMKUtils
import InputMethodKit
extension AppDelegate {
public extension MainView {
func removeThenInstallInputMethod() {
// if !FileManager.default.fileExists(atPath: kTargetPartialPath) {
// installInputMethod(
// previousExists: false, previousVersionNotFullyDeactivatedWarning: false
// )
// return
// }
guard let window = window else { return }
let shouldWaitForTranslocationRemoval =
Reloc.isAppBundleTranslocated(atPath: kTargetPartialPath)
&& window.responds(to: #selector(NSWindow.beginSheet(_:completionHandler:)))
let shouldWaitForTranslocationRemoval = Reloc.isAppBundleTranslocated(atPath: kTargetPartialPath)
//
do {
@ -58,28 +46,7 @@ extension AppDelegate {
killTask2.waitUntilExit()
if shouldWaitForTranslocationRemoval {
progressIndicator.startAnimation(self)
window.beginSheet(progressSheet) { returnCode in
DispatchQueue.main.async {
if returnCode == .continue {
self.installInputMethod(
previousExists: true,
previousVersionNotFullyDeactivatedWarning: false
)
} else {
self.installInputMethod(
previousExists: true,
previousVersionNotFullyDeactivatedWarning: true
)
}
}
}
translocationRemovalStartTime = Date()
Timer.scheduledTimer(
timeInterval: kTranslocationRemovalTickInterval, target: self,
selector: #selector(timerTick(_:)), userInfo: nil, repeats: true
)
pendingSheetPresenting = true
} else {
installInputMethod(
previousExists: false, previousVersionNotFullyDeactivatedWarning: false
@ -105,20 +72,16 @@ extension AppDelegate {
cpTask.waitUntilExit()
if cpTask.terminationStatus != 0 {
runAlertPanel(
title: NSLocalizedString("Install Failed", comment: ""),
message: NSLocalizedString("Cannot copy the file to the destination.", comment: ""),
buttonTitle: NSLocalizedString("Cancel", comment: "")
)
endAppWithDelay()
isShowingAlertForFailedInstallation = true
NSApp.terminateWithDelay()
}
_ = try? shell("/usr/bin/xattr -drs com.apple.quarantine \(kTargetPartialPath)")
_ = try? NSApp.shell("/usr/bin/xattr -drs com.apple.quarantine \(kTargetPartialPath)")
guard let theBundle = Bundle(url: imeURLInstalled),
let imeIdentifier = theBundle.bundleIdentifier
else {
endAppWithDelay()
NSApp.terminateWithDelay()
return
}
@ -128,18 +91,8 @@ extension AppDelegate {
NSLog("Registering input source \(imeIdentifier) at \(imeBundleURL.absoluteString).")
let status = (TISRegisterInputSource(imeBundleURL as CFURL) == noErr)
if !status {
let message = String(
format: NSLocalizedString(
"Cannot find input source %@ after registration.", comment: ""
),
imeIdentifier
)
runAlertPanel(
title: NSLocalizedString("Fatal Error", comment: ""), message: message,
buttonTitle: NSLocalizedString("Abort", comment: "")
)
endAppWithDelay()
return
isShowingAlertForMissingPostInstall = true
NSApp.terminateWithDelay()
}
if allRegisteredInstancesOfThisInputMethod.isEmpty {
@ -172,35 +125,14 @@ extension AppDelegate {
}
// Alert Panel
let ntfPostInstall = NSAlert()
if warning {
ntfPostInstall.messageText = NSLocalizedString("Attention", comment: "")
ntfPostInstall.informativeText = NSLocalizedString(
"vChewing is upgraded, but please log out or reboot for the new version to be fully functional.",
comment: ""
)
ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: ""))
currentAlertContent = .postInstallAttention
} else if !mainInputSourceEnabled {
currentAlertContent = .postInstallWarning
} else {
if !mainInputSourceEnabled {
ntfPostInstall.messageText = NSLocalizedString("Warning", comment: "")
ntfPostInstall.informativeText = NSLocalizedString(
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources.",
comment: ""
)
ntfPostInstall.addButton(withTitle: NSLocalizedString("Continue", comment: ""))
} else {
ntfPostInstall.messageText = NSLocalizedString(
"Installation Successful", comment: ""
)
ntfPostInstall.informativeText = NSLocalizedString(
"vChewing is ready to use. \n\nPlease relogin if this is the first time you install it in this user account.",
comment: ""
)
ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: ""))
}
}
ntfPostInstall.beginSheetModal(for: window!) { _ in
self.endAppWithDelay()
currentAlertContent = .postInstallOK
}
isShowingPostInstallNotification = true
NSApp.terminateWithDelay()
}
}

View File

@ -1,366 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21225" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21225"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="494" id="495"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<menu title="AMainMenu" systemMenu="main" id="29">
<items>
<menuItem title="vChewing Installer" id="56">
<menu key="submenu" title="vChewing Installer" systemMenu="apple" id="57">
<items>
<menuItem title="About vChewing Installer" id="58">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="-2" id="142"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="236">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Services" id="131">
<menu key="submenu" title="Services" systemMenu="services" id="130"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="144">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Hide vChewing Installer" keyEquivalent="h" id="134">
<connections>
<action selector="hide:" target="-1" id="367"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="145">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="368"/>
</connections>
</menuItem>
<menuItem title="Show All" id="150">
<connections>
<action selector="unhideAllApplications:" target="-1" id="370"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="149">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Quit vChewing Installer" keyEquivalent="q" id="136">
<connections>
<action selector="terminate:" target="-3" id="449"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="File" id="83"/>
<menuItem title="Edit" id="LJX-Bb-mhU">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="6QY-kP-PQQ">
<items>
<menuItem title="Undo" keyEquivalent="z" id="5tq-5G-Yoy">
<connections>
<action selector="undo:" target="-1" id="P9n-jj-WpM"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="GRe-Pk-1EX">
<connections>
<action selector="redo:" target="-1" id="cbT-AB-slM"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="cYt-uT-CAh"/>
<menuItem title="Cut" keyEquivalent="x" id="zAh-7y-AvL">
<connections>
<action selector="cut:" target="-1" id="arZ-EA-CgM"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="WoU-zb-uKy">
<connections>
<action selector="copy:" target="-1" id="0JC-Jc-0Xl"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="Fid-E7-Ykc">
<connections>
<action selector="paste:" target="-1" id="fVk-V0-Sbq"/>
</connections>
</menuItem>
<menuItem title="Delete" id="Ier-IT-JZa">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="-1" id="X7x-wD-fWC"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="ZsT-7a-SE6">
<connections>
<action selector="selectAll:" target="-1" id="iwd-aI-lml"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
<point key="canvasLocation" x="139" y="154"/>
</menu>
<window title="vChewing Installer" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" animationBehavior="default" titlebarAppearsTransparent="YES" id="371">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
<rect key="contentRect" x="335" y="390" width="533" height="457"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="875"/>
<value key="minSize" type="size" width="533" height="457"/>
<value key="maxSize" type="size" width="533" height="457"/>
<view key="contentView" id="372">
<rect key="frame" x="0.0" y="0.0" width="533" height="457"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button verticalHuggingPriority="750" imageHugsTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="575">
<rect key="frame" x="378" y="101" width="147" height="32"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="133" id="S4F-Qe-xuk"/>
</constraints>
<buttonCell key="cell" type="push" title="I Accept" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="576">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" size="13" name="Tahoma-Bold"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
</buttonCell>
<connections>
<action selector="agreeAndInstallAction:" target="494" id="708"/>
</connections>
</button>
<scrollView horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" findBarPosition="belowContent" translatesAutoresizingMaskIntoConstraints="NO" id="YCR-wo-M5a">
<rect key="frame" x="91" y="165" width="427" height="196"/>
<clipView key="contentView" drawsBackground="NO" id="NrY-FL-PVu" userLabel="appEULAContentClip">
<rect key="frame" x="1" y="1" width="425" height="194"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView editable="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" findStyle="bar" smartInsertDelete="YES" id="47J-tO-8TZ" userLabel="appEULAContent">
<rect key="frame" x="0.0" y="-2" width="425" height="194"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<size key="minSize" width="425" height="194"/>
<size key="maxSize" width="427" height="10000000"/>
<attributedString key="textStorage">
<fragment content="Placeholder for EULA Texts.">
<attributes>
<color key="NSColor" name="textColor" catalog="System" colorSpace="catalog"/>
<font key="NSFont" metaFont="systemLight" size="11"/>
<paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural" tighteningFactorForTruncation="0.0"/>
</attributes>
</fragment>
</attributedString>
<color key="insertionPointColor" name="textColor" catalog="System" colorSpace="catalog"/>
</textView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="YOZ-MC-EF2">
<rect key="frame" x="-100" y="-100" width="240" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" controlSize="mini" horizontal="NO" id="E5B-3B-faV">
<rect key="frame" x="412" y="1" width="14" height="194"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ir5-sQ-sJc">
<rect key="frame" x="89" y="443" width="130" height="14"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="vChewing for macOS" id="GNc-8S-1VG" userLabel="appNameLabel">
<font key="font" size="12" name="Tahoma-Bold"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="bzR-Oa-BZa">
<rect key="frame" x="89" y="428" width="362" height="14"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Was derived from OpenVanilla McBopopmofo Project (MIT-License)." id="QYf-Nf-hoi">
<font key="font" size="12" name="Tahoma"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="03l-rN-zf9">
<rect key="frame" x="89" y="413" width="297" height="14"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="293" id="v2b-OK-WGD"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" title="Placeholder for showing copyright information." id="eo3-TK-0rB" userLabel="appCopyrightLabel">
<font key="font" size="12" name="Tahoma"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="XLb-mv-73s">
<rect key="frame" x="89" y="391" width="431" height="14"/>
<textFieldCell key="cell" title="Placeholder for detailed credits." id="VW8-s5-Wpn">
<font key="font" size="12" name="Tahoma"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<box verticalHuggingPriority="750" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="Yyh-Nw-Sba">
<rect key="frame" x="15" y="137" width="503" height="5"/>
</box>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="k5O-zZ-gQY">
<rect key="frame" x="89" y="369" width="431" height="14"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="MIT-NTL License:" id="AVS-ih-FXM">
<font key="font" size="12" name="Tahoma"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="miu-08-dZk">
<rect key="frame" x="13" y="47" width="360" height="84"/>
<constraints>
<constraint firstAttribute="width" constant="356" id="pu3-zr-hJy"/>
</constraints>
<textFieldCell key="cell" id="Q9M-ni-kUM">
<font key="font" size="12" name="Tahoma"/>
<string key="title">DISCLAIMER: The vChewing project, having no relationship of cooperation or affiliation with the OpenVanilla project, is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, are not included in vChewing official phrase database.</string>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<imageView translatesAutoresizingMaskIntoConstraints="NO" id="Ked-gt-bjE">
<rect key="frame" x="15" y="147" width="63" height="310"/>
<constraints>
<constraint firstAttribute="width" constant="63" id="fgC-vo-Ho8"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" animates="YES" imageScaling="proportionallyDown" image="AboutBanner" id="akk-zO-Abm"/>
</imageView>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="z1m-8k-Z63">
<rect key="frame" x="218" y="443" width="126" height="14"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="122" id="yKq-Fv-W1J"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" title="version_placeholder" id="JRP-At-H9q" userLabel="appVersionLabel">
<font key="font" size="12" name="Tahoma"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nul-TQ-gOI">
<rect key="frame" x="89" y="148" width="431" height="14"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="By installing the software, you must accept the terms above." id="mf8-6e-z7X">
<font key="font" size="12" name="Tahoma-Bold"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" imageHugsTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="592">
<rect key="frame" x="378" y="74" width="147" height="32"/>
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="593">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" size="13" name="Tahoma"/>
<string key="keyEquivalent" base64-UTF8="YES">
Gw
</string>
</buttonCell>
<connections>
<action selector="cancelAction:" target="494" id="707"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="575" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="miu-08-dZk" secondAttribute="trailing" constant="8" symbolic="YES" id="18X-Qf-xUC"/>
<constraint firstItem="Yyh-Nw-Sba" firstAttribute="top" secondItem="Ked-gt-bjE" secondAttribute="bottom" constant="7" id="1Hx-8o-xpF"/>
<constraint firstItem="Yyh-Nw-Sba" firstAttribute="top" secondItem="nul-TQ-gOI" secondAttribute="bottom" constant="8" symbolic="YES" id="1Mz-Yp-lqA"/>
<constraint firstItem="575" firstAttribute="leading" secondItem="592" secondAttribute="leading" id="2Kf-DA-DXH"/>
<constraint firstItem="Ked-gt-bjE" firstAttribute="leading" secondItem="372" secondAttribute="leading" constant="15" id="2ne-pt-ddK"/>
<constraint firstItem="03l-rN-zf9" firstAttribute="leading" secondItem="XLb-mv-73s" secondAttribute="leading" id="6Mv-X8-W55"/>
<constraint firstItem="nul-TQ-gOI" firstAttribute="trailing" secondItem="Yyh-Nw-Sba" secondAttribute="trailing" id="6yu-Wm-g26"/>
<constraint firstItem="592" firstAttribute="top" secondItem="575" secondAttribute="bottom" constant="7" id="7Q7-30-gTO"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="bzR-Oa-BZa" secondAttribute="trailing" constant="20" symbolic="YES" id="8td-FZ-tnM"/>
<constraint firstItem="ir5-sQ-sJc" firstAttribute="baseline" secondItem="z1m-8k-Z63" secondAttribute="baseline" id="9AX-QJ-G9U"/>
<constraint firstItem="ir5-sQ-sJc" firstAttribute="leading" secondItem="Ked-gt-bjE" secondAttribute="trailing" constant="13" id="Brw-UI-0WK"/>
<constraint firstItem="k5O-zZ-gQY" firstAttribute="trailing" secondItem="YCR-wo-M5a" secondAttribute="trailing" id="DfC-Ke-tb5"/>
<constraint firstItem="k5O-zZ-gQY" firstAttribute="leading" secondItem="YCR-wo-M5a" secondAttribute="leading" id="FIo-Op-SV8"/>
<constraint firstItem="Yyh-Nw-Sba" firstAttribute="leading" secondItem="miu-08-dZk" secondAttribute="leading" id="H4v-4O-xZY"/>
<constraint firstItem="YCR-wo-M5a" firstAttribute="trailing" secondItem="nul-TQ-gOI" secondAttribute="trailing" id="HX6-hi-PJs"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="z1m-8k-Z63" secondAttribute="trailing" constant="20" symbolic="YES" id="Hg9-8d-O7s"/>
<constraint firstItem="z1m-8k-Z63" firstAttribute="leading" secondItem="ir5-sQ-sJc" secondAttribute="trailing" constant="3" id="LSc-gD-CbY"/>
<constraint firstItem="575" firstAttribute="top" secondItem="Yyh-Nw-Sba" secondAttribute="bottom" constant="11" id="Nw2-bH-vTF"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="03l-rN-zf9" secondAttribute="trailing" constant="20" symbolic="YES" id="PRC-Y1-rIz"/>
<constraint firstItem="Ked-gt-bjE" firstAttribute="leading" secondItem="Yyh-Nw-Sba" secondAttribute="leading" id="SKi-gn-JeS"/>
<constraint firstItem="XLb-mv-73s" firstAttribute="trailing" secondItem="k5O-zZ-gQY" secondAttribute="trailing" id="VOo-Q9-rki"/>
<constraint firstItem="Ked-gt-bjE" firstAttribute="top" secondItem="372" secondAttribute="top" id="WPX-gk-uqh"/>
<constraint firstItem="XLb-mv-73s" firstAttribute="top" secondItem="03l-rN-zf9" secondAttribute="bottom" constant="8" symbolic="YES" id="bJX-f1-1PU"/>
<constraint firstItem="Ked-gt-bjE" firstAttribute="top" secondItem="ir5-sQ-sJc" secondAttribute="top" id="caT-rm-xEa"/>
<constraint firstItem="bzR-Oa-BZa" firstAttribute="top" secondItem="ir5-sQ-sJc" secondAttribute="bottom" constant="1" id="dyJ-9C-f56"/>
<constraint firstItem="bzR-Oa-BZa" firstAttribute="leading" secondItem="03l-rN-zf9" secondAttribute="leading" id="etY-2E-2Sa"/>
<constraint firstItem="YCR-wo-M5a" firstAttribute="leading" secondItem="nul-TQ-gOI" secondAttribute="leading" id="fl0-wm-8Pa"/>
<constraint firstItem="miu-08-dZk" firstAttribute="top" secondItem="Yyh-Nw-Sba" secondAttribute="bottom" constant="8" symbolic="YES" id="lY7-Se-lpo"/>
<constraint firstItem="XLb-mv-73s" firstAttribute="leading" secondItem="k5O-zZ-gQY" secondAttribute="leading" id="qzW-qc-9yQ"/>
<constraint firstItem="575" firstAttribute="trailing" secondItem="592" secondAttribute="trailing" id="sIp-L2-QLj"/>
<constraint firstItem="575" firstAttribute="trailing" secondItem="Yyh-Nw-Sba" secondAttribute="trailing" id="tap-mY-bvB"/>
<constraint firstItem="nul-TQ-gOI" firstAttribute="top" secondItem="YCR-wo-M5a" secondAttribute="bottom" constant="3" id="tqc-zq-Egb"/>
<constraint firstItem="YCR-wo-M5a" firstAttribute="top" secondItem="k5O-zZ-gQY" secondAttribute="bottom" constant="8" symbolic="YES" id="u3L-Nh-ELP"/>
<constraint firstItem="k5O-zZ-gQY" firstAttribute="top" secondItem="XLb-mv-73s" secondAttribute="bottom" constant="8" symbolic="YES" id="uxg-3X-XtF"/>
<constraint firstItem="ir5-sQ-sJc" firstAttribute="leading" secondItem="bzR-Oa-BZa" secondAttribute="leading" id="vkK-Vc-WOf"/>
<constraint firstItem="Yyh-Nw-Sba" firstAttribute="centerX" secondItem="372" secondAttribute="centerX" id="xqG-pX-j0d"/>
<constraint firstItem="03l-rN-zf9" firstAttribute="top" secondItem="bzR-Oa-BZa" secondAttribute="bottom" constant="1" id="yKI-MD-nsC"/>
</constraints>
</view>
<connections>
<outlet property="delegate" destination="494" id="706"/>
</connections>
<point key="canvasLocation" x="-306.5" y="-230"/>
</window>
<customObject id="494" customClass="AppDelegate">
<connections>
<outlet property="appCopyrightLabel" destination="03l-rN-zf9" id="XS5-cZ-k9H"/>
<outlet property="appEULAContent" destination="47J-tO-8TZ" id="kRU-X2-8kX"/>
<outlet property="appVersionLabel" destination="z1m-8k-Z63" id="75X-uy-0Iz"/>
<outlet property="cancelButton" destination="592" id="710"/>
<outlet property="installButton" destination="575" id="709"/>
<outlet property="progressIndicator" destination="deb-uT-yNv" id="Cpk-6Z-0rj"/>
<outlet property="progressSheet" destination="gHl-Hx-eQn" id="gD4-XO-YO1"/>
<outlet property="window" destination="371" id="532"/>
</connections>
</customObject>
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="gHl-Hx-eQn">
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="283" y="305" width="480" height="180"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="875"/>
<view key="contentView" id="wAe-c8-Vh9">
<rect key="frame" x="0.0" y="0.0" width="480" height="180"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<progressIndicator wantsLayer="YES" maxValue="1" style="bar" translatesAutoresizingMaskIntoConstraints="NO" id="deb-uT-yNv">
<rect key="frame" x="20" y="67" width="440" height="20"/>
</progressIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="VDL-Yq-heb">
<rect key="frame" x="18" y="94" width="444" height="17"/>
<constraints>
<constraint firstAttribute="height" constant="17" id="MLj-KG-mL8"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Stopping the old version. This may take up to one minute…" id="nTo-dx-qfZ">
<font key="font" size="13" name="Tahoma"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="VDL-Yq-heb" firstAttribute="trailing" secondItem="deb-uT-yNv" secondAttribute="trailing" id="DCe-Xh-ee1"/>
<constraint firstItem="deb-uT-yNv" firstAttribute="top" secondItem="VDL-Yq-heb" secondAttribute="bottom" constant="8" symbolic="YES" id="HUE-gU-UFS"/>
<constraint firstItem="VDL-Yq-heb" firstAttribute="top" secondItem="wAe-c8-Vh9" secondAttribute="top" constant="69" id="IwI-63-e9H"/>
<constraint firstItem="VDL-Yq-heb" firstAttribute="leading" secondItem="deb-uT-yNv" secondAttribute="leading" id="UUz-sT-D9I"/>
<constraint firstItem="VDL-Yq-heb" firstAttribute="leading" secondItem="wAe-c8-Vh9" secondAttribute="leading" constant="20" symbolic="YES" id="Vgg-bw-6wt"/>
<constraint firstAttribute="trailing" secondItem="VDL-Yq-heb" secondAttribute="trailing" constant="20" symbolic="YES" id="ft0-oZ-8HD"/>
</constraints>
</view>
<point key="canvasLocation" x="529" y="-282"/>
</window>
<customObject id="420" customClass="NSFontManager"/>
</objects>
<resources>
<image name="AboutBanner" width="63" height="310"/>
</resources>
</document>

View File

@ -1,19 +1,30 @@
"vChewing Input Method" = "vChewing Input Method";
"Upgrade" = "Accept & Upgrade";
"Abort" = "Abort";
"Attention" = "Attention";
"Cancel" = "Cancel";
"Cannot activate the input method." = "Cannot activate the input method.";
"Cannot copy the file to the destination." = "Cannot copy the file to the destination.";
"Cannot find input source %@ after registration." = "Cannot find input source %@ after registration.";
"Cannot register input source %@ at %@." = "Cannot register input source %@ at %@.";
"Continue" = "Continue";
"Fatal Error" = "Fatal Error";
"i18n:installer.ACCEPT_INSTALLATION" = "I Accept";
"i18n:installer.APP_DERIVED_FROM" = "Was derived from OpenVanilla McBopopmofo Project (MIT-License).";
"i18n:installer.APP_NAME" = "vChewing for macOS";
"i18n:installer.CANCEL_INSTALLATION" = "Cancel";
"i18n:installer.DEV_CREW" = "vChewing macOS Development: Shiki Suen, Isaac Xen, Hiraku Wang, etc.\nvChewing Phrase Database Maintained by Shiki Suen.\nWalking algorithm by Lukhnos Liu (from Gramambular 2, MIT-License).";
"i18n:installer.DISCLAIMER_TEXT" = "DISCLAIMER: The vChewing project, having no relationship of cooperation or affiliation with the OpenVanilla project, is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, are not included in vChewing official phrase database.";
"i18n:installer.DO_APP_UPGRADE" = "Accept & Upgrade";
"i18n:installer.EULA_PROMPT_NOTICE" = "By installing the software, you must accept the terms above.";
"i18n:installer.INSTALLER_APP_TITLE_FULL" = "vChewing Installer";
"i18n:installer.INSTALLER_APP_TITLE" = "vChewing Installer";
"i18n:installer.LICENSE_TITLE" = "MIT-NTL License:";
"i18n:installer.STOPPING_THE_OLD_VERSION" = "Stopping the old version. This may take up to one minute…";
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources.";
"Install Failed" = "Install Failed";
"Installation Successful" = "Installation Successful";
"OK" = "OK";
"vChewing is ready to use. \n\nPlease relogin if this is the first time you install it in this user account." = "vChewing is ready to use. \n\nPlease relogin if this is the first time you install it in this user account.";
"Stopping the old version. This may take up to one minute…" = "Stopping the old version. This may take up to one minute…";
"Attention" = "Attention";
"vChewing Input Method" = "vChewing Input Method";
"vChewing is ready to use. \n\nPlease relogin if this is the first time you install it in this user account." = "vChewing is ready to use. \n\nPlease relogin if this is the first time you install it in this user account.";
"vChewing is upgraded, but please log out or reboot for the new version to be fully functional." = "vChewing is upgraded, but please log out or reboot for the new version to be fully functional.";
"Fatal Error" = "Fatal Error";
"Abort" = "Abort";
"Cannot register input source %@ at %@." = "Cannot register input source %@ at %@.";
"Cannot find input source %@ after registration." = "Cannot find input source %@ after registration.";
"Warning" = "Warning";
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources.";
"Continue" = "Continue";

View File

@ -1,72 +0,0 @@
/* Class = "NSMenu"; title = "AMainMenu"; ObjectID = "29"; */
"29.title" = "AMainMenu";
/* Class = "NSMenuItem"; title = "vChewing Installer"; ObjectID = "56"; */
"56.title" = "vChewing Installer";
/* Class = "NSMenu"; title = "vChewing Installer"; ObjectID = "57"; */
"57.title" = "vChewing Installer";
/* Class = "NSMenuItem"; title = "About vChewing Installer"; ObjectID = "58"; */
"58.title" = "About vChewing Installer";
/* Class = "NSMenuItem"; title = "File"; ObjectID = "83"; */
"83.title" = "File";
/* Class = "NSMenu"; title = "Services"; ObjectID = "130"; */
"130.title" = "Services";
/* Class = "NSMenuItem"; title = "Services"; ObjectID = "131"; */
"131.title" = "Services";
/* Class = "NSMenuItem"; title = "Hide vChewing Installer"; ObjectID = "134"; */
"134.title" = "Hide vChewing Installer";
/* Class = "NSMenuItem"; title = "Quit vChewing Installer"; ObjectID = "136"; */
"136.title" = "Quit vChewing Installer";
/* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "145"; */
"145.title" = "Hide Others";
/* Class = "NSMenuItem"; title = "Show All"; ObjectID = "150"; */
"150.title" = "Show All";
/* Class = "NSWindow"; title = "vChewing Installer"; ObjectID = "371"; */
"371.title" = "vChewing Installer";
/* Class = "NSButtonCell"; title = "I Accept"; ObjectID = "576"; */
"576.title" = "I Accept";
/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "593"; */
"593.title" = "Cancel";
/* Class = "NSTextFieldCell"; title = "MIT-NTL License:"; ObjectID = "AVS-ih-FXM"; */
"AVS-ih-FXM.title" = "MIT-NTL License:";
/* Class = "NSTextFieldCell"; title = "vChewing for macOS"; ObjectID = "GNc-8S-1VG"; */
"GNc-8S-1VG.title" = "vChewing for macOS";
/* Class = "NSTextFieldCell"; title = "version_placeholder"; ObjectID = "JRP-At-H9q"; */
// "JRP-At-H9q.title" = "version_placeholder";
/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project, having no relationship of cooperation or affiliation with the OpenVanilla project, is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, are not included in vChewing official phrase database."; ObjectID = "Q9M-ni-kUM"; */
"Q9M-ni-kUM.title" = "DISCLAIMER: The vChewing project, having no relationship of cooperation or affiliation with the OpenVanilla project, is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, are not included in vChewing official phrase database.";
/* Class = "NSTextFieldCell"; title = "Was derived from OpenVanilla McBopopmofo Project (MIT-License)."; ObjectID = "QYf-Nf-hoi"; */
"QYf-Nf-hoi.title" = "Was derived from OpenVanilla McBopopmofo Project (MIT-License).";
/* Class = "NSTextFieldCell"; title = "vChewing macOS Development: Shiki Suen, Isaac Xen, Hiraku Wang, etc.\nvChewing Phrase Database Maintained by Shiki Suen.\nWalking algorithm by Lukhnos Liu (from Gramambular 2, MIT-License)."; ObjectID = "VW8-s5-Wpn"; */
"VW8-s5-Wpn.title" = "vChewing macOS Development: Shiki Suen, Isaac Xen, Hiraku Wang, etc.\nvChewing Phrase Database Maintained by Shiki Suen.\nWalking algorithm by Lukhnos Liu (from Gramambular 2, MIT-License).\nApp-style installer is derived from OpenVanilla (MIT-License).";
/* Class = "NSTextFieldCell"; title = "Placeholder for showing copyright information."; ObjectID = "eo3-TK-0rB"; */
// "eo3-TK-0rB.title" = "Placeholder for showing copyright information.";
/* Class = "NSWindow"; title = "Window"; ObjectID = "gHl-Hx-eQn"; */
"gHl-Hx-eQn.title" = "Window";
/* Class = "NSTextFieldCell"; title = "By installing the software, click the \"I Accept\" to the terms above:"; ObjectID = "mf8-6e-z7X"; */
"mf8-6e-z7X.title" = "By installing the software, you must accept the terms above.";
/* Class = "NSTextFieldCell"; title = "Stopping the old version. This may take up to one minute…"; ObjectID = "nTo-dx-qfZ"; */
"nTo-dx-qfZ.title" = "Stopping the old version. This may take up to one minute…";

View File

@ -1,19 +1,30 @@
"vChewing Input Method" = "威注音入力アプリ";
"Upgrade" = "承認と更新";
"Abort" = "中止";
"Attention" = "ご注意";
"Cancel" = "取消";
"Cannot activate the input method." = "入力アプリ、起動失敗。";
"Cannot copy the file to the destination." = "目標へファイルのコピーできません。";
"Cannot find input source %@ after registration." = "登録済みですが「%@」は見つけませんでした。";
"Cannot register input source %@ at %@." = "「%2$@」で入力アプリ「\"%1$@\"」の実装は失敗しました。";
"Continue" = "続行";
"Fatal Error" = "致命錯乱";
"i18n:installer.ACCEPT_INSTALLATION" = "承認する";
"i18n:installer.APP_DERIVED_FROM" = "曾て OpenVanilla 小麦注音プロジェクト (MIT-License) から派生。";
"i18n:installer.APP_NAME" = "vChewing for macOS";
"i18n:installer.CANCEL_INSTALLATION" = "取消";
"i18n:installer.DEV_CREW" = "macOS 版威注音の開発Shiki Suen, Isaac Xen, Hiraku Wang, など。\n威注音語彙データの維持Shiki Suen。\nウォーキング算法Lukhnos Liu (Gramambular 2, MIT-License)。";
"i18n:installer.DISCLAIMER_TEXT" = "免責事項vChewing Project は、OpenVanilla と協力関係や提携関係にあるわけではなく、OpenVanilla が小麦注音プロジェクトに同梱した辞書データについて、vChewing Project は一切責任負い兼ねる。特定な地政学的・観念形態的な内容は、vChewing アプリの世界的な普及に妨害する恐れがあるため、vChewing 公式辞書データに不収録。";
"i18n:installer.DO_APP_UPGRADE" = "承認と更新";
"i18n:installer.EULA_PROMPT_NOTICE" = "このアプリを実装するために、上記の条約を承認すべきである。";
"i18n:installer.INSTALLER_APP_TITLE_FULL" = "威注音入力 実装用アプリ";
"i18n:installer.INSTALLER_APP_TITLE" = "威注音入力 実装用アプリ";
"i18n:installer.LICENSE_TITLE" = "MIT商標不許可ライセンス (MIT-NTL License):";
"i18n:installer.STOPPING_THE_OLD_VERSION" = "古いバージョンを強制停止中。1分かかると恐れ入りますが……";
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "入力アプリの自動起動はうまく出来なかったかもしれません。ご自分で「システム環境設定→キーボード→入力ソース」で起動してください。";
"Install Failed" = "実装失敗。";
"Installation Successful" = "実装完了";
"OK" = "うむ";
"vChewing is ready to use. \n\nPlease relogin if this is the first time you install it in this user account." = "威注音入力、利用準備完了。\n\nこのシステムユーザーアカウントで初めて実装した場合、再ログインしてください。";
"Stopping the old version. This may take up to one minute…" = "古いバージョンを強制停止中。1分かかると恐れ入りますが……";
"Attention" = "ご注意";
"vChewing Input Method" = "威注音入力アプリ";
"vChewing is ready to use. \n\nPlease relogin if this is the first time you install it in this user account." = "威注音入力、利用準備完了。\n\nこのシステムユーザーアカウントで初めて実装した場合、再ログインしてください。";
"vChewing is upgraded, but please log out or reboot for the new version to be fully functional." = "威注音入力の更新は実装完了しましたが、うまく作動できるために、このパソコンの再起動および再ログインが必要だと恐れ入ります。";
"Fatal Error" = "致命錯乱";
"Abort" = "中止";
"Cannot register input source %@ at %@." = "「%2$@」で入力アプリ「\"%1$@\"」の実装は失敗しました。";
"Cannot find input source %@ after registration." = "登録済みですが「%@」は見つけませんでした。";
"Warning" = "お知らせ";
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "入力アプリの自動起動はうまく出来なかったかもしれません。ご自分で「システム環境設定→キーボード→入力ソース」で起動してください。";
"Continue" = "続行";

View File

@ -1,72 +0,0 @@
/* Class = "NSMenu"; title = "AMainMenu"; ObjectID = "29"; */
"29.title" = "AMainMenu";
/* Class = "NSMenuItem"; title = "vChewing Installer"; ObjectID = "56"; */
"56.title" = "威注音入力 実装用アプリ";
/* Class = "NSMenu"; title = "vChewing Installer"; ObjectID = "57"; */
"57.title" = "威注音入力 実装用アプリ";
/* Class = "NSMenuItem"; title = "About vChewing Installer"; ObjectID = "58"; */
"58.title" = "威注音入力 実装用アプリについて…";
/* Class = "NSMenuItem"; title = "File"; ObjectID = "83"; */
"83.title" = "ファイル";
/* Class = "NSMenu"; title = "Services"; ObjectID = "130"; */
"130.title" = "サービス";
/* Class = "NSMenuItem"; title = "Services"; ObjectID = "131"; */
"131.title" = "サービス";
/* Class = "NSMenuItem"; title = "Hide vChewing Installer"; ObjectID = "134"; */
"134.title" = "全ウィンドウ隠す";
/* Class = "NSMenuItem"; title = "Quit vChewing Installer"; ObjectID = "136"; */
"136.title" = "威注音入力 実装用アプリ を終了";
/* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "145"; */
"145.title" = "他のアプリのウィンドウを隠す";
/* Class = "NSMenuItem"; title = "Show All"; ObjectID = "150"; */
"150.title" = "隠したウィンドウを全部表示する";
/* Class = "NSWindow"; title = "vChewing Installer"; ObjectID = "371"; */
"371.title" = "威注音入力 実装用アプリ";
/* Class = "NSButtonCell"; title = "I Accept"; ObjectID = "576"; */
"576.title" = "承認する";
/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "593"; */
"593.title" = "取消";
/* Class = "NSTextFieldCell"; title = "3-Clause BSD License:"; ObjectID = "AVS-ih-FXM"; */
"AVS-ih-FXM.title" = "MIT商標不許可ライセンス (MIT-NTL License):";
/* Class = "NSTextFieldCell"; title = "vChewing for macOS"; ObjectID = "GNc-8S-1VG"; */
"GNc-8S-1VG.title" = "vChewing for macOS";
/* Class = "NSTextFieldCell"; title = "version_placeholder"; ObjectID = "JRP-At-H9q"; */
"JRP-At-H9q.title" = "version_placeholder";
/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project, having no relationship of cooperation or affiliation with the OpenVanilla project, is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, are not included in vChewing official phrase database."; ObjectID = "Q9M-ni-kUM"; */
"Q9M-ni-kUM.title" = "免責事項vChewing Project は、OpenVanilla と協力関係や提携関係にあるわけではなく、OpenVanilla が小麦注音プロジェクトに同梱した辞書データについて、vChewing Project は一切責任負い兼ねる。特定な地政学的・観念形態的な内容は、vChewing アプリの世界的な普及に妨害する恐れがあるため、vChewing 公式辞書データに不収録。";
/* Class = "NSTextFieldCell"; title = "Was derived from OpenVanilla McBopopmofo Project (MIT-License)."; ObjectID = "QYf-Nf-hoi"; */
"QYf-Nf-hoi.title" = "曾て OpenVanilla 小麦注音プロジェクト (MIT-License) から派生。";
/* Class = "NSTextFieldCell"; title = "vChewing macOS Development: Shiki Suen, Isaac Xen, Hiraku Wang, etc.\nvChewing Phrase Database Maintained by Shiki Suen.\nWalking algorithm by Lukhnos Liu (from Gramambular 2, MIT-License)."; ObjectID = "VW8-s5-Wpn"; */
"VW8-s5-Wpn.title" = "macOS 版威注音の開発Shiki Suen, Isaac Xen, Hiraku Wang, など。\n威注音語彙データの維持Shiki Suen。\nウォーキング算法Lukhnos Liu (Gramambular 2, MIT-License)。\nApp フォーマットで出来た実装アプリは OpenVanilla (MIT-License) から受け継ぎたものである。";
/* Class = "NSTextFieldCell"; title = "Placeholder for showing copyright information."; ObjectID = "eo3-TK-0rB"; */
"eo3-TK-0rB.title" = "Placeholder for showing copyright information.";
/* Class = "NSWindow"; title = "Window"; ObjectID = "gHl-Hx-eQn"; */
"gHl-Hx-eQn.title" = "Window";
/* Class = "NSTextFieldCell"; title = "By installing the software, you must accept the terms above."; ObjectID = "mf8-6e-z7X"; */
"mf8-6e-z7X.title" = "このアプリを実装するために、上記の条約を承認すべきである。";
/* Class = "NSTextFieldCell"; title = "Stopping the old version. This may take up to one minute…"; ObjectID = "nTo-dx-qfZ"; */
"nTo-dx-qfZ.title" = "古いバージョンを強制停止中。1分かかると恐れ入りますが……";

View File

@ -1,19 +1,30 @@
"vChewing Input Method" = "威注音输入法";
"Upgrade" = "接受并升级";
"Abort" = "放弃安装";
"Attention" = "请注意";
"Cancel" = "取消";
"Cannot activate the input method." = "无法启用输入法。";
"Cannot copy the file to the destination." = "无法将输入法拷贝至目的地。";
"Cannot find input source %@ after registration." = "在注册完输入法 \"%@\" 之后仍然无法找到该输入法。";
"Cannot register input source %@ at %@." = "无法从档案位置 %2$@ 安装输入法 \"%1$@\"。";
"Continue" = "继续";
"Fatal Error" = "安装错误";
"i18n:installer.ACCEPT_INSTALLATION" = "我接受";
"i18n:installer.APP_DERIVED_FROM" = "该专案曾由 OpenVanilla 小麦注音专案 (MIT-License) 衍生而来。";
"i18n:installer.APP_NAME" = "vChewing for macOS";
"i18n:installer.CANCEL_INSTALLATION" = "取消安装";
"i18n:installer.DEV_CREW" = "威注音 macOS 程式研发Shiki Suen, Isaac Xen, Hiraku Wang, 等。\n威注音词库维护Shiki Suen。\n爬轨算法Lukhnos Liu (Gramambular 2, MIT-License)。";
"i18n:installer.DISCLAIMER_TEXT" = "免责声明:威注音专案对小麦注音官方专案内赠的小麦注音原版词库内容不负任何责任。威注音输入法专用的威注音官方词库不包含任何「会在法理上妨碍威注音在全球传播」的「与地缘政治及政治意识形态有关的」内容。威注音专案与 OpenVanilla 专案之间无合作关系、无隶属关系。";
"i18n:installer.DO_APP_UPGRADE" = "接受并升级";
"i18n:installer.EULA_PROMPT_NOTICE" = "若要安装该软件,请接受上述条款。";
"i18n:installer.INSTALLER_APP_TITLE_FULL" = "威注音输入法安装程式";
"i18n:installer.INSTALLER_APP_TITLE" = "威注音安装程式";
"i18n:installer.LICENSE_TITLE" = "麻理去商标授权合约 (MIT-NTL License):";
"i18n:installer.STOPPING_THE_OLD_VERSION" = "等待旧版完全停用,大约需要一分钟…";
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "输入法已经安装好,但可能没有完全启用。请从「系统偏好设定」 > 「键盘」 > 「输入方式」分页加入输入法。";
"Install Failed" = "安装失败";
"Installation Successful" = "安装成功";
"OK" = "确定";
"vChewing is ready to use. \n\nPlease relogin if this is the first time you install it in this user account." = "威注音输入法安装成功。\n\n若是在當前使用者帳戶內首次安裝的話請重新登入。";
"Stopping the old version. This may take up to one minute…" = "正在试图结束正在运行的旧版输入法,大概需要一分钟…";
"Attention" = "请注意";
"vChewing Input Method" = "威注音输入法";
"vChewing is ready to use. \n\nPlease relogin if this is the first time you install it in this user account." = "威注音输入法安装成功。\n\n若是在當前使用者帳戶內首次安裝的話請重新登入。";
"vChewing is upgraded, but please log out or reboot for the new version to be fully functional." = "vChewing 安装完成,但建议您登出或重新开机,以便顺利使用新版。";
"Fatal Error" = "安装错误";
"Abort" = "放弃安装";
"Cannot register input source %@ at %@." = "无法从档案位置 %2$@ 安装输入法 \"%1$@\"。";
"Cannot find input source %@ after registration." = "在注册完输入法 \"%@\" 之后仍然无法找到该输入法。";
"Warning" = "安装不完整";
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "输入法已经安装好,但可能没有完全启用。请从「系统偏好设定」 > 「键盘」 > 「输入方式」分页加入输入法。";
"Continue" = "继续";

View File

@ -1,72 +0,0 @@
/* Class = "NSMenu"; title = "AMainMenu"; ObjectID = "29"; */
"29.title" = "AMainMenu";
/* Class = "NSMenuItem"; title = "vChewing Installer"; ObjectID = "56"; */
"56.title" = "威注音安装程式";
/* Class = "NSMenu"; title = "vChewing Installer"; ObjectID = "57"; */
"57.title" = "威注音安装程式";
/* Class = "NSMenuItem"; title = "About vChewing Installer"; ObjectID = "58"; */
"58.title" = "关于威注音安装程式";
/* Class = "NSMenuItem"; title = "File"; ObjectID = "83"; */
"83.title" = "档案";
/* Class = "NSMenu"; title = "Services"; ObjectID = "130"; */
"130.title" = "服务";
/* Class = "NSMenuItem"; title = "Services"; ObjectID = "131"; */
"131.title" = "服务";
/* Class = "NSMenuItem"; title = "Hide vChewing Installer"; ObjectID = "134"; */
"134.title" = "隐藏威注音安装程式";
/* Class = "NSMenuItem"; title = "Quit vChewing Installer"; ObjectID = "136"; */
"136.title" = "结束威注音安装程式";
/* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "145"; */
"145.title" = "隐藏其他程式";
/* Class = "NSMenuItem"; title = "Show All"; ObjectID = "150"; */
"150.title" = "显示所有程式";
/* Class = "NSWindow"; title = "vChewing Installer"; ObjectID = "371"; */
"371.title" = "威注音输入法安装程式";
/* Class = "NSButtonCell"; title = "I Accept"; ObjectID = "576"; */
"576.title" = "我接受";
/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "593"; */
"593.title" = "取消安装";
/* Class = "NSTextFieldCell"; title = "MIT-NTL License:"; ObjectID = "AVS-ih-FXM"; */
"AVS-ih-FXM.title" = "麻理去商标授权合约 (MIT-NTL License):";
/* Class = "NSTextFieldCell"; title = "vChewing for macOS"; ObjectID = "GNc-8S-1VG"; */
"GNc-8S-1VG.title" = "vChewing for macOS";
/* Class = "NSTextFieldCell"; title = "version_placeholder"; ObjectID = "JRP-At-H9q"; */
// "JRP-At-H9q.title" = "version_placeholder";
/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project, having no relationship of cooperation or affiliation with the OpenVanilla project, is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, are not included in vChewing official phrase database."; ObjectID = "Q9M-ni-kUM"; */
"Q9M-ni-kUM.title" = "免责声明:威注音专案对小麦注音官方专案内赠的小麦注音原版词库内容不负任何责任。威注音输入法专用的威注音官方词库不包含任何「会在法理上妨碍威注音在全球传播」的「与地缘政治及政治意识形态有关的」内容。威注音专案与 OpenVanilla 专案之间无合作关系、无隶属关系。";
/* Class = "NSTextFieldCell"; title = "Was derived from OpenVanilla McBopopmofo Project (MIT-License)."; ObjectID = "QYf-Nf-hoi"; */
"QYf-Nf-hoi.title" = "该专案曾由 OpenVanilla 小麦注音专案 (MIT-License) 衍生而来。";
/* Class = "NSTextFieldCell"; title = "vChewing macOS Development: Shiki Suen, Isaac Xen, Hiraku Wang, etc.\nvChewing Phrase Database Maintained by Shiki Suen.\nWalking algorithm by Lukhnos Liu (from Gramambular 2, MIT-License)."; ObjectID = "VW8-s5-Wpn"; */
"VW8-s5-Wpn.title" = "威注音 macOS 程式研发Shiki Suen, Isaac Xen, Hiraku Wang, 等。\n威注音词库维护Shiki Suen。\n爬轨算法Lukhnos Liu (Gramambular 2, MIT-License)。\nApp 格式的安装程式继承自 OpenVanilla (MIT-License)。";
/* Class = "NSTextFieldCell"; title = "Placeholder for showing copyright information."; ObjectID = "eo3-TK-0rB"; */
// "eo3-TK-0rB.title" = "Placeholder for showing copyright information.";
/* Class = "NSWindow"; title = "Window"; ObjectID = "gHl-Hx-eQn"; */
"gHl-Hx-eQn.title" = "视窗";
/* Class = "NSTextFieldCell"; title = "By installing the software, click the \"I Accept\" to the terms above:"; ObjectID = "mf8-6e-z7X"; */
"mf8-6e-z7X.title" = "若要安装该软件,请接受上述条款。";
/* Class = "NSTextFieldCell"; title = "Stopping the old version. This may take up to one minute…"; ObjectID = "nTo-dx-qfZ"; */
"nTo-dx-qfZ.title" = "等待旧版完全停用,大约需要一分钟…";

View File

@ -1,19 +1,30 @@
"vChewing Input Method" = "威注音輸入法";
"Upgrade" = "接受並升級";
"Abort" = "放棄安裝";
"Attention" = "請注意";
"Cancel" = "取消";
"Cannot activate the input method." = "無法啟用輸入法。";
"Cannot copy the file to the destination." = "無法將輸入法拷貝至目的地。";
"Cannot find input source %@ after registration." = "在註冊完輸入法 \"%@\" 之後仍然無法找到該輸入法。";
"Cannot register input source %@ at %@." = "無法從檔案位置 %2$@ 安裝輸入法 \"%1$@\"。";
"Continue" = "繼續";
"Fatal Error" = "安裝錯誤";
"i18n:installer.ACCEPT_INSTALLATION" = "我接受";
"i18n:installer.APP_DERIVED_FROM" = "該專案曾由 OpenVanilla 小麥注音專案 (MIT-License) 衍生而來。";
"i18n:installer.APP_NAME" = "vChewing for macOS";
"i18n:installer.CANCEL_INSTALLATION" = "取消安裝";
"i18n:installer.DEV_CREW" = "威注音 macOS 程式研發Shiki Suen, Isaac Xen, Hiraku Wang, 等。\n威注音詞庫維護Shiki Suen。\n爬軌算法Lukhnos Liu (Gramambular 2, MIT-License)。";
"i18n:installer.DISCLAIMER_TEXT" = "免責聲明:威注音專案對小麥注音官方專案內贈的小麥注音原版詞庫內容不負任何責任。威注音輸入法專用的威注音官方詞庫不包含任何「會在法理上妨礙威注音在全球傳播」的「與地緣政治及政治意識形態有關的」內容。威註音專案與 OpenVanilla 專案之間無合作關係、無隸屬關係。";
"i18n:installer.DO_APP_UPGRADE" = "接受並升級";
"i18n:installer.EULA_PROMPT_NOTICE" = "若要安裝該軟體,請接受上述條款。";
"i18n:installer.INSTALLER_APP_TITLE_FULL" = "威注音輸入法安裝程式";
"i18n:installer.INSTALLER_APP_TITLE" = "威注音安裝程式";
"i18n:installer.LICENSE_TITLE" = "麻理去商標授權合約 (MIT-NTL License):";
"i18n:installer.STOPPING_THE_OLD_VERSION" = "等待舊版完全停用,大約需要一分鐘…";
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "輸入法已經安裝好,但可能沒有完全啟用。請從「系統偏好設定」 > 「鍵盤」 > 「輸入方式」分頁加入輸入法。";
"Install Failed" = "安裝失敗";
"Installation Successful" = "安裝成功";
"OK" = "確定";
"vChewing is ready to use. \n\nPlease relogin if this is the first time you install it in this user account." = "威注音輸入法安裝成功。\n\n若是在当前使用者帐户内首次安装的话请重新登入。";
"Stopping the old version. This may take up to one minute…" = "正在試圖結束正在運行的舊版輸入法,大概需要一分鐘…";
"Attention" = "請注意";
"vChewing Input Method" = "威注音輸入法";
"vChewing is ready to use. \n\nPlease relogin if this is the first time you install it in this user account." = "威注音輸入法安裝成功。\n\n若是在當前使用者帳戶內首次安裝的話請重新登入。";
"vChewing is upgraded, but please log out or reboot for the new version to be fully functional." = "vChewing 安裝完成,但建議您登出或重新開機,以便順利使用新版。";
"Fatal Error" = "安裝錯誤";
"Abort" = "放棄安裝";
"Cannot register input source %@ at %@." = "無法從檔案位置 %2$@ 安裝輸入法 \"%1$@\"。";
"Cannot find input source %@ after registration." = "在註冊完輸入法 \"%@\" 之後仍然無法找到該輸入法。";
"Warning" = "安裝不完整";
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "輸入法已經安裝好,但可能沒有完全啟用。請從「系統偏好設定」 > 「鍵盤」 > 「輸入方式」分頁加入輸入法。";
"Continue" = "繼續";

View File

@ -1,72 +0,0 @@
/* Class = "NSMenu"; title = "AMainMenu"; ObjectID = "29"; */
"29.title" = "AMainMenu";
/* Class = "NSMenuItem"; title = "vChewing Installer"; ObjectID = "56"; */
"56.title" = "威注音安裝程式";
/* Class = "NSMenu"; title = "vChewing Installer"; ObjectID = "57"; */
"57.title" = "威注音安裝程式";
/* Class = "NSMenuItem"; title = "About vChewing Installer"; ObjectID = "58"; */
"58.title" = "關於威注音安裝程式";
/* Class = "NSMenuItem"; title = "File"; ObjectID = "83"; */
"83.title" = "檔案";
/* Class = "NSMenu"; title = "Services"; ObjectID = "130"; */
"130.title" = "服務";
/* Class = "NSMenuItem"; title = "Services"; ObjectID = "131"; */
"131.title" = "服務";
/* Class = "NSMenuItem"; title = "Hide vChewing Installer"; ObjectID = "134"; */
"134.title" = "隱藏威注音安裝程式";
/* Class = "NSMenuItem"; title = "Quit vChewing Installer"; ObjectID = "136"; */
"136.title" = "結束威注音安裝程式";
/* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "145"; */
"145.title" = "隱藏其他程式";
/* Class = "NSMenuItem"; title = "Show All"; ObjectID = "150"; */
"150.title" = "顯示所有程式";
/* Class = "NSWindow"; title = "vChewing Installer"; ObjectID = "371"; */
"371.title" = "威注音輸入法安裝程式";
/* Class = "NSButtonCell"; title = "I Accept"; ObjectID = "576"; */
"576.title" = "我接受";
/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "593"; */
"593.title" = "取消安裝";
/* Class = "NSTextFieldCell"; title = "MIT-NTL License:"; ObjectID = "AVS-ih-FXM"; */
"AVS-ih-FXM.title" = "麻理去商標授權合約 (MIT-NTL License):";
/* Class = "NSTextFieldCell"; title = "vChewing for macOS"; ObjectID = "GNc-8S-1VG"; */
"GNc-8S-1VG.title" = "vChewing for macOS";
/* Class = "NSTextFieldCell"; title = "version_placeholder"; ObjectID = "JRP-At-H9q"; */
// "JRP-At-H9q.title" = "version_placeholder";
/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project, having no relationship of cooperation or affiliation with the OpenVanilla project, is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, are not included in vChewing official phrase database."; ObjectID = "Q9M-ni-kUM"; */
"Q9M-ni-kUM.title" = "免責聲明:威注音專案對小麥注音官方專案內贈的小麥注音原版詞庫內容不負任何責任。威注音輸入法專用的威注音官方詞庫不包含任何「會在法理上妨礙威注音在全球傳播」的「與地緣政治及政治意識形態有關的」內容。威註音專案與 OpenVanilla 專案之間無合作關係、無隸屬關係。";
/* Class = "NSTextFieldCell"; title = "Was derived from OpenVanilla McBopopmofo Project (MIT-License)."; ObjectID = "QYf-Nf-hoi"; */
"QYf-Nf-hoi.title" = "該專案曾由 OpenVanilla 小麥注音專案 (MIT-License) 衍生而來。";
/* Class = "NSTextFieldCell"; title = "vChewing macOS Development: Shiki Suen, Isaac Xen, Hiraku Wang, etc.\nvChewing Phrase Database Maintained by Shiki Suen.\nWalking algorithm by Lukhnos Liu (from Gramambular 2, MIT-License)."; ObjectID = "VW8-s5-Wpn"; */
"VW8-s5-Wpn.title" = "威注音 macOS 程式研發Shiki Suen, Isaac Xen, Hiraku Wang, 等。\n威注音詞庫維護Shiki Suen。\n爬軌算法Lukhnos Liu (Gramambular 2, MIT-License)。\nApp 格式的安裝程式繼承自 OpenVanilla (MIT-License)。";
/* Class = "NSTextFieldCell"; title = "Placeholder for showing copyright information."; ObjectID = "eo3-TK-0rB"; */
// "eo3-TK-0rB.title" = "Placeholder for showing copyright information.";
/* Class = "NSWindow"; title = "Window"; ObjectID = "gHl-Hx-eQn"; */
"gHl-Hx-eQn.title" = "視窗";
/* Class = "NSTextFieldCell"; title = "By installing the software, click the \"I Accept\" to the terms above:"; ObjectID = "mf8-6e-z7X"; */
"mf8-6e-z7X.title" = "若要安裝該軟體,請接受上述條款。";
/* Class = "NSTextFieldCell"; title = "Stopping the old version. This may take up to one minute…"; ObjectID = "nTo-dx-qfZ"; */
"nTo-dx-qfZ.title" = "等待舊版完全停用,大約需要一分鐘…";

View File

@ -19,4 +19,4 @@ OS_Version=$(sw_vers -productVersion)
##### fi
# Finally, register the input method:
/Users/"${login_user}"/Library/Input\ Methods/"${TARGET}".app/Contents/MacOS/"${TARGET}" install --all || true
/Users/"${login_user}"/Library/Input\ Methods/"${TARGET}".app/Contents/MacOS/"${TARGET}" install || true

View File

@ -13,10 +13,10 @@ if [ "${login_user}" = root ]; then
rm -rf /Library/Keyboard\ Layouts/vChewing\ MiTAC.keylayout || true
fi
rm -rf ~/Library/Input\ Methods/vChewing.app
rm -rf ~/Library/Keyboard\ Layouts/vChewingKeyLayout.bundle
rm -rf ~/Library/Keyboard\ Layouts/vChewing\ Dachen.keylayout
rm -rf ~/Library/Keyboard\ Layouts/vChewing\ ETen.keylayout
rm -rf ~/Library/Keyboard\ Layouts/vChewing\ FakeSeigyou.keylayout
rm -rf ~/Library/Keyboard\ Layouts/vChewing\ IBM.keylayout
rm -rf ~/Library/Keyboard\ Layouts/vChewing\ MiTAC.keylayout
rm -rf ~/Library/Input\ Methods/vChewing.app || true
rm -rf ~/Library/Keyboard\ Layouts/vChewingKeyLayout.bundle || true
rm -rf ~/Library/Keyboard\ Layouts/vChewing\ Dachen.keylayout || true
rm -rf ~/Library/Keyboard\ Layouts/vChewing\ ETen.keylayout || true
rm -rf ~/Library/Keyboard\ Layouts/vChewing\ FakeSeigyou.keylayout || true
rm -rf ~/Library/Keyboard\ Layouts/vChewing\ IBM.keylayout || true
rm -rf ~/Library/Keyboard\ Layouts/vChewing\ MiTAC.keylayout || true

View File

@ -0,0 +1,60 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import AppKit
import SwiftUI
@main
struct vChewingInstallerApp: App {
var body: some Scene {
WindowGroup {
ZStack(alignment: .center) {
LinearGradient(
gradient: Gradient(
colors: [
Color(red: 0, green: 0, blue: 0xF4 / 255),
.black,
]
),
startPoint: .top, endPoint: .bottom
).overlay(alignment: .topLeading) {
Text("vChewing Input Method")
.font(.system(size: 30))
.italic().bold()
.padding()
.foregroundStyle(Color.white)
.shadow(color: .black, radius: 0, x: 5, y: 5)
}
MainView()
.shadow(color: .black, radius: 3, x: 0, y: 0)
}.frame(width: 1000, height: 630)
.onAppear {
NSWindow.allowsAutomaticWindowTabbing = false
NSApp.windows.forEach { window in
window.titlebarAppearsTransparent = true
window.setContentSize(.init(width: 1000, height: 630))
window.standardWindowButton(.closeButton)?.isHidden = true
window.standardWindowButton(.miniaturizeButton)?.isHidden = true
window.standardWindowButton(.zoomButton)?.isHidden = true
window.styleMask.remove(.resizable)
window.orderFront(self)
}
}
.onDisappear {
NSApp.terminate(self)
}
}
.commands {
CommandGroup(replacing: .newItem) {}
CommandGroup(replacing: .appInfo) {}
CommandGroup(replacing: .help) {}
CommandGroup(replacing: .appVisibility) {}
CommandGroup(replacing: .systemServices) {}
}
}
}

View File

@ -0,0 +1,33 @@
#!/usr/bin/env swift
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
let strDataPath = "./"
func handleFiles(_ handler: @escaping ((url: URL, fileName: String)) -> Void) {
let rawURLs = FileManager.default.enumerator(at: URL(fileURLWithPath: strDataPath), includingPropertiesForKeys: nil)?.compactMap { $0 as? URL }
rawURLs?.forEach { url in
guard let fileName = url.pathComponents.last, fileName.lowercased() == "localizable.strings" else { return }
handler((url, fileName))
}
}
handleFiles { url, fileName in
guard let rawStr = try? String(contentsOf: url, encoding: .utf8) else { return }
let locale = Locale(identifier: "zh@collation=stroke")
do {
try rawStr.components(separatedBy: .newlines).filter { !$0.isEmpty }.sorted {
$0.compare($1, locale: locale) == .orderedAscending
}.joined(separator: "\n").description.appending("\n").write(to: url, atomically: false, encoding: .utf8)
} catch {
print("!! Error writing to \(fileName)")
}
}

View File

@ -11,6 +11,24 @@ BUILD_SETTINGS += ARCHS="$(ARCHS)"
BUILD_SETTINGS += ONLY_ACTIVE_ARCH=NO
endif
spmDebug:
swift build -c debug --package-path ./Packages/vChewing_MainAssembly/
spmRelease:
swift build -c release --package-path ./Packages/vChewing_MainAssembly/
spmLintFormat:
make lint --file=./Packages/Makefile || true
make format --file=./Packages/Makefile || true
spmClean:
@for currentDir in $$(ls ./Packages/); do \
if [ -d $$a ]; then \
echo "processing folder $$currentDir"; \
swift package clean --package-path ./Packages/$$currentDir || true; \
fi; \
done;
release:
xcodebuild -project vChewing.xcodeproj -scheme vChewingInstaller -configuration Release $(BUILD_SETTINGS) build
@ -42,6 +60,7 @@ install-release: permission-check
.PHONY: clean
clean:
make clean --file=./Packages/Makefile || true
xcodebuild -scheme vChewingInstaller -configuration Debug $(BUILD_SETTINGS) clean
xcodebuild -scheme vChewingInstaller -configuration Release $(BUILD_SETTINGS) clean
make clean --file=./Source/Data/Makefile || true

1
Packages/.clang-format Normal file
View File

@ -0,0 +1 @@
BasedOnStyle: Microsoft

104
Packages/.swiftformat.json Normal file
View File

@ -0,0 +1,104 @@
# SwiftFormat config compliant with Google Swift Guideline
# https://google.github.io/swift/#control-flow-statements
# Specify version used in a project
--swiftversion 5.5
# Rules explicitly required by the guideline
--rules \
blankLinesAroundMark, \
blankLinesAtEndOfScope, \
blankLinesAtStartOfScope, \
blankLinesBetweenScopes, \
braces, \
consecutiveBlankLines, \
consecutiveSpaces, \
duplicateImports, \
elseOnSameLine, \
emptyBraces, \
enumNamespaces, \
extensionAccessControl, \
hoistPatternLet, \
indent, \
leadingDelimiters, \
linebreakAtEndOfFile, \
markTypes, \
organizeDeclarations, \
redundantInit, \
redundantParens, \
redundantPattern, \
redundantRawValues, \
redundantType, \
redundantVoidReturnType, \
semicolons, \
sortedImports, \
sortedSwitchCases, \
spaceAroundBraces, \
spaceAroundBrackets, \
spaceAroundComments, \
spaceAroundGenerics, \
spaceAroundOperators, \
spaceAroundParens, \
spaceInsideBraces, \
spaceInsideBrackets, \
spaceInsideComments, \
spaceInsideGenerics, \
spaceInsideParens, \
todos, \
trailingClosures, \
trailingCommas, \
trailingSpace, \
typeSugar, \
void, \
wrap, \
wrapArguments, \
wrapAttributes, \
#
#
# Additional rules not mentioned in the guideline, but helping to keep the codebase clean
# Quoting the guideline:
# Common themes among the rules in this section are:
# avoid redundancy, avoid ambiguity, and prefer implicitness over explicitness
# unless being explicit improves readability and/or reduces ambiguity.
#
#
andOperator, \
isEmpty, \
redundantBackticks, \
redundantBreak, \
redundantExtensionACL, \
redundantGet, \
redundantLetError, \
redundantNilInit, \
redundantObjc, \
redundantReturn, \
redundantSelf, \
strongifiedSelf
# Options for basic rules
--extensionacl on-declarations
--funcattributes prev-line
--indent 2
--maxwidth 100
--typeattributes prev-line
--varattributes prev-line
--voidtype tuple
--wraparguments before-first
--wrapparameters before-first
--wrapcollections before-first
--wrapreturntype if-multiline
--wrapconditions after-first
# Option for additional rules
--self init-only
# Excluded folders
--exclude Pods,**/UNTESTED_TODO,vendor,fastlane
# https://github.com/NoemiRozpara/Google-SwiftFormat-Config

View File

@ -4,7 +4,7 @@ import PackageDescription
let package = Package(
name: "FolderMonitor",
platforms: [
.macOS(.v10_11),
.macOS(.v11),
],
products: [
.library(

View File

@ -4,7 +4,7 @@ import PackageDescription
let package = Package(
name: "NSAttributedTextView",
platforms: [
.macOS(.v10_11),
.macOS(.v11),
],
products: [
.library(
@ -13,13 +13,13 @@ let package = Package(
),
],
dependencies: [
.package(path: "../vChewing_CocoaExtension"),
.package(path: "../vChewing_OSFrameworkImpl"),
],
targets: [
.target(
name: "NSAttributedTextView",
dependencies: [
.product(name: "CocoaExtension", package: "vChewing_CocoaExtension"),
.product(name: "OSFrameworkImpl", package: "vChewing_OSFrameworkImpl"),
]
),
.testTarget(

View File

@ -6,7 +6,7 @@
// Modified by The vChewing Project in order to use it with AppKit.
import AppKit
import CocoaExtension
import OSFrameworkImpl
import SwiftUI
@available(macOS 10.15, *)

View File

@ -7,9 +7,9 @@
// requirements defined in MIT License.
import AppKit
import CocoaExtension
import Foundation
@testable import NSAttributedTextView
import OSFrameworkImpl
import Shared
import XCTest

View File

@ -0,0 +1,4 @@
// Ref: https://stackoverflow.com/a/75870807/4162914
#import <IOKit/IOKitLib.h>
#import <IOKit/hid/IOHIDBase.h>

View File

@ -0,0 +1,33 @@
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "SwiftyCapsLockToggler",
platforms: [
.macOS(.v11),
],
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
name: "SwiftyCapsLockToggler",
targets: ["SwiftyCapsLockToggler"]
),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.target(
name: "CapsLockToggler",
path: "Framework",
cSettings: [
.headerSearchPath("include"),
]
),
.target(
name: "SwiftyCapsLockToggler",
dependencies: ["CapsLockToggler"]
),
]
)

View File

@ -0,0 +1,64 @@
// Ref: https://stackoverflow.com/a/75870807/4162914
// #import <IOKit/IOKitLib.h>
// #import <IOKit/hid/IOHIDBase.h>
import CapsLockToggler
public enum CapsLockToggler {
public static func toggle() {
try? IOKit.handleHIDSystemService { ioConnect in
var state = false
IOHIDGetModifierLockState(ioConnect, Int32(kIOHIDCapsLockState), &state)
state.toggle()
IOHIDSetModifierLockState(ioConnect, Int32(kIOHIDCapsLockState), state)
}
}
public static var isOn: Bool {
var state = false
try? IOKit.handleHIDSystemService { ioConnect in
IOHIDGetModifierLockState(ioConnect, Int32(kIOHIDCapsLockState), &state)
}
return state
}
public static func turnOff() {
try? IOKit.handleHIDSystemService { ioConnect in
IOHIDSetModifierLockState(ioConnect, Int32(kIOHIDCapsLockState), false)
}
}
}
// Refactored by Shiki Suen (MIT License)
public enum IOKit {
public static func handleHIDSystemService(_ taskHandler: @escaping (io_connect_t) -> Void) throws {
let ioService: io_service_t = IOServiceGetMatchingService(0, IOServiceMatching(kIOHIDSystemClass))
var connect: io_connect_t = 0
let x = IOServiceOpen(ioService, mach_task_self_, UInt32(kIOHIDParamConnectType), &connect)
if let errorOne = Mach.KernReturn(rawValue: x), errorOne != .success {
throw errorOne
}
taskHandler(connect)
let y = IOServiceClose(connect)
if let errorTwo = Mach.KernReturn(rawValue: y), errorTwo != .success {
throw errorTwo
}
}
}
// Refactored by Shiki Suen (MIT License)
public enum Mach {
public enum KernReturn: Int32, Error {
case success = 0
case invalidAddress = 1
case protectionFailure = 2
case noSpace = 3
case invalidArgument = 4
case failure = 5
case resourceShortage = 6
case notReceiver = 7
case noAccess = 8
case memoryFailure = 9
}
}

View File

@ -4,7 +4,7 @@ import PackageDescription
let package = Package(
name: "BookmarkManager",
platforms: [
.macOS(.v10_11),
.macOS(.v10_13),
],
products: [
.library(

View File

@ -14,14 +14,17 @@ public class BookmarkManager {
return
}
if #available(macOS 10.13, *) {
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: bookmarkDic, requiringSecureCoding: false)
try data.write(to: bookmarkURL)
NSLog("Did save data to url")
} catch {
NSLog("Couldn't save bookmarks")
do {
var data: Data?
if #unavailable(macOS 10.13) {
data = NSKeyedArchiver.archivedData(withRootObject: bookmarkDic)
} else {
data = try NSKeyedArchiver.archivedData(withRootObject: bookmarkDic, requiringSecureCoding: false)
}
try data?.write(to: bookmarkURL)
NSLog("Did save data to url")
} catch {
NSLog("Couldn't save bookmarks")
}
}
@ -34,9 +37,23 @@ public class BookmarkManager {
if fileExists(url) {
do {
let fileData = try Data(contentsOf: url)
if let fileBookmarks = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(fileData) as! [URL: Data]? {
for bookmark in fileBookmarks {
restoreBookmark(key: bookmark.key, value: bookmark.value)
if #available(macOS 11.0, *) {
if let fileBookmarks = try NSKeyedUnarchiver.unarchivedDictionary(ofKeyClass: NSURL.self, objectClass: NSData.self, from: fileData) as [URL: Data]? {
for bookmark in fileBookmarks {
restoreBookmark(key: bookmark.key, value: bookmark.value)
}
}
} else if #available(macOS 10.11, *) {
if let fileBookmarks = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(fileData) as! [URL: Data]? {
for bookmark in fileBookmarks {
restoreBookmark(key: bookmark.key, value: bookmark.value)
}
}
} else {
if let fileBookmarks = NSKeyedUnarchiver.unarchiveObject(with: fileData) as! [URL: Data]? {
for bookmark in fileBookmarks {
restoreBookmark(key: bookmark.key, value: bookmark.value)
}
}
}
} catch {
@ -82,12 +99,7 @@ public class BookmarkManager {
}
private func getBookmarkURL() -> URL? {
let urls = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
if let appSupportURL = urls.last {
let url = appSupportURL.appendingPathComponent("Bookmarks.dict")
return url
}
return nil
FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).last?.appendingPathComponent("Bookmarks.dict")
}
private func fileExists(_ url: URL) -> Bool {

29
Packages/Makefile Normal file
View File

@ -0,0 +1,29 @@
+.PHONY: all
all: debug
debug:
swift build -c debug --package-path ./vChewing_MainAssembly/
release:
swift build -c release --package-path ./vChewing_MainAssembly/
clean:
@for currentDir in $$(ls ./); do \
if [ -d $$a ]; then \
echo "processing folder $$currentDir"; \
swift package clean --package-path ./$$currentDir || true; \
fi; \
done;
.PHONY: lint format
lintFormat: lint format
format:
@swiftformat --swiftversion 5.5 --indent 2 ./
lint:
@git ls-files --exclude-standard | grep -E '\.swift$$' | swiftlint --fix --autocorrect
.PHONY: permission-check install-debug install-release

View File

@ -4,7 +4,7 @@ import PackageDescription
let package = Package(
name: "ShiftKeyUpChecker",
platforms: [
.macOS(.v10_11),
.macOS(.v11),
],
products: [
.library(

View File

@ -37,7 +37,7 @@ public struct ShiftKeyUpChecker {
///
private let delayInterval = 0.3
private let delayInterval = 0.2
private var previousKeyCode: UInt16?
private var lastTime: Date = .init()

View File

@ -4,7 +4,7 @@ import PackageDescription
let package = Package(
name: "LineReader",
platforms: [
.macOS(.v10_11),
.macOS(.v11),
],
products: [
.library(

View File

@ -1,7 +1,6 @@
// (c) 2019 and onwards Robert Muckle-Jones (Apache 2.0 License).
import Foundation
import SwiftExtension
public class LineReader {
let encoding: String.Encoding

View File

@ -1,9 +1,8 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View File

@ -0,0 +1,35 @@
// swift-tools-version: 5.7
import PackageDescription
let package = Package(
name: "BrailleSputnik",
platforms: [
.macOS(.v11),
],
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
name: "BrailleSputnik",
targets: ["BrailleSputnik"]
),
],
dependencies: [
.package(path: "../vChewing_Shared"),
.package(path: "../vChewing_Tekkon"),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.target(
name: "BrailleSputnik",
dependencies: [
.product(name: "Shared", package: "vChewing_Shared"),
.product(name: "Tekkon", package: "vChewing_Tekkon"),
]
),
.testTarget(
name: "BrailleSputnikTests",
dependencies: ["BrailleSputnik"]
),
]
)

View File

@ -0,0 +1,284 @@
// (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
extension BrailleSputnik {
enum Braille: String {
case blank = "" // U+2800
case d1 = ""
case d2 = ""
case d12 = ""
case d3 = ""
case d13 = ""
case d23 = ""
case d123 = ""
case d4 = ""
case d14 = ""
case d24 = ""
case d124 = ""
case d34 = ""
case d134 = ""
case d234 = ""
case d1234 = ""
case d5 = ""
case d15 = ""
case d25 = ""
case d125 = ""
case d35 = ""
case d135 = ""
case d235 = ""
case d1235 = ""
case d45 = ""
case d145 = ""
case d245 = ""
case d1245 = ""
case d345 = ""
case d1345 = ""
case d2345 = ""
case d12345 = ""
case d6 = ""
case d16 = ""
case d26 = ""
case d126 = ""
case d36 = ""
case d136 = ""
case d236 = ""
case d1236 = ""
case d46 = ""
case d146 = ""
case d246 = ""
case d1246 = ""
case d346 = ""
case d1346 = ""
case d2346 = ""
case d12346 = ""
case d56 = ""
case d156 = ""
case d256 = ""
case d1256 = ""
case d356 = ""
case d1356 = ""
case d2356 = ""
case d12356 = ""
case d456 = ""
case d1456 = ""
case d2456 = ""
case d12456 = ""
case d3456 = ""
case d13456 = ""
case d23456 = ""
case d123456 = ""
}
public enum BrailleStandard: Int {
case of1947 = 1
case of2018 = 2
}
}
protocol BrailleProcessingUnit {
var mapConsonants: [String: String] { get }
var mapSemivowels: [String: String] { get }
var mapVowels: [String: String] { get }
var mapIntonations: [String: String] { get }
var mapIntonationSpecialCases: [String: String] { get }
var mapCombinedVowels: [String: String] { get }
var mapPunctuations: [String: String] { get }
func handleSpecialCases(target: inout String, value: String?) -> Bool
}
// MARK: - Static Data conforming to 1947 Standard.
extension BrailleSputnik {
class BrailleProcessingUnit1947: BrailleProcessingUnit {
func handleSpecialCases(target _: inout String, value _: String?) -> Bool {
//
false
}
let mapConsonants: [String: String] = [
"": "", "": "", "": "",
"": "", "": "", "": "",
"": "", "": "", "": "",
"": "", "": "", "": "",
"": "", "": "", "": "",
"": "", "": "", "": "",
"": "", "": "", "": "",
]
let mapSemivowels: [String: String] = [
"": "", "": "", "": "",
]
let mapVowels: [String: String] = [
"": "", "": "", "": "",
"": "", "": "", "": "",
"": "", "": "", "": "",
"": "", "": "", "": "",
]
let mapIntonations: [String: String] = [
"˙": "⠱⠁", "ˇ": "", "ˊ": "", " ": "", "ˋ": "",
]
let mapIntonationSpecialCases: [String: String] = [
"ㄜ˙": "⠮⠁", "ㄚ˙": "⠜⠁", "ㄛ˙": "⠣⠁", "ㄣ˙": "⠥⠁",
]
let mapCombinedVowels: [String: String] = [
"ㄧㄝ": "", "ㄧㄣ": "", "ㄩㄝ": "",
"ㄨㄟ": "", "ㄨㄥ": "", "ㄨㄣ": "",
"ㄨㄚ": "", "ㄧㄡ": "", "ㄧㄤ": "",
"ㄧㄚ": "", "ㄨㄛ": "", "ㄧㄥ": "",
"ㄨㄞ": "", "ㄩㄥ": "", "ㄧㄠ": "",
"ㄧㄞ": "", "ㄨㄤ": "", "ㄩㄣ": "",
"ㄧㄢ": "", "ㄩㄢ": "", "ㄨㄢ": "",
]
let mapPunctuations: [String: String] = [
"": "⠤⠀", "·": "⠤⠀", "": "", "": "",
"": "", "": "⠕⠀", "": "⠇⠀", "": "⠒⠒",
"╴╴": "⠰⠰", "": "⠠⠤", "……": "⠐⠐⠐",
"": "⠐⠂", "—— ——": "⠐⠂⠐⠂", "": "⠈⠼", "": "⠪⠕",
"": "⠦⠦", "": "⠴⠴", "": "⠰⠤", "": "⠤⠆",
"": "⠦⠦", "": "⠴⠴", "": "⠰⠤", "": "⠤⠆",
"": "", "": "", "": "", "": "",
"": "", "": "", "": "", "": "",
]
}
}
// MARK: - Static Data conforming to 2018 Standard (GF0019-2018)
extension BrailleSputnik {
class BrailleProcessingUnit2018: BrailleProcessingUnit {
func handleSpecialCases(target: inout String, value: String?) -> Bool {
guard let value = value else { return false }
switch value {
case "": target = Braille.d2345.rawValue + Braille.d35.rawValue
case "": target = Braille.d4.rawValue + Braille.d2345.rawValue + Braille.d35.rawValue
default: return false
}
return true
}
let mapConsonants: [String: String] = [
"": Braille.d12.rawValue,
"": Braille.d1234.rawValue,
"": Braille.d134.rawValue,
"": Braille.d124.rawValue,
"": Braille.d145.rawValue,
"": Braille.d2345.rawValue,
"": Braille.d1345.rawValue,
"": Braille.d123.rawValue,
"": Braille.d1245.rawValue,
"": Braille.d13.rawValue,
"": Braille.d125.rawValue,
"": Braille.d1245.rawValue,
"": Braille.d13.rawValue,
"": Braille.d125.rawValue,
"": Braille.d34.rawValue,
"": Braille.d12345.rawValue,
"": Braille.d156.rawValue,
"": Braille.d245.rawValue,
"": Braille.d1356.rawValue,
"": Braille.d14.rawValue,
"": Braille.d234.rawValue,
]
let mapSemivowels: [String: String] = [
"": Braille.d24.rawValue,
"": Braille.d136.rawValue,
"": Braille.d346.rawValue,
]
let mapVowels: [String: String] = [
"": Braille.d35.rawValue,
"": Braille.d26.rawValue,
"": Braille.d26.rawValue,
"": Braille.d246.rawValue,
"": Braille.d2346.rawValue,
"": Braille.d235.rawValue,
"": Braille.d12356.rawValue,
"": Braille.d1236.rawValue,
"": Braille.d356.rawValue,
"": Braille.d236.rawValue,
"": Braille.d3456.rawValue, //
"": Braille.d1235.rawValue,
]
let mapIntonations: [String: String] = [
" ": Braille.d1.rawValue,
"ˊ": Braille.d2.rawValue,
"ˇ": Braille.d3.rawValue,
"ˋ": Braille.d23.rawValue,
// "˙": nil, //
]
let mapIntonationSpecialCases: [String: String] = [:]
let mapCombinedVowels: [String: String] = [
"ㄧㄚ": Braille.d1246.rawValue,
"ㄧㄝ": Braille.d15.rawValue,
"ㄧㄞ": Braille.d1246.rawValue, //
"ㄧㄠ": Braille.d345.rawValue,
"ㄧㄡ": Braille.d1256.rawValue,
"ㄧㄢ": Braille.d146.rawValue,
"ㄧㄣ": Braille.d126.rawValue,
"ㄧㄤ": Braille.d1346.rawValue,
"ㄧㄥ": Braille.d16.rawValue,
"ㄨㄚ": Braille.d123456.rawValue,
"ㄨㄛ": Braille.d135.rawValue,
"ㄨㄞ": Braille.d13456.rawValue,
"ㄨㄟ": Braille.d2456.rawValue,
"ㄨㄢ": Braille.d12456.rawValue,
"ㄨㄣ": Braille.d25.rawValue,
"ㄨㄤ": Braille.d2356.rawValue,
"ㄨㄥ": Braille.d256.rawValue,
"ㄩㄝ": Braille.d23456.rawValue,
"ㄩㄢ": Braille.d12346.rawValue,
"ㄩㄣ": Braille.d456.rawValue,
"ㄩㄥ": Braille.d1456.rawValue,
]
let mapPunctuations: [String: String] = [
"": Braille.d5.rawValue + Braille.d23.rawValue,
"·": Braille.d6.rawValue + Braille.d3.rawValue,
"": Braille.d5.rawValue,
"": Braille.d56.rawValue,
"": Braille.d4.rawValue,
"": Braille.d5.rawValue + Braille.d3.rawValue,
"": Braille.d56.rawValue + Braille.d2.rawValue,
"": Braille.d36.rawValue,
"——": Braille.d6.rawValue + Braille.d36.rawValue,
"……": Braille.d5.rawValue + Braille.d5.rawValue + Braille.d5.rawValue,
"-": Braille.d36.rawValue,
"": Braille.d5.rawValue, //
"": Braille.d2356.rawValue + Braille.d35.rawValue,
"": Braille.d5.rawValue + Braille.d36.rawValue,
"": Braille.d36.rawValue + Braille.d2.rawValue,
"": Braille.d5.rawValue + Braille.d3.rawValue,
"": Braille.d6.rawValue + Braille.d2.rawValue,
"": Braille.d45.rawValue + Braille.d45.rawValue,
"": Braille.d45.rawValue + Braille.d45.rawValue,
"": Braille.d45.rawValue,
"": Braille.d45.rawValue,
"": Braille.d45.rawValue + Braille.d45.rawValue,
"": Braille.d45.rawValue + Braille.d45.rawValue,
"": Braille.d45.rawValue,
"": Braille.d45.rawValue,
"": Braille.d56.rawValue + Braille.d3.rawValue,
"": Braille.d6.rawValue + Braille.d23.rawValue,
"": Braille.d56.rawValue + Braille.d23.rawValue,
"": Braille.d56.rawValue + Braille.d23.rawValue,
"": Braille.d56.rawValue + Braille.d23.rawValue,
"": Braille.d56.rawValue + Braille.d23.rawValue,
// "": "", "": "", // 2018
]
}
}

View File

@ -0,0 +1,109 @@
// (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Tekkon
public class BrailleSputnik {
public var standard: BrailleStandard
public init(standard: BrailleStandard) {
self.standard = standard
}
var staticData: BrailleProcessingUnit {
switch standard {
case .of1947: return Self.staticData1947
case .of2018: return Self.staticData2018
}
}
static var sharedComposer = Tekkon.Composer("", arrange: .ofDachen, correction: true)
private static let staticData1947: BrailleProcessingUnit = BrailleProcessingUnit1947()
private static let staticData2018: BrailleProcessingUnit = BrailleProcessingUnit2018()
}
public extension BrailleSputnik {
func convertToBraille(smashedPairs: [(key: String, value: String)], extraInsertion: (reading: String, cursor: Int)? = nil) -> String {
var convertedStack: [String?] = []
var processedKeysCount = 0
var extraInsertion = extraInsertion
smashedPairs.forEach { key, value in
let subKeys = key.split(separator: "\t")
switch subKeys.count {
case 0: return
case 1:
guard !key.isEmpty else { break }
let isPunctuation: Bool = key.first == "_" //
if isPunctuation {
convertedStack.append(convertPunctuationToBraille(value))
} else {
var key = key.description
fixToneOne(target: &key)
convertedStack.append(convertPhonabetReadingToBraille(key, value: value))
}
processedKeysCount += 1
default:
//
subKeys.forEach { subKey in
var subKey = subKey.description
fixToneOne(target: &subKey)
convertedStack.append(convertPhonabetReadingToBraille(subKey))
processedKeysCount += 1
}
}
if let theExtraInsertion = extraInsertion, processedKeysCount == theExtraInsertion.cursor {
convertedStack.append(convertPhonabetReadingToBraille(theExtraInsertion.reading))
extraInsertion = nil
}
}
return convertedStack.compactMap(\.?.description).joined()
}
private func fixToneOne(target key: inout String) {
for char in key {
guard Tekkon.Phonabet(char.description).type != .null else { return }
}
if let lastChar = key.last?.description, Tekkon.Phonabet(lastChar).type != .intonation {
key += " "
}
}
func convertPunctuationToBraille(_ givenTarget: any StringProtocol) -> String? {
staticData.mapPunctuations[givenTarget.description]
}
func convertPhonabetReadingToBraille(_ rawReading: any StringProtocol, value referredValue: String? = nil) -> String? {
var resultStack = ""
//
guard !staticData.handleSpecialCases(target: &resultStack, value: referredValue) else { return resultStack }
Self.sharedComposer.clear()
rawReading.forEach { char in
Self.sharedComposer.receiveKey(fromPhonabet: char.description)
}
let consonant = Self.sharedComposer.consonant.value
let semivowel = Self.sharedComposer.semivowel.value
let vowel = Self.sharedComposer.vowel.value
let intonation = Self.sharedComposer.intonation.value
if !consonant.isEmpty {
resultStack.append(staticData.mapConsonants[consonant] ?? "")
}
let combinedVowels = Self.sharedComposer.semivowel.value + Self.sharedComposer.vowel.value
if combinedVowels.count == 2 {
resultStack.append(staticData.mapCombinedVowels[combinedVowels] ?? "")
} else {
resultStack.append(staticData.mapSemivowels[semivowel] ?? "")
resultStack.append(staticData.mapVowels[vowel] ?? "")
}
// 調
if let intonationSpecialCaseMetResult = staticData.mapIntonationSpecialCases[vowel + intonation] {
resultStack.append(intonationSpecialCaseMetResult.last?.description ?? "")
} else {
resultStack.append(staticData.mapIntonations[intonation] ?? "")
}
return resultStack
}
}

View File

@ -0,0 +1,28 @@
// (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
@testable import BrailleSputnik
import XCTest
final class BrailleSputnikTests: XCTestCase {
func testBrailleConversion() throws {
//
var rawReadingStr = "ㄉㄚˋ-ㄑㄧㄡ-ㄑㄧㄡ-ㄅㄧㄥˋ-ㄌㄜ˙-ㄦˋ-ㄑㄧㄡ-ㄑㄧㄡ-ㄑㄧㄠˊ-_"
rawReadingStr += "-ㄙㄢ-ㄑㄧㄡ-ㄑㄧㄡ-ㄘㄞˇ-ㄧㄠˋ-ㄙˋ-ㄑㄧㄡ-ㄑㄧㄡ-ㄠˊ-_。"
let rawReadingArray: [(key: String, value: String)] = rawReadingStr.split(separator: "-").map {
let value: String = $0.first == "_" ? $0.last?.description ?? "" : ""
return (key: $0.description, value: value)
}
let processor = BrailleSputnik(standard: .of1947)
let result1947 = processor.convertToBraille(smashedPairs: rawReadingArray)
XCTAssertEqual(result1947, "⠙⠜⠐⠚⠎⠄⠚⠎⠄⠕⠽⠐⠉⠮⠁⠱⠐⠚⠎⠄⠚⠎⠄⠚⠪⠂⠆⠑⠧⠄⠚⠎⠄⠚⠎⠄⠚⠺⠈⠪⠐⠑⠐⠚⠎⠄⠚⠎⠄⠩⠂⠤⠀")
processor.standard = .of2018
let result2018 = processor.convertToBraille(smashedPairs: rawReadingArray)
XCTAssertEqual(result2018, "⠙⠔⠆⠅⠳⠁⠅⠳⠁⠃⠡⠆⠇⠢⠗⠆⠅⠳⠁⠅⠳⠁⠅⠜⠂⠐⠎⠧⠁⠅⠳⠁⠅⠳⠁⠉⠪⠄⠜⠆⠎⠆⠅⠳⠁⠅⠳⠁⠖⠂⠐⠆")
}
}

View File

@ -4,7 +4,7 @@ import PackageDescription
let package = Package(
name: "CandidateWindow",
platforms: [
.macOS(.v10_11),
.macOS(.v11),
],
products: [
.library(

View File

@ -19,6 +19,7 @@ public class CandidateCellData: Hashable {
public static var unifiedSize: Double = 16
public static var unifiedCharDimension: Double { ceil(unifiedSize * 1.0125 + 7) }
public static var unifiedTextHeight: Double { ceil(unifiedSize * 19 / 16) }
static var internalPrefs = PrefMgr()
public var selectionKey: String
public let displayedText: String
public private(set) var textDimension: NSSize
@ -81,7 +82,8 @@ public class CandidateCellData: Hashable {
}
public func cellLength(isMatrix: Bool = true) -> Double {
let minLength = ceil(Self.unifiedCharDimension * 2 + size * 1.25)
let factor: CGFloat = (Self.internalPrefs.minCellWidthForHorizontalMatrix == 0) ? 1.5 : 2
let minLength = ceil(Self.unifiedCharDimension * factor + size * 1.25)
if displayedText.count <= 2, isMatrix { return minLength }
return textDimension.width
}
@ -93,7 +95,7 @@ public class CandidateCellData: Hashable {
if #available(macOS 10.15, *) {
return NSFont.monospacedSystemFont(ofSize: fontSizeKey, weight: .regular)
}
return NSFont(name: "Menlo", size: size) ?? phraseFont(size: size)
return NSFont(name: "Courier New", size: size) ?? phraseFont(size: size)
}
func phraseFont(size: CGFloat? = nil) -> NSFont {
@ -200,14 +202,14 @@ public class CandidateCellData: Hashable {
return attrStrCandidate
}
public var charDescriptions: [String] {
public func charDescriptions(shortened: Bool = false) -> [String] {
var result = displayedText
if displayedText.contains("("), displayedText.count > 2 {
result = displayedText.replacingOccurrences(of: "(", with: "").replacingOccurrences(of: ")", with: "")
}
return result.flatMap(\.unicodeScalars).compactMap {
let theName: String = $0.properties.name ?? ""
return String(format: "U+%02X %@", $0.value, theName)
return shortened ? String(format: "U+%02X", $0.value) : String(format: "U+%02X %@", $0.value, theName)
}
}

View File

@ -185,7 +185,7 @@ public extension CandidatePool {
guard !candidatesShown.filter(\.isHighlighted).isEmpty else { return }
isExpanded = true
if candidateLines.count <= _maxLinesPerPage {
recordedLineRangeForCurrentPage = max(0, currentLineNumber - _maxLinesPerPage + 1) ..< currentLineNumber + 1
recordedLineRangeForCurrentPage = lineRangeForFirstPage
} else {
switch isBackward {
case true:

View File

@ -301,10 +301,12 @@ extension CandidatePool {
.font: Self.blankCell.phraseFont(size: reverseLookupTextSize),
]
let result = NSMutableAttributedString(string: "", attributes: attrReverseLookupSpacer)
var addedCounter = 0
for neta in reverseLookupResult {
result.append(NSAttributedString(string: " ", attributes: attrReverseLookupSpacer))
result.append(NSAttributedString(string: " \(neta) ", attributes: attrReverseLookup))
if maxLinesPerPage == 1 { break }
addedCounter += 1
if maxLinesPerPage == 1, addedCounter == 2 { break }
}
return result
}

View File

@ -17,26 +17,24 @@ open class CtlCandidate: NSWindowController, CtlCandidateProtocol {
open var reverseLookupResult: [String] = []
open func highlightedColor() -> NSColor {
var result = NSColor.alternateSelectedControlColor
var colorBlendAmount: Double = NSApplication.isDarkMode ? 0.3 : 0.0
if #available(macOS 10.14, *), !NSApplication.isDarkMode, locale == "zh-Hant" {
colorBlendAmount = 0.15
var result = NSColor.clear
if #available(macOS 10.14, *) {
result = .controlAccentColor
} else {
result = .alternateSelectedControlTextColor
}
let colorBlendAmount = 0.3
//
switch locale {
case "zh-Hans":
result = NSColor.systemRed
result = NSColor.red
case "zh-Hant":
result = NSColor.systemBlue
result = NSColor.blue
case "ja":
result = NSColor.systemBrown
result = NSColor.brown
default: break
}
var blendingAgainstTarget: NSColor = NSApplication.isDarkMode ? NSColor.black : NSColor.white
if #unavailable(macOS 10.14) {
colorBlendAmount = 0.3
blendingAgainstTarget = NSColor.white
}
let blendingAgainstTarget: NSColor = NSApplication.isDarkMode ? NSColor.black : NSColor.white
return result.blended(withFraction: colorBlendAmount, of: blendingAgainstTarget)!
}

View File

@ -7,7 +7,7 @@
// requirements defined in MIT License.
import AppKit
import CocoaExtension
import OSFrameworkImpl
import Shared
private extension NSUserInterfaceLayoutOrientation {
@ -24,12 +24,19 @@ private extension NSUserInterfaceLayoutOrientation {
}
public class CtlCandidateTDK: CtlCandidate, NSWindowDelegate {
@objc var observation: NSKeyValueObservation?
public var maxLinesPerPage: Int = 0
public var useCocoa: Bool = false
public var useMouseScrolling: Bool = true
private static var thePool: CandidatePool = .init(candidates: [])
private static var currentView: NSView = .init()
public static var currentMenu: NSMenu?
public static var currentMenu: NSMenu? {
willSet {
currentMenu?.cancelTracking()
}
}
public static var currentWindow: NSWindow? {
willSet {
currentWindow?.orderOut(nil)
@ -72,6 +79,10 @@ public class CtlCandidateTDK: CtlCandidate, NSWindowDelegate {
Self.currentWindow = panel
window?.delegate = self
currentLayout = layout
observation = Broadcaster.shared.observe(\.eventForClosingAllPanels, options: [.new]) { _, _ in
self.visible = false
}
}
@available(*, unavailable)
@ -97,16 +108,34 @@ public class CtlCandidateTDK: CtlCandidate, NSWindowDelegate {
override open func updateDisplay() {
guard let window = window else { return }
if let currentCandidateText = Self.thePool.currentSelectedCandidateText {
reverseLookupResult = delegate?.reverseLookup(for: currentCandidateText) ?? []
Self.thePool.reverseLookupResult = reverseLookupResult
Self.thePool.tooltip = delegate?.candidateToolTip(shortened: !Self.thePool.isMatrix) ?? ""
}
delegate?.candidatePairHighlightChanged(at: highlightedIndex)
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.updateNSWindowModern(window)
}
//
reverseLookupResult = []
//
if let currentCandidate = Self.thePool.currentCandidate {
let displayedText = currentCandidate.displayedText
var lookupResult: [String?] = delegate?.reverseLookup(for: displayedText) ?? []
if displayedText.count == 1, delegate?.showCodePointForCurrentCandidate ?? false {
if lookupResult.isEmpty {
lookupResult.append(currentCandidate.charDescriptions(shortened: !Self.thePool.isMatrix).first)
} else {
lookupResult.insert(currentCandidate.charDescriptions(shortened: true).first, at: lookupResult.startIndex)
}
reverseLookupResult = lookupResult.compactMap { $0 }
} else {
reverseLookupResult = lookupResult.compactMap { $0 }
// UNICODE
if !Self.thePool.isMatrix {
reverseLookupResult = [reverseLookupResult.first].compactMap { $0 }
}
}
}
Self.thePool.reverseLookupResult = reverseLookupResult
Self.thePool.tooltip = delegate?.candidateToolTip(shortened: !Self.thePool.isMatrix) ?? ""
delegate?.candidatePairHighlightChanged(at: highlightedIndex)
}
func updateNSWindowModern(_ window: NSWindow) {

View File

@ -7,6 +7,7 @@
// requirements defined in MIT License.
import AppKit
import OSFrameworkImpl
import Shared
/// AppKit SwiftUI
@ -56,8 +57,9 @@ public extension VwrCandidateTDKAppKit {
override var fittingSize: NSSize { thePool.metrics.fittingSize }
static var candidateListBackground: NSColor {
let delta = NSApplication.isDarkMode ? 0.05 : 0.99
return .init(white: delta, alpha: 1)
let brightBackground = NSColor(red: 0.99, green: 0.99, blue: 0.99, alpha: 1.00)
let darkBackground = NSColor(red: 0.13, green: 0.13, blue: 0.14, alpha: 1.00)
return NSApplication.isDarkMode ? darkBackground : brightBackground
}
override func draw(_: NSRect) {
@ -74,7 +76,9 @@ public extension VwrCandidateTDKAppKit {
let allCells = thePool.candidateLines[thePool.lineRangeForCurrentPage].flatMap { $0 }
allCells.forEach { currentCell in
if currentCell.isHighlighted, !cellHighlightedDrawn {
currentCell.themeColorCocoa.setFill()
let alphaRatio = NSApplication.isDarkMode ? 0.75 : 1
let themeColor = controller?.delegate?.clientAccentColor?.withAlphaComponent(alphaRatio)
(themeColor ?? currentCell.themeColorCocoa).setFill()
NSBezierPath(roundedRect: sizesCalculated.highlightedCandidate, xRadius: cellRadius, yRadius: cellRadius).fill()
cellHighlightedDrawn = true
}
@ -148,30 +152,17 @@ public extension VwrCandidateTDKAppKit {
private extension VwrCandidateTDKAppKit {
private func prepareMenu() {
let newMenu = NSMenu()
let boostMenuItem = NSMenuItem(
title: "\(clickedCell.displayedText)",
action: #selector(menuActionOfBoosting(_:)),
keyEquivalent: ""
)
boostMenuItem.target = self
newMenu.addItem(boostMenuItem)
let nerfMenuItem = NSMenuItem(
title: "\(clickedCell.displayedText)",
action: #selector(menuActionOfNerfing(_:)),
keyEquivalent: ""
)
nerfMenuItem.target = self
newMenu.addItem(nerfMenuItem)
if thePool.isFilterable(target: clickedCell.index) {
let filterMenuItem = NSMenuItem(
title: "✖︎ \(clickedCell.displayedText)",
action: #selector(menuActionOfFiltering(_:)),
keyEquivalent: ""
)
filterMenuItem.target = self
newMenu.addItem(filterMenuItem)
newMenu.appendItems(self) {
NSMenu.Item(
verbatim: "\(clickedCell.displayedText)"
)?.act(#selector(menuActionOfBoosting(_:)))
NSMenu.Item(
verbatim: "\(clickedCell.displayedText)"
)?.act(#selector(menuActionOfNerfing(_:)))
NSMenu.Item(
verbatim: "✖︎ \(clickedCell.displayedText)"
)?.act(#selector(menuActionOfFiltering(_:)))
.nulled(!thePool.isFilterable(target: clickedCell.index))
}
theMenu = newMenu
@ -207,16 +198,7 @@ private extension VwrCandidateTDKAppKit {
private extension VwrCandidateTDKAppKit {
private func lineBackground(isCurrentLine: Bool, isMatrix: Bool) -> NSColor {
if !isCurrentLine { return .clear }
let absBg: NSColor = NSApplication.isDarkMode ? .black : .white
switch thePool.layout {
case .horizontal where isMatrix:
return NSApplication.isDarkMode ? .controlTextColor.withAlphaComponent(0.05) : .white
case .vertical where isMatrix:
return absBg.withAlphaComponent(0.9)
default:
return .clear
}
(isCurrentLine && isMatrix) ? (NSApplication.isDarkMode ? .controlTextColor.withAlphaComponent(0.05) : .white) : .clear
}
private var finalContainerOrientation: NSUserInterfaceLayoutOrientation {

View File

@ -1,10 +1,11 @@
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "Hotenka",
platforms: [
.macOS(.v10_11),
.macOS(.v11),
],
products: [
.library(

View File

@ -24,6 +24,7 @@
*/
import Foundation
import SQLite3
#if os(Linux)
import Glibc
@ -31,18 +32,56 @@ import Foundation
import Darwin
#endif
public enum DictType {
case zhHantTW
case zhHantHK
case zhHansSG
case zhHansJP
case zhHantKX
case zhHansCN
public enum DictType: Int, CaseIterable {
case zhHantTW = 0
case zhHantHK = 1
case zhHansSG = 2
case zhHansJP = 3
case zhHantKX = 4
case zhHansCN = 5
public static func match(rawKeyString: String) -> DictType? {
DictType.allCases.filter { $0.rawKeyString == rawKeyString }.first
}
public var rawKeyString: String {
switch self {
case .zhHantTW:
return "zh2TW"
case .zhHantHK:
return "zh2HK"
case .zhHansSG:
return "zh2SG"
case .zhHansJP:
return "zh2JP"
case .zhHantKX:
return "zh2KX"
case .zhHansCN:
return "zh2CN"
}
}
}
public class HotenkaChineseConverter {
private(set) var dict: [String: [String: String]]
private var dictFiles: [String: [String]]
var ptrSQL: OpaquePointer?
deinit {
sqlite3_close_v2(ptrSQL)
ptrSQL = nil
}
public init(sqliteDir dbPath: String) {
dict = .init()
dictFiles = .init()
guard sqlite3_open(dbPath, &ptrSQL) == SQLITE_OK else {
NSLog("// Exception happened when connecting to SQLite database at: \(dbPath).")
ptrSQL = nil
return
}
sqlite3_exec(ptrSQL, "PRAGMA journal_mode = OFF;", nil, nil, nil)
}
public init(plistDir: String) {
dictFiles = .init()
@ -61,9 +100,7 @@ public class HotenkaChineseConverter {
dictFiles = .init()
do {
let rawData = try Data(contentsOf: URL(fileURLWithPath: jsonDir))
guard let rawJSON: [String: [String: String]] = try JSONSerialization.jsonObject(with: rawData) as? [String: [String: String]] else {
throw NSError()
}
let rawJSON = try JSONDecoder().decode([String: [String: String]].self, from: rawData)
dict = rawJSON
} catch {
NSLog("// Exception happened when reading dict json at: \(jsonDir).")
@ -137,26 +174,28 @@ public class HotenkaChineseConverter {
// MARK: - Public Methods
public func convert(_ target: String, to dictType: DictType) -> String {
var dictTypeKey: String
switch dictType {
case .zhHantTW:
dictTypeKey = "zh2TW"
case .zhHantHK:
dictTypeKey = "zh2HK"
case .zhHansSG:
dictTypeKey = "zh2SG"
case .zhHansJP:
dictTypeKey = "zh2JP"
case .zhHantKX:
dictTypeKey = "zh2KX"
case .zhHansCN:
dictTypeKey = "zh2CN"
public func query(dict dictType: DictType, key searchKey: String) -> String? {
guard ptrSQL != nil else { return dict[dictType.rawKeyString]?[searchKey] }
var ptrStatement: OpaquePointer?
let sqlQuery = "SELECT * FROM DATA_HOTENKA WHERE dict=\(dictType.rawValue) AND theKey='\(searchKey)';"
sqlite3_prepare_v2(ptrSQL, sqlQuery, -1, &ptrStatement, nil)
defer {
sqlite3_finalize(ptrStatement)
ptrStatement = nil
}
//
while sqlite3_step(ptrStatement) == SQLITE_ROW {
guard let rawValue = sqlite3_column_text(ptrStatement, 2) else { continue }
return String(cString: rawValue)
}
return nil
}
public func convert(_ target: String, to dictType: DictType) -> String {
var result = ""
guard let useDict = dict[dictTypeKey] else { return target }
if ptrSQL == nil {
guard dict[dictType.rawKeyString] != nil else { return target }
}
var i = 0
while i < (target.count) {
@ -167,7 +206,7 @@ public class HotenkaChineseConverter {
innerloop: while j > 0 {
let start = target.index(target.startIndex, offsetBy: i)
let end = target.index(target.startIndex, offsetBy: i + j)
guard let useDictSubStr = useDict[String(target[start ..< end])] else {
guard let useDictSubStr = query(dict: dictType, key: String(target[start ..< end])) else {
j -= 1
continue
}

View File

@ -40,7 +40,10 @@ extension HotenkaTests {
let testInstance: HotenkaChineseConverter = .init(dictDir: testDataPath)
NSLog("// Loading complete. Generating json dict file.")
do {
try JSONSerialization.data(withJSONObject: testInstance.dict, options: .sortedKeys).write(to: URL(fileURLWithPath: testDataPath + "convdict.json"))
let urlOutput = URL(fileURLWithPath: testDataPath + "convdict.json")
let encoder = JSONEncoder()
encoder.outputFormatting = .sortedKeys
try encoder.encode(testInstance.dict).write(to: urlOutput, options: .atomic)
} catch {
NSLog("// Error on writing strings to file: \(error)")
}

View File

@ -0,0 +1,113 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Nick Chen's Obj-C library "NCChineseConverter" (MIT License).
/*
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:
1. The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
2. No trademark license is granted to use the trade names, trademarks, service
marks, or product names of Contributor, except as required to fulfill notice
requirements above.
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
import SQLite3
import XCTest
@testable import Hotenka
private let packageRootPath = URL(fileURLWithPath: #file).pathComponents.prefix(while: { $0 != "Tests" }).joined(
separator: "/"
).dropFirst()
private let testDataPath: String = packageRootPath + "/Tests/TestDictData/"
extension HotenkaTests {
func testGeneratingSQLiteDB() throws {
NSLog("// Start loading from: \(packageRootPath)")
let testInstance: HotenkaChineseConverter = .init(dictDir: testDataPath)
NSLog("// Loading complete. Generating SQLite database.")
var ptrSQL: OpaquePointer?
let dbPath = testDataPath + "convdict.sqlite"
XCTAssertTrue(
sqlite3_open(dbPath, &ptrSQL) == SQLITE_OK,
"HOTENKA: SQLite Database Initialization Error."
)
XCTAssertTrue(
sqlite3_exec(ptrSQL, "PRAGMA synchronous = OFF;", nil, nil, nil) == SQLITE_OK,
"HOTENKA: SQLite synchronous OFF failed."
)
XCTAssertTrue(
sqlite3_exec(ptrSQL, "PRAGMA journal_mode = OFF;", nil, nil, nil) == SQLITE_OK,
"HOTENKA: SQLite journal_mode OFF failed."
)
let sqlMakeTableHotenka = """
DROP TABLE IF EXISTS DATA_HOTENKA;
CREATE TABLE IF NOT EXISTS DATA_HOTENKA (
dict INTEGER,
theKey TEXT,
theValue TEXT,
PRIMARY KEY (dict, theKey)
) WITHOUT ROWID;
"""
XCTAssertTrue(
sqlite3_exec(ptrSQL, sqlMakeTableHotenka, nil, nil, nil) == SQLITE_OK,
"HOTENKA: SQLite Table Creation Failed."
)
assert(sqlite3_exec(ptrSQL, "begin;", nil, nil, nil) == SQLITE_OK)
testInstance.dict.forEach { dictName, subDict in
guard let dictID = DictType.match(rawKeyString: dictName)?.rawValue else { return }
subDict.forEach { key, value in
var ptrStatement: OpaquePointer?
let sqlInsertion = "INSERT INTO DATA_HOTENKA (dict, theKey, theValue) VALUES (\(dictID), '\(key)', '\(value)')"
assert(
sqlite3_prepare_v2(
ptrSQL, sqlInsertion, -1, &ptrStatement, nil
) == SQLITE_OK,
"HOTENKA: Failed from preparing: \(sqlInsertion)"
)
assert(
sqlite3_step(ptrStatement) == SQLITE_DONE,
"HOTENKA: Failed from stepping: \(sqlInsertion)"
)
sqlite3_finalize(ptrStatement)
ptrStatement = nil
}
}
assert(sqlite3_exec(ptrSQL, "commit;", nil, nil, nil) == SQLITE_OK)
sqlite3_close_v2(ptrSQL)
}
func testSampleWithSQLiteDB() throws {
NSLog("// Start loading plist from: \(packageRootPath)")
let testInstance2: HotenkaChineseConverter = .init(sqliteDir: testDataPath + "convdict.sqlite")
NSLog("// Successfully loading sql dictionary.")
let oriString = "为中华崛起而读书"
let result1 = testInstance2.convert(oriString, to: .zhHantTW)
let result2 = testInstance2.convert(result1, to: .zhHantKX)
let result3 = testInstance2.convert(result2, to: .zhHansJP)
NSLog("// Results: \(result1) \(result2) \(result3)")
XCTAssertEqual(result1, "為中華崛起而讀書")
XCTAssertEqual(result2, "爲中華崛起而讀書")
XCTAssertEqual(result3, "為中華崛起而読書")
}
}

View File

@ -4,7 +4,7 @@ import PackageDescription
let package = Package(
name: "IMKUtils",
platforms: [
.macOS(.v10_11),
.macOS(.v11),
],
products: [
.library(

View File

@ -15,23 +15,13 @@ public enum IMKHelper {
///
/// SwiftUI 便使
public static let arrWhitelistedKeyLayoutsASCII: [String] = {
var result = [
"com.apple.keylayout.ABC",
"com.apple.keylayout.ABC-AZERTY",
"com.apple.keylayout.ABC-QWERTZ",
"com.apple.keylayout.British",
"com.apple.keylayout.Colemak",
"com.apple.keylayout.Dvorak",
"com.apple.keylayout.Dvorak-Left",
"com.apple.keylayout.DVORAK-QWERTYCMD",
"com.apple.keylayout.Dvorak-Right",
]
if #unavailable(macOS 10.13) {
result.append("com.apple.keylayout.US")
result.append("com.apple.keylayout.German")
result.append("com.apple.keylayout.French")
var results = LatinKeyboardMappings.allCases
if #available(macOS 10.13, *) {
results = results.filter {
![.qwertyUS, .qwertzGerman, .azertyFrench].contains($0)
}
}
return result
return results.map(\.rawValue)
}()
public static let arrDynamicBasicKeyLayouts: [String] = [
@ -49,31 +39,29 @@ public enum IMKHelper {
"org.unknown.keylayout.vChewingMiTAC",
]
public static var allowedAlphanumericalTISInputSources: [TISInputSource] {
arrWhitelistedKeyLayoutsASCII.compactMap { TISInputSource.generate(from: $0) }
public static var allowedAlphanumericalTISInputSources: [TISInputSource.KeyboardLayout] {
let allTISKeyboardLayouts = TISInputSource.getAllTISInputKeyboardLayoutMap()
return arrWhitelistedKeyLayoutsASCII.compactMap { allTISKeyboardLayouts[$0] }
}
public static var allowedBasicLayoutsAsTISInputSources: [TISInputSource?] {
//
var containerA: [TISInputSource?] = []
var containerB: [TISInputSource?] = []
var containerC: [TISInputSource?] = []
public static var allowedBasicLayoutsAsTISInputSources: [TISInputSource.KeyboardLayout?] {
let allTISKeyboardLayouts = TISInputSource.getAllTISInputKeyboardLayoutMap()
//
var containerA: [TISInputSource.KeyboardLayout?] = []
var containerB: [TISInputSource.KeyboardLayout?] = []
var containerC: [TISInputSource.KeyboardLayout] = []
let rawDictionary = TISInputSource.rawTISInputSources(onlyASCII: false)
Self.arrWhitelistedKeyLayoutsASCII.forEach {
if let neta = rawDictionary[$0], !arrDynamicBasicKeyLayouts.contains(neta.identifier) {
containerC.append(neta)
}
let filterSet = Array(Set(arrWhitelistedKeyLayoutsASCII).subtracting(Set(arrDynamicBasicKeyLayouts)))
let matchedGroupBasic = (arrWhitelistedKeyLayoutsASCII + arrDynamicBasicKeyLayouts).compactMap {
allTISKeyboardLayouts[$0]
}
Self.arrDynamicBasicKeyLayouts.forEach {
if let neta = rawDictionary[$0] {
if neta.identifier.contains("com.apple") {
containerA.append(neta)
} else {
containerB.append(neta)
}
matchedGroupBasic.forEach { neta in
if filterSet.contains(neta.id) {
containerC.append(neta)
} else if neta.id.hasPrefix("com.apple") {
containerA.append(neta)
} else {
containerB.append(neta)
}
}

View File

@ -8,25 +8,27 @@
import Foundation
public enum LatinKeyboardMappings: String {
public enum LatinKeyboardMappings: String, CaseIterable {
case qwerty = "com.apple.keylayout.ABC"
case qwertyUS = "com.apple.keylayout.US"
case qwertyBritish = "com.apple.keylayout.British"
case qwertyUS = "com.apple.keylayout.US" // 10.9 - 10.12
case azerty = "com.apple.keylayout.ABC-AZERTY"
case azertyFrench = "com.apple.keylayout.French"
case qwertz = "com.apple.keylayout.ABC-QWERTZ"
case qwertyGerman = "com.apple.keylayout.German"
case azertyFrench = "com.apple.keylayout.French" // 10.9 - 10.12
case qwertzGerman = "com.apple.keylayout.German" // 10.9 - 10.12
case colemak = "com.apple.keylayout.Colemak"
case dvorak = "com.apple.keylayout.Dvorak"
case dvorakQwertyCMD = "com.apple.keylayout.DVORAK-QWERTYCMD"
case dvorakLeft = "com.apple.keylayout.Dvorak-Left"
case dvorakRight = "com.apple.keylayout.Dvorak-Right"
public var mapTable: [UInt16: (String, String)] {
switch self {
case .qwerty, .qwertyUS: return Self.dictQwerty
case .qwerty, .qwertyUS, .qwertyBritish: return Self.dictQwerty
case .azerty, .azertyFrench: return Self.dictAzerty
case .qwertz, .qwertyGerman: return Self.dictQwertz
case .qwertz, .qwertzGerman: return Self.dictQwertz
case .colemak: return Self.dictColemak
case .dvorak: return Self.dictDvorak
case .dvorak, .dvorakQwertyCMD: return Self.dictDvorak
case .dvorakLeft: return Self.dictDvorakLeft
case .dvorakRight: return Self.dictDvorakRight
}

View File

@ -12,8 +12,13 @@ import InputMethodKit
// MARK: - TISInputSource Extension by The vChewing Project (MIT-NTL License).
public extension TISInputSource {
struct KeyboardLayout: Identifiable {
public var id: String
public var titleLocalized: String
}
static var allRegisteredInstancesOfThisInputMethod: [TISInputSource] {
TISInputSource.modes.compactMap { TISInputSource.generate(from: $0) }
TISInputSource.match(modeIDs: TISInputSource.modes)
}
static var modes: [String] {
@ -22,7 +27,7 @@ public extension TISInputSource {
else {
return []
}
return tsInputModeListKey.keys.map { $0 }
return tsInputModeListKey.keys.map(\.description)
}
@discardableResult static func registerInputMethod() -> Bool {
@ -80,10 +85,6 @@ public extension TISInputSource {
== kCFBooleanTrue
}
static func generate(from identifier: String) -> TISInputSource? {
TISInputSource.rawTISInputSources(onlyASCII: false)[identifier]
}
var inputModeID: String {
unsafeBitCast(TISGetInputSourceProperty(self, kTISPropertyInputModeID), to: NSString.self) as String? ?? ""
}
@ -120,9 +121,34 @@ public extension TISInputSource {
return unsafeBitCast(r, to: NSString.self).integerValue as Int? ?? 0
}
static func rawTISInputSources(onlyASCII: Bool = false) -> [String: TISInputSource] {
// Refactored by Shiki Suen.
static func match(identifiers: [String] = [], modeIDs: [String] = [], onlyASCII: Bool = false) -> [TISInputSource] {
let dicConditions: [CFString: Any] = !onlyASCII ? [:] : [
kTISPropertyInputSourceType: kTISTypeKeyboardLayout as CFString,
kTISPropertyInputSourceIsASCIICapable: kCFBooleanTrue as CFBoolean,
]
let cfDict = !onlyASCII ? nil : dicConditions as CFDictionary
var resultStack: [TISInputSource] = []
let unionedIDs = NSOrderedSet(array: modeIDs + identifiers).compactMap { $0 as? String }
let retrieved = (TISCreateInputSourceList(cfDict, true)?.takeRetainedValue() as? [TISInputSource]) ?? []
retrieved.forEach { tis in
unionedIDs.forEach { id in
guard tis.identifier == id || tis.inputModeID == id else { return }
if onlyASCII {
guard tis.scriptCode == 0 else { return }
}
resultStack.append(tis)
}
}
// retrieved
return unionedIDs.compactMap { currentIdentifier in
retrieved.first { $0.identifier == currentIdentifier || $0.inputModeID == currentIdentifier }
}
}
/// Mzp .match()
static func rawTISInputSources(onlyASCII: Bool = false) -> [TISInputSource] {
// CFDictionary
//
let dicConditions: [CFString: Any] = !onlyASCII ? [:] : [
kTISPropertyInputSourceType: kTISTypeKeyboardLayout as CFString,
kTISPropertyInputSourceIsASCIICapable: kCFBooleanTrue as CFBoolean,
@ -132,10 +158,21 @@ public extension TISInputSource {
if onlyASCII {
result = result.filter { $0.scriptCode == 0 }
}
var resultDictionary: [String: TISInputSource] = [:]
return result
}
/// Derived from rawTISInputSources().
static func getAllTISInputKeyboardLayoutMap() -> [String: TISInputSource.KeyboardLayout] {
// CFDictionary
let dicConditions: [CFString: Any] = [kTISPropertyInputSourceType: kTISTypeKeyboardLayout as CFString]
//
let result = TISCreateInputSourceList(dicConditions as CFDictionary, true)?.takeRetainedValue() as? [TISInputSource] ?? .init()
var resultDictionary: [String: TISInputSource.KeyboardLayout] = [:]
result.forEach {
resultDictionary[$0.inputModeID] = $0
resultDictionary[$0.identifier] = $0
let newNeta1 = TISInputSource.KeyboardLayout(id: $0.inputModeID, titleLocalized: $0.vChewingLocalizedName)
let newNeta2 = TISInputSource.KeyboardLayout(id: $0.identifier, titleLocalized: $0.vChewingLocalizedName)
resultDictionary[$0.inputModeID] = newNeta1
resultDictionary[$0.identifier] = newNeta2
}
return resultDictionary
}

View File

@ -0,0 +1,212 @@
---
# BasedOnStyle: Google
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveMacros: None
AlignConsecutiveAssignments: None
AlignConsecutiveBitFields: None
AlignConsecutiveDeclarations: None
AlignEscapedNewlines: Left
AlignOperands: Align
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortEnumsOnASingleLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: true
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 80
CommentPragmas: "^ IWYU pragma:"
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: true
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^<ext/.*\.h>'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^<.*\.h>'
Priority: 1
SortPriority: 0
CaseSensitive: false
- Regex: "^<.*"
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: ".*"
Priority: 3
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: "([-_](test|unittest))?$"
IncludeIsMainSourceRegex: ""
IndentAccessModifiers: false
IndentCaseLabels: true
IndentCaseBlocks: false
IndentGotoLabels: true
IndentPPDirectives: None
IndentExternBlock: AfterExternBlock
IndentRequires: false
IndentWidth: 2
IndentWrappedFunctionNames: false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
LambdaBodyIndentation: Signature
MacroBlockBegin: ""
MacroBlockEnd: ""
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Never
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PenaltyIndentedWhitespace: 0
PointerAlignment: Left
PPIndentWidth: -1
RawStringFormats:
- Language: Cpp
Delimiters:
- cc
- CC
- cpp
- Cpp
- CPP
- "c++"
- "C++"
- "cs"
CanonicalDelimiter: ""
BasedOnStyle: google
- Language: TextProto
Delimiters:
- pb
- PB
- proto
- PROTO
EnclosingFunctions:
- EqualsProto
- EquivToProto
- PARSE_PARTIAL_TEXT_PROTO
- PARSE_TEST_PROTO
- PARSE_TEXT_PROTO
- ParseTextOrDie
- ParseTextProtoOrDie
- ParseTestProto
- ParsePartialTestProto
CanonicalDelimiter: pb
BasedOnStyle: google
ReferenceAlignment: Pointer
ReflowComments: true
ShortNamespaceLines: 1
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceAroundPointerQualifiers: Default
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: Never
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
BitFieldColonSpacing: Both
Standard: Auto
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseCRLF: false
UseTab: Never
WhitespaceSensitiveMacros:
- STRINGIZE
- PP_STRINGIZE
- BOOST_PP_STRINGIZE
- NS_SWIFT_NAME
- CF_SWIFT_NAME
---

View File

@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View File

@ -0,0 +1,31 @@
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "KimoDataReader",
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
name: "KimoDataReader",
targets: ["KimoDataReader"]
),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.target(
name: "ObjcKimoCommunicator",
publicHeadersPath: "include"
),
.target(
name: "KimoDataReader",
dependencies: ["ObjcKimoCommunicator"]
),
.testTarget(
name: "KimoDataReaderTests",
dependencies: ["KimoDataReader"]
),
]
)

View File

@ -0,0 +1,19 @@
# KimoCommunicator
用來與奇摩輸入法進行 NSConnection 通訊的模組,便於直接從奇摩輸入法讀入使用者自訂詞資料庫的資料。
> 免責聲明:
> 與奇摩輸入法有關的原始碼是由 Yahoo 奇摩以 `SPDX Identifier: BSD-3-Clause` 釋出的,
> 但敝模組只是藉由其 Protocol API 與該當程式進行跨執行緒通訊,所以屬於合理使用範圍。
```
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
```
$ EOF.

View File

@ -0,0 +1,25 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import ObjcKimoCommunicator
public class KimoCommunicator: ObjcKimoCommunicator {
public static let shared: KimoCommunicator = .init()
public func prepareData(handler: @escaping (_ key: String, _ value: String) -> Void) {
guard KimoCommunicator.shared.establishConnection() else { return }
assert(KimoCommunicator.shared.hasValidConnection())
let loopAmount = KimoCommunicator.shared.userPhraseDBTotalAmountOfRows()
for i in 0 ..< loopAmount {
let fetched = KimoCommunicator.shared.userPhraseDBDictionary(atRow: i)
guard let key = fetched["BPMF"], let text = fetched["Text"] else { continue }
handler(key, text)
}
}
}

View File

@ -0,0 +1,86 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
//
// Yahoo `SPDX Identifier: BSD-3-Clause`
// Protocol API 使
#import "KimoCommunicator.h"
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
#define kYahooKimoDataObjectConnectionName @"YahooKeyKeyService"
@implementation ObjcKimoCommunicator {
id _xpcConnection;
}
///
- (void)dealloc {
[self disconnect];
}
///
- (void)disconnect {
_xpcConnection = nil;
}
///
- (bool)establishConnection {
// 2012 NSXPCConnection
// NSXPCConnection 使 NSXPCConnection
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
_xpcConnection = [NSConnection rootProxyForConnectionWithRegisteredName:
kYahooKimoDataObjectConnectionName
host:nil];
#pragma GCC diagnostic pop
BOOL result = false;
if (_xpcConnection) {
result = true;
}
if (result) {
[_xpcConnection setProtocolForProxy:@protocol(KimoUserDataReaderService)];
NSLog(@"vChewingDebug: Connection successful. Available data amount: %d.\n",
[_xpcConnection userPhraseDBNumberOfRow]);
}
return result;
}
///
- (bool)hasValidConnection {
BOOL result = false;
if (_xpcConnection) result = true;
return result;
}
- (BOOL)userPhraseDBCanProvideService {
return [self hasValidConnection]
? [_xpcConnection userPhraseDBCanProvideService]
: NO;
}
- (int)userPhraseDBTotalAmountOfRows {
return [self hasValidConnection] ? [_xpcConnection userPhraseDBNumberOfRow]
: 0;
}
- (NSDictionary<NSString*, NSString*> *)userPhraseDBDictionaryAtRow:(int)row {
return [self hasValidConnection]
? [_xpcConnection userPhraseDBDictionaryAtRow:row]
: [NSDictionary alloc];
}
- (bool)exportUserPhraseDBToFile:(NSString *)path {
return [self hasValidConnection]
? [_xpcConnection exportUserPhraseDBToFile:path]
: NO;
}
@end

View File

@ -0,0 +1,46 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
// 免責聲明:
// 與奇摩輸入法有關的原始碼是由 Yahoo 奇摩以 `SPDX Identifier: BSD-3-Clause` 釋出的,
// 但敝模組只是藉由其 Protocol API 與該當程式進行跨執行緒通訊,所以屬於合理使用範圍。
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol KimoUserDataReaderService
- (BOOL)userPhraseDBCanProvideService;
- (int)userPhraseDBNumberOfRow;
- (NSDictionary *)userPhraseDBDictionaryAtRow:(int)row;
- (bool)exportUserPhraseDBToFile:(NSString *)path;
@end
/// 不要理會 Xcode 對 NSDistantObject 的過期狗吠。
/// 奇摩輸入法是用 NSConnection 寫的,
/// 換用 NSXPCConnection 只會製造更多的問題。
@interface ObjcKimoCommunicator : NSObject
/// 嘗試連線。
- (bool)establishConnection;
/// 偵測連線是否有效。
- (bool)hasValidConnection;
/// 斷開連線。
- (void)disconnect;
// Conforming KimoUserDataReaderService protocol.
- (BOOL)userPhraseDBCanProvideService;
- (int)userPhraseDBTotalAmountOfRows;
- (NSDictionary<NSString*, NSString*> *)userPhraseDBDictionaryAtRow:(int)row;
- (bool)exportUserPhraseDBToFile:(NSString *)path;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,10 @@
@testable import ObjcKimoCommunicator
import XCTest
final class KimoDataReaderTests: XCTestCase {
//
func testExample() throws {
let shared = ObjcKimoCommunicator()
print(shared.establishConnection())
}
}

View File

@ -0,0 +1,6 @@
find . -regex '.*\.\(hh\)' -exec clang-format -style=file -i {} \;
find . -regex '.*\.\(cc\)' -exec clang-format -style=file -i {} \;
find . -regex '.*\.\(mm\)' -exec clang-format -style=file -i {} \;
find . -regex '.*\.\(h\)' -exec clang-format -style=file -i {} \;
find . -regex '.*\.\(c\)' -exec clang-format -style=file -i {} \;
find . -regex '.*\.\(m\)' -exec clang-format -style=file -i {} \;

View File

@ -4,7 +4,7 @@ import PackageDescription
let package = Package(
name: "LangModelAssembly",
platforms: [
.macOS(.v10_11),
.macOS(.v11),
],
products: [
.library(
@ -15,8 +15,7 @@ let package = Package(
dependencies: [
.package(path: "../RMJay_LineReader"),
.package(path: "../vChewing_Megrez"),
.package(path: "../vChewing_PinyinPhonaConverter"),
.package(path: "../vChewing_Shared"),
.package(path: "../vChewing_SwiftExtension"),
],
targets: [
.target(
@ -24,8 +23,7 @@ let package = Package(
dependencies: [
.product(name: "LineReader", package: "RMJay_LineReader"),
.product(name: "Megrez", package: "vChewing_Megrez"),
.product(name: "Shared", package: "vChewing_Shared"),
.product(name: "PinyinPhonaConverter", package: "vChewing_PinyinPhonaConverter"),
.product(name: "SwiftExtension", package: "vChewing_SwiftExtension"),
]
),
.testTarget(

View File

@ -1,17 +1,17 @@
# LangModelAssembly
威注音輸入法的語言模組總成套裝
威注音輸入法的語言模組總成套裝,以 LMAssembly 命名空間承載下述唯二對外物件:
- vChewingLM總命名空間也承載一些在套裝內共用的工具函式。
- LMConsolidator自動格式整理模組。
- LMInstantiator語言模組副本化模組。另有其日期時間擴充模組可用對 CIN 磁帶模式無效)。
- LMInstantiator語言模組副本化模組亦集成一些自身功能擴展。
LMAssembly 總命名空間也承載一些在套裝內共用的工具函式。
以下是子模組:
- LMAssociates關聯詞語模組。
- lmCassette專門用來處理 CIN 磁帶檔案的模組,命名為「遠野」引擎。
- LMAssociates聯想詞模組。
- LMCoreEX可以直接讀取 TXT 格式的帶有權重資料的語彙檔案的模組。
- LMCoreJSON專門用來讀取原廠 JSON 檔案的模組。
- lmPlainBopomofo專門用來讀取使用者自訂ㄅ半候選字順序覆蓋定義檔案plist的模組。
- lmReplacements專門用來讀取使用者語彙置換模式的辭典資料的模組。
- lmUserOverride半衰記憶模組。

View File

@ -0,0 +1,249 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
/// InputToken.parse Token
/// Token .translated()
extension LMAssembly {
enum InputToken {
case timeZone(shortened: Bool)
case timeNow(shortened: Bool)
case date(dayDelta: Int = 0, yearDelta: Int = 0, shortened: Bool = true, luna: Bool = false)
case week(dayDelta: Int = 0, shortened: Bool = true)
case year(yearDelta: Int = 0)
case yearGanzhi(yearDelta: Int = 0)
case yearZodiac(yearDelta: Int = 0)
}
}
// MARK: - 使 API
public extension String {
func parseAsInputToken(isCHS: Bool) -> [String] {
LMAssembly.InputToken.parse(from: self).map { $0.translated(isCHS: isCHS) }.flatMap { $0 }.deduplicated
}
}
// MARK: - Parser parsing raw token value to construct token.
extension LMAssembly.InputToken {
static func parse(from rawToken: String) -> [LMAssembly.InputToken] {
var result: [LMAssembly.InputToken] = []
guard rawToken.prefix(6) == "MACRO@" else { return result }
var mapParams: [String: Int] = [:]
let tokenComponents = rawToken.dropFirst(6).split(separator: "_").map { param in
let result = param.uppercased()
let kvPair = param.split(separator: ":")
guard kvPair.count == 2 else { return result }
guard let pairValue = Int(kvPair[1]) else { return result }
mapParams[kvPair[0].description] = pairValue
return result
}
guard !tokenComponents.isEmpty else { return result }
//
let dayDelta: Int = mapParams["dayDelta".uppercased()] ?? 0
let yearDelta: Int = mapParams["yearDelta".uppercased()] ?? 0
let shortened: Bool = tokenComponents.contains("SHORTENED")
let hasZodiac: Bool = tokenComponents.contains("ZODIAC")
let hasGanzhi: Bool = tokenComponents.contains("GANZHI")
let hasLuna: Bool = tokenComponents.contains("LUNA")
switch tokenComponents[0] {
case "TIMEZONE": result.append(.timeZone(shortened: shortened))
case "TIME": result.append(.timeNow(shortened: shortened))
case "DATE": result.append(.date(dayDelta: dayDelta, yearDelta: yearDelta, shortened: shortened, luna: hasLuna))
case "WEEK": result.append(.week(dayDelta: dayDelta, shortened: shortened))
case "YEAR": result.append(.year(yearDelta: yearDelta)) // 便
if hasZodiac { result.append(.yearZodiac(yearDelta: yearDelta)) }
if hasGanzhi { result.append(.yearGanzhi(yearDelta: yearDelta)) }
default: break
}
return result
}
}
// MARK: - Parser parsing token itself.
extension LMAssembly.InputToken {
func translated(isCHS: Bool) -> [String] {
let locale = Locale(identifier: isCHS ? "zh-Hans" : "zh-Hant-TW")
let formatter = DateFormatter()
formatter.locale = locale
let currentDate = Date()
var dateToDescribe = currentDate //
var results: [String] = []
/// dateToDescribe
func applyDelta(for type: Calendar.Component, delta deltaValue: Int) {
switch type {
case .year:
var delta = DateComponents()
let thisYear = Calendar.current.dateComponents([.year], from: currentDate).year ?? 2018
delta.year = max(deltaValue, thisYear * -1)
dateToDescribe = Calendar.current.date(byAdding: delta, to: currentDate) ?? currentDate
case .day:
let dayLength = 60 * 60 * 24
dateToDescribe = dateToDescribe.addingTimeInterval(Double(dayLength * deltaValue))
default: break
}
}
//
switch self {
case let .timeZone(shortened): //
let resultToAdd = TimeZone.current.localizedName(
for: shortened ? .shortGeneric : .standard, locale: locale
) ?? TimeZone.current.description
results.append(resultToAdd)
case let .timeNow(shortened): //
var formats = [String]()
switch (isCHS, shortened) {
case (false, true): formats.append(contentsOf: ["HH:mm", "HH點mm分", "HH時mm分"])
case (false, false): formats.append(contentsOf: ["HH:mm:ss", "HH點mm分ss秒", "HH時mm分ss秒"])
case (true, true): formats.append(contentsOf: ["HH:mm", "HH点mm分", "HH时mm分"])
case (true, false): formats.append(contentsOf: ["HH:mm:ss", "HH点mm分ss秒", "HH时mm分ss秒"])
}
formats.forEach { formatString in
formatter.dateFormat = formatString
results.append(formatter.string(from: dateToDescribe))
}
let resultsExtra: [String] = results.compactMap {
guard !$0.contains(":") else { return nil }
var newResult = $0
if newResult.first == "0" { newResult = newResult.dropFirst().description }
if newResult.prefix(2) == "2点" || newResult.prefix(2) == "2點" {
newResult = (isCHS ? "两点" : "兩點") + newResult.dropFirst(2).description
}
newResult = newResult.convertArabicNumeralsToChinese(onlyDigits: false)
newResult = newResult.replacingOccurrences(of: "", with: "")
return newResult
}
results.append(contentsOf: resultsExtra)
case let .date(dayDelta, yearDelta, shortened, hasLuna): //
applyDelta(for: .year, delta: yearDelta)
applyDelta(for: .day, delta: dayDelta)
//
guard !hasLuna else {
formatter.calendar = .init(identifier: .chinese)
formatter.dateStyle = .medium
formatter.dateFormat = "MMMd"
let dateString = formatter.string(from: dateToDescribe)
formatter.dateFormat = "U"
let yearGanzhi = formatter.string(from: dateToDescribe)
results.append("\(yearGanzhi)\(dateString)")
if let yearZodiac = mapGanzhiToZodiac[yearGanzhi] {
results.append("\(isCHS ? yearZodiac.1 : yearZodiac.0)\(dateString)")
}
break
}
let formats: [String] = [
"MM-dd", "M月d日", "MM月dd日",
]
var additionalResult: String?
for (i, formatString) in formats.enumerated() {
formatter.dateFormat = formatString
let dateStr = formatter.string(from: dateToDescribe)
switch (i == 0, shortened) {
case (false, true): formatter.dateFormat = "yy年"
case (true, false): formatter.dateFormat = "y-"
case (false, false): formatter.dateFormat = "y年"
case (true, true): formatter.dateFormat = "yy-"
}
let yearStr = formatter.string(from: dateToDescribe)
if i == 1 {
let anotherDateStr = dateStr.convertArabicNumeralsToChinese(onlyDigits: false)
let anotherYearStr = yearStr.convertArabicNumeralsToChinese(onlyDigits: true)
additionalResult = anotherYearStr + anotherDateStr
}
let newResult = yearStr + dateStr
guard !results.contains(newResult) else { continue }
results.append(newResult)
}
if let additionalResult = additionalResult {
results.append(additionalResult)
}
case let .week(dayDelta, shortened): //
applyDelta(for: .day, delta: dayDelta)
formatter.dateFormat = shortened ? "EE" : "EEEE"
results.append(formatter.string(from: dateToDescribe))
case let .year(yearDelta): //
applyDelta(for: .year, delta: yearDelta)
formatter.dateFormat = "U年"
formatter.calendar = .init(identifier: .gregorian)
let result = formatter.string(from: dateToDescribe)
results.append(result)
results.append(result.convertArabicNumeralsToChinese(onlyDigits: true))
case let .yearGanzhi(yearDelta): //
applyDelta(for: .year, delta: yearDelta)
formatter.dateFormat = "U年"
formatter.calendar = .init(identifier: .chinese)
let result = formatter.string(from: dateToDescribe)
results.append(result)
case let .yearZodiac(yearDelta): //
applyDelta(for: .year, delta: yearDelta)
formatter.dateFormat = "U"
formatter.calendar = .init(identifier: .chinese)
let rawKey = formatter.string(from: dateToDescribe)
guard let rawResultPair = mapGanzhiToZodiac[rawKey] else { break }
let rawResult = isCHS ? rawResultPair.1 : rawResultPair.0
results.append(rawResult + "")
}
return results
}
}
///
///
private let mapGanzhiToZodiac: [String: (String, String)] = [
"甲子": ("木鼠", "木鼠"), "乙丑": ("木牛", "木牛"), "丙寅": ("火虎", "火虎"), "丁卯": ("火兔", "火兔"),
"戊辰": ("土龍", "土龙"), "己巳": ("土蛇", "土蛇"), "庚午": ("金馬", "金马"), "辛未": ("金羊", "金羊"),
"壬申": ("水猴", "水猴"), "癸酉": ("水雞", "水鸡"), "甲戌": ("木狗", "木狗"), "乙亥": ("木豬", "木猪"),
"丙子": ("火鼠", "火鼠"), "丁丑": ("火牛", "火牛"), "戊寅": ("土虎", "土虎"), "己卯": ("土兔", "土兔"),
"庚辰": ("金龍", "金龙"), "辛巳": ("金蛇", "金蛇"), "壬午": ("水馬", "水马"), "癸未": ("水羊", "水羊"),
"甲申": ("木猴", "木猴"), "乙酉": ("木雞", "木鸡"), "丙戌": ("火狗", "火狗"), "丁亥": ("火豬", "火猪"),
"戊子": ("土鼠", "土鼠"), "己丑": ("土牛", "土牛"), "庚寅": ("金虎", "金虎"), "辛卯": ("金兔", "金兔"),
"壬辰": ("水龍", "水龙"), "癸巳": ("水蛇", "水蛇"), "甲午": ("木馬", "木马"), "乙未": ("木羊", "木羊"),
"丙申": ("火猴", "火猴"), "丁酉": ("火雞", "火鸡"), "戊戌": ("土狗", "土狗"), "己亥": ("土豬", "土猪"),
"庚子": ("金鼠", "金鼠"), "辛丑": ("金牛", "金牛"), "壬寅": ("水虎", "水虎"), "癸卯": ("水兔", "水兔"),
"甲辰": ("木龍", "木龙"), "乙巳": ("木蛇", "木蛇"), "丙午": ("火馬", "火马"), "丁未": ("火羊", "火羊"),
"戊申": ("土猴", "土猴"), "己酉": ("土雞", "土鸡"), "庚戌": ("金狗", "金狗"), "辛亥": ("金豬", "金猪"),
"壬子": ("水鼠", "水鼠"), "癸丑": ("水牛", "水牛"), "甲寅": ("木虎", "木虎"), "乙卯": ("木兔", "木兔"),
"丙辰": ("火龍", "火龙"), "丁巳": ("火蛇", "火蛇"), "戊午": ("土馬", "土马"), "己未": ("土羊", "土羊"),
"庚申": ("金猴", "金猴"), "辛酉": ("金雞", "金鸡"), "壬戌": ("水狗", "水狗"), "癸亥": ("水豬", "水猪"),
]
// MARK: - Date Time Language Conversion Extension
private let tableMappingArabicDatesToChinese: [String: String] = {
let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "zh-Hant-TW") //
formatter.numberStyle = .spellOut
var result = [String: String]()
for i in 0 ... 60 {
result[i.description] = formatter.string(from: NSNumber(value: i))
}
return result
}()
private extension String {
///
/// - Parameter target:
func convertArabicNumeralsToChinese(onlyDigits: Bool) -> String {
var target = self
let sortedKeys = tableMappingArabicDatesToChinese.keys.sorted { $0.count > $1.count }
for key in sortedKeys {
if onlyDigits, key.count > 1 { continue }
guard let result = tableMappingArabicDatesToChinese[key] else { continue }
target = target.replacingOccurrences(of: key, with: result)
}
return target
}
}

View File

@ -8,9 +8,8 @@
import Foundation
import LineReader
import Shared
public extension vChewingLM {
public extension LMAssembly {
enum LMConsolidator {
public static let kPragmaHeader = "# 𝙵𝙾𝚁𝙼𝙰𝚃 𝚘𝚛𝚐.𝚊𝚝𝚎𝚕𝚒𝚎𝚛𝙸𝚗𝚖𝚞.𝚟𝚌𝚑𝚎𝚠𝚒𝚗𝚐.𝚞𝚜𝚎𝚛𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎𝙼𝚘𝚍𝚎𝚕𝙳𝚊𝚝𝚊.𝚏𝚘𝚛𝚖𝚊𝚝𝚝𝚎𝚍"
@ -26,19 +25,19 @@ public extension vChewingLM {
let lineReader = try LineReader(file: fileHandle)
for strLine in lineReader { // i=0
if strLine != kPragmaHeader {
vCLog("Header Mismatch, Starting In-Place Consolidation.")
vCLMLog("Header Mismatch, Starting In-Place Consolidation.")
return false
} else {
vCLog("Header Verification Succeeded: \(strLine).")
vCLMLog("Header Verification Succeeded: \(strLine).")
return true
}
}
} catch {
vCLog("Header Verification Failed: File Access Error.")
vCLMLog("Header Verification Failed: File Access Error.")
return false
}
}
vCLog("Header Verification Failed: File Missing.")
vCLMLog("Header Verification Failed: File Missing.")
return false
}
@ -51,12 +50,12 @@ public extension vChewingLM {
let dict = try FileManager.default.attributesOfItem(atPath: path)
if let value = dict[FileAttributeKey.size] as? UInt64 { fileSize = value }
} catch {
vCLog("EOF Fix Failed: File Missing at \(path).")
vCLMLog("EOF Fix Failed: File Missing at \(path).")
return false
}
guard let fileSize = fileSize else { return false }
guard let writeFile = FileHandle(forUpdatingAtPath: path) else {
vCLog("EOF Fix Failed: File Not Writable at \(path).")
vCLMLog("EOF Fix Failed: File Not Writable at \(path).")
return false
}
defer { writeFile.closeFile() }
@ -64,11 +63,11 @@ public extension vChewingLM {
/// consolidate()
writeFile.seek(toFileOffset: fileSize - 1)
if writeFile.readDataToEndOfFile().first != 0x0A {
vCLog("EOF Missing Confirmed, Start Fixing.")
vCLMLog("EOF Missing Confirmed, Start Fixing.")
var newData = Data()
newData.append(0x0A)
writeFile.write(newData)
vCLog("EOF Successfully Assured.")
vCLMLog("EOF Successfully Assured.")
}
return false
}
@ -142,14 +141,29 @@ public extension vChewingLM {
// Write consolidated file contents.
try strProcessed.write(to: urlPath, atomically: false, encoding: .utf8)
} catch {
vCLog("Consolidation Failed w/ File: \(path), error: \(error)")
vCLMLog("Consolidation Failed w/ File: \(path), error: \(error)")
return false
}
vCLog("Either Consolidation Successful Or No-Need-To-Consolidate.")
vCLMLog("Either Consolidation Successful Or No-Need-To-Consolidate.")
return true
}
vCLog("Consolidation Failed: File Missing at \(path).")
vCLMLog("Consolidation Failed: File Missing at \(path).")
return false
}
}
}
private extension String {
mutating func regReplace(pattern: String, replaceWith: String = "") {
// Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914
do {
let regex = try NSRegularExpression(
pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines]
)
let range = NSRange(startIndex..., in: self)
self = regex.stringByReplacingMatches(
in: self, options: [], range: range, withTemplate: replaceWith
)
} catch { return }
}
}

View File

@ -8,9 +8,8 @@
import Foundation
import Megrez
import Shared
public extension vChewingLM {
public extension LMAssembly {
/// LMInstantiatorLMI
/// LangModelProtocol 使
///
@ -29,18 +28,49 @@ public extension vChewingLM {
/// LMI LMI
///
class LMInstantiator: LangModelProtocol {
public struct Config {
/// nil
/// true = false =
public var numPadFWHWStatus: Bool?
public var isCassetteEnabled = false
public var isPhraseReplacementEnabled = false
public var isCNSEnabled = false
public var isSymbolEnabled = false
public var isSCPCEnabled = false
public var filterNonCNSReadings = false
public var deltaOfCalendarYears: Int = -2000
}
public static var asyncLoadingUserData: Bool = true
// SQLite
static var ptrSQL: OpaquePointer?
// SQLite
public internal(set) static var isSQLDBConnected: Bool = false
//
public let isCHS: Bool
//
public var isCassetteEnabled = false
public var isPhraseReplacementEnabled = false
public var isCNSEnabled = false
public var isSymbolEnabled = false
public var isSCPCEnabled = false
public var isCHS = false
public var deltaOfCalendarYears: Int = -2000
public private(set) var config = Config()
// package
public init(isCHS: Bool = false) {
public init(
isCHS: Bool = false,
uomDataURL: URL? = nil
) {
self.isCHS = isCHS
lmUserOverride = .init(dataURL: uomDataURL)
}
@discardableResult public func setOptions(handler: (inout Config) -> Void) -> LMInstantiator {
handler(&config)
return self
}
public static func setCassetCandidateKeyValidator(_ validator: @escaping (String) -> Bool) {
Self.lmCassette.candidateKeysValidator = validator
}
///
@ -49,31 +79,13 @@ public extension vChewingLM {
///
/// LMCoreEX Unigram
/// LMCoreEX 滿
/// LMReplacements LMAssociates 使
/// LMReplacements LMAssociates 使
/// LMCoreEX 2010-2013 mac
/// LMCoreJSON JSON
//
// Reverse
// Reverse
var lmCore = LMCoreJSON(
reverse: false, consolidate: false, defaultScore: -9.9, forceDefaultScore: false
)
var lmMisc = LMCoreJSON(
reverse: true, consolidate: false, defaultScore: -1.0, forceDefaultScore: false
)
//
// 100MB
static var lmCNS = vChewingLM.LMCoreJSON(
reverse: true, consolidate: false, defaultScore: -11.0, forceDefaultScore: false
)
static var lmSymbols = vChewingLM.LMCoreJSON(
reverse: true, consolidate: false, defaultScore: -13.0, forceDefaultScore: false
)
// currentCassetteMetadata
static var lmCassette = LMCassette()
static var lmPlainBopomofo = LMPlainBopomofo()
// 使
// 使使
@ -88,77 +100,46 @@ public extension vChewingLM {
)
var lmReplacements = LMReplacements()
var lmAssociates = LMAssociates()
var lmPlainBopomofo = LMPlainBopomofo()
//
var lmUserOverride: LMUserOverride
// MARK: -
public func resetFactoryJSONModels() {
lmCore.clear()
lmMisc.clear()
Self.lmCNS.clear()
Self.lmSymbols.clear()
}
public var isCoreLMLoaded: Bool { lmCore.isLoaded }
public func loadLanguageModel(json: (dict: [String: [String]]?, path: String)) {
guard let jsonDict = json.dict else {
vCLog("lmCore: File access failure: \(json.path)")
return
}
lmCore.load((dict: jsonDict, path: json.path))
vCLog("lmCore: \(lmCore.count) entries of data loaded from: \(json.path)")
}
public var isCNSDataLoaded: Bool { Self.lmCNS.isLoaded }
public func loadCNSData(json: (dict: [String: [String]]?, path: String)) {
guard let jsonDict = json.dict else {
vCLog("lmCNS: File access failure: \(json.path)")
return
}
Self.lmCNS.load((dict: jsonDict, path: json.path))
vCLog("lmCNS: \(Self.lmCNS.count) entries of data loaded from: \(json.path)")
}
public var isMiscDataLoaded: Bool { lmMisc.isLoaded }
public func loadMiscData(json: (dict: [String: [String]]?, path: String)) {
guard let jsonDict = json.dict else {
vCLog("lmCore: File access failure: \(json.path)")
return
}
lmMisc.load((dict: jsonDict, path: json.path))
vCLog("lmMisc: \(lmMisc.count) entries of data loaded from: \(json.path)")
}
public var isSymbolDataLoaded: Bool { Self.lmSymbols.isLoaded }
public func loadSymbolData(json: (dict: [String: [String]]?, path: String)) {
guard let jsonDict = json.dict else {
vCLog("lmCore: File access failure: \(json.path)")
return
}
Self.lmSymbols.load((dict: jsonDict, path: json.path))
vCLog("lmSymbols: \(Self.lmSymbols.count) entries of data loaded from: \(json.path)")
}
// Async LMMgr Async GCD
public func resetFactoryJSONModels() {}
public func loadUserPhrasesData(path: String, filterPath: String?) {
DispatchQueue.main.async {
func loadMain() {
if FileManager.default.isReadableFile(atPath: path) {
self.lmUserPhrases.clear()
self.lmUserPhrases.open(path)
vCLog("lmUserPhrases: \(self.lmUserPhrases.count) entries of data loaded from: \(path)")
lmUserPhrases.clear()
lmUserPhrases.open(path)
vCLMLog("lmUserPhrases: \(lmUserPhrases.count) entries of data loaded from: \(path)")
} else {
vCLog("lmUserPhrases: File access failure: \(path)")
vCLMLog("lmUserPhrases: File access failure: \(path)")
}
}
if !Self.asyncLoadingUserData {
loadMain()
} else {
DispatchQueue.main.async {
loadMain()
}
}
guard let filterPath = filterPath else { return }
DispatchQueue.main.async {
func loadFilter() {
if FileManager.default.isReadableFile(atPath: filterPath) {
self.lmFiltered.clear()
self.lmFiltered.open(filterPath)
vCLog("lmFiltered: \(self.lmFiltered.count) entries of data loaded from: \(path)")
lmFiltered.clear()
lmFiltered.open(filterPath)
vCLMLog("lmFiltered: \(lmFiltered.count) entries of data loaded from: \(path)")
} else {
vCLog("lmFiltered: File access failure: \(path)")
vCLMLog("lmFiltered: File access failure: \(path)")
}
}
if !Self.asyncLoadingUserData {
loadFilter()
} else {
DispatchQueue.main.async {
loadFilter()
}
}
}
@ -168,69 +149,85 @@ public extension vChewingLM {
if FileManager.default.isReadableFile(atPath: path) {
lmFiltered.clear()
lmFiltered.open(path)
vCLog("lmFiltered: \(lmFiltered.count) entries of data loaded from: \(path)")
vCLMLog("lmFiltered: \(lmFiltered.count) entries of data loaded from: \(path)")
} else {
vCLog("lmFiltered: File access failure: \(path)")
vCLMLog("lmFiltered: File access failure: \(path)")
}
}
public func loadUserSymbolData(path: String) {
DispatchQueue.main.async {
func load() {
if FileManager.default.isReadableFile(atPath: path) {
self.lmUserSymbols.clear()
self.lmUserSymbols.open(path)
vCLog("lmUserSymbol: \(self.lmUserSymbols.count) entries of data loaded from: \(path)")
lmUserSymbols.clear()
lmUserSymbols.open(path)
vCLMLog("lmUserSymbol: \(lmUserSymbols.count) entries of data loaded from: \(path)")
} else {
vCLog("lmUserSymbol: File access failure: \(path)")
vCLMLog("lmUserSymbol: File access failure: \(path)")
}
}
if !Self.asyncLoadingUserData {
load()
} else {
DispatchQueue.main.async {
load()
}
}
}
public func loadUserAssociatesData(path: String) {
DispatchQueue.main.async {
func load() {
if FileManager.default.isReadableFile(atPath: path) {
self.lmAssociates.clear()
self.lmAssociates.open(path)
vCLog("lmAssociates: \(self.lmAssociates.count) entries of data loaded from: \(path)")
lmAssociates.clear()
lmAssociates.open(path)
vCLMLog("lmAssociates: \(lmAssociates.count) entries of data loaded from: \(path)")
} else {
vCLog("lmAssociates: File access failure: \(path)")
vCLMLog("lmAssociates: File access failure: \(path)")
}
}
if !Self.asyncLoadingUserData {
load()
} else {
DispatchQueue.main.async {
load()
}
}
}
public func loadReplacementsData(path: String) {
DispatchQueue.main.async {
func load() {
if FileManager.default.isReadableFile(atPath: path) {
self.lmReplacements.clear()
self.lmReplacements.open(path)
vCLog("lmReplacements: \(self.lmReplacements.count) entries of data loaded from: \(path)")
lmReplacements.clear()
lmReplacements.open(path)
vCLMLog("lmReplacements: \(lmReplacements.count) entries of data loaded from: \(path)")
} else {
vCLog("lmReplacements: File access failure: \(path)")
vCLMLog("lmReplacements: File access failure: \(path)")
}
}
}
public func loadUserSCPCSequencesData(path: String) {
DispatchQueue.main.async {
if FileManager.default.isReadableFile(atPath: path) {
self.lmPlainBopomofo.clear()
self.lmPlainBopomofo.open(path)
vCLog("lmPlainBopomofo: \(self.lmPlainBopomofo.count) entries of data loaded from: \(path)")
} else {
vCLog("lmPlainBopomofo: File access failure: \(path)")
if !Self.asyncLoadingUserData {
load()
} else {
DispatchQueue.main.async {
load()
}
}
}
public var isCassetteDataLoaded: Bool { Self.lmCassette.isLoaded }
public static func loadCassetteData(path: String) {
DispatchQueue.main.async {
func load() {
if FileManager.default.isReadableFile(atPath: path) {
Self.lmCassette.clear()
Self.lmCassette.open(path)
vCLog("lmCassette: \(Self.lmCassette.count) entries of data loaded from: \(path)")
vCLMLog("lmCassette: \(Self.lmCassette.count) entries of data loaded from: \(path)")
} else {
vCLog("lmCassette: File access failure: \(path)")
vCLMLog("lmCassette: File access failure: \(path)")
}
}
if !Self.asyncLoadingUserData {
load()
} else {
DispatchQueue.main.async {
load()
}
}
}
@ -311,6 +308,9 @@ public extension vChewingLM {
/// - Returns:
public func hasUnigramsFor(keyArray: [String]) -> Bool {
let keyChain = keyArray.joined(separator: "-")
// .unigramsFor()
// SQL SQLite
// 2010
return keyChain == " " || (!unigramsFor(keyArray: keyArray).isEmpty && !keyChain.isEmpty)
}
@ -322,7 +322,7 @@ public extension vChewingLM {
/// - Returns:
public func hasKeyValuePairFor(keyArray: [String], value: String, factoryDictionaryOnly: Bool = false) -> Bool {
factoryDictionaryOnly
? lmCore.unigramsFor(key: keyArray.joined(separator: "-")).map(\.value).contains(value)
? factoryCoreUnigramsFor(key: keyArray.joined(separator: "-")).map(\.value).contains(value)
: unigramsFor(keyArray: keyArray).map(\.value).contains(value)
}
@ -333,7 +333,7 @@ public extension vChewingLM {
/// - Returns:
public func countKeyValuePairs(keyArray: [String], factoryDictionaryOnly: Bool = false) -> Int {
factoryDictionaryOnly
? lmCore.unigramsFor(key: keyArray.joined(separator: "-")).count
? factoryCoreUnigramsFor(key: keyArray.joined(separator: "-")).count
: unigramsFor(keyArray: keyArray).count
}
@ -349,41 +349,79 @@ public extension vChewingLM {
///
var rawAllUnigrams: [Megrez.Unigram] = []
if isCassetteEnabled { rawAllUnigrams += Self.lmCassette.unigramsFor(key: keyChain) }
if config.isCassetteEnabled { rawAllUnigrams += Self.lmCassette.unigramsFor(key: keyChain) }
// 使
if isSCPCEnabled {
rawAllUnigrams += lmPlainBopomofo.valuesFor(key: keyChain).map { Megrez.Unigram(value: $0, score: 0) }
if config.isSCPCEnabled {
rawAllUnigrams += Self.lmPlainBopomofo.valuesFor(key: keyChain, isCHS: isCHS).map {
Megrez.Unigram(value: $0, score: 0)
}
}
if !config.isCassetteEnabled || config.isCassetteEnabled && keyChain.map(\.description)[0] == "_" {
// NumPad
rawAllUnigrams += supplyNumPadUnigrams(key: keyChain)
// LMMisc LMCore score (-10.0, 0.0)
rawAllUnigrams += factoryUnigramsFor(key: keyChain, column: .theDataCHEW)
//
var coreUnigramsResult: [Megrez.Unigram] = factoryCoreUnigramsFor(key: keyChain)
// CNS11643
if config.filterNonCNSReadings, !isCHS {
coreUnigramsResult.removeAll { thisUnigram in
!checkCNSConformation(for: thisUnigram, keyArray: keyArray)
}
}
//
rawAllUnigrams += coreUnigramsResult
if config.isCNSEnabled {
rawAllUnigrams += factoryUnigramsFor(key: keyChain, column: .theDataCNS)
}
}
if config.isSymbolEnabled {
rawAllUnigrams += lmUserSymbols.unigramsFor(key: keyChain)
if !config.isCassetteEnabled {
rawAllUnigrams += factoryUnigramsFor(key: keyChain, column: .theDataSYMB)
}
}
// reversed 使
//
// rawUserUnigrams
rawAllUnigrams += lmUserPhrases.unigramsFor(key: keyChain).reversed()
if !isCassetteEnabled || isCassetteEnabled && keyChain.map(\.description)[0] == "_" {
// LMMisc LMCore score (-10.0, 0.0)
rawAllUnigrams += lmMisc.unigramsFor(key: keyChain)
rawAllUnigrams += lmCore.unigramsFor(key: keyChain)
if isCNSEnabled { rawAllUnigrams += Self.lmCNS.unigramsFor(key: keyChain) }
}
if isSymbolEnabled {
rawAllUnigrams += lmUserSymbols.unigramsFor(key: keyChain)
if !isCassetteEnabled {
rawAllUnigrams += Self.lmSymbols.unigramsFor(key: keyChain)
var userPhraseUnigrams = Array(lmUserPhrases.unigramsFor(key: keyChain).reversed())
if keyArray.count == 1, let topScore = rawAllUnigrams.map(\.score).max() {
// 使
userPhraseUnigrams = userPhraseUnigrams.map { currentUnigram in
Megrez.Unigram(
value: currentUnigram.value,
score: Swift.min(topScore + 0.000_114_514, currentUnigram.score)
)
}
}
rawAllUnigrams = userPhraseUnigrams + rawAllUnigrams
//
// InputToken
rawAllUnigrams = rawAllUnigrams.map { unigram in
let convertedValues = unigram.value.parseAsInputToken(isCHS: isCHS)
guard !convertedValues.isEmpty else { return [unigram] }
var result = [Megrez.Unigram]()
convertedValues.enumerated().forEach { absDelta, value in
let newScore: Double = -80 - Double(absDelta) * 0.01
result.append(.init(value: value, score: newScore))
}
return result
}.flatMap { $0 }
//
rawAllUnigrams.append(contentsOf: queryDateTimeUnigrams(with: keyChain))
if keyChain == "_punctuation_list" {
rawAllUnigrams.append(contentsOf: lmCore.getHaninSymbolMenuUnigrams())
rawAllUnigrams.append(contentsOf: getHaninSymbolMenuUnigrams())
}
//
if isPhraseReplacementEnabled {
//
if config.isPhraseReplacementEnabled {
for i in 0 ..< rawAllUnigrams.count {
let newValue = lmReplacements.valuesFor(key: rawAllUnigrams[i].value)
guard !newValue.isEmpty else { continue }

View File

@ -8,9 +8,9 @@
import Foundation
import Megrez
import Shared
import SwiftExtension
public extension vChewingLM.LMInstantiator {
public extension LMAssembly.LMInstantiator {
///
var cassetteWildcardKey: String { Self.lmCassette.wildcardKey }
///
@ -19,6 +19,8 @@ public extension vChewingLM.LMInstantiator {
var nullCandidateInCassette: String { Self.lmCassette.nullCandidate }
/// Shift
var areCassetteCandidateKeysShiftHeld: Bool { Self.lmCassette.areCandidateKeysShiftHeld }
///
var keysToDirectlyCommit: String { Self.lmCassette.keysToDirectlyCommit }
///
var cassetteSelectionKey: String? {
let result = Self.lmCassette.selectionKeys

View File

@ -11,87 +11,72 @@ import Megrez
// MARK: - 便
extension vChewingLM.LMInstantiator {
extension LMAssembly.LMInstantiator {
func queryDateTimeUnigrams(with key: String = "") -> [Megrez.Unigram] {
if !["ㄖˋ-ㄑㄧ", "ㄖˋ-ㄑㄧˊ", "ㄕˊ-ㄐㄧㄢ", "ㄒㄧㄥ-ㄑㄧ", "ㄒㄧㄥ-ㄑㄧˊ"].contains(key) { return .init() }
guard let tokenTrigger = TokenTrigger(rawValue: key) else { return [] }
var results = [Megrez.Unigram]()
let theLocale = Locale(identifier: "zh-Hant")
let currentDate = Date()
var delta = DateComponents()
let thisYear = Calendar.current.dateComponents([.year], from: currentDate).year ?? 2018
delta.year = max(min(deltaOfCalendarYears, 0), thisYear * -1)
let currentDateShortened = Calendar.current.date(byAdding: delta, to: currentDate)
switch key {
case "ㄖˋ-ㄑㄧ", "ㄖˋ-ㄑㄧˊ":
let formatterDate1 = DateFormatter()
let formatterDate2 = DateFormatter()
formatterDate1.dateFormat = "yyyy-MM-dd"
formatterDate2.dateFormat = "yyyy年MM月dd日"
let date1 = formatterDate1.string(from: currentDate)
let date2 = formatterDate2.string(from: currentDate)
var date3 = date2.convertArabicNumeralsToChinese
date3 = date3.replacingOccurrences(of: "年〇", with: "")
date3 = date3.replacingOccurrences(of: "月〇", with: "")
results.append(.init(value: date1, score: -94))
results.append(.init(value: date2, score: -95))
results.append(.init(value: date3, score: -96))
if let currentDateShortened = currentDateShortened, delta.year != 0 {
var dateAlt1: String = formatterDate1.string(from: currentDateShortened)
dateAlt1.regReplace(pattern: #"^0+"#)
var dateAlt2: String = formatterDate2.string(from: currentDateShortened)
dateAlt2.regReplace(pattern: #"^0+"#)
var dateAlt3 = dateAlt2.convertArabicNumeralsToChinese
dateAlt3 = dateAlt3.replacingOccurrences(of: "年〇", with: "")
dateAlt3 = dateAlt3.replacingOccurrences(of: "月〇", with: "")
results.append(.init(value: dateAlt1, score: -97))
results.append(.init(value: dateAlt2, score: -98))
results.append(.init(value: dateAlt3, score: -99))
}
case "ㄕˊ-ㄐㄧㄢ":
let formatterTime1 = DateFormatter()
let formatterTime2 = DateFormatter()
let formatterTime3 = DateFormatter()
formatterTime1.dateFormat = "HH:mm"
formatterTime2.dateFormat = isCHS ? "HH点mm分" : "HH點mm分"
formatterTime3.dateFormat = isCHS ? "HH时mm分" : "HH時mm分"
let time1 = formatterTime1.string(from: currentDate)
let time2 = formatterTime2.string(from: currentDate)
let time3 = formatterTime3.string(from: currentDate)
results.append(.init(value: time1, score: -97))
results.append(.init(value: time2, score: -98))
results.append(.init(value: time3, score: -99))
case "ㄒㄧㄥ-ㄑㄧ", "ㄒㄧㄥ-ㄑㄧˊ":
let formatterWeek1 = DateFormatter()
let formatterWeek2 = DateFormatter()
formatterWeek1.dateFormat = "EEEE"
formatterWeek2.dateFormat = "EE"
formatterWeek1.locale = theLocale
formatterWeek2.locale = theLocale
let week1 = formatterWeek1.string(from: currentDate)
let week2 = formatterWeek2.string(from: currentDate)
results.append(.init(value: week1, score: -98))
results.append(.init(value: week2, score: -99))
default: return .init()
var tokens: [String] = []
func processDateWithDayDelta(_ delta: Int) {
tokens = ["MACRO@DATE_DAYDELTA:\(delta)"]
if config.deltaOfCalendarYears != 0 { tokens.append("MACRO@DATE_DAYDELTA:\(delta)_YEARDELTA:\(config.deltaOfCalendarYears)") }
tokens.append("MACRO@DATE_DAYDELTA:\(delta)_SHORTENED")
tokens.append("MACRO@DATE_DAYDELTA:\(delta)_LUNA")
}
func processYearWithYearDelta(_ delta: Int) {
tokens = ["MACRO@YEAR_YEARDELTA:\(delta)"]
if config.deltaOfCalendarYears != 0 { tokens.append("MACRO@YEAR_YEARDELTA:\(delta + config.deltaOfCalendarYears)") }
tokens.append("MACRO@YEAR_GANZHI_YEARDELTA:\(delta)")
tokens.append("MACRO@YEAR_ZODIAC_YEARDELTA:\(delta)")
}
switch tokenTrigger {
case .jin1tian1ri4qi2, .jin1tian1ri4qi1: processDateWithDayDelta(0) //
case .zuo2tian1ri4qi2, .zuo2tian1ri4qi1: processDateWithDayDelta(-1) //
case .qian2tian1ri4qi2, .qian2tian1ri4qi1: processDateWithDayDelta(-2) //
case .ming2tian1ri4qi2, .ming2tian1ri4qi1: processDateWithDayDelta(1) //
case .hou4tian1ri4qi1, .hou4tian1ri4qi2: processDateWithDayDelta(2) //
case .jin1nian2nian2du4: processYearWithYearDelta(0) //
case .qu4nian2nian2du4: processYearWithYearDelta(-1) //
case .qian2nian2nian2du4: processYearWithYearDelta(-2) //
case .ming2nian2nian2du4: processYearWithYearDelta(1) //
case .hou4nian2nian2du4: processYearWithYearDelta(2) //
case .shi2jian1: tokens = ["MACRO@TIME_SHORTENED"] //
case .xing1qi1, .xing1qi2: tokens = ["MACRO@WEEK_SHORTENED", "MACRO@WEEK"] //
case .suo3zai4shi2qu1, .dang1qian2shi2qu1, .mu4qian2shi2qu1: tokens = ["MACRO@TIMEZONE", "MACRO@TIMEZONE_SHORTENED"] //
}
//
let values = tokens.map { $0.parseAsInputToken(isCHS: isCHS) }.flatMap { $0 }.deduplicated
var i: Double = -99
for strValue in values.reversed() {
results.insert(.init(value: strValue, score: i), at: 0)
i += 1
}
return results
}
}
// MARK: - Date Time Language Conversion Extension
private let tableMappingArabicNumeralsToChinese: [String: String] = [
"0": "", "1": "", "2": "", "3": "", "4": "", "5": "", "6": "", "7": "", "8": "", "9": "",
]
private extension String {
///
/// - Parameter target:
var convertArabicNumeralsToChinese: String {
var target = self
for key in tableMappingArabicNumeralsToChinese.keys {
guard let result = tableMappingArabicNumeralsToChinese[key] else { continue }
target = target.replacingOccurrences(of: key, with: result)
}
return target
}
private enum TokenTrigger: String {
case shi2jian1 = "ㄕˊ-ㄐㄧㄢ"
case xing1qi1 = "ㄒㄧㄥ-ㄑㄧ"
case xing1qi2 = "ㄒㄧㄥ-ㄑㄧˊ"
case jin1nian2nian2du4 = "ㄐㄧㄣ-ㄋㄧㄢˊ-ㄋㄧㄢˊ-ㄉㄨˋ"
case qu4nian2nian2du4 = "ㄑㄩˋ-ㄋㄧㄢˊ-ㄋㄧㄢˊ-ㄉㄨˋ"
case ming2nian2nian2du4 = "ㄇㄧㄥˊ-ㄋㄧㄢˊ-ㄋㄧㄢˊ-ㄉㄨˋ"
case qian2nian2nian2du4 = "ㄑㄧㄢˊ-ㄋㄧㄢˊ-ㄋㄧㄢˊ-ㄉㄨˋ"
case hou4nian2nian2du4 = "ㄏㄡˋ-ㄋㄧㄢˊ-ㄋㄧㄢˊ-ㄉㄨˋ"
case jin1tian1ri4qi2 = "ㄐㄧㄣ-ㄊㄧㄢ-ㄖˋ-ㄑㄧˊ"
case ming2tian1ri4qi2 = "ㄇㄧㄥˊ-ㄊㄧㄢ-ㄖˋ-ㄑㄧˊ"
case zuo2tian1ri4qi2 = "ㄗㄨㄛˊ-ㄊㄧㄢ-ㄖˋ-ㄑㄧˊ"
case qian2tian1ri4qi2 = "ㄑㄧㄢˊ-ㄊㄧㄢ-ㄖˋ-ㄑㄧˊ"
case hou4tian1ri4qi2 = "ㄏㄡˋ-ㄊㄧㄢ-ㄖˋ-ㄑㄧˊ"
case jin1tian1ri4qi1 = "ㄐㄧㄣ-ㄊㄧㄢ-ㄖˋ-ㄑㄧ"
case ming2tian1ri4qi1 = "ㄇㄧㄥˊ-ㄊㄧㄢ-ㄖˋ-ㄑㄧ"
case zuo2tian1ri4qi1 = "ㄗㄨㄛˊ-ㄊㄧㄢ-ㄖˋ-ㄑㄧ"
case qian2tian1ri4qi1 = "ㄑㄧㄢˊ-ㄊㄧㄢ-ㄖˋ-ㄑㄧ"
case hou4tian1ri4qi1 = "ㄏㄡˋ-ㄊㄧㄢ-ㄖˋ-ㄑㄧ"
case dang1qian2shi2qu1 = "ㄉㄤ-ㄑㄧㄢˊ-ㄕˊ-ㄑㄩ"
case mu4qian2shi2qu1 = "ㄇㄨˋ-ㄑㄧㄢˊ-ㄕˊ-ㄑㄩ"
case suo3zai4shi2qu1 = "ㄙㄨㄛˇ-ㄗㄞˋ-ㄕˊ-ㄑㄩ"
}

View File

@ -0,0 +1,23 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import Megrez
public extension LMAssembly.LMInstantiator {
func supplyNumPadUnigrams(key: String) -> [Megrez.Unigram] {
guard let status = config.numPadFWHWStatus else { return [] }
let initials = "_NumPad_"
guard key.hasPrefix(initials) else { return [] }
let char = key.replacingOccurrences(of: initials, with: "")
guard char.count == 1 else { return [] }
let gram1 = Megrez.Unigram(value: char.applyingTransformFW2HW(reverse: status), score: 0)
let gram2 = Megrez.Unigram(value: char.applyingTransformFW2HW(reverse: !status), score: -0.1)
return [gram1, gram2]
}
}

View File

@ -0,0 +1,295 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import Megrez
import SQLite3
/* ==============
Apple 8GB SQLite
CREATE TABLE IF NOT EXISTS DATA_MAIN (
theKey TEXT NOT NULL,
theDataCHS TEXT,
theDataCHT TEXT,
theDataCNS TEXT,
theDataMISC TEXT,
theDataSYMB TEXT,
theDataCHEW TEXT,
PRIMARY KEY (theKey)
) WITHOUT ROWID;
CREATE TABLE IF NOT EXISTS DATA_REV (
theChar TEXT NOT NULL,
theReadings TEXT NOT NULL,
PRIMARY KEY (theChar)
) WITHOUT ROWID;
*/
extension LMAssembly.LMInstantiator {
enum CoreColumn: Int32 {
case theDataCHS = 1 //
case theDataCHT = 2 //
case theDataCNS = 3 //
case theDataMISC = 4 //
case theDataSYMB = 5 //
case theDataCHEW = 6 //
var name: String { String(describing: self) }
var id: Int32 { rawValue }
var defaultScore: Double {
switch self {
case .theDataCHEW: return -1
case .theDataCNS: return -11
case .theDataSYMB: return -13
case .theDataMISC: return -10
default: return -9.9
}
}
}
}
extension LMAssembly.LMInstantiator {
@discardableResult public static func connectSQLDB(dbPath: String, dropPreviousConnection: Bool = true) -> Bool {
if dropPreviousConnection { disconnectSQLDB() }
vCLMLog("Establishing SQLite connection to: \(dbPath)")
guard sqlite3_open(dbPath, &Self.ptrSQL) == SQLITE_OK else { return false }
guard "PRAGMA journal_mode = OFF;".runAsSQLExec(dbPointer: &ptrSQL) else { return false }
isSQLDBConnected = true
return true
}
public static func disconnectSQLDB() {
if Self.ptrSQL != nil {
sqlite3_close_v2(Self.ptrSQL)
Self.ptrSQL = nil
}
isSQLDBConnected = false
}
fileprivate static func querySQL(strStmt sqlQuery: String, coreColumn column: CoreColumn, handler: (String) -> Void) {
guard Self.ptrSQL != nil else { return }
performStatementSansResult { ptrStatement in
sqlite3_prepare_v2(Self.ptrSQL, sqlQuery, -1, &ptrStatement, nil)
while sqlite3_step(ptrStatement) == SQLITE_ROW {
guard let rawValue = sqlite3_column_text(ptrStatement, column.id) else { continue }
handler(String(cString: rawValue))
}
}
}
fileprivate static func hasSQLResult(strStmt sqlQuery: String) -> Bool {
guard Self.ptrSQL != nil else { return false }
var sqlQuery = sqlQuery
if sqlQuery.last == ";" { sqlQuery = sqlQuery.dropLast(1).description } //
guard !sqlQuery.isEmpty else { return false }
return performStatement { ptrStatement in
let wrappedQuery = "SELECT EXISTS(\(sqlQuery));"
sqlite3_prepare_v2(Self.ptrSQL, wrappedQuery, -1, &ptrStatement, nil)
while sqlite3_step(ptrStatement) == SQLITE_ROW {
return sqlite3_column_int(ptrStatement, 0) == 1
}
return false
}
}
///
public static func getFactoryReverseLookupData(with kanji: String) -> [String]? {
var results: [String] = []
let sqlQuery = "SELECT * FROM DATA_REV WHERE theChar='\(kanji)';"
guard Self.ptrSQL != nil else { return nil }
performStatementSansResult { ptrStatement in
sqlite3_prepare_v2(Self.ptrSQL, sqlQuery, -1, &ptrStatement, nil)
while sqlite3_step(ptrStatement) == SQLITE_ROW {
guard let rawValue = sqlite3_column_text(ptrStatement, 1) else { continue }
results.append(
contentsOf: String(cString: rawValue).split(separator: "\t").map { reading in
Self.restorePhonabetFromASCII(reading.description)
}
)
}
}
return results.isEmpty ? nil : results
}
func getHaninSymbolMenuUnigrams() -> [Megrez.Unigram] {
let column: CoreColumn = isCHS ? .theDataCHS : .theDataCHT
var grams: [Megrez.Unigram] = []
let sqlQuery = "SELECT * FROM DATA_MAIN WHERE theKey='_punctuation_list';"
Self.querySQL(strStmt: sqlQuery, coreColumn: column) { currentResult in
let arrRangeRecords = currentResult.split(separator: "\t")
for strNetaSet in arrRangeRecords {
let neta = Array(strNetaSet.trimmingCharacters(in: .newlines).split(separator: " ").reversed())
let theValue: String = .init(neta[0])
var theScore = column.defaultScore
if neta.count >= 2, let thisScore = Double(String(neta[1])) {
theScore = thisScore
}
if theScore > 0 {
theScore *= -1 //
}
grams.append(Megrez.Unigram(value: theValue, score: theScore))
}
}
return grams
}
/// UTF8
/// - Remark: 使
/// - parameters:
/// - key:
public func factoryCoreUnigramsFor(key: String) -> [Megrez.Unigram] {
// ASCII SQLite
factoryUnigramsFor(key: key, column: isCHS ? .theDataCHS : .theDataCHT)
}
/// UTF8
/// - parameters:
/// - key:
/// - column:
func factoryUnigramsFor(
key: String, column: LMAssembly.LMInstantiator.CoreColumn
) -> [Megrez.Unigram] {
if key == "_punctuation_list" { return [] }
var grams: [Megrez.Unigram] = []
var gramsHW: [Megrez.Unigram] = []
// ASCII SQLite
let encryptedKey = Self.cnvPhonabetToASCII(key.replacingOccurrences(of: "'", with: "''"))
let sqlQuery = "SELECT * FROM DATA_MAIN WHERE theKey='\(encryptedKey)';"
Self.querySQL(strStmt: sqlQuery, coreColumn: column) { currentResult in
var i: Double = 0
var previousScore: Double?
currentResult.split(separator: "\t").forEach { strNetaSet in
// stable sort
let neta = Array(strNetaSet.trimmingCharacters(in: .newlines).split(separator: " ").reversed())
let theValue: String = .init(neta[0])
var theScore = column.defaultScore
if neta.count >= 2, let thisScore = Double(String(neta[1])) {
theScore = thisScore
}
if theScore > 0 {
theScore *= -1 //
}
if previousScore == theScore {
theScore -= i * 0.000_001
i += 1
} else {
previousScore = theScore
i = 0
}
grams.append(Megrez.Unigram(value: theValue, score: theScore))
if !key.contains("_punctuation") { return }
let halfValue = theValue.applyingTransformFW2HW(reverse: false)
if halfValue != theValue {
gramsHW.append(Megrez.Unigram(value: halfValue, score: theScore))
}
}
}
grams.append(contentsOf: gramsHW)
return grams
}
/// CNS UTF8
/// CNS
/// - parameters:
/// - key:
/// - column:
private func factoryCNSFilterThreadFor(key: String) -> String? {
let column = CoreColumn.theDataCNS
if key == "_punctuation_list" { return nil }
var results: [String] = []
// ASCII SQLite
let encryptedKey = Self.cnvPhonabetToASCII(key.replacingOccurrences(of: "'", with: "''"))
let sqlQuery = "SELECT * FROM DATA_MAIN WHERE theKey='\(encryptedKey)';"
Self.querySQL(strStmt: sqlQuery, coreColumn: column) { currentResult in
results.append(currentResult)
}
return results.joined(separator: "\t")
}
/// UTF8
/// - remark:
/// - parameters:
/// - key:
func hasFactoryCoreUnigramsFor(keyArray: [String]) -> Bool {
let column: CoreColumn = isCHS ? .theDataCHS : .theDataCHT
// ASCII SQLite
let encryptedKey = Self.cnvPhonabetToASCII(keyArray.joined(separator: "-").replacingOccurrences(of: "'", with: "''"))
// SELECT EXISTS();
let sqlQuery = "SELECT * FROM DATA_MAIN WHERE theKey='\(encryptedKey)' AND \(column.name) IS NOT NULL"
return Self.hasSQLResult(strStmt: sqlQuery)
}
/// Unigram CNS11643
/// 使
func checkCNSConformation(for unigram: Megrez.Unigram, keyArray: [String]) -> Bool {
guard unigram.value.count == keyArray.count else { return true }
let chars = unigram.value.map(\.description)
for (i, key) in keyArray.enumerated() {
guard !key.hasPrefix("_") else { continue }
guard let matchedCNSResult = factoryCNSFilterThreadFor(key: key) else { continue }
guard matchedCNSResult.contains(chars[i]) else { return false }
}
return true
}
}
private extension LMAssembly.LMInstantiator {
///
///
/// 使 json
///
/// ASCII
/// - parameters:
/// - incoming:
static func cnvPhonabetToASCII(_ incoming: String) -> String {
var strOutput = incoming
if !strOutput.contains("_") {
for entry in Self.dicPhonabet2ASCII {
strOutput = strOutput.replacingOccurrences(of: entry.key, with: entry.value)
}
}
return strOutput
}
static let dicPhonabet2ASCII: [String: String] = [
"": "b", "": "p", "": "m", "": "f", "": "d", "": "t", "": "n", "": "l", "": "g", "": "k", "": "h",
"": "j", "": "q", "": "x", "": "Z", "": "C", "": "S", "": "r", "": "z", "": "c", "": "s", "": "i",
"": "u", "": "v", "": "a", "": "o", "": "e", "": "E", "": "B", "": "P", "": "M", "": "F", "": "D",
"": "T", "": "N", "": "L", "": "R", "ˊ": "2", "ˇ": "3", "ˋ": "4", "˙": "5",
]
///
///
/// ASCII
/// - parameters:
/// - incoming:
static func restorePhonabetFromASCII(_ incoming: String) -> String {
var strOutput = incoming
if !strOutput.contains("_") {
for entry in Self.dicPhonabet4ASCII {
strOutput = strOutput.replacingOccurrences(of: entry.key, with: entry.value)
}
}
return strOutput
}
static let dicPhonabet4ASCII: [String: String] = [
"b": "", "p": "", "m": "", "f": "", "d": "", "t": "", "n": "", "l": "", "g": "", "k": "", "h": "",
"j": "", "q": "", "x": "", "Z": "", "C": "", "S": "", "r": "", "z": "", "c": "", "s": "", "i": "",
"u": "", "v": "", "a": "", "o": "", "e": "", "E": "", "B": "", "P": "", "M": "", "F": "", "D": "",
"T": "", "N": "", "L": "", "R": "", "2": "ˊ", "3": "ˇ", "4": "ˋ", "5": "˙",
]
}
public extension LMAssembly.LMInstantiator {
@discardableResult static func connectToTestSQLDB() -> Bool {
Self.connectSQLDB(dbPath: #":memory:"#) && sqlTestCoreLMData.runAsSQLExec(dbPointer: &ptrSQL)
}
}

View File

@ -0,0 +1,60 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import Megrez
public extension LMAssembly.LMInstantiator {
func performUOMObservation(
walkedBefore: [Megrez.Node],
walkedAfter: [Megrez.Node],
cursor: Int,
timestamp: Double,
saveCallback: (() -> Void)? = nil
) {
lmUserOverride.performObservation(
walkedBefore: walkedBefore,
walkedAfter: walkedAfter,
cursor: cursor,
timestamp: timestamp,
saveCallback: saveCallback
)
}
func fetchUOMSuggestion(
currentWalk: [Megrez.Node],
cursor: Int,
timestamp: Double
) -> LMAssembly.OverrideSuggestion {
lmUserOverride.fetchSuggestion(
currentWalk: currentWalk,
cursor: cursor,
timestamp: timestamp
)
}
func loadUOMData(fromURL fileURL: URL? = nil) {
lmUserOverride.loadData(fromURL: fileURL)
}
func saveUOMData(toURL fileURL: URL? = nil) {
lmUserOverride.saveData(toURL: fileURL)
}
func clearUOMData(withURL fileURL: URL? = nil) {
lmUserOverride.clearData(withURL: fileURL)
}
func bleachSpecifiedUOMSuggestions(targets: [String], saveCallback: (() -> Void)? = nil) {
lmUserOverride.bleachSpecifiedSuggestions(targets: targets, saveCallback: saveCallback)
}
func bleachUOMUnigrams(saveCallback: (() -> Void)? = nil) {
lmUserOverride.bleachUnigrams(saveCallback: saveCallback)
}
}

View File

@ -0,0 +1,33 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
public extension LMAssembly {
struct UserDictionarySummarized: Codable {
let isCHS: Bool
let userPhrases: [String: [String]]
let filter: [String: [String]]
let userSymbols: [String: [String]]
let replacements: [String: String]
let associates: [String: [String]]
}
}
public extension LMAssembly.LMInstantiator {
func summarize(all: Bool) -> LMAssembly.UserDictionarySummarized {
LMAssembly.UserDictionarySummarized(
isCHS: isCHS,
userPhrases: lmUserPhrases.dictRepresented,
filter: lmFiltered.dictRepresented,
userSymbols: lmUserSymbols.dictRepresented,
replacements: lmReplacements.dictRepresented,
associates: all ? lmAssociates.dictRepresented : [:]
)
}
}

View File

@ -0,0 +1,123 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
// 使 LMAssembly Tekkon
private typealias LengthSortedDictionary = [Int: [String: String]]
private let mapHanyuPinyinToPhonabets: LengthSortedDictionary = {
let parsed = try? JSONDecoder().decode(LengthSortedDictionary.self, from: jsnHanyuPinyinToMPS.data(using: .utf8) ?? Data([]))
return parsed ?? [:]
}()
extension String {
mutating func convertToPhonabets(newToneOne: String = "") {
if isEmpty || contains("_") || !isNotPureAlphanumerical { return }
let lengths = mapHanyuPinyinToPhonabets.keys.sorted().reversed()
lengths.forEach { length in
mapHanyuPinyinToPhonabets[length]?.forEach { key, value in
self = replacingOccurrences(of: key, with: value)
}
}
self = replacingOccurrences(of: " ", with: newToneOne)
}
}
///
private extension String {
var isNotPureAlphanumerical: Bool {
let regex = ".*[^A-Za-z0-9].*"
let testString = NSPredicate(format: "SELF MATCHES %@", regex)
return testString.evaluate(with: self)
}
}
private let jsnHanyuPinyinToMPS = #"""
{
"1":{"1":" ","2":"ˊ","3":"ˇ","4":"ˋ","5":"˙","a":"","e":"","o":"","q":""},
"2":{"ai":"","an":"","ao":"","ba":"ㄅㄚ","bi":"ㄅㄧ","bo":"ㄅㄛ","bu":"ㄅㄨ",
"ca":"ㄘㄚ","ce":"ㄘㄜ","ci":"","cu":"ㄘㄨ","da":"ㄉㄚ","de":"ㄉㄜ","di":"ㄉㄧ",
"du":"ㄉㄨ","eh":"","ei":"","en":"","er":"","fa":"ㄈㄚ","fo":"ㄈㄛ",
"fu":"ㄈㄨ","ga":"ㄍㄚ","ge":"ㄍㄜ","gi":"ㄍㄧ","gu":"ㄍㄨ","ha":"ㄏㄚ","he":"ㄏㄜ",
"hu":"ㄏㄨ","ji":"ㄐㄧ","ju":"ㄐㄩ","ka":"ㄎㄚ","ke":"ㄎㄜ","ku":"ㄎㄨ","la":"ㄌㄚ",
"le":"ㄌㄜ","li":"ㄌㄧ","lo":"ㄌㄛ","lu":"ㄌㄨ","lv":"ㄌㄩ","ma":"ㄇㄚ","me":"ㄇㄜ",
"mi":"ㄇㄧ","mo":"ㄇㄛ","mu":"ㄇㄨ","na":"ㄋㄚ","ne":"ㄋㄜ","ni":"ㄋㄧ","nu":"ㄋㄨ",
"nv":"ㄋㄩ","ou":"","pa":"ㄆㄚ","pi":"ㄆㄧ","po":"ㄆㄛ","pu":"ㄆㄨ","qi":"ㄑㄧ",
"qu":"ㄑㄩ","re":"ㄖㄜ","ri":"","ru":"ㄖㄨ","sa":"ㄙㄚ","se":"ㄙㄜ","si":"",
"su":"ㄙㄨ","ta":"ㄊㄚ","te":"ㄊㄜ","ti":"ㄊㄧ","tu":"ㄊㄨ","wa":"ㄨㄚ","wo":"ㄨㄛ",
"wu":"","xi":"ㄒㄧ","xu":"ㄒㄩ","ya":"ㄧㄚ","ye":"ㄧㄝ","yi":"","yo":"ㄧㄛ",
"yu":"","za":"ㄗㄚ","ze":"ㄗㄜ","zi":"","zu":"ㄗㄨ"},
"3":{"ang":"","bai":"ㄅㄞ","ban":"ㄅㄢ","bao":"ㄅㄠ","bei":"ㄅㄟ","ben":"ㄅㄣ",
"bie":"ㄅㄧㄝ","bin":"ㄅㄧㄣ","cai":"ㄘㄞ","can":"ㄘㄢ","cao":"ㄘㄠ","cei":"ㄘㄟ",
"cen":"ㄘㄣ","cha":"ㄔㄚ","che":"ㄔㄜ","chi":"","chu":"ㄔㄨ","cou":"ㄘㄡ",
"cui":"ㄘㄨㄟ","cun":"ㄘㄨㄣ","cuo":"ㄘㄨㄛ","dai":"ㄉㄞ","dan":"ㄉㄢ","dao":"ㄉㄠ",
"dei":"ㄉㄟ","den":"ㄉㄣ","dia":"ㄉㄧㄚ","die":"ㄉㄧㄝ","diu":"ㄉㄧㄡ","dou":"ㄉㄡ",
"dui":"ㄉㄨㄟ","dun":"ㄉㄨㄣ","duo":"ㄉㄨㄛ","eng":"","fan":"ㄈㄢ","fei":"ㄈㄟ",
"fen":"ㄈㄣ","fou":"ㄈㄡ","gai":"ㄍㄞ","gan":"ㄍㄢ","gao":"ㄍㄠ","gei":"ㄍㄟ",
"gen":"ㄍㄣ","gin":"ㄍㄧㄣ","gou":"ㄍㄡ","gua":"ㄍㄨㄚ","gue":"ㄍㄨㄜ","gui":"ㄍㄨㄟ",
"gun":"ㄍㄨㄣ","guo":"ㄍㄨㄛ","hai":"ㄏㄞ","han":"ㄏㄢ","hao":"ㄏㄠ","hei":"ㄏㄟ",
"hen":"ㄏㄣ","hou":"ㄏㄡ","hua":"ㄏㄨㄚ","hui":"ㄏㄨㄟ","hun":"ㄏㄨㄣ","huo":"ㄏㄨㄛ",
"jia":"ㄐㄧㄚ","jie":"ㄐㄧㄝ","jin":"ㄐㄧㄣ","jiu":"ㄐㄧㄡ","jue":"ㄐㄩㄝ",
"jun":"ㄐㄩㄣ","kai":"ㄎㄞ","kan":"ㄎㄢ","kao":"ㄎㄠ","ken":"ㄎㄣ","kiu":"ㄎㄧㄡ",
"kou":"ㄎㄡ","kua":"ㄎㄨㄚ","kui":"ㄎㄨㄟ","kun":"ㄎㄨㄣ","kuo":"ㄎㄨㄛ","lai":"ㄌㄞ",
"lan":"ㄌㄢ","lao":"ㄌㄠ","lei":"ㄌㄟ","lia":"ㄌㄧㄚ","lie":"ㄌㄧㄝ","lin":"ㄌㄧㄣ",
"liu":"ㄌㄧㄡ","lou":"ㄌㄡ","lun":"ㄌㄨㄣ","luo":"ㄌㄨㄛ","lve":"ㄌㄩㄝ","mai":"ㄇㄞ",
"man":"ㄇㄢ","mao":"ㄇㄠ","mei":"ㄇㄟ","men":"ㄇㄣ","mie":"ㄇㄧㄝ","min":"ㄇㄧㄣ",
"miu":"ㄇㄧㄡ","mou":"ㄇㄡ","nai":"ㄋㄞ","nan":"ㄋㄢ","nao":"ㄋㄠ","nei":"ㄋㄟ",
"nen":"ㄋㄣ","nie":"ㄋㄧㄝ","nin":"ㄋㄧㄣ","niu":"ㄋㄧㄡ","nou":"ㄋㄡ","nui":"ㄋㄨㄟ",
"nun":"ㄋㄨㄣ","nuo":"ㄋㄨㄛ","nve":"ㄋㄩㄝ","pai":"ㄆㄞ","pan":"ㄆㄢ","pao":"ㄆㄠ",
"pei":"ㄆㄟ","pen":"ㄆㄣ","pia":"ㄆㄧㄚ","pie":"ㄆㄧㄝ","pin":"ㄆㄧㄣ","pou":"ㄆㄡ",
"qia":"ㄑㄧㄚ","qie":"ㄑㄧㄝ","qin":"ㄑㄧㄣ","qiu":"ㄑㄧㄡ","que":"ㄑㄩㄝ",
"qun":"ㄑㄩㄣ","ran":"ㄖㄢ","rao":"ㄖㄠ","ren":"ㄖㄣ","rou":"ㄖㄡ","rui":"ㄖㄨㄟ",
"run":"ㄖㄨㄣ","ruo":"ㄖㄨㄛ","sai":"ㄙㄞ","san":"ㄙㄢ","sao":"ㄙㄠ","sei":"ㄙㄟ",
"sen":"ㄙㄣ","sha":"ㄕㄚ","she":"ㄕㄜ","shi":"","shu":"ㄕㄨ","sou":"ㄙㄡ",
"sui":"ㄙㄨㄟ","sun":"ㄙㄨㄣ","suo":"ㄙㄨㄛ","tai":"ㄊㄞ","tan":"ㄊㄢ","tao":"ㄊㄠ",
"tie":"ㄊㄧㄝ","tou":"ㄊㄡ","tui":"ㄊㄨㄟ","tun":"ㄊㄨㄣ","tuo":"ㄊㄨㄛ",
"wai":"ㄨㄞ","wan":"ㄨㄢ","wei":"ㄨㄟ","wen":"ㄨㄣ","xia":"ㄒㄧㄚ","xie":"ㄒㄧㄝ",
"xin":"ㄒㄧㄣ","xiu":"ㄒㄧㄡ","xue":"ㄒㄩㄝ","xun":"ㄒㄩㄣ","yai":"ㄧㄞ",
"yan":"ㄧㄢ","yao":"ㄧㄠ","yin":"ㄧㄣ","you":"ㄧㄡ","yue":"ㄩㄝ","yun":"ㄩㄣ",
"zai":"ㄗㄞ","zan":"ㄗㄢ","zao":"ㄗㄠ","zei":"ㄗㄟ","zen":"ㄗㄣ","zha":"ㄓㄚ",
"zhe":"ㄓㄜ","zhi":"","zhu":"ㄓㄨ","zou":"ㄗㄡ","zui":"ㄗㄨㄟ","zun":"ㄗㄨㄣ",
"zuo":"ㄗㄨㄛ"},
"4":{"bang":"ㄅㄤ","beng":"ㄅㄥ","bian":"ㄅㄧㄢ","biao":"ㄅㄧㄠ","bing":"ㄅㄧㄥ",
"cang":"ㄘㄤ","ceng":"ㄘㄥ","chai":"ㄔㄞ","chan":"ㄔㄢ","chao":"ㄔㄠ","chen":"ㄔㄣ",
"chou":"ㄔㄡ","chua":"ㄔㄨㄚ","chui":"ㄔㄨㄟ","chun":"ㄔㄨㄣ","chuo":"ㄔㄨㄛ",
"cong":"ㄘㄨㄥ","cuan":"ㄘㄨㄢ","dang":"ㄉㄤ","deng":"ㄉㄥ","dian":"ㄉㄧㄢ",
"diao":"ㄉㄧㄠ","ding":"ㄉㄧㄥ","dong":"ㄉㄨㄥ","duan":"ㄉㄨㄢ","fang":"ㄈㄤ",
"feng":"ㄈㄥ","fiao":"ㄈㄧㄠ","fong":"ㄈㄨㄥ","gang":"ㄍㄤ","geng":"ㄍㄥ",
"giao":"ㄍㄧㄠ","gong":"ㄍㄨㄥ","guai":"ㄍㄨㄞ","guan":"ㄍㄨㄢ","hang":"ㄏㄤ",
"heng":"ㄏㄥ","hong":"ㄏㄨㄥ","huai":"ㄏㄨㄞ","huan":"ㄏㄨㄢ","jian":"ㄐㄧㄢ",
"jiao":"ㄐㄧㄠ","jing":"ㄐㄧㄥ","juan":"ㄐㄩㄢ","kang":"ㄎㄤ","keng":"ㄎㄥ",
"kong":"ㄎㄨㄥ","kuai":"ㄎㄨㄞ","kuan":"ㄎㄨㄢ","lang":"ㄌㄤ","leng":"ㄌㄥ",
"lian":"ㄌㄧㄢ","liao":"ㄌㄧㄠ","ling":"ㄌㄧㄥ","long":"ㄌㄨㄥ","luan":"ㄌㄨㄢ",
"lvan":"ㄌㄩㄢ","mang":"ㄇㄤ","meng":"ㄇㄥ","mian":"ㄇㄧㄢ","miao":"ㄇㄧㄠ",
"ming":"ㄇㄧㄥ","nang":"ㄋㄤ","neng":"ㄋㄥ","nian":"ㄋㄧㄢ","niao":"ㄋㄧㄠ",
"ning":"ㄋㄧㄥ","nong":"ㄋㄨㄥ","nuan":"ㄋㄨㄢ","pang":"ㄆㄤ","peng":"ㄆㄥ",
"pian":"ㄆㄧㄢ","piao":"ㄆㄧㄠ","ping":"ㄆㄧㄥ","qian":"ㄑㄧㄢ","qiao":"ㄑㄧㄠ",
"qing":"ㄑㄧㄥ","quan":"ㄑㄩㄢ","rang":"ㄖㄤ","reng":"ㄖㄥ","rong":"ㄖㄨㄥ",
"ruan":"ㄖㄨㄢ","sang":"ㄙㄤ","seng":"ㄙㄥ","shai":"ㄕㄞ","shan":"ㄕㄢ",
"shao":"ㄕㄠ","shei":"ㄕㄟ","shen":"ㄕㄣ","shou":"ㄕㄡ","shua":"ㄕㄨㄚ",
"shui":"ㄕㄨㄟ","shun":"ㄕㄨㄣ","shuo":"ㄕㄨㄛ","song":"ㄙㄨㄥ","suan":"ㄙㄨㄢ",
"tang":"ㄊㄤ","teng":"ㄊㄥ","tian":"ㄊㄧㄢ","tiao":"ㄊㄧㄠ","ting":"ㄊㄧㄥ",
"tong":"ㄊㄨㄥ","tuan":"ㄊㄨㄢ","wang":"ㄨㄤ","weng":"ㄨㄥ","xian":"ㄒㄧㄢ",
"xiao":"ㄒㄧㄠ","xing":"ㄒㄧㄥ","xuan":"ㄒㄩㄢ","yang":"ㄧㄤ","ying":"ㄧㄥ",
"yong":"ㄩㄥ","yuan":"ㄩㄢ","zang":"ㄗㄤ","zeng":"ㄗㄥ","zhai":"ㄓㄞ",
"zhan":"ㄓㄢ","zhao":"ㄓㄠ","zhei":"ㄓㄟ","zhen":"ㄓㄣ","zhou":"ㄓㄡ",
"zhua":"ㄓㄨㄚ","zhui":"ㄓㄨㄟ","zhun":"ㄓㄨㄣ","zhuo":"ㄓㄨㄛ",
"zong":"ㄗㄨㄥ","zuan":"ㄗㄨㄢ"},
"5":{"biang":"ㄅㄧㄤ","chang":"ㄔㄤ","cheng":"ㄔㄥ","chong":"ㄔㄨㄥ","chuai":"ㄔㄨㄞ",
"chuan":"ㄔㄨㄢ","duang":"ㄉㄨㄤ","guang":"ㄍㄨㄤ","huang":"ㄏㄨㄤ","jiang":"ㄐㄧㄤ",
"jiong":"ㄐㄩㄥ","kiang":"ㄎㄧㄤ","kuang":"ㄎㄨㄤ","liang":"ㄌㄧㄤ","niang":"ㄋㄧㄤ",
"qiang":"ㄑㄧㄤ","qiong":"ㄑㄩㄥ","shang":"ㄕㄤ","sheng":"ㄕㄥ","shuai":"ㄕㄨㄞ",
"shuan":"ㄕㄨㄢ","xiang":"ㄒㄧㄤ","xiong":"ㄒㄩㄥ","zhang":"ㄓㄤ","zheng":"ㄓㄥ",
"zhong":"ㄓㄨㄥ","zhuai":"ㄓㄨㄞ","zhuan":"ㄓㄨㄢ"},
"6":{"chuang":"ㄔㄨㄤ","shuang":"ㄕㄨㄤ","zhuang":"ㄓㄨㄤ"}
}
"""#

View File

@ -7,13 +7,11 @@
// requirements defined in MIT License.
import Megrez
import PinyinPhonaConverter
import Shared
public extension vChewingLM {
@frozen struct LMAssociates {
extension LMAssembly {
struct LMAssociates {
public private(set) var filePath: String?
var rangeMap: [String: [(Range<String.Index>, Int)]] = [:]
var rangeMap: [String: [(Range<String.Index>, Int)]] = [:] // Range index
var strData: String = ""
public var count: Int { rangeMap.count }
@ -48,8 +46,8 @@ public extension vChewingLM {
replaceData(textData: rawStrData)
} catch {
filePath = oldPath
vCLog("\(error)")
vCLog("↑ Exception happened when reading data at: \(path).")
vCLMLog("\(error)")
vCLMLog("↑ Exception happened when reading data at: \(path).")
return false
}
@ -93,28 +91,21 @@ public extension vChewingLM {
do {
try strData.write(toFile: filePath, atomically: true, encoding: .utf8)
} catch {
vCLog("Failed to save current database to: \(filePath)")
vCLMLog("Failed to save current database to: \(filePath)")
}
}
public func valuesFor(pair: Megrez.KeyValuePaired) -> [String] {
var pairs: [String] = []
if let arrRangeRecords: [(Range<String.Index>, Int)] = rangeMap[pair.toNGramKey] {
for (netaRange, index) in arrRangeRecords {
let availableResults = [rangeMap[pair.toNGramKey], rangeMap[pair.value]].compactMap { $0 }
availableResults.forEach { arrRangeRecords in
arrRangeRecords.forEach { netaRange, index in
let neta = strData[netaRange].split(separator: " ")
let theValue: String = .init(neta[index])
pairs.append(theValue)
}
}
if let arrRangeRecords: [(Range<String.Index>, Int)] = rangeMap[pair.value] {
for (netaRange, index) in arrRangeRecords {
let neta = strData[netaRange].split(separator: " ")
let theValue: String = .init(neta[index])
pairs.append(theValue)
}
}
var set = Set<String>()
return pairs.filter { set.insert($0).inserted }
return pairs.deduplicated
}
public func hasValuesFor(pair: Megrez.KeyValuePaired) -> Bool {
@ -123,3 +114,17 @@ public extension vChewingLM {
}
}
}
extension LMAssembly.LMAssociates {
var dictRepresented: [String: [String]] {
var result = [String: [String]]()
rangeMap.forEach { key, arrRangeRecords in
arrRangeRecords.forEach { netaRange, index in
let neta = strData[netaRange].split(separator: " ")
let theValue: String = .init(neta[index])
result[key, default: []].append(theValue)
}
}
return result
}
}

View File

@ -10,11 +10,10 @@
import Foundation
import LineReader
import Megrez
import Shared
public extension vChewingLM {
extension LMAssembly {
/// 便使
@frozen struct LMCassette {
struct LMCassette {
public private(set) var filePath: String?
public private(set) var nameShort: String = ""
public private(set) var nameENG: String = ""
@ -26,6 +25,7 @@ public extension vChewingLM {
public private(set) var selectionKeys: String = ""
public private(set) var endKeys: [String] = []
public private(set) var wildcardKey: String = ""
public private(set) var keysToDirectlyCommit: String = ""
public private(set) var keyNameMap: [String: String] = [:]
public private(set) var quickDefMap: [String: String] = [:]
public private(set) var charDefMap: [String: [String]] = [:]
@ -39,311 +39,278 @@ public extension vChewingLM {
public private(set) var areCandidateKeysShiftHeld: Bool = false
public private(set) var supplyQuickResults: Bool = false
public private(set) var supplyPartiallyMatchedResults: Bool = false
/// 西
private static let fscale = 2.7
public var candidateKeysValidator: (String) -> Bool = { _ in false }
/// 西 - NORM
private var norm = 0.0
}
}
///
public var wildcard: String { wildcardKey.isEmpty ? "" : wildcardKey }
/// charDef
public var count: Int { charDefMap.count }
///
public var isLoaded: Bool { !charDefMap.isEmpty }
/// 使
public var allowedKeys: [String] { Array(keyNameMap.keys + [" "]).deduplicated }
///
public func convertKeyToDisplay(char: String) -> String {
keyNameMap[char] ?? char
}
extension LMAssembly.LMCassette {
/// 西 - fscale
private static let fscale = 2.7
///
var wildcard: String { wildcardKey.isEmpty ? "" : wildcardKey }
/// charDef
var count: Int { charDefMap.count }
///
var isLoaded: Bool { !charDefMap.isEmpty }
/// 使
var allowedKeys: [String] { Array(keyNameMap.keys + [" "]).deduplicated }
///
func convertKeyToDisplay(char: String) -> String {
keyNameMap[char] ?? char
}
/// CIN
/// - Note:
/// - `%gen_inp` `%ename` cin
/// - `%ename` `%cname` CJK
/// `%sname` `%intlname`
/// - `%encoding` Swift UTF-8
/// - `%selkey`
/// - `%endkey`
/// - `%wildcardkey`
/// - `%nullcandidate` `%quick`
/// - `%keyname begin` `%keyname end` Swift
/// - `%quick begin` `%quick end` value
/// - `%chardef begin` `%chardef end`
/// - `%symboldef begin` `%symboldef end`
/// - `%octagram begin` `%octagram end`
///
/// - Parameter path:
/// - Returns:
@discardableResult public mutating func open(_ path: String) -> Bool {
if isLoaded { return false }
let oldPath = filePath
filePath = nil
if FileManager.default.fileExists(atPath: path) {
do {
guard let fileHandle = FileHandle(forReadingAtPath: path) else {
throw FileErrors.fileHandleError("")
/// CIN
/// - Note:
/// - `%gen_inp` `%ename` cin
/// - `%ename` `%cname` CJK
/// `%sname` `%intlname`
/// - `%encoding` Swift UTF-8
/// - `%selkey`
/// - `%endkey`
/// - `%wildcardkey`
/// - `%nullcandidate` `%quick`
/// - `%keyname begin` `%keyname end` Swift
/// - `%quick begin` `%quick end` value
/// - `%chardef begin` `%chardef end`
/// - `%symboldef begin` `%symboldef end`
/// - `%octagram begin` `%octagram end`
///
/// - Parameter path:
/// - Returns:
@discardableResult mutating func open(_ path: String) -> Bool {
if isLoaded { return false }
let oldPath = filePath
filePath = nil
if FileManager.default.fileExists(atPath: path) {
do {
guard let fileHandle = FileHandle(forReadingAtPath: path) else {
throw LMAssembly.FileErrors.fileHandleError("")
}
let lineReader = try LineReader(file: fileHandle)
var theMaxKeyLength = 1
var loadingKeys = false
var loadingQuickSets = false {
willSet {
supplyQuickResults = true
if !newValue, quickDefMap.keys.contains(wildcardKey) { wildcardKey = "" }
}
let lineReader = try LineReader(file: fileHandle)
var theMaxKeyLength = 1
var loadingKeys = false
var loadingQuickSets = false
var loadingCharDefinitions = false
var loadingSymbolDefinitions = false
var loadingOctagramData = false
var keysUsedInCharDef: Set<String> = .init()
for strLine in lineReader {
if strLine.starts(with: "%keyname") {
if !loadingKeys, strLine.contains("begin") { loadingKeys = true }
if loadingKeys, strLine.contains("end") { loadingKeys = false }
}
}
var loadingCharDefinitions = false {
willSet {
if !newValue, charDefMap.keys.contains(wildcardKey) { wildcardKey = "" }
}
}
var loadingSymbolDefinitions = false {
willSet {
if !newValue, symbolDefMap.keys.contains(wildcardKey) { wildcardKey = "" }
}
}
var loadingOctagramData = false
var keysUsedInCharDef: Set<String> = .init()
for strLine in lineReader {
let isTabDelimiting = strLine.contains("\t")
let cells = isTabDelimiting ? strLine.split(separator: "\t") : strLine.split(separator: " ")
guard cells.count >= 1 else { continue }
let strFirstCell = cells[0].trimmingCharacters(in: .newlines)
let strSecondCell = cells.count >= 2 ? cells[1].trimmingCharacters(in: .newlines) : nil
//
if strLine.first == "%", strFirstCell != "%" {
// %flag_disp_partial_match
if strLine == "%flag_disp_partial_match" {
supplyPartiallyMatchedResults = true
supplyQuickResults = true
}
// %quick
if strLine.starts(with: "%quick") {
supplyQuickResults = true
if !loadingQuickSets, strLine.contains("begin") {
loadingQuickSets = true
}
if loadingQuickSets, strLine.contains("end") {
loadingQuickSets = false
if quickDefMap.keys.contains(wildcardKey) { wildcardKey = "" }
}
}
// %chardef
if strLine.starts(with: "%chardef") {
if !loadingCharDefinitions, strLine.contains("begin") {
loadingCharDefinitions = true
}
if loadingCharDefinitions, strLine.contains("end") {
loadingCharDefinitions = false
if charDefMap.keys.contains(wildcardKey) { wildcardKey = "" }
}
}
// %symboldef
if strLine.starts(with: "%symboldef") {
if !loadingSymbolDefinitions, strLine.contains("begin") {
loadingSymbolDefinitions = true
}
if loadingSymbolDefinitions, strLine.contains("end") {
loadingSymbolDefinitions = false
if symbolDefMap.keys.contains(wildcardKey) { wildcardKey = "" }
}
}
// %octagram
if strLine.starts(with: "%octagram") {
if !loadingOctagramData, strLine.contains("begin") {
loadingOctagramData = true
}
if loadingOctagramData, strLine.contains("end") {
loadingOctagramData = false
}
}
// Start data parsing.
let cells: [String.SubSequence] =
strLine.contains("\t") ? strLine.split(separator: "\t") : strLine.split(separator: " ")
guard cells.count >= 2 else { continue }
let strFirstCell = cells[0].trimmingCharacters(in: .newlines)
let strSecondCell = cells[1].trimmingCharacters(in: .newlines)
if loadingKeys, !cells[0].starts(with: "%keyname") {
keyNameMap[strFirstCell] = cells[1].trimmingCharacters(in: .newlines)
} else if loadingQuickSets, !strLine.starts(with: "%quick") {
theMaxKeyLength = max(theMaxKeyLength, cells[0].count)
quickDefMap[strFirstCell, default: .init()].append(strSecondCell)
} else if loadingCharDefinitions, !loadingSymbolDefinitions,
!strLine.starts(with: "%chardef"), !strLine.starts(with: "%symboldef")
{
theMaxKeyLength = max(theMaxKeyLength, cells[0].count)
charDefMap[strFirstCell, default: []].append(strSecondCell)
if strFirstCell.count > 1 {
strFirstCell.map(\.description).forEach { keyChar in
keysUsedInCharDef.insert(keyChar.description)
}
}
reverseLookupMap[strSecondCell, default: []].append(strFirstCell)
var keyComps = strFirstCell.map(\.description)
while !keyComps.isEmpty {
keyComps.removeLast()
charDefWildcardMap[keyComps.joined() + wildcard, default: []].append(strSecondCell)
}
} else if loadingSymbolDefinitions, !strLine.starts(with: "%chardef"), !strLine.starts(with: "%symboldef") {
theMaxKeyLength = max(theMaxKeyLength, cells[0].count)
symbolDefMap[strFirstCell, default: []].append(strSecondCell)
reverseLookupMap[strSecondCell, default: []].append(strFirstCell)
} else if loadingOctagramData, !strLine.starts(with: "%octagram") {
guard let countValue = Int(cells[1]) else { continue }
switch cells.count {
case 2: octagramMap[strFirstCell] = countValue
case 3: octagramDividedMap[strFirstCell] = (countValue, cells[2].trimmingCharacters(in: .newlines))
default: break
}
norm += Self.fscale ** (Double(cells[0].count) / 3.0 - 1.0) * Double(countValue)
}
guard !loadingKeys, !loadingQuickSets, !loadingCharDefinitions, !loadingOctagramData else { continue }
if nameENG.isEmpty, strLine.starts(with: "%ename ") {
for neta in cells[1].components(separatedBy: ";") {
guard let strSecondCell = strSecondCell else { continue }
processTags: switch strFirstCell {
case "%keyname" where strSecondCell == "begin": loadingKeys = true
case "%keyname" where strSecondCell == "end": loadingKeys = false
case "%quick" where strSecondCell == "begin": loadingQuickSets = true
case "%quick" where strSecondCell == "end": loadingQuickSets = false
case "%chardef" where strSecondCell == "begin": loadingCharDefinitions = true
case "%chardef" where strSecondCell == "end": loadingCharDefinitions = false
case "%symboldef" where strSecondCell == "begin": loadingSymbolDefinitions = true
case "%symboldef" where strSecondCell == "end": loadingSymbolDefinitions = false
case "%octagram" where strSecondCell == "begin": loadingOctagramData = true
case "%octagram" where strSecondCell == "end": loadingOctagramData = false
case "%ename" where nameENG.isEmpty:
parseSubCells: for neta in strSecondCell.components(separatedBy: ";") {
let subNetaGroup = neta.components(separatedBy: ":")
if subNetaGroup.count == 2, subNetaGroup[1].contains("en") {
nameENG = String(subNetaGroup[0])
break
}
guard subNetaGroup.count == 2, subNetaGroup[1].contains("en") else { continue }
nameENG = String(subNetaGroup[0])
break parseSubCells
}
if nameENG.isEmpty { nameENG = strSecondCell }
}
if nameIntl.isEmpty, strLine.starts(with: "%intlname ") {
nameIntl = strSecondCell.replacingOccurrences(of: "_", with: " ")
}
if nameCJK.isEmpty, strLine.starts(with: "%cname ") { nameCJK = strSecondCell }
if nameShort.isEmpty, strLine.starts(with: "%sname ") { nameShort = strSecondCell }
if nullCandidate.isEmpty, strLine.starts(with: "%nullcandidate ") { nullCandidate = strSecondCell }
if selectionKeys.isEmpty, strLine.starts(with: "%selkey ") {
selectionKeys = cells[1].map(\.description).deduplicated.joined()
}
if endKeys.isEmpty, strLine.starts(with: "%endkey ") {
endKeys = cells[1].map(\.description).deduplicated
}
if wildcardKey.isEmpty, strLine.starts(with: "%wildcardkey ") {
wildcardKey = cells[1].first?.description ?? ""
guard nameENG.isEmpty else { break processTags }
nameENG = strSecondCell
case "%intlname" where nameIntl.isEmpty: nameIntl = strSecondCell.replacingOccurrences(of: "_", with: " ")
case "%cname" where nameCJK.isEmpty: nameCJK = strSecondCell
case "%sname" where nameShort.isEmpty: nameShort = strSecondCell
case "%nullcandidate" where nullCandidate.isEmpty: nullCandidate = strSecondCell
case "%selkey" where selectionKeys.isEmpty: selectionKeys = strSecondCell.map(\.description).deduplicated.joined()
case "%endkey" where endKeys.isEmpty: endKeys = strSecondCell.map(\.description).deduplicated
case "%wildcardkey" where wildcardKey.isEmpty: wildcardKey = strSecondCell.first?.description ?? ""
case "%keys_to_directly_commit" where keysToDirectlyCommit.isEmpty: keysToDirectlyCommit = strSecondCell
default: break processTags
}
continue
}
// Post process.
if CandidateKey.validate(keys: selectionKeys) != nil { selectionKeys = "1234567890" }
if !keysUsedInCharDef.intersection(selectionKeys.map(\.description)).isEmpty {
areCandidateKeysShiftHeld = true
//
guard let strSecondCell = strSecondCell else { continue }
if loadingKeys {
keyNameMap[strFirstCell] = strSecondCell.trimmingCharacters(in: .newlines)
} else if loadingQuickSets {
theMaxKeyLength = max(theMaxKeyLength, cells[0].count)
quickDefMap[strFirstCell, default: .init()].append(strSecondCell)
} else if loadingCharDefinitions, !loadingSymbolDefinitions {
theMaxKeyLength = max(theMaxKeyLength, cells[0].count)
charDefMap[strFirstCell, default: []].append(strSecondCell)
if strFirstCell.count > 1 {
strFirstCell.map(\.description).forEach { keyChar in
keysUsedInCharDef.insert(keyChar.description)
}
}
reverseLookupMap[strSecondCell, default: []].append(strFirstCell)
var keyComps = strFirstCell.map(\.description)
while !keyComps.isEmpty {
keyComps.removeLast()
charDefWildcardMap[keyComps.joined() + wildcard, default: []].append(strSecondCell)
}
} else if loadingSymbolDefinitions {
theMaxKeyLength = max(theMaxKeyLength, cells[0].count)
symbolDefMap[strFirstCell, default: []].append(strSecondCell)
reverseLookupMap[strSecondCell, default: []].append(strFirstCell)
} else if loadingOctagramData {
guard let countValue = Int(strSecondCell) else { continue }
switch cells.count {
case 2: octagramMap[strFirstCell] = countValue
case 3: octagramDividedMap[strFirstCell] = (countValue, cells[2].trimmingCharacters(in: .newlines))
default: break
}
norm += Self.fscale ** (Double(cells[0].count) / 3.0 - 1.0) * Double(countValue)
}
maxKeyLength = theMaxKeyLength
keyNameMap[wildcardKey] = keyNameMap[wildcardKey] ?? ""
filePath = path
return true
} catch {
vCLog("CIN Loading Failed: File Access Error.")
}
// Post process.
// Package 便 J / K
//
if !candidateKeysValidator(selectionKeys) { selectionKeys = "1234567890" }
if !keysUsedInCharDef.intersection(selectionKeys.map(\.description)).isEmpty {
areCandidateKeysShiftHeld = true
}
maxKeyLength = theMaxKeyLength
keyNameMap[wildcardKey] = keyNameMap[wildcardKey] ?? ""
filePath = path
return true
} catch {
vCLMLog("CIN Loading Failed: File Access Error.")
}
} else {
vCLMLog("CIN Loading Failed: File Missing.")
}
filePath = oldPath
return false
}
mutating func clear() {
self = .init()
}
func quickSetsFor(key: String) -> String? {
guard !key.isEmpty else { return nil }
var result = [String]()
if let specifiedResult = quickDefMap[key], !specifiedResult.isEmpty {
result.append(contentsOf: specifiedResult.map(\.description))
}
if supplyQuickResults, result.isEmpty {
if supplyPartiallyMatchedResults {
let fetched = charDefMap.compactMap {
$0.key.starts(with: key) ? $0 : nil
}.stableSort {
$0.key.count < $1.key.count
}.flatMap(\.value).filter {
$0.count == 1
}
result.append(contentsOf: fetched.deduplicated.prefix(selectionKeys.count * 6))
} else {
vCLog("CIN Loading Failed: File Missing.")
let fetched = (charDefMap[key] ?? [String]()).filter { $0.count == 1 }
result.append(contentsOf: fetched.deduplicated.prefix(selectionKeys.count * 6))
}
filePath = oldPath
return false
}
return result.isEmpty ? nil : result.joined(separator: "\t")
}
public mutating func clear() {
filePath = nil
nullCandidate.removeAll()
keyNameMap.removeAll()
quickDefMap.removeAll()
charDefMap.removeAll()
charDefWildcardMap.removeAll()
nameShort.removeAll()
nameENG.removeAll()
nameCJK.removeAll()
selectionKeys.removeAll()
endKeys.removeAll()
reverseLookupMap.removeAll()
octagramMap.removeAll()
octagramDividedMap.removeAll()
wildcardKey.removeAll()
nameIntl.removeAll()
maxKeyLength = 1
norm = 0
///
/// - parameters:
/// - key:
func unigramsFor(key: String) -> [Megrez.Unigram] {
let arrRaw = charDefMap[key]?.deduplicated ?? []
var arrRawWildcard: [String] = []
if let arrRawWildcardValues = charDefWildcardMap[key]?.deduplicated,
key.contains(wildcard), key.first?.description != wildcard
{
arrRawWildcard.append(contentsOf: arrRawWildcardValues)
}
public func quickSetsFor(key: String) -> String? {
guard !key.isEmpty else { return nil }
var result = [String]()
if let specifiedResult = quickDefMap[key], !specifiedResult.isEmpty {
result.append(contentsOf: specifiedResult.map(\.description))
}
if supplyQuickResults, result.isEmpty {
if supplyPartiallyMatchedResults {
let fetched = charDefMap.compactMap {
$0.key.starts(with: key) ? $0 : nil
}.stableSort {
$0.key.count < $1.key.count
}.flatMap(\.value).filter {
$0.count == 1
}
result.append(contentsOf: fetched.deduplicated.prefix(selectionKeys.count * 6))
} else {
let fetched = (charDefMap[key] ?? [String]()).filter { $0.count == 1 }
result.append(contentsOf: fetched.deduplicated.prefix(selectionKeys.count * 6))
var arrResults = [Megrez.Unigram]()
var lowestScore: Double = 0
for neta in arrRaw {
let theScore: Double = {
if let freqDataPair = octagramDividedMap[neta], key == freqDataPair.1 {
return calculateWeight(count: freqDataPair.0, phraseLength: neta.count)
} else if let freqData = octagramMap[neta] {
return calculateWeight(count: freqData, phraseLength: neta.count)
}
}
return result.isEmpty ? nil : result.joined(separator: "\t")
return Double(arrResults.count) * -0.001 - 9.5
}()
lowestScore = min(theScore, lowestScore)
arrResults.append(.init(value: neta, score: theScore))
}
///
/// - parameters:
/// - key:
public func unigramsFor(key: String) -> [Megrez.Unigram] {
let arrRaw = charDefMap[key]?.deduplicated ?? []
var arrRawWildcard: [String] = []
if let arrRawWildcardValues = charDefWildcardMap[key]?.deduplicated,
key.contains(wildcard), key.first?.description != wildcard
{
arrRawWildcard.append(contentsOf: arrRawWildcardValues)
}
var arrResults = [Megrez.Unigram]()
var lowestScore: Double = 0
for neta in arrRaw {
let theScore: Double = {
lowestScore = min(-9.5, lowestScore)
if !arrRawWildcard.isEmpty {
for neta in arrRawWildcard {
var theScore: Double = {
if let freqDataPair = octagramDividedMap[neta], key == freqDataPair.1 {
return calculateWeight(count: freqDataPair.0, phraseLength: neta.count)
} else if let freqData = octagramMap[neta] {
return calculateWeight(count: freqData, phraseLength: neta.count)
}
return Double(arrResults.count) * -0.001 - 9.5
return Double(arrResults.count) * -0.001 - 9.7
}()
lowestScore = min(theScore, lowestScore)
theScore += lowestScore
arrResults.append(.init(value: neta, score: theScore))
}
lowestScore = min(-9.5, lowestScore)
if !arrRawWildcard.isEmpty {
for neta in arrRawWildcard {
var theScore: Double = {
if let freqDataPair = octagramDividedMap[neta], key == freqDataPair.1 {
return calculateWeight(count: freqDataPair.0, phraseLength: neta.count)
} else if let freqData = octagramMap[neta] {
return calculateWeight(count: freqData, phraseLength: neta.count)
}
return Double(arrResults.count) * -0.001 - 9.7
}()
theScore += lowestScore
arrResults.append(.init(value: neta, score: theScore))
}
}
return arrResults
}
return arrResults
}
///
/// - parameters:
/// - key:
public func hasUnigramsFor(key: String) -> Bool {
charDefMap[key] != nil
|| (charDefWildcardMap[key] != nil && key.contains(wildcard) && key.first?.description != wildcard)
}
// MARK: - Private Functions.
private func calculateWeight(count theCount: Int, phraseLength: Int) -> Double {
var weight: Double = 0
switch theCount {
case -2: //
weight = -13
case -1: //
weight = -13
case 0: //
weight = log10(
Self.fscale ** (Double(phraseLength) / 3.0 - 1.0) * 0.25 / norm)
default:
weight = log10(
Self.fscale ** (Double(phraseLength) / 3.0 - 1.0)
* Double(theCount) / norm
)
}
return weight
///
/// - parameters:
/// - key:
func hasUnigramsFor(key: String) -> Bool {
charDefMap[key] != nil
|| (charDefWildcardMap[key] != nil && key.contains(wildcard) && key.first?.description != wildcard)
}
// MARK: - Private Functions.
private func calculateWeight(count theCount: Int, phraseLength: Int) -> Double {
var weight: Double = 0
switch theCount {
case -2: //
weight = -13
case -1: //
weight = -13
case 0: //
weight = log10(
Self.fscale ** (Double(phraseLength) / 3.0 - 1.0) * 0.25 / norm)
default:
weight = log10(
Self.fscale ** (Double(phraseLength) / 3.0 - 1.0)
* Double(theCount) / norm
)
}
return weight
}
}

View File

@ -7,15 +7,13 @@
// requirements defined in MIT License.
import Megrez
import PinyinPhonaConverter
import Shared
public extension vChewingLM {
extension LMAssembly {
/// LMCore LMCoreEX range
/// range strData
/// C++ ParselessLM Swift
/// For
@frozen struct LMCoreEX {
struct LMCoreEX {
public private(set) var filePath: String?
/// 便 strData
var rangeMap: [String: [Range<String.Index>]] = [:]
@ -81,8 +79,8 @@ public extension vChewingLM {
replaceData(textData: rawStrData)
} catch {
filePath = oldPath
vCLog("\(error)")
vCLog("↑ Exception happened when reading data at: \(path).")
vCLMLog("\(error)")
vCLMLog("↑ Exception happened when reading data at: \(path).")
return false
}
@ -133,7 +131,7 @@ public extension vChewingLM {
}
try dataToWrite.write(toFile: filePath, atomically: true, encoding: .utf8)
} catch {
vCLog("Failed to save current database to: \(filePath)")
vCLMLog("Failed to save current database to: \(filePath)")
}
}
@ -150,7 +148,7 @@ public extension vChewingLM {
strDump += addline
}
}
vCLog(strDump)
vCLMLog(strDump)
}
/// strData
@ -186,3 +184,15 @@ public extension vChewingLM {
}
}
}
extension LMAssembly.LMCoreEX {
var dictRepresented: [String: [String]] {
var result = [String: [String]]()
rangeMap.forEach { key, arrValueRanges in
result[key, default: []] = arrValueRanges.map { currentRange in
strData[currentRange].description
}
}
return result
}
}

View File

@ -1,236 +0,0 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import Megrez
import Shared
public extension vChewingLM {
/// LMCore LMCoreJSON json
/// mac
/// 使 json
@frozen struct LMCoreJSON {
public private(set) var filePath: String?
/// UTF8
var dataMap: [String: [String]] = [:]
/// LMCoreJSON
var strData: String = ""
///
var shouldReverse = false
/// 使
var allowConsolidation = false
///
var defaultScore: Double = 0
///
var shouldForceDefaultScore = false
///
public var count: Int { dataMap.count }
///
///
/// LMCoreJSON 便
///
/// - parameters:
/// - reverse:
/// - consolidate: 使
/// - defaultScore:
/// - forceDefaultScore:
public init(
reverse: Bool = false, consolidate: Bool = false, defaultScore scoreDefault: Double = 0,
forceDefaultScore: Bool = false
) {
dataMap = [:]
allowConsolidation = consolidate
shouldReverse = reverse
defaultScore = scoreDefault
shouldForceDefaultScore = forceDefaultScore
}
///
public var isLoaded: Bool { !dataMap.isEmpty }
///
/// - parameters:
/// - dictData: URL
public mutating func load(_ dictData: (dict: [String: [String]], path: String)) {
if isLoaded { return }
filePath = dictData.path
dataMap = dictData.dict
}
///
/// - parameters:
/// - path:
@discardableResult public mutating func open(_ path: String) -> Bool {
if isLoaded { return false }
let oldPath = filePath
filePath = nil
do {
let rawData = try Data(contentsOf: URL(fileURLWithPath: path))
if let rawJSON = try? JSONSerialization.jsonObject(with: rawData) as? [String: [String]] {
dataMap = rawJSON
} else {
filePath = oldPath
vCLog("↑ Exception happened when reading JSON file at: \(path).")
return false
}
} catch {
filePath = oldPath
vCLog("↑ Exception happened when reading JSON file at: \(path).")
return false
}
filePath = path
return true
}
///
public mutating func clear() {
filePath = nil
strData.removeAll()
dataMap.removeAll()
}
// MARK: - Advanced features
public func saveData() {
guard let filePath = filePath, let jsonURL = URL(string: filePath) else { return }
do {
try JSONSerialization.data(withJSONObject: dataMap, options: .sortedKeys).write(to: jsonURL)
} catch {
vCLog("Failed to save current database to: \(filePath)")
}
}
/// macOS Console.app
///
///
public func dump() {
var strDump = ""
for entry in dataMap {
let netaSets: [String] = entry.value
let theKey = entry.key
for strNetaSet in netaSets {
let neta = Array(strNetaSet.trimmingCharacters(in: .newlines).components(separatedBy: " ").reversed())
let theValue = neta[0]
var theScore = defaultScore
if neta.count >= 2, !shouldForceDefaultScore {
theScore = .init(String(neta[1])) ?? defaultScore
}
strDump += "\(Self.cnvPhonabetToASCII(theKey)) \(theValue) \(theScore)\n"
}
}
vCLog(strDump)
}
public func getHaninSymbolMenuUnigrams() -> [Megrez.Unigram] {
let key = "_punctuation_list"
var grams: [Megrez.Unigram] = []
guard let arrRangeRecords: [String] = dataMap[Self.cnvPhonabetToASCII(key)] else { return grams }
for strNetaSet in arrRangeRecords {
let neta = Array(strNetaSet.trimmingCharacters(in: .newlines).split(separator: " ").reversed())
let theValue: String = .init(neta[0])
var theScore = defaultScore
if neta.count >= 2, !shouldForceDefaultScore {
theScore = .init(String(neta[1])) ?? defaultScore
}
if theScore > 0 {
theScore *= -1 //
}
grams.append(Megrez.Unigram(value: theValue, score: theScore))
}
return grams
}
/// UTF8
/// - parameters:
/// - key:
public func unigramsFor(key: String) -> [Megrez.Unigram] {
if key == "_punctuation_list" { return [] }
var grams: [Megrez.Unigram] = []
var gramsHW: [Megrez.Unigram] = []
guard let arrRangeRecords: [String] = dataMap[Self.cnvPhonabetToASCII(key)] else { return grams }
for strNetaSet in arrRangeRecords {
let neta = Array(strNetaSet.trimmingCharacters(in: .newlines).split(separator: " ").reversed())
let theValue: String = .init(neta[0])
var theScore = defaultScore
if neta.count >= 2, !shouldForceDefaultScore {
theScore = .init(String(neta[1])) ?? defaultScore
}
if theScore > 0 {
theScore *= -1 //
}
grams.append(Megrez.Unigram(value: theValue, score: theScore))
if !key.contains("_punctuation") { continue }
let halfValue = theValue.applyingTransformFW2HW(reverse: false)
if halfValue != theValue {
gramsHW.append(Megrez.Unigram(value: halfValue, score: theScore))
}
}
grams.append(contentsOf: gramsHW)
return grams
}
///
/// - parameters:
/// - key:
public func hasUnigramsFor(key: String) -> Bool {
dataMap[Self.cnvPhonabetToASCII(key)] != nil
}
///
///
/// 使 json
///
/// ASCII
/// - parameters:
/// - incoming:
public static func cnvPhonabetToASCII(_ incoming: String) -> String {
var strOutput = incoming
if !strOutput.contains("_") {
for entry in Self.dicPhonabet2ASCII {
strOutput = strOutput.replacingOccurrences(of: entry.key, with: entry.value)
}
}
return strOutput
}
///
///
/// ASCII
/// - parameters:
/// - incoming:
public static func restorePhonabetFromASCII(_ incoming: String) -> String {
var strOutput = incoming
if !strOutput.contains("_") {
for entry in Self.dicPhonabet4ASCII {
strOutput = strOutput.replacingOccurrences(of: entry.key, with: entry.value)
}
}
return strOutput
}
// MARK: - Constants
static let dicPhonabet2ASCII: [String: String] = [
"": "b", "": "p", "": "m", "": "f", "": "d", "": "t", "": "n", "": "l", "": "g", "": "k", "": "h",
"": "j", "": "q", "": "x", "": "Z", "": "C", "": "S", "": "r", "": "z", "": "c", "": "s", "": "i",
"": "u", "": "v", "": "a", "": "o", "": "e", "": "E", "": "B", "": "P", "": "M", "": "F", "": "D",
"": "T", "": "N", "": "L", "": "R", "ˊ": "2", "ˇ": "3", "ˋ": "4", "˙": "5",
]
static let dicPhonabet4ASCII: [String: String] = [
"b": "", "p": "", "m": "", "f": "", "d": "", "t": "", "n": "", "l": "", "g": "", "k": "", "h": "",
"j": "", "q": "", "x": "", "Z": "", "C": "", "S": "", "r": "", "z": "", "c": "", "s": "", "i": "",
"u": "", "v": "", "a": "", "o": "", "e": "", "E": "", "B": "", "P": "", "M": "", "F": "", "D": "",
"T": "", "N": "", "L": "", "R": "", "2": "ˊ", "3": "ˇ", "4": "ˋ", "5": "˙",
]
}
}

View File

@ -7,63 +7,36 @@
// requirements defined in MIT License.
import Foundation
import Shared
public extension vChewingLM {
@frozen struct LMPlainBopomofo {
public private(set) var filePath: String?
var dataMap: [String: String] = [:]
extension LMAssembly {
struct LMPlainBopomofo {
@usableFromInline typealias DataMap = [String: [String: String]]
let dataMap: DataMap
public var count: Int { dataMap.count }
public init() {
dataMap = [:]
do {
let rawData = jsnEtenDosSequence.data(using: .utf8) ?? .init([])
let rawJSON = try JSONDecoder().decode([String: [String: String]].self, from: rawData)
dataMap = rawJSON
} catch {
vCLMLog("\(error)")
vCLMLog("↑ Exception happened when parsing raw JSON sequence data from vChewing LMAssembly.")
dataMap = [:]
}
}
public var isLoaded: Bool { !dataMap.isEmpty }
@discardableResult public mutating func open(_ path: String) -> Bool {
if isLoaded { return false }
let oldPath = filePath
filePath = nil
do {
let rawData = try Data(contentsOf: URL(fileURLWithPath: path))
let rawPlist: [String: String] =
try PropertyListSerialization.propertyList(from: rawData, format: nil) as? [String: String] ?? .init()
dataMap = rawPlist
} catch {
filePath = oldPath
vCLog("\(error)")
vCLog("↑ Exception happened when reading data at: \(path).")
return false
}
filePath = path
return true
}
public mutating func clear() {
filePath = nil
dataMap.removeAll()
}
public func saveData() {
guard let filePath = filePath, let plistURL = URL(string: filePath) else { return }
do {
let plistData = try PropertyListSerialization.data(fromPropertyList: dataMap, format: .binary, options: 0)
try plistData.write(to: plistURL)
} catch {
vCLog("Failed to save current database to: \(filePath)")
}
}
public func valuesFor(key: String) -> [String] {
public func valuesFor(key: String, isCHS: Bool) -> [String] {
var pairs: [String] = []
if let arrRangeRecords: String = dataMap[key]?.trimmingCharacters(in: .newlines) {
let subKey = isCHS ? "S" : "T"
if let arrRangeRecords: String = dataMap[key]?[subKey] {
pairs.append(contentsOf: arrRangeRecords.map(\.description))
}
return pairs.deduplicated
//
return pairs
}
public func hasValuesFor(key: String) -> Bool { dataMap.keys.contains(key) }

View File

@ -6,10 +6,8 @@
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Shared
public extension vChewingLM {
@frozen struct LMReplacements {
extension LMAssembly {
struct LMReplacements {
public private(set) var filePath: String?
var rangeMap: [String: Range<String.Index>] = [:]
var strData: String = ""
@ -35,8 +33,8 @@ public extension vChewingLM {
replaceData(textData: rawStrData)
} catch {
filePath = oldPath
vCLog("\(error)")
vCLog("↑ Exception happened when reading data at: \(path).")
vCLMLog("\(error)")
vCLMLog("↑ Exception happened when reading data at: \(path).")
return false
}
@ -72,7 +70,7 @@ public extension vChewingLM {
do {
try strData.write(toFile: filePath, atomically: true, encoding: .utf8)
} catch {
vCLog("Failed to save current database to: \(filePath)")
vCLMLog("Failed to save current database to: \(filePath)")
}
}
@ -81,7 +79,7 @@ public extension vChewingLM {
for entry in rangeMap {
strDump += strData[entry.value] + "\n"
}
vCLog(strDump)
vCLMLog(strDump)
}
public func valuesFor(key: String) -> String {
@ -100,3 +98,13 @@ public extension vChewingLM {
}
}
}
extension LMAssembly.LMReplacements {
var dictRepresented: [String: String] {
var result = [String: String]()
rangeMap.forEach { key, valueRange in
result[key] = strData[valueRange].description
}
return result
}
}

View File

@ -1,76 +0,0 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import Shared
public extension vChewingLM {
@frozen struct LMRevLookup {
public private(set) var dataMap: [String: [String]] = [:]
public private(set) var filePath: String = ""
public init(data dictData: (dict: [String: [String]]?, path: String)) {
guard let theDict = dictData.dict else {
vCLog("↑ Exception happened when reading JSON file at: \(dictData.path).")
return
}
filePath = dictData.path
dataMap = theDict
}
public init(path: String) {
if path.isEmpty { return }
do {
let rawData = try Data(contentsOf: URL(fileURLWithPath: path))
if let rawJSON = try? JSONSerialization.jsonObject(with: rawData) as? [String: [String]] {
dataMap = rawJSON
} else {
vCLog("↑ Exception happened when reading JSON file at: \(path).")
return
}
} catch {
vCLog("↑ Exception happened when reading JSON file at: \(path).")
return
}
filePath = path
}
public func query(with kanji: String) -> [String]? {
guard let resultData = dataMap[kanji] else { return nil }
let resultArray = resultData.compactMap {
let result = restorePhonabetFromASCII($0)
return result.isEmpty ? nil : result
}
return resultArray.isEmpty ? nil : resultArray
}
///
///
/// ASCII
/// - parameters:
/// - incoming:
func restorePhonabetFromASCII(_ incoming: String) -> String {
var strOutput = incoming
if !strOutput.contains("_") {
for entry in Self.dicPhonabet4ASCII {
strOutput = strOutput.replacingOccurrences(of: entry.key, with: entry.value)
}
}
return strOutput
}
// MARK: - Constants
static let dicPhonabet4ASCII: [String: String] = [
"b": "", "p": "", "m": "", "f": "", "d": "", "t": "", "n": "", "l": "", "g": "", "k": "", "h": "",
"j": "", "q": "", "x": "", "Z": "", "C": "", "S": "", "r": "", "z": "", "c": "", "s": "", "i": "",
"u": "", "v": "", "a": "", "o": "", "e": "", "E": "", "B": "", "P": "", "M": "", "F": "", "D": "",
"T": "", "N": "", "L": "", "R": "", "2": "ˊ", "3": "ˇ", "4": "ˋ", "5": "˙",
]
}
}

View File

@ -9,74 +9,41 @@
import Foundation
import Megrez
import Shared
public extension vChewingLM {
// MARK: - Public Types.
public extension LMAssembly {
struct OverrideSuggestion {
public var candidates = [(String, Megrez.Unigram)]()
public var forceHighScoreOverride = false
public var isEmpty: Bool { candidates.isEmpty }
}
}
// MARK: - LMUserOverride Class Definition.
extension LMAssembly {
class LMUserOverride {
// MARK: - Main
var mutCapacity: Int
var mutDecayExponent: Double
var mutLRUList: [KeyObservationPair] = []
var mutLRUMap: [String: KeyObservationPair] = [:]
let kDecayThreshold: Double = 1.0 / 1_048_576.0 //
var fileSaveLocationURL: URL
var fileSaveLocationURL: URL?
public static let kObservedOverrideHalfLife: Double = 3600.0 * 6 // 6
public init(capacity: Int = 500, decayConstant: Double = LMUserOverride.kObservedOverrideHalfLife, dataURL: URL) {
public init(capacity: Int = 500, decayConstant: Double = LMUserOverride.kObservedOverrideHalfLife, dataURL: URL? = nil) {
mutCapacity = max(capacity, 1) // Ensures that this integer value is always > 0.
mutDecayExponent = log(0.5) / decayConstant
fileSaveLocationURL = dataURL
}
public func performObservation(
walkedBefore: [Megrez.Node], walkedAfter: [Megrez.Node],
cursor: Int, timestamp: Double, saveCallback: @escaping () -> Void
) {
//
guard !walkedAfter.isEmpty, !walkedBefore.isEmpty else { return }
guard walkedBefore.totalKeyCount == walkedAfter.totalKeyCount else { return }
//
var actualCursor = 0
guard let currentNode = walkedAfter.findNode(at: cursor, target: &actualCursor) else { return }
// 使
guard currentNode.spanLength <= 3 else { return }
//
guard actualCursor > 0 else { return } //
let currentNodeIndex = actualCursor
actualCursor -= 1
var prevNodeIndex = 0
guard let prevNode = walkedBefore.findNode(at: actualCursor, target: &prevNodeIndex) else { return }
let forceHighScoreOverride: Bool = currentNode.spanLength > prevNode.spanLength
let breakingUp = currentNode.spanLength == 1 && prevNode.spanLength > 1
let targetNodeIndex = breakingUp ? currentNodeIndex : prevNodeIndex
let key: String = vChewingLM.LMUserOverride.formObservationKey(
walkedNodes: walkedAfter, headIndex: targetNodeIndex
)
guard !key.isEmpty else { return }
doObservation(
key: key, candidate: currentNode.currentUnigram.value, timestamp: timestamp,
forceHighScoreOverride: forceHighScoreOverride, saveCallback: { saveCallback() }
)
}
public func fetchSuggestion(
currentWalk: [Megrez.Node], cursor: Int, timestamp: Double
) -> Suggestion {
var headIndex = 0
guard let nodeIter = currentWalk.findNode(at: cursor, target: &headIndex) else { return .init() }
let key = vChewingLM.LMUserOverride.formObservationKey(walkedNodes: currentWalk, headIndex: headIndex)
return getSuggestion(key: key, timestamp: timestamp, headReading: nodeIter.joinedKey())
}
}
}
// MARK: - Private Structures
extension vChewingLM.LMUserOverride {
extension LMAssembly.LMUserOverride {
enum OverrideUnit: CodingKey { case count, timestamp, forceHighScoreOverride }
enum ObservationUnit: CodingKey { case count, overrides }
enum KeyObservationPairUnit: CodingKey { case key, observation }
@ -153,10 +120,52 @@ extension vChewingLM.LMUserOverride {
}
}
// MARK: - Hash and Dehash the entire UOM data, etc.
// MARK: - Internal Methods in LMAssembly.
public extension vChewingLM.LMUserOverride {
func bleachSpecifiedSuggestions(targets: [String], saveCallback: @escaping () -> Void) {
extension LMAssembly.LMUserOverride {
func performObservation(
walkedBefore: [Megrez.Node], walkedAfter: [Megrez.Node],
cursor: Int, timestamp: Double, saveCallback: (() -> Void)? = nil
) {
//
guard !walkedAfter.isEmpty, !walkedBefore.isEmpty else { return }
guard walkedBefore.totalKeyCount == walkedAfter.totalKeyCount else { return }
//
var actualCursor = 0
guard let currentNode = walkedAfter.findNode(at: cursor, target: &actualCursor) else { return }
// 使
guard currentNode.spanLength <= 3 else { return }
//
guard actualCursor > 0 else { return } //
let currentNodeIndex = actualCursor
actualCursor -= 1
var prevNodeIndex = 0
guard let prevNode = walkedBefore.findNode(at: actualCursor, target: &prevNodeIndex) else { return }
let forceHighScoreOverride: Bool = currentNode.spanLength > prevNode.spanLength
let breakingUp = currentNode.spanLength == 1 && prevNode.spanLength > 1
let targetNodeIndex = breakingUp ? currentNodeIndex : prevNodeIndex
let key: String = LMAssembly.LMUserOverride.formObservationKey(
walkedNodes: walkedAfter, headIndex: targetNodeIndex
)
guard !key.isEmpty else { return }
doObservation(
key: key, candidate: currentNode.currentUnigram.value, timestamp: timestamp,
forceHighScoreOverride: forceHighScoreOverride, saveCallback: saveCallback
)
}
func fetchSuggestion(
currentWalk: [Megrez.Node], cursor: Int, timestamp: Double
) -> LMAssembly.OverrideSuggestion {
var headIndex = 0
guard let nodeIter = currentWalk.findNode(at: cursor, target: &headIndex) else { return .init() }
let key = LMAssembly.LMUserOverride.formObservationKey(walkedNodes: currentWalk, headIndex: headIndex)
return getSuggestion(key: key, timestamp: timestamp, headReading: nodeIter.joinedKey())
}
func bleachSpecifiedSuggestions(targets: [String], saveCallback: (() -> Void)? = nil) {
if targets.isEmpty { return }
for neta in mutLRUMap {
for target in targets {
@ -166,82 +175,86 @@ public extension vChewingLM.LMUserOverride {
}
}
resetMRUList()
saveCallback()
saveCallback?() ?? saveData()
}
/// LRU
func bleachUnigrams(saveCallback: @escaping () -> Void) {
func bleachUnigrams(saveCallback: (() -> Void)? = nil) {
for key in mutLRUMap.keys {
if !key.contains("(),()") { continue }
mutLRUMap.removeValue(forKey: key)
}
resetMRUList()
saveCallback()
saveCallback?() ?? saveData()
}
internal func resetMRUList() {
func resetMRUList() {
mutLRUList.removeAll()
for neta in mutLRUMap.reversed() {
mutLRUList.append(neta.value)
}
}
func clearData(withURL fileURL: URL) {
func clearData(withURL fileURL: URL? = nil) {
mutLRUMap = .init()
mutLRUList = .init()
do {
let nullData = "{}"
guard let fileURL = fileURL ?? fileSaveLocationURL else {
throw UOMError(rawValue: "given fileURL is invalid or nil.")
}
try nullData.write(to: fileURL, atomically: false, encoding: .utf8)
} catch {
vCLog("UOM Error: Unable to clear data. Details: \(error)")
vCLMLog("UOM Error: Unable to clear the data in the UOM file. Details: \(error)")
return
}
}
func saveData(toURL fileURL: URL? = nil) {
guard let fileURL: URL = fileURL ?? fileSaveLocationURL else {
vCLMLog("UOM saveData() failed. At least the file Save URL is not set for the current UOM.")
return
}
// 使 JSONSerialization
let encoder = JSONEncoder()
do {
guard let jsonData = try? encoder.encode(mutLRUMap) else { return }
let fileURL: URL = fileURL ?? fileSaveLocationURL
try jsonData.write(to: fileURL, options: .atomic)
} catch {
vCLog("UOM Error: Unable to save data, abort saving. Details: \(error)")
vCLMLog("UOM Error: Unable to save data, abort saving. Details: \(error)")
return
}
}
func loadData(fromURL fileURL: URL) {
func loadData(fromURL fileURL: URL? = nil) {
guard let fileURL: URL = fileURL ?? fileSaveLocationURL else {
vCLMLog("UOM loadData() failed. At least the file Load URL is not set for the current UOM.")
return
}
// 使 JSONSerialization
let decoder = JSONDecoder()
do {
let data = try Data(contentsOf: fileURL, options: .mappedIfSafe)
if ["", "{}"].contains(String(data: data, encoding: .utf8)) { return }
guard let jsonResult = try? decoder.decode([String: KeyObservationPair].self, from: data) else {
vCLog("UOM Error: Read file content type invalid, abort loading.")
vCLMLog("UOM Error: Read file content type invalid, abort loading.")
return
}
mutLRUMap = jsonResult
resetMRUList()
} catch {
vCLog("UOM Error: Unable to read file or parse the data, abort loading. Details: \(error)")
vCLMLog("UOM Error: Unable to read file or parse the data, abort loading. Details: \(error)")
return
}
}
struct Suggestion {
public var candidates = [(String, Megrez.Unigram)]()
public var forceHighScoreOverride = false
public var isEmpty: Bool { candidates.isEmpty }
}
}
// MARK: - Private Methods
// MARK: - Other Non-Public Internal Methods
extension vChewingLM.LMUserOverride {
extension LMAssembly.LMUserOverride {
func doObservation(
key: String, candidate: String, timestamp: Double, forceHighScoreOverride: Bool,
saveCallback: @escaping () -> Void
saveCallback: (() -> Void)?
) {
guard mutLRUMap[key] != nil else {
var observation: Observation = .init()
@ -257,8 +270,8 @@ extension vChewingLM.LMUserOverride {
mutLRUMap.removeValue(forKey: mutLRUList[mutLRUList.endIndex - 1].key)
mutLRUList.removeLast()
}
vCLog("UOM: Observation finished with new observation: \(key)")
saveCallback()
vCLMLog("UOM: Observation finished with new observation: \(key)")
saveCallback?() ?? saveData()
return
}
// decayCallback
@ -268,12 +281,12 @@ extension vChewingLM.LMUserOverride {
)
mutLRUList.insert(theNeta, at: 0)
mutLRUMap[key] = theNeta
vCLog("UOM: Observation finished with existing observation: \(key)")
saveCallback()
vCLMLog("UOM: Observation finished with existing observation: \(key)")
saveCallback?() ?? saveData()
}
}
func getSuggestion(key: String, timestamp: Double, headReading: String) -> Suggestion {
func getSuggestion(key: String, timestamp: Double, headReading: String) -> LMAssembly.OverrideSuggestion {
guard !key.isEmpty, let kvPair = mutLRUMap[key] else { return .init() }
let observation: Observation = kvPair.observation
var candidates: [(String, Megrez.Unigram)] = .init()
@ -386,3 +399,10 @@ extension vChewingLM.LMUserOverride {
return result
}
}
struct UOMError: LocalizedError {
var rawValue: String
var errorDescription: String? {
NSLocalizedString("rawValue", comment: "")
}
}

View File

@ -0,0 +1,62 @@
// libTaBE (http://sourceforge.net/projects/libtabe/)
// (2002 ). 1999 Pai-Hsiang Hsiao BSD
import Foundation
let sqlTestCoreLMData: String = """
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE DATA_MAIN (
theKey TEXT NOT NULL,
theDataCHS TEXT,
theDataCHT TEXT,
theDataCNS TEXT,
theDataMISC TEXT,
theDataSYMB TEXT,
theDataCHEW TEXT,
PRIMARY KEY (theKey)
) WITHOUT ROWID;
INSERT INTO DATA_MAIN VALUES('CuP-niF2-bi','-7.375 \t-7.399 ','-7.375 \t-7.399 ','','','🌳🆕🐝','');
INSERT INTO DATA_MAIN VALUES('Ze4','-6.0 ','-6.0 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('Ze4-iN4','-6.0 ','-6.0 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('ZuL','-5.809297 \t-99.0 \t-9.87758 \t-9.685671 \t-99.0 \t-99.0 ','-5.809297 \t-99.0 \t-9.87758 \t-9.685671 \t-99.0 \t-99.0 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('_punctuation_list',' \t\t\t',' \t\t\t','','','','');
INSERT INTO DATA_MAIN VALUES('de5','-3.516024 \t-7.427179 ','-3.516024 \t-7.427179 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('di2','-3.516024 ','-3.516024 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('di4','-3.516024 ','-3.516024 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('duP3','-9.544 ','-9.544 ','\t䇏\t𦞙\t謉\t𠡒\t𡑈\t𥫉\t𦞱\t𧫏\t𩛔','','','');
INSERT INTO DATA_MAIN VALUES('uP','-6.0 ','-6.0 ',NULL,NULL,NULL,NULL); /* CNS */
INSERT INTO DATA_MAIN VALUES('uP2','-6.0 ','-6.0 ','-6.0 ',NULL,NULL,NULL); /* CNS */
INSERT INTO DATA_MAIN VALUES('fL','-11.0 🐝','-11.0 🐝',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('gM','-7.171551 \t-11.92872 \t-13.624335 \t-12.390804 ','-7.171551 \t-11.92872 \t-13.624335 \t-12.390804 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('gM-ke-ji4','-9.842421 ','-9.842421 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('guL','-8.381971 \t-8.501463 \t-8.858181 \t-7.877973 \t-7.822167 \t-99.0 \t-99.0 \t-99.0 \t-99.0 \t-99.0 \t-99.0 ','-8.381971 \t-8.501463 \t-8.858181 \t-7.877973 \t-7.822167 \t-99.0 \t-99.0 \t-99.0 \t-99.0 \t-99.0 \t-99.0 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('guL-s','-6.299461 ','-6.299461 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('iN4','-6.0 ','-6.0 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('ji4','-99.0 \t-7.608341 \t-99.0 \t-10.939895 \t-99.0 \t-99.0 \t-9.715317 \t-7.926683 \t-8.373022 \t-10.425662 \t-8.888722 \t-10.204425 \t-99.0 \t-8.450826 \t-12.045357 \t-99.0 \t-9.517568 \t-12.021587 ','-99.0 \t-7.608341 \t-99.0 \t-10.939895 \t-99.0 \t-99.0 \t-9.715317 \t-7.926683 \t-8.373022 \t-10.425662 \t-8.888722 \t-10.204425 \t-99.0 \t-8.450826 \t-12.045357 \t-99.0 \t-9.517568 \t-12.021587 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('ji4-guL','-13.336653 ','-13.336653 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('jiM4','-3.676169 \t-3.24869962 ','-3.676169 \t-3.24869962 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('jiM4-v4','-3.32220565 ','-3.32220565 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('jiN3','-9.164384 \t-8.690941 \t-10.127828 \t-12.492933 ','-9.164384 \t-8.690941 \t-10.127828 \t-12.492933 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('jiN3-jiT','-10.344678 ','-10.344678 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('jiT','-8.034095 \t-7.290109 \t-99.0 \t-10.711079 \t-11.378321 \t-11.07489 \t-99.0 \t-12.784206 ','-8.034095 \t-7.290109 \t-99.0 \t-10.711079 \t-11.378321 \t-11.07489 \t-99.0 \t-12.784206 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('ke','-10.574273 \t-11.504072 \t-10.450457 \t-7.171052 \t-99.0 ','-10.574273 \t-11.504072 \t-10.450457 \t-7.171052 \t-99.0 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('ke-ji4','-6.736613 ','-6.736613 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('ke-ke','-8.0 ','-8.0 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('mi4','-4.6231 ','-4.6231 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('mi4-fL','-11.0 🐝','-11.0 🐝',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('nP-nP','','','','','','');
INSERT INTO DATA_MAIN VALUES('ni3','-6.0 ','-6.0 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('ni3-Ze4','-9.0 ','-9.0 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('niD2','-6.086515 \t-11.336864 \t-11.28574 ','-6.086515 \t-11.336864 \t-11.28574 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('niD2-ZuL','-11.668947 \t-11.373044 ','-11.668947 \t-11.373044 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('s','-9.495858 \t-9.006414 \t-99.0 \t-8.091803 \t-99.0 \t-13.513987 \t-12.259095 ','-9.495858 \t-9.006414 \t-99.0 \t-8.091803 \t-99.0 \t-13.513987 \t-12.259095 ',NULL,NULL,NULL,NULL);
INSERT INTO DATA_MAIN VALUES('v4','-3.30192952 ','-3.30192952 ',NULL,NULL,NULL,NULL);
CREATE TABLE DATA_REV (
theChar TEXT NOT NULL,
theReadings TEXT NOT NULL,
PRIMARY KEY (theChar)
) WITHOUT ROWID;
INSERT INTO DATA_REV VALUES('','huo2\the5\thuo\tduL\the2\the4\thD4\thu2\thuo5\thuo4');
COMMIT;
"""

View File

@ -1,25 +0,0 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
public enum vChewingLM {
enum FileErrors: Error {
case fileHandleError(String)
}
public enum ReplacableUserDataType: String, CaseIterable, Identifiable {
public var id: ObjectIdentifier { .init(rawValue as AnyObject) }
case thePhrases
case theFilter
case theReplacements
case theAssociates
case theSymbols
}
}

View File

@ -0,0 +1,94 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import SQLite3
public enum LMAssembly {
enum FileErrors: Error {
case fileHandleError(String)
}
public enum ReplacableUserDataType: String, CaseIterable, Identifiable {
public var id: ObjectIdentifier { .init(rawValue as AnyObject) }
public var localizedDescription: String { NSLocalizedString(rawValue, comment: "") }
case thePhrases
case theFilter
case theReplacements
case theAssociates
case theSymbols
}
}
// MARK: - String as SQL Command
extension String {
@discardableResult func runAsSQLExec(dbPointer ptrDB: inout OpaquePointer?) -> Bool {
ptrDB != nil && sqlite3_exec(ptrDB, self, nil, nil, nil) == SQLITE_OK
}
@discardableResult func runAsSQLPreparedStep(dbPointer ptrDB: inout OpaquePointer?) -> Bool {
guard ptrDB != nil else { return false }
return performStatement { ptrStmt in
sqlite3_prepare_v2(ptrDB, self, -1, &ptrStmt, nil) == SQLITE_OK && sqlite3_step(ptrStmt) == SQLITE_DONE
}
}
}
extension Array where Element == String {
@discardableResult func runAsSQLPreparedSteps(dbPointer ptrDB: inout OpaquePointer?) -> Bool {
guard ptrDB != nil else { return false }
guard "begin;".runAsSQLExec(dbPointer: &ptrDB) else { return false }
defer {
let looseEnds = sqlite3_exec(ptrDB, "commit;", nil, nil, nil) == SQLITE_OK
assert(looseEnds)
}
for strStmt in self {
let thisResult = performStatement { ptrStmt in
sqlite3_prepare_v2(ptrDB, strStmt, -1, &ptrStmt, nil) == SQLITE_OK && sqlite3_step(ptrStmt) == SQLITE_DONE
}
guard thisResult else {
vCLMLog("SQL Query Error. Statement: \(strStmt)")
return false
}
}
return true
}
}
// MARK: - Safe APIs for using SQLite Statements.
func performStatement(_ handler: (inout OpaquePointer?) -> Bool) -> Bool {
var ptrStmt: OpaquePointer?
defer {
sqlite3_finalize(ptrStmt)
ptrStmt = nil
}
return handler(&ptrStmt)
}
func performStatementSansResult(_ handler: (inout OpaquePointer?) -> Void) {
var ptrStmt: OpaquePointer?
defer {
sqlite3_finalize(ptrStmt)
ptrStmt = nil
}
handler(&ptrStmt)
}
func vCLMLog(_ strPrint: StringLiteralType) {
guard let toLog = UserDefaults.standard.object(forKey: "_DebugMode") as? Bool else {
NSLog("vChewingDebug: %@", strPrint)
return
}
if toLog {
NSLog("vChewingDebug: %@", strPrint)
}
}

View File

@ -0,0 +1,75 @@
//// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import XCTest
@testable import LangModelAssembly
final class InputTokenTests: XCTestCase {
func testTranslatingTokens_1_TimeZone() throws {
print("測試時區俗稱:" + "MACRO@TIMEZONE_SHORTENED".parseAsInputToken(isCHS: false).description)
print("測試時區全稱:" + "MACRO@TIMEZONE".parseAsInputToken(isCHS: false).description)
}
func testTranslatingTokens_2_TimeNow() throws {
print("測試時間時分:" + "MACRO@TIME_SHORTENED".parseAsInputToken(isCHS: false).description)
print("測試帶秒時間:" + "MACRO@TIME".parseAsInputToken(isCHS: true).description)
}
func testTranslatingTokens_3_Date() throws {
print("測試農曆:" + "MACRO@DATE_LUNA".parseAsInputToken(isCHS: true).description)
print("測試二戰勝利紀年:" + "MACRO@DATE_YEARDELTA:-1945".parseAsInputToken(isCHS: true).description)
print("測試短日期之135天前" + "MACRO@DATE_DAYDELTA:-135_SHORTENED".parseAsInputToken(isCHS: true).description)
print("測試長日期之135天前" + "MACRO@DATE_DAYDELTA:-135".parseAsInputToken(isCHS: true).description)
print("測試短日期之今天:" + "MACRO@DATE_SHORTENED".parseAsInputToken(isCHS: true).description)
print("測試長日期之今天:" + "MACRO@DATE".parseAsInputToken(isCHS: true).description)
print("測試短日期之明天:" + "MACRO@DATE_SHORTENED_DAYDELTA:1".parseAsInputToken(isCHS: true).description)
print("測試長日期之明天:" + "MACRO@DATE_DAYDELTA:1".parseAsInputToken(isCHS: true).description)
print("測試短日期之明年:" + "MACRO@DATE_SHORTENED_YEARDELTA:1".parseAsInputToken(isCHS: true).description)
print("測試長日期之明年:" + "MACRO@DATE_YEARDELTA:1".parseAsInputToken(isCHS: true).description)
}
func testTranslatingTokens_4_Week() throws {
print("測試今天星期幾:" + "MACRO@WEEK".parseAsInputToken(isCHS: false).description)
print("測試今天週幾:" + "MACRO@WEEK_SHORTENED".parseAsInputToken(isCHS: false).description)
print("測試明天星期幾:" + "MACRO@WEEK_DAYDELTA:1".parseAsInputToken(isCHS: false).description)
print("測試明天週幾:" + "MACRO@WEEK_SHORTENED_DAYDELTA:1".parseAsInputToken(isCHS: false).description)
print("測試後天星期幾:" + "MACRO@WEEK_DAYDELTA:+2".parseAsInputToken(isCHS: false).description)
print("測試後天週幾:" + "MACRO@WEEK_SHORTENED_DAYDELTA:+2".parseAsInputToken(isCHS: false).description)
}
func testTranslatingTokens_5_Year() throws {
print("測試今年:" + "MACRO@YEAR".parseAsInputToken(isCHS: false).description)
print("測試今年干支:" + "MACRO@YEAR_GANZHI".parseAsInputToken(isCHS: false).description)
print("測試今年生肖:" + "MACRO@YEAR_ZODIAC".parseAsInputToken(isCHS: false).description)
print("測試一千年以前:" + "MACRO@YEAR_YEARDELTA:-1000".parseAsInputToken(isCHS: false).description)
print("測試一千年以前干支:" + "MACRO@YEAR_GANZHI_YEARDELTA:-1000".parseAsInputToken(isCHS: false).description)
print("測試一千年以前生肖:" + "MACRO@YEAR_ZODIAC_YEARDELTA:-1000".parseAsInputToken(isCHS: false).description)
print("測試一千年以後:" + "MACRO@YEAR_YEARDELTA:1000".parseAsInputToken(isCHS: false).description)
print("測試一千年以後干支:" + "MACRO@YEAR_GANZHI_YEARDELTA:1000".parseAsInputToken(isCHS: false).description)
print("測試一千年以後生肖:" + "MACRO@YEAR_ZODIAC_YEARDELTA:1000".parseAsInputToken(isCHS: false).description)
}
func testGeneratedResultsFromLMInstantiator() throws {
let instance = LMAssembly.LMInstantiator(isCHS: true)
XCTAssertTrue(LMAssembly.LMInstantiator.connectToTestSQLDB())
instance.setOptions { config in
config.isCNSEnabled = false
config.isSymbolEnabled = false
}
instance.insertTemporaryData(
keyArray: ["ㄐㄧㄣ", "ㄊㄧㄢ", "ㄖˋ", "ㄑㄧˊ"],
unigram: .init(value: "MACRO@DATE_YEARDELTA:-1945", score: -97.5),
isFiltering: false
)
let x = instance.unigramsFor(keyArray: ["ㄐㄧㄣ", "ㄊㄧㄢ", "ㄖˋ", "ㄑㄧˊ"]).description
print(x)
LMAssembly.LMInstantiator.disconnectSQLDB()
}
}

View File

@ -20,7 +20,7 @@ private let testDataPath: String = packageRootPath + "/Tests/TestCINData/"
final class LMCassetteTests: XCTestCase {
func testCassetteLoadWubi86() throws {
let pathCINFile = testDataPath + "wubi.cin"
var lmCassette = vChewingLM.LMCassette()
var lmCassette = LMAssembly.LMCassette()
NSLog("LMCassette: Start loading CIN.")
lmCassette.open(pathCINFile)
NSLog("LMCassette: Finished loading CIN. Entries: \(lmCassette.count)")
@ -41,13 +41,13 @@ final class LMCassetteTests: XCTestCase {
func testCassetteLoadArray30() throws {
let pathCINFile = testDataPath + "array30.cin2"
var lmCassette = vChewingLM.LMCassette()
var lmCassette = LMAssembly.LMCassette()
NSLog("LMCassette: Start loading CIN.")
lmCassette.open(pathCINFile)
NSLog("LMCassette: Finished loading CIN. Entries: \(lmCassette.count)")
XCTAssertFalse(lmCassette.quickDefMap.isEmpty)
print(lmCassette.quickSetsFor(key: ",.") ?? "")
XCTAssertEqual(lmCassette.keyNameMap.count, 41)
XCTAssertEqual(lmCassette.keyNameMap.count, 31)
XCTAssertEqual(lmCassette.charDefMap.count, 29491)
XCTAssertEqual(lmCassette.charDefWildcardMap.count, 11946)
XCTAssertEqual(lmCassette.octagramMap.count, 0)

View File

@ -38,7 +38,7 @@ private let sampleData: String = #"""
final class LMCoreEXTests: XCTestCase {
func testLMCoreEXAsFactoryCoreDict() throws {
var lmTest = vChewingLM.LMCoreEX(
var lmTest = LMAssembly.LMCoreEX(
reverse: false, consolidate: false, defaultScore: 0, forceDefaultScore: false
)
lmTest.replaceData(textData: sampleData)

View File

@ -0,0 +1,64 @@
//// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import XCTest
@testable import LangModelAssembly
private let strBloatingKey: [String] = ["ㄔㄨㄟ", "ㄋㄧㄡˊ", "ㄅㄧ"]
private let strHaninSymbolMenuKey: [String] = ["_punctuation_list"]
private let strRefutationKey: [String] = ["ㄉㄨㄟˇ"]
private let strBoobsKey: [String] = ["ㄋㄟ", "ㄋㄟ"]
private let expectedReverseLookupResults: [String] = [
"ㄏㄨㄛˊ", "ㄏㄜ˙", "ㄏㄨㄛ", "ㄉㄨㄥ", "ㄏㄜˊ",
"ㄏㄜˋ", "ㄏㄢˋ", "ㄏㄨˊ", "ㄏㄨㄛ˙", "ㄏㄨㄛˋ",
]
final class LMInstantiatorSQLTests: XCTestCase {
func testSQL() throws {
let instance = LMAssembly.LMInstantiator(isCHS: true)
XCTAssertTrue(LMAssembly.LMInstantiator.connectToTestSQLDB())
instance.setOptions { config in
config.isCNSEnabled = false
config.isSymbolEnabled = false
}
XCTAssertEqual(instance.unigramsFor(keyArray: strBloatingKey).description, "[(吹牛逼,-7.375), (吹牛屄,-7.399)]")
XCTAssertEqual(instance.unigramsFor(keyArray: strHaninSymbolMenuKey)[1].description, "(,-9.9)")
XCTAssertEqual(instance.unigramsFor(keyArray: strRefutationKey).description, "[(㨃,-9.544)]")
XCTAssertEqual(instance.unigramsFor(keyArray: strBoobsKey).description, "[(ㄋㄟㄋㄟ,-1.0)]")
instance.setOptions { config in
config.isCNSEnabled = true
config.isSymbolEnabled = true
}
XCTAssertEqual(instance.unigramsFor(keyArray: strBloatingKey).last?.description, "(🌳🆕🐝,-13.0)")
XCTAssertEqual(instance.unigramsFor(keyArray: strHaninSymbolMenuKey)[1].description, "(,-9.9)")
XCTAssertEqual(instance.unigramsFor(keyArray: strRefutationKey).count, 10)
XCTAssertEqual(instance.unigramsFor(keyArray: strBoobsKey).last?.description, "(☉☉,-13.0)")
//
XCTAssertEqual(LMAssembly.LMInstantiator.getFactoryReverseLookupData(with: ""), expectedReverseLookupResults)
LMAssembly.LMInstantiator.disconnectSQLDB()
}
func testCNSMask() throws {
let instance = LMAssembly.LMInstantiator(isCHS: false)
XCTAssertTrue(LMAssembly.LMInstantiator.connectToTestSQLDB())
instance.setOptions { config in
config.isCNSEnabled = false
config.isSymbolEnabled = false
config.filterNonCNSReadings = false
}
XCTAssertEqual(instance.unigramsFor(keyArray: ["ㄨㄟ"]).description, "[(危,-6.0)]")
XCTAssertEqual(instance.unigramsFor(keyArray: ["ㄨㄟˊ"]).description, "[(危,-6.0)]")
instance.setOptions { config in
config.filterNonCNSReadings = true
}
XCTAssertEqual(instance.unigramsFor(keyArray: ["ㄨㄟ"]).description, "[]")
XCTAssertEqual(instance.unigramsFor(keyArray: ["ㄨㄟˊ"]).description, "[(危,-6.0)]")
}
}

View File

@ -0,0 +1,36 @@
//// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// StringView Ranges extension by (c) 2022 and onwards Isaac Xen (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import XCTest
@testable import LangModelAssembly
final class LMPlainBPMFTests: XCTestCase {
func testLMPlainBPMFDataQuery() throws {
let instance1 = LMAssembly.LMInstantiator(isCHS: false).setOptions { config in
config.isSCPCEnabled = true
}
var liu2 = instance1.unigramsFor(keyArray: ["ㄌㄧㄡˊ"]).map(\.value).prefix(3)
var bao3 = instance1.unigramsFor(keyArray: ["ㄅㄠˇ"]).map(\.value).prefix(3)
var jie2 = instance1.unigramsFor(keyArray: ["ㄐㄧㄝˊ"]).map(\.value).prefix(3)
XCTAssertEqual(liu2, ["", "", ""])
XCTAssertEqual(bao3, ["", "", ""])
XCTAssertEqual(jie2, ["", "", ""])
let instance2 = LMAssembly.LMInstantiator(isCHS: true).setOptions { config in
config.isSCPCEnabled = true
}
liu2 = instance2.unigramsFor(keyArray: ["ㄌㄧㄡˊ"]).map(\.value).prefix(3)
bao3 = instance2.unigramsFor(keyArray: ["ㄅㄠˇ"]).map(\.value).prefix(3)
jie2 = instance2.unigramsFor(keyArray: ["ㄐㄧㄝˊ"]).map(\.value).prefix(3)
XCTAssertEqual(liu2, ["", "", ""])
XCTAssertEqual(bao3, ["", "", ""])
XCTAssertEqual(jie2, ["", "", ""])
}
}

View File

@ -17,15 +17,15 @@ private let halfLife: Double = 5400
private let nullURL = URL(fileURLWithPath: "/dev/null")
final class LMUserOverrideTests: XCTestCase {
private func observe(who uom: vChewingLM.LMUserOverride, key: String, candidate: String, timestamp stamp: Double) {
private func observe(who uom: LMAssembly.LMUserOverride, key: String, candidate: String, timestamp stamp: Double) {
uom.doObservation(key: key, candidate: candidate, timestamp: stamp, forceHighScoreOverride: false, saveCallback: {})
}
func testUOM_1_BasicOps() throws {
let uom = vChewingLM.LMUserOverride(capacity: capacity, decayConstant: Double(halfLife), dataURL: nullURL)
let key = "((ㄍㄨㄥ-ㄙ,公司),(ㄉㄜ˙,的),ㄋㄧㄢˊ-ㄓㄨㄥ)"
let headReading = "ㄋㄧㄢˊ-ㄓㄨㄥ"
let expectedSuggestion = "年終"
let uom = LMAssembly.LMUserOverride(capacity: capacity, decayConstant: Double(halfLife), dataURL: nullURL)
let key = "((ㄕㄣˊ-ㄌㄧˇ-ㄌㄧㄥˊ-ㄏㄨㄚˊ,神里綾華),(ㄉㄜ˙,的),ㄍㄡˇ)"
let headReading = "ㄍㄡˇ"
let expectedSuggestion = ""
observe(who: uom, key: key, candidate: expectedSuggestion, timestamp: nowTimeStamp)
var suggested = uom.getSuggestion(key: key, timestamp: nowTimeStamp, headReading: headReading)
XCTAssertEqual(Set(suggested.candidates.map(\.1.value)).first ?? "", expectedSuggestion)
@ -45,11 +45,11 @@ final class LMUserOverrideTests: XCTestCase {
}
func testUOM_2_NewestAgainstRepeatedlyUsed() throws {
let uom = vChewingLM.LMUserOverride(capacity: capacity, decayConstant: Double(halfLife), dataURL: nullURL)
let key = "((ㄍㄨㄥ-ㄙ,公司),(ㄉㄜ˙,的),ㄋㄧㄢˊ-ㄓㄨㄥ)"
let headReading = "ㄋㄧㄢˊ-ㄓㄨㄥ"
let valRepeatedlyUsed = "年終" //
let valNewest = "年中" //
let uom = LMAssembly.LMUserOverride(capacity: capacity, decayConstant: Double(halfLife), dataURL: nullURL)
let key = "((ㄕㄣˊ-ㄌㄧˇ-ㄌㄧㄥˊ-ㄏㄨㄚˊ,神里綾華),(ㄉㄜ˙,的),ㄍㄡˇ)"
let headReading = "ㄍㄡˇ"
let valRepeatedlyUsed = "" //
let valNewest = "" //
let stamps: [Double] = [0, 0.5, 2, 2.5, 4, 4.5, 5.3].map { nowTimeStamp + halfLife * $0 }
stamps.forEach { stamp in
observe(who: uom, key: key, candidate: valRepeatedlyUsed, timestamp: stamp)
@ -62,8 +62,6 @@ final class LMUserOverrideTests: XCTestCase {
}
//
observe(who: uom, key: key, candidate: valNewest, timestamp: nowTimeStamp + halfLife * 23.4)
suggested = uom.getSuggestion(key: key, timestamp: nowTimeStamp + halfLife * 23.6, headReading: headReading)
XCTAssertEqual(Set(suggested.candidates.map(\.1.value)).first ?? "", valNewest)
suggested = uom.getSuggestion(key: key, timestamp: nowTimeStamp + halfLife * 26, headReading: headReading)
XCTAssertEqual(Set(suggested.candidates.map(\.1.value)).first ?? "", valNewest)
suggested = uom.getSuggestion(key: key, timestamp: nowTimeStamp + halfLife * 50, headReading: headReading)
@ -72,11 +70,11 @@ final class LMUserOverrideTests: XCTestCase {
}
func testUOM_3_LRUTable() throws {
let a = (key: "((ㄍㄨㄥ-ㄙ,公司),(ㄉㄜ˙,的),ㄋㄧㄢˊ-ㄓㄨㄥ)", value: "年終", head: "ㄋㄧㄢˊ-ㄓㄨㄥ")
let b = (key: "((ㄑㄧˋ-ㄧㄝˋ,企業),(ㄉㄜ˙,的),ㄐㄧㄤˇ-ㄐㄧㄣ)", value: "獎金", head: "ㄐㄧㄤˇ-ㄐㄧㄣ")
let c = (key: "((ㄒㄩㄝˊ-ㄕㄥ,學生),(ㄉㄜ˙,的),ㄈㄨˊ-ㄌㄧˋ)", value: "福利", head: "ㄈㄨˊ-ㄌㄧˋ")
let a = (key: "((ㄕㄣˊ-ㄌㄧˇ-ㄌㄧㄥˊ-ㄏㄨㄚˊ,神里綾華),(ㄉㄜ˙,的),ㄍㄡˇ)", value: "", head: "ㄍㄡˇ")
let b = (key: "((ㄆㄞˋ-ㄇㄥˊ,派蒙),(ㄉㄜ˙,的),ㄐㄧㄤˇ-ㄐㄧㄣ)", value: "伙食費", head: "ㄏㄨㄛˇ-ㄕˊ-ㄈㄟˋ")
let c = (key: "((ㄍㄨㄛˊ-ㄅㄥ,國崩),(ㄉㄜ˙,的),ㄇㄠˋ-ㄗ˙)", value: "帽子", head: "ㄇㄠˋ-ㄗ˙")
let d = (key: "((ㄌㄟˊ-ㄉㄧㄢˋ-ㄐㄧㄤ-ㄐㄩㄣ,雷電將軍),(ㄉㄜ˙,的),ㄐㄧㄠˇ-ㄔㄡˋ)", value: "腳臭", head: "ㄐㄧㄠˇ-ㄔㄡˋ")
let uom = vChewingLM.LMUserOverride(capacity: 2, decayConstant: Double(halfLife), dataURL: nullURL)
let uom = LMAssembly.LMUserOverride(capacity: 2, decayConstant: Double(halfLife), dataURL: nullURL)
observe(who: uom, key: a.key, candidate: a.value, timestamp: nowTimeStamp)
observe(who: uom, key: b.key, candidate: b.value, timestamp: nowTimeStamp + halfLife * 1)
observe(who: uom, key: c.key, candidate: c.value, timestamp: nowTimeStamp + halfLife * 2)

View File

@ -0,0 +1,30 @@
//// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import XCTest
@testable import LangModelAssembly
final class LMInstantiatorNumericPadTests: XCTestCase {
func testSQL() throws {
let instance = LMAssembly.LMInstantiator(isCHS: true)
instance.setOptions { config in
config.numPadFWHWStatus = nil
}
XCTAssertEqual(instance.unigramsFor(keyArray: ["_NumPad_0"]).description, "[]")
instance.setOptions { config in
config.numPadFWHWStatus = true
}
XCTAssertEqual(instance.unigramsFor(keyArray: ["_NumPad_0"]).description, "[(,0.0), (0,-0.1)]")
instance.setOptions { config in
config.numPadFWHWStatus = false
}
XCTAssertEqual(instance.unigramsFor(keyArray: ["_NumPad_0"]).description, "[(0,0.0), (,-0.1)]")
}
}

View File

@ -16,47 +16,38 @@
%phase_auto_skip_endkey
%flag_disp_full_match
%flag_disp_partial_match
%keys_to_directly_commit !@#$%^&*()-_=+[{]}\|:'"<>?
%keyname begin
a 1-
b 5v
c 3v
d 3-
e 3^
f 4-
g 5-
h 6-
i 8^
j 7-
k 8-
l 9-
m 7v
n 6v
o 9^
p 0^
q 1^
r 4^
s 2-
t 5^
u 7^
v 4v
w 2^
x 2v
y 6^
z 1v
. 9v
/ 0v
; 0-
, 8v
1
2
3
4
5
6
7
8
9
0
a 1-
b 5v
c 3v
d 3-
e 3^
f 4-
g 5-
h 6-
i 8^
j 7-
k 8-
l 9-
m 7v
n 6v
o 9^
p 0^
q 1^
r 4^
s 2-
t 5^
u 7^
v 4v
w 2^
x 2v
y 6^
z 1v
. 9v
/ 0v
; 0-
, 8v
%keyname end
%quick begin
, ,火米精燈料鄰勞類營

View File

@ -1,10 +1,10 @@
// swift-tools-version:5.3
// swift-tools-version:5.7
import PackageDescription
let package = Package(
name: "MainAssembly",
platforms: [
.macOS(.v10_13),
.macOS(.v11),
],
products: [
.library(
@ -14,12 +14,15 @@ let package = Package(
],
dependencies: [
.package(path: "../DanielGalasko_FolderMonitor"),
.package(path: "../HangarRash_SwiftyCapsLockToggler"),
.package(path: "../Jad_BookmarkManager"),
.package(path: "../Qwertyyb_ShiftKeyUpChecker"),
.package(path: "../vChewing_BrailleSputnik"),
.package(path: "../vChewing_CandidateWindow"),
.package(path: "../vChewing_CocoaExtension"),
.package(path: "../vChewing_OSFrameworkImpl"),
.package(path: "../vChewing_Hotenka"),
.package(path: "../vChewing_IMKUtils"),
.package(path: "../vChewing_KimoDataReader"),
.package(path: "../vChewing_LangModelAssembly"),
.package(path: "../vChewing_Megrez"),
.package(path: "../vChewing_NotifierUI"),
@ -36,12 +39,14 @@ let package = Package(
.target(
name: "MainAssembly",
dependencies: [
.product(name: "BrailleSputnik", package: "vChewing_BrailleSputnik"),
.product(name: "BookmarkManager", package: "Jad_BookmarkManager"),
.product(name: "CandidateWindow", package: "vChewing_CandidateWindow"),
.product(name: "CocoaExtension", package: "vChewing_CocoaExtension"),
.product(name: "OSFrameworkImpl", package: "vChewing_OSFrameworkImpl"),
.product(name: "FolderMonitor", package: "DanielGalasko_FolderMonitor"),
.product(name: "Hotenka", package: "vChewing_Hotenka"),
.product(name: "IMKUtils", package: "vChewing_IMKUtils"),
.product(name: "KimoDataReader", package: "vChewing_KimoDataReader"),
.product(name: "LangModelAssembly", package: "vChewing_LangModelAssembly"),
.product(name: "Megrez", package: "vChewing_Megrez"),
.product(name: "NotifierUI", package: "vChewing_NotifierUI"),
@ -50,10 +55,14 @@ let package = Package(
.product(name: "Shared", package: "vChewing_Shared"),
.product(name: "ShiftKeyUpChecker", package: "Qwertyyb_ShiftKeyUpChecker"),
.product(name: "SwiftExtension", package: "vChewing_SwiftExtension"),
.product(name: "SwiftyCapsLockToggler", package: "HangarRash_SwiftyCapsLockToggler"),
.product(name: "Tekkon", package: "vChewing_Tekkon"),
.product(name: "TooltipUI", package: "vChewing_TooltipUI"),
.product(name: "Uninstaller", package: "vChewing_Uninstaller"),
.product(name: "UpdateSputnik", package: "vChewing_UpdateSputnik"),
],
resources: [
.process("Resources/convdict.sqlite"),
]
),
.testTarget(

Some files were not shown because too many files have changed in this diff Show More