feat(desktop): move dictionary entries in and out as files

Users could only rebuild their spoken-word dictionary entry by entry. Import
and export now round-trip the whole list, reporting duplicate and invalid
entries per row instead of failing the batch, so a dictionary survives a
reinstall or a move to another machine.
This commit is contained in:
Yun Chan 2026-09-16 23:23:30 +09:00
parent 7953706142
commit 911c9f0229
5 changed files with 625 additions and 15 deletions

View file

@ -1,6 +1,6 @@
// src/main/ipc/dictionary-handlers.ts
import { ipcMain } from 'electron'
import { ipcMain, dialog } from 'electron'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode, D3ROError } from '@d3ro/core/errors'
import { getDictionaryService } from '../services/DictionaryService'
@ -9,7 +9,9 @@ import type {
DictionaryAddParams,
DictionaryUpdateParams,
DictionaryDeleteParams,
DictionarySearchParams
DictionarySearchParams,
DictionaryImportParams,
DictionaryExportParams
} from '@d3ro/core/types'
export function registerDictionaryHandlers(): void {
@ -74,4 +76,43 @@ export function registerDictionaryHandlers(): void {
return ipcError(ErrorCode.DBQueryFailed, `Failed to search dictionary: ${message}`)
}
})
ipcMain.handle(IPC_CHANNELS.DICTIONARY.IMPORT, async (_event, params: DictionaryImportParams) => {
try {
let filePath = params.filePath
if (!filePath) {
const picked = await dialog.showOpenDialog({
properties: ['openFile'],
filters: [
params.format === 'csv'
? { name: 'CSV', extensions: ['csv'] }
: { name: 'JSON', extensions: ['json'] }
]
})
if (picked.canceled || picked.filePaths.length === 0) {
return ipcSuccess({ imported: 0, skipped: 0, errors: 0 })
}
filePath = picked.filePaths[0]
}
return ipcSuccess(getDictionaryService().importDictionary({ ...params, filePath }))
} catch (error) {
if (error instanceof D3ROError) {
return ipcError(error.code, error.message)
}
const message = error instanceof Error ? error.message : String(error)
return ipcError(ErrorCode.DictionaryImportFailed, `Failed to import dictionary: ${message}`)
}
})
ipcMain.handle(IPC_CHANNELS.DICTIONARY.EXPORT, async (_event, params: DictionaryExportParams) => {
try {
return ipcSuccess(await getDictionaryService().exportDictionary(params))
} catch (error) {
if (error instanceof D3ROError) {
return ipcError(error.code, error.message)
}
const message = error instanceof Error ? error.message : String(error)
return ipcError(ErrorCode.DictionaryExportFailed, `Failed to export dictionary: ${message}`)
}
})
}