feat(VocabPracticeDialog, localization): add hard vocabulary marking feature
All checks were successful
Deploy to production / deploy (push) Successful in 1m53s

- Introduced functionality to mark vocabulary items as difficult, allowing users to track and manage challenging words during practice sessions.
- Added buttons for marking and unmarking current vocabulary as hard, enhancing user engagement and learning effectiveness.
- Updated the UI to display the count of hard-marked vocabulary items, providing users with better insights into their learning progress.
- Enhanced localization files for German and English to support new vocabulary marking features, ensuring a consistent user experience across languages.
This commit is contained in:
Torsten Schulz (local)
2026-04-21 15:57:21 +02:00
parent 4cc2aace6b
commit 44b40d5a46
3 changed files with 81 additions and 0 deletions

View File

@@ -94,6 +94,12 @@
<button v-else-if="showSkipButton" @click="skip">
{{ $t('socialnetwork.vocab.practice.skip') }}
</button>
<button v-if="current && !isCurrentMarkedHard" @click="markCurrentAsHard">
{{ $t('socialnetwork.vocab.practice.markHard') }}
</button>
<button v-else-if="current && isCurrentMarkedHard" @click="unmarkCurrentAsHard">
{{ $t('socialnetwork.vocab.practice.unmarkHard') }}
</button>
</div>
<div v-if="lastWrongReview" class="solution-card solution-card--persistent">
@@ -137,6 +143,10 @@
<span class="k">{{ $t('socialnetwork.vocab.practice.fail') }}</span>
<span class="v">{{ wrongCount }} ({{ failPercent }}%)</span>
</div>
<div class="statRow">
<span class="k">{{ $t('socialnetwork.vocab.practice.hardCount') }}</span>
<span class="v">{{ hardCount }}</span>
</div>
</div>
</div>
</div>
@@ -188,6 +198,7 @@ export default {
lastCorrect: false,
locked: false,
autoAdvanceTimer: null,
hardVocabMap: {}, // { [normalizedPairKey]: { learning, reference, markedAt } }
};
},
computed: {
@@ -277,8 +288,70 @@ export default {
const total = this.srsTotalDue || 0;
return Math.max(0, total - this.srsDoneCount);
},
hardCount() {
return Object.keys(this.hardVocabMap || {}).length;
},
isCurrentMarkedHard() {
if (!this.current) return false;
return Boolean(this.hardVocabMap[this.getHardKey(this.current)]);
}
},
methods: {
hardStorageKey() {
const courseId = this.openParams?.courseId;
if (!courseId) return null;
return `yourpart:vocab:hardList:${courseId}`;
},
loadHardVocabMap() {
const key = this.hardStorageKey();
if (!key) {
this.hardVocabMap = {};
return;
}
try {
const raw = localStorage.getItem(key);
const parsed = raw ? JSON.parse(raw) : {};
this.hardVocabMap = parsed && typeof parsed === 'object' ? parsed : {};
} catch (_) {
this.hardVocabMap = {};
}
},
saveHardVocabMap() {
const key = this.hardStorageKey();
if (!key) return;
try {
localStorage.setItem(key, JSON.stringify(this.hardVocabMap || {}));
} catch (_) {
// ignore quota/private-mode issues
}
},
getHardKey(item) {
const learning = this.normalize(item?.learning || '');
const reference = this.normalize(item?.reference || '');
return `${learning}|${reference}`;
},
markCurrentAsHard() {
if (!this.current) return;
const key = this.getHardKey(this.current);
this.hardVocabMap = {
...this.hardVocabMap,
[key]: {
learning: this.current.learning,
reference: this.current.reference,
markedAt: new Date().toISOString()
}
};
this.saveHardVocabMap();
},
unmarkCurrentAsHard() {
if (!this.current) return;
const key = this.getHardKey(this.current);
if (!this.hardVocabMap[key]) return;
const next = { ...this.hardVocabMap };
delete next[key];
this.hardVocabMap = next;
this.saveHardVocabMap();
},
getLocalDateKey() {
const d = new Date();
const y = d.getFullYear();
@@ -381,6 +454,7 @@ export default {
this.lastWrongReview = null;
this.pendingRetry = null;
this.pool = [];
this.hardVocabMap = {};
this.locked = false;
this.resetQuestion();
this.$refs.dialog.open();
@@ -388,6 +462,7 @@ export default {
document.addEventListener('keydown', this.handleKeyDown);
});
this.reloadPool();
this.loadHardVocabMap();
},
close() {
if (this.autoAdvanceTimer) {

View File

@@ -461,6 +461,9 @@
"remaining": "Noch offen",
"success": "Erfolg",
"fail": "Misserfolg",
"hardCount": "Schwer markiert",
"markHard": "Als schwer markieren",
"unmarkHard": "Aus schwer entfernen",
"srsRateTitle": "Wie sicher war das?",
"srsAgain": "Nochmal",
"srsAgainHint": "sehr bald wiederholen",

View File

@@ -461,6 +461,9 @@
"remaining": "Remaining",
"success": "Success",
"fail": "Fail",
"hardCount": "Marked difficult",
"markHard": "Mark as difficult",
"unmarkHard": "Remove difficult mark",
"srsRateTitle": "How solid did it feel?",
"srsAgain": "Again",
"srsAgainHint": "repeat very soon",