@@ -56,6 +56,9 @@ class McBopomofoInputMethodController: IMKInputController {
5656 var state : InputState = InputState . Empty ( )
5757 lazy var charInfo : SystemCharacterInfo ? = try ? SystemCharacterInfo ( )
5858
59+ // Share the stored issues, so a set of issues is shown as notification only once.
60+ static var latestUserFileIssues : [ String ] = [ ]
61+
5962 // MARK: - IMKInputController methods
6063
6164 override init !( server: IMKServer ! , delegate: Any ! , client inputClient: Any ! ) {
@@ -122,6 +125,16 @@ class McBopomofoInputMethodController: IMKInputController {
122125 menu. addItem (
123126 withTitle: NSLocalizedString ( " Reload User Phrases " , comment: " " ) ,
124127 action: #selector( reloadUserPhrases ( _: ) ) , keyEquivalent: " " )
128+
129+ if !McBopomofoInputMethodController. latestUserFileIssues. isEmpty {
130+ // Setting menuItem.image does not work in input method menus even on macOS 26,
131+ // so we just use the alert emoji in the menu item title.
132+ let menuItem = NSMenuItem (
133+ title: NSLocalizedString ( " Show Issues in User Files ⚠️ " , comment: " " ) ,
134+ action: #selector( showUserFileIssues ( _: ) ) , keyEquivalent: " " )
135+ menu. addItem ( menuItem)
136+ }
137+
125138 menu. addItem ( NSMenuItem . separator ( ) )
126139
127140 menu. addItem (
@@ -171,6 +184,9 @@ class McBopomofoInputMethodController: IMKInputController {
171184 keyHandler. inputMode = newInputMode
172185 self . handle ( state: . Empty( ) , client: client)
173186 }
187+
188+ // Since setValue is called after activateServer, show user file issues here, if any.
189+ checkUserFileIssues ( )
174190 }
175191
176192 // MARK: - IMKServerInput protocol methods
@@ -311,6 +327,34 @@ class McBopomofoInputMethodController: IMKInputController {
311327 LanguageModelManager . loadUserPhrases (
312328 enableForPlainBopomofo: Preferences . enableUserPhrasesInPlainBopomofo)
313329 LanguageModelManager . loadUserPhraseReplacement ( )
330+
331+ // Empty the issues so that if there are still the same issues, a
332+ // notification will be shown.
333+ McBopomofoInputMethodController . latestUserFileIssues = [ ]
334+ checkUserFileIssues ( )
335+ }
336+
337+ @objc func showUserFileIssues( _ sender: Any ? ) {
338+ let header = NSLocalizedString (
339+ " Issues were found in the following user phrase files: " , comment: " " )
340+ let report =
341+ header + " \n \n "
342+ + McBopomofoInputMethodController. latestUserFileIssues. joined ( separator: " \n " )
343+ let tempDir = URL ( fileURLWithPath: NSTemporaryDirectory ( ) , isDirectory: true )
344+ let now = Date ( )
345+ let formatter = DateFormatter ( )
346+ formatter. locale = Locale ( identifier: " en_US_POSIX " )
347+ formatter. dateFormat = " yyyyMMdd-HHmmss.SSS "
348+ let dateString = formatter. string ( from: now)
349+ let fileName = " UserFileIssues- \( dateString) .txt "
350+ let fileURL = tempDir. appendingPathComponent ( fileName)
351+ do {
352+ try report. write ( to: fileURL, atomically: true , encoding: . utf8)
353+ NSWorkspace . shared. open ( fileURL)
354+ } catch {
355+ NSLog ( " Failed to write report to temporary file: \( error) " )
356+ return
357+ }
314358 }
315359
316360 @objc func showAbout( _ sender: Any ? ) {
@@ -849,4 +893,20 @@ extension McBopomofoInputMethodController {
849893 private func hideTooltip( ) {
850894 McBopomofoInputMethodController . tooltipController. hide ( )
851895 }
896+
897+ private func checkUserFileIssues( ) {
898+ let issues : [ String ] = keyHandler. collectUserFileIssues ( )
899+
900+ // McBopomofoLM caps the maximum number of issues collected, and so
901+ // we'll just do this O(n) comparison since n is small.
902+ if McBopomofoInputMethodController . latestUserFileIssues != issues {
903+ McBopomofoInputMethodController . latestUserFileIssues = issues
904+
905+ if !McBopomofoInputMethodController. latestUserFileIssues. isEmpty {
906+ NotifierController . notify (
907+ message: NSLocalizedString (
908+ " Check McBopomofo menu for user file issues " , comment: " " ) , stay: true )
909+ }
910+ }
911+ }
852912}
0 commit comments