fix(reader): preserve full context sentence with abbreviations (Mr., Mrs., etc.) and log bug fix

This commit is contained in:
Antigravity committed 2026-10-06 17:03:38 +03:00
1 parent 6a6b165c7a
commit 0ff4465d34
3 files changed
+40 -4

No files matched your search

+8
View File
@@ -369,6 +369,14 @@
---
### 🐛 Устранённые дефекты и хотфиксы (Bug Tracker & Hotfixes)
- [x] **Баг читалки: Преждевременный обрыв контекстного предложения на точках сокращений (`Mr.`, `Mrs.`, `Dr.`, `Prof.`, `St.`, `etc.`, `e.g.`, `i.e.`, `1.5`)**:
- **Симптом**: При клике на слово в предложении с обращением или сокращением (например, *«Mr. and Mrs. Dursley were proud...»*) кнопка перевода предложения («🌐 Перевести предложение») отправляла на перевод лишь обрезок до или после точки (*«Mr.»*, *«and Mrs.»* или *«Dursley were proud...»*). Если перед словом была точка в обращении, текст дальше не переводился.
- **Причина**: В функции `extractContextSentence(fullText, word)` в [`app/static/js/modules/reader.js`](file:///home/ai-projects/services/study.qpid.pro/app/static/js/modules/reader.js) использовалось наивное регулярное выражение `[^.!?\n]*`, считавшее абсолютно любую точку концом предложения.
- **Решение**: Реализована безопасная защита списка английских титулов и сокращений (`READER_HONORIFICS`) и дробных чисел перед сегментацией. Предложения корректно делятся по терминальным знакам препинания с закрывающими кавычками/скобками (`[.!?]['"”’»)]*`), гарантируя передачу полного, связного контекста в переводчик и словарь.
---
### 17. 🍔 Разгрузка хеддера и единая навигация через боковое меню / Drawer (Unified Navigation & Header Redesign)
- [ ] **Проблема перегруженного хеддера**:
- В шапке скопилось 8+ кнопок (`📚 Читалка`, `🎯 Тесты CEFR`, `📖 Грамматика`, `🎓 Тренажёр`, `📊 Статистика`, `📤 Экспорт`, тема, шестеренка, профиль), на экранах планшетов и ноутбуков возникает переполнение и визуальный шум.
+31 -3
View File
@@ -1070,11 +1070,39 @@ export function renderInteractiveSpans(container, text, contextText, knownWordsM
});
}
const READER_HONORIFICS = [
'Mr.', 'Mrs.', 'Ms.', 'Dr.', 'Prof.', 'St.', 'vs.', 'etc.', 'e.g.', 'i.e.', 'Jr.', 'Sr.',
'Capt.', 'Col.', 'Gen.', 'Lt.', 'Sgt.', 'Rev.', 'Hon.'
];
export function extractContextSentence(fullText, word) {
if (!fullText) return '';
const re = new RegExp(`([^.!?\\n]*?\\b${escapeRegex(word)}\\b[^.!?\\n]*[.!?]?)`, 'i');
const m = fullText.match(re);
if (m && m[1]) return m[1].trim();
let protectedText = fullText;
for (const h of READER_HONORIFICS) {
if (protectedText.toLowerCase().includes(h.toLowerCase())) {
const safe = h.replace(/\./g, '§DOT§');
const reH = new RegExp(escapeRegex(h), 'gi');
protectedText = protectedText.replace(reH, safe);
}
}
// Protect decimal numbers (e.g. 3.14, 1.5)
protectedText = protectedText.replace(/(\d+)\.(\d+)/g, '$1§DOT§$2');
// Split sentences on terminal punctuation (.!?) followed by optional quotes/brackets and whitespace
const marked = protectedText.replace(/([.!?]['"”’»)]*)\s+/g, '$1§SEP§');
const rawSentences = marked.split('§SEP§');
const cleanWord = (word || '').trim();
if (cleanWord) {
const escaped = escapeRegex(cleanWord);
const wordRe = new RegExp(`(?:^|\\W)${escaped}(?:$|\\W)`, 'i');
for (const s of rawSentences) {
if (wordRe.test(s)) {
return s.replace(/§DOT§/g, '.').trim();
}
}
}
return fullText.slice(0, 160).trim();
}
+1 -1
View File
@@ -1,7 +1,7 @@
// Service Worker for Notes English PWA (Stage 2)
// Strategy: Cache-first for static assets, Network-first for API calls.
const CACHE_NAME = 'notes-v5.5';
const CACHE_NAME = 'notes-v5.6';
const OFFLINE_URL = '/';
const PRECACHE_URLS = [