fix(dictionary): let an edit clear the pronunciation and report duplicate renames
This commit is contained in:
parent
9d5d043e8b
commit
de1e8a82a4
6 changed files with 350 additions and 24 deletions
109
packages/core/src/dictionary-policy.ts
Normal file
109
packages/core/src/dictionary-policy.ts
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
// packages/core/src/dictionary-policy.ts
|
||||
// 사용자 사전 편집 정책 — 순수 함수만 둔다 (DB/IPC/UI 의존 없음).
|
||||
// 데스크톱 DictionaryService(쓰기 규칙)와 DictionaryPage(폼 → 요청 변환)가 같은 규칙을 공유한다.
|
||||
|
||||
import { ErrorCode } from './errors'
|
||||
import type { DictionaryAddParams, DictionaryEntry, DictionaryUpdateParams } from './types'
|
||||
|
||||
export type DictionaryCategory = DictionaryEntry['category']
|
||||
|
||||
/** 단어를 정규화한다. 공백뿐이면 null (= 유효하지 않음). */
|
||||
export function normalizeDictionaryWord(raw: string): string | null {
|
||||
const word = raw.trim()
|
||||
return word.length > 0 ? word : null
|
||||
}
|
||||
|
||||
/**
|
||||
* 발음 입력을 정규화한다.
|
||||
* - undefined → undefined: "변경 없음"
|
||||
* - null 또는 공백뿐인 문자열 → null: "발음 지우기"
|
||||
* - 그 외 → 앞뒤 공백을 제거한 값
|
||||
*/
|
||||
export function normalizeDictionaryPronunciation(
|
||||
raw: string | null | undefined
|
||||
): string | null | undefined {
|
||||
if (raw === undefined) return undefined
|
||||
if (raw === null) return null
|
||||
const pronunciation = raw.trim()
|
||||
return pronunciation.length > 0 ? pronunciation : null
|
||||
}
|
||||
|
||||
/** DB에 그대로 쓸 수 있는 부분 갱신 값. 키가 없으면 해당 필드는 바꾸지 않는다. */
|
||||
export interface DictionaryUpdatePatch {
|
||||
word?: string
|
||||
pronunciation?: string | null
|
||||
category?: DictionaryCategory
|
||||
}
|
||||
|
||||
export type DictionaryUpdatePatchResult =
|
||||
| { ok: true; patch: DictionaryUpdatePatch }
|
||||
| { ok: false; reason: 'empty-word' }
|
||||
|
||||
/** 갱신 요청을 검증·정규화해 쓰기 패치로 바꾼다. */
|
||||
export function buildDictionaryUpdatePatch(
|
||||
params: DictionaryUpdateParams
|
||||
): DictionaryUpdatePatchResult {
|
||||
const patch: DictionaryUpdatePatch = {}
|
||||
if (params.word !== undefined) {
|
||||
const word = normalizeDictionaryWord(params.word)
|
||||
if (word === null) return { ok: false, reason: 'empty-word' }
|
||||
patch.word = word
|
||||
}
|
||||
const pronunciation = normalizeDictionaryPronunciation(params.pronunciation)
|
||||
if (pronunciation !== undefined) patch.pronunciation = pronunciation
|
||||
if (params.category !== undefined) patch.category = params.category
|
||||
return { ok: true, patch }
|
||||
}
|
||||
|
||||
/** SQLite UNIQUE 제약 위반 여부 (better-sqlite3 오류 메시지 기준). */
|
||||
export function isUniqueConstraintViolation(err: unknown): boolean {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
return /unique/i.test(message)
|
||||
}
|
||||
|
||||
/** 추가/편집 다이얼로그의 입력값. */
|
||||
export interface DictionaryFormValues {
|
||||
word: string
|
||||
pronunciation: string
|
||||
}
|
||||
|
||||
/** 새 단어 추가 요청: 빈 발음은 보내지 않는다. */
|
||||
export function toDictionaryAddParams(form: DictionaryFormValues): DictionaryAddParams {
|
||||
const pronunciation = normalizeDictionaryPronunciation(form.pronunciation)
|
||||
return {
|
||||
word: form.word.trim(),
|
||||
...(pronunciation ? { pronunciation } : {})
|
||||
}
|
||||
}
|
||||
|
||||
/** 편집 요청: 비운 발음은 null로 보내 "지우기"를 명시한다. */
|
||||
export function toDictionaryUpdateParams(
|
||||
id: string,
|
||||
form: DictionaryFormValues
|
||||
): DictionaryUpdateParams {
|
||||
return {
|
||||
id,
|
||||
word: form.word.trim(),
|
||||
pronunciation: normalizeDictionaryPronunciation(form.pronunciation) ?? null
|
||||
}
|
||||
}
|
||||
|
||||
/** 사전 저장 실패를 사용자 메시지 i18n 키로 바꾼다. */
|
||||
export type DictionarySaveErrorKey =
|
||||
| 'mobile.dictionary.error.duplicate'
|
||||
| 'mobile.dictionary.error.notFound'
|
||||
| 'mobile.dictionary.error.validation'
|
||||
| 'mobile.dictionary.error.server'
|
||||
|
||||
export function dictionarySaveErrorKey(code: ErrorCode): DictionarySaveErrorKey {
|
||||
switch (code) {
|
||||
case ErrorCode.DictionaryDuplicate:
|
||||
return 'mobile.dictionary.error.duplicate'
|
||||
case ErrorCode.DictionaryNotFound:
|
||||
return 'mobile.dictionary.error.notFound'
|
||||
case ErrorCode.DictionaryImportInvalidFormat:
|
||||
return 'mobile.dictionary.error.validation'
|
||||
default:
|
||||
return 'mobile.dictionary.error.server'
|
||||
}
|
||||
}
|
||||
|
|
@ -718,7 +718,8 @@ export interface DictionaryAddParams {
|
|||
export interface DictionaryUpdateParams {
|
||||
id: string
|
||||
word?: string
|
||||
pronunciation?: string
|
||||
/** undefined = 변경 없음, null(또는 빈 문자열) = 발음 지우기 */
|
||||
pronunciation?: string | null
|
||||
category?: 'user' | 'auto' | 'technical'
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue