feat(backend+frontend): add is_regex support to dictionary entries

Add support for regex-based dictionary entries across the full stack:

Backend:
- DictionaryEntry model: add is_regex column (Boolean, default False)
- DictionaryEntryCRUD: validate regex on add_entry(), compile on creation
- _enforce_dictionary: match by regex pattern when is_regex=True
- dictionary_filter: support is_regex in filter/query
- metrics: include is_regex entries in metrics calculations
- Alembic migration: 20260604_add_is_regex_to_dictionary_entries
- Merge migration: 351afb8f961a (merge is_regex + composite index heads)

Frontend:
- DictionaryDetailModel: add is_regex field to DictionaryEntry interface,
  EditForm, and addForm; sync edit/add form state with backend schema
This commit is contained in:
2026-06-04 16:16:02 +03:00
parent b823bc559b
commit a1a855e670
8 changed files with 132 additions and 11 deletions

View File

@@ -26,6 +26,7 @@ interface DictionaryEntry {
context_notes?: string;
context_data?: unknown;
usage_notes?: string;
is_regex?: boolean;
}
interface EditForm {
@@ -36,6 +37,7 @@ interface EditForm {
context_notes: string;
context_data: unknown;
usage_notes: string;
is_regex: boolean;
}
interface ImportResult {
@@ -54,12 +56,12 @@ export class DictionaryDetailModel {
// ── Edit ──────────────────────────────────────────────────────
editEntryId: string | null = $state(null);
editForm: EditForm = $state({ source_term: '', target_term: '', source_language: '', target_language: '', context_notes: '', context_data: null, usage_notes: '' });
editForm: EditForm = $state({ source_term: '', target_term: '', source_language: '', target_language: '', context_notes: '', context_data: null, usage_notes: '', is_regex: false });
editContextDataRaw: string = $state('');
// ── Add new ───────────────────────────────────────────────────
showAddForm: boolean = $state(false);
addForm: { source_term: string; target_term: string; source_language: string; target_language: string; context_notes: string } = $state({ source_term: '', target_term: '', source_language: 'und', target_language: 'und', context_notes: '' });
addForm: { source_term: string; target_term: string; source_language: string; target_language: string; context_notes: string; is_regex: boolean } = $state({ source_term: '', target_term: '', source_language: 'und', target_language: 'und', context_notes: '', is_regex: false });
isAdding: boolean = $state(false);
// ── Expand (view detail) ──────────────────────────────────────
@@ -135,7 +137,7 @@ export class DictionaryDetailModel {
startEdit(entry: DictionaryEntry): void {
this.editEntryId = entry.id;
this.editForm = { source_term: entry.source_term, target_term: entry.target_term, source_language: entry.source_language || '', target_language: entry.target_language || '', context_notes: entry.context_notes || '', context_data: entry.context_data, usage_notes: entry.usage_notes || '' };
this.editForm = { source_term: entry.source_term, target_term: entry.target_term, source_language: entry.source_language || '', target_language: entry.target_language || '', context_notes: entry.context_notes || '', context_data: entry.context_data, usage_notes: entry.usage_notes || '', is_regex: entry.is_regex || false };
this.editContextDataRaw = entry.context_data ? JSON.stringify(entry.context_data, null, 2) : '';
this.appState = 'editing';
}
@@ -169,7 +171,7 @@ export class DictionaryDetailModel {
await api.requestApi(`/translate/dictionaries/${this.dictionaryId}/entries`, 'POST', this.addForm);
addToast(t.translate?.dictionaries?.entry_added || 'Entry added', 'success');
this.showAddForm = false;
this.addForm = { source_term: '', target_term: '', source_language: 'und', target_language: 'und', context_notes: '' };
this.addForm = { source_term: '', target_term: '', source_language: 'und', target_language: 'und', context_notes: '', is_regex: false };
this.loadEntries();
} catch (e: unknown) {
addToast(e instanceof Error ? e.message : 'Failed to add entry', 'error');