feat(localization): add difficult vocabulary marking features in multiple languages
All checks were successful
Deploy to production / deploy (push) Successful in 1m58s
All checks were successful
Deploy to production / deploy (push) Successful in 1m58s
- Introduced new localization strings for marking vocabulary as difficult, including options to save and remove difficult marks. - Enhanced user experience by providing feedback on the status of difficult vocabulary in Cebuano, Spanish, and French. - Updated existing localization files to ensure consistency across languages, supporting the recent vocabulary management features.
This commit is contained in:
@@ -199,6 +199,9 @@ export default {
|
|||||||
locked: false,
|
locked: false,
|
||||||
autoAdvanceTimer: null,
|
autoAdvanceTimer: null,
|
||||||
hardVocabMap: {}, // { [normalizedPairKey]: { learning, reference, markedAt } }
|
hardVocabMap: {}, // { [normalizedPairKey]: { learning, reference, markedAt } }
|
||||||
|
hardPhaseActive: false,
|
||||||
|
hardMasteryByKey: {}, // { [hardKey]: consecutiveCorrect }
|
||||||
|
cycleAskedIds: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -294,6 +297,17 @@ export default {
|
|||||||
isCurrentMarkedHard() {
|
isCurrentMarkedHard() {
|
||||||
if (!this.current) return false;
|
if (!this.current) return false;
|
||||||
return Boolean(this.hardVocabMap[this.getHardKey(this.current)]);
|
return Boolean(this.hardVocabMap[this.getHardKey(this.current)]);
|
||||||
|
},
|
||||||
|
hardPoolItems() {
|
||||||
|
if (!Array.isArray(this.pool) || this.pool.length === 0) return [];
|
||||||
|
return this.pool.filter((item) => Boolean(this.hardVocabMap[this.getHardKey(item)]));
|
||||||
|
},
|
||||||
|
hardRemainingCount() {
|
||||||
|
const requiredConsecutiveCorrect = 2;
|
||||||
|
return this.hardPoolItems.filter((item) => {
|
||||||
|
const key = this.getHardKey(item);
|
||||||
|
return (Number(this.hardMasteryByKey[key]) || 0) < requiredConsecutiveCorrect;
|
||||||
|
}).length;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -341,6 +355,9 @@ export default {
|
|||||||
markedAt: new Date().toISOString()
|
markedAt: new Date().toISOString()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (this.hardMasteryByKey[key] == null) {
|
||||||
|
this.hardMasteryByKey[key] = 0;
|
||||||
|
}
|
||||||
this.saveHardVocabMap();
|
this.saveHardVocabMap();
|
||||||
},
|
},
|
||||||
unmarkCurrentAsHard() {
|
unmarkCurrentAsHard() {
|
||||||
@@ -350,8 +367,33 @@ export default {
|
|||||||
const next = { ...this.hardVocabMap };
|
const next = { ...this.hardVocabMap };
|
||||||
delete next[key];
|
delete next[key];
|
||||||
this.hardVocabMap = next;
|
this.hardVocabMap = next;
|
||||||
|
if (this.hardMasteryByKey[key] != null) {
|
||||||
|
const nextMastery = { ...this.hardMasteryByKey };
|
||||||
|
delete nextMastery[key];
|
||||||
|
this.hardMasteryByKey = nextMastery;
|
||||||
|
}
|
||||||
|
if (this.hardPhaseActive && this.hardRemainingCount <= 0) {
|
||||||
|
this.hardPhaseActive = false;
|
||||||
|
}
|
||||||
this.saveHardVocabMap();
|
this.saveHardVocabMap();
|
||||||
},
|
},
|
||||||
|
addCurrentToCycle() {
|
||||||
|
if (!this.current?.id) return;
|
||||||
|
if (this.cycleAskedIds.includes(this.current.id)) return;
|
||||||
|
this.cycleAskedIds = [...this.cycleAskedIds, this.current.id];
|
||||||
|
},
|
||||||
|
maybeStartHardPhase() {
|
||||||
|
if (this.srsMode || this.hardPhaseActive) return;
|
||||||
|
if (!this.pool.length || !this.hardPoolItems.length) return;
|
||||||
|
if (this.cycleAskedIds.length < this.pool.length) return;
|
||||||
|
this.hardPhaseActive = true;
|
||||||
|
},
|
||||||
|
maybeFinishHardPhase() {
|
||||||
|
if (!this.hardPhaseActive) return;
|
||||||
|
if (this.hardRemainingCount > 0) return;
|
||||||
|
this.hardPhaseActive = false;
|
||||||
|
this.cycleAskedIds = [];
|
||||||
|
},
|
||||||
getLocalDateKey() {
|
getLocalDateKey() {
|
||||||
const d = new Date();
|
const d = new Date();
|
||||||
const y = d.getFullYear();
|
const y = d.getFullYear();
|
||||||
@@ -455,14 +497,17 @@ export default {
|
|||||||
this.pendingRetry = null;
|
this.pendingRetry = null;
|
||||||
this.pool = [];
|
this.pool = [];
|
||||||
this.hardVocabMap = {};
|
this.hardVocabMap = {};
|
||||||
|
this.hardPhaseActive = false;
|
||||||
|
this.hardMasteryByKey = {};
|
||||||
|
this.cycleAskedIds = [];
|
||||||
this.locked = false;
|
this.locked = false;
|
||||||
this.resetQuestion();
|
this.resetQuestion();
|
||||||
this.$refs.dialog.open();
|
this.$refs.dialog.open();
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
document.addEventListener('keydown', this.handleKeyDown);
|
document.addEventListener('keydown', this.handleKeyDown);
|
||||||
});
|
});
|
||||||
this.reloadPool();
|
|
||||||
this.loadHardVocabMap();
|
this.loadHardVocabMap();
|
||||||
|
this.reloadPool();
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
if (this.autoAdvanceTimer) {
|
if (this.autoAdvanceTimer) {
|
||||||
@@ -736,6 +781,36 @@ export default {
|
|||||||
if (!nextId) return null;
|
if (!nextId) return null;
|
||||||
return items.find((it) => it.id === nextId) || null;
|
return items.find((it) => it.id === nextId) || null;
|
||||||
}
|
}
|
||||||
|
if (this.hardPhaseActive) {
|
||||||
|
const requiredConsecutiveCorrect = 2;
|
||||||
|
const hardItems = this.hardPoolItems.filter((item) => {
|
||||||
|
const key = this.getHardKey(item);
|
||||||
|
return (Number(this.hardMasteryByKey[key]) || 0) < requiredConsecutiveCorrect;
|
||||||
|
});
|
||||||
|
if (hardItems.length === 0) {
|
||||||
|
this.hardPhaseActive = false;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const rankedHard = hardItems
|
||||||
|
.map((item) => {
|
||||||
|
const key = this.getHardKey(item);
|
||||||
|
const mastery = Number(this.hardMasteryByKey[key]) || 0;
|
||||||
|
const st = this.perId[item.id] || { c: 0, w: 0, streak: 0, lastAsked: 0 };
|
||||||
|
return {
|
||||||
|
item,
|
||||||
|
mastery,
|
||||||
|
wrong: Number(st.w) || 0,
|
||||||
|
attempts: (Number(st.c) || 0) + (Number(st.w) || 0),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.mastery !== b.mastery) return a.mastery - b.mastery;
|
||||||
|
if (a.wrong !== b.wrong) return b.wrong - a.wrong;
|
||||||
|
if (a.attempts !== b.attempts) return a.attempts - b.attempts;
|
||||||
|
return Math.random() - 0.5;
|
||||||
|
});
|
||||||
|
return rankedHard[0]?.item || hardItems[Math.floor(Math.random() * hardItems.length)];
|
||||||
|
}
|
||||||
if (this.pendingRetry?.id) {
|
if (this.pendingRetry?.id) {
|
||||||
const retryItem = items.find((it) => it.id === this.pendingRetry.id);
|
const retryItem = items.find((it) => it.id === this.pendingRetry.id);
|
||||||
if (retryItem) {
|
if (retryItem) {
|
||||||
@@ -861,9 +936,23 @@ export default {
|
|||||||
if (isCorrect) {
|
if (isCorrect) {
|
||||||
st.c += 1;
|
st.c += 1;
|
||||||
st.streak = st.streak >= 0 ? st.streak + 1 : 1;
|
st.streak = st.streak >= 0 ? st.streak + 1 : 1;
|
||||||
|
if (this.hardPhaseActive && this.current) {
|
||||||
|
const key = this.getHardKey(this.current);
|
||||||
|
this.hardMasteryByKey[key] = Math.max(0, Number(this.hardMasteryByKey[key]) || 0) + 1;
|
||||||
|
if ((Number(this.hardMasteryByKey[key]) || 0) >= 2 && this.hardVocabMap[key]) {
|
||||||
|
const next = { ...this.hardVocabMap };
|
||||||
|
delete next[key];
|
||||||
|
this.hardVocabMap = next;
|
||||||
|
this.saveHardVocabMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
st.w += 1;
|
st.w += 1;
|
||||||
st.streak = st.streak <= 0 ? st.streak - 1 : -1;
|
st.streak = st.streak <= 0 ? st.streak - 1 : -1;
|
||||||
|
if (this.hardPhaseActive && this.current) {
|
||||||
|
const key = this.getHardKey(this.current);
|
||||||
|
this.hardMasteryByKey[key] = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
st.lastAsked = Date.now();
|
st.lastAsked = Date.now();
|
||||||
this.perId[id] = st;
|
this.perId[id] = st;
|
||||||
@@ -943,12 +1032,23 @@ export default {
|
|||||||
this.resetQuestion();
|
this.resetQuestion();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (this.current?.id) {
|
||||||
|
this.addCurrentToCycle();
|
||||||
|
}
|
||||||
|
this.maybeStartHardPhase();
|
||||||
|
this.maybeFinishHardPhase();
|
||||||
const retryDirection = this.pendingRetry?.direction || null;
|
const retryDirection = this.pendingRetry?.direction || null;
|
||||||
this.resetQuestion();
|
this.resetQuestion();
|
||||||
if (retryDirection) {
|
if (retryDirection) {
|
||||||
this.direction = retryDirection;
|
this.direction = retryDirection;
|
||||||
}
|
}
|
||||||
this.current = this.pickNextItem();
|
this.current = this.pickNextItem();
|
||||||
|
if (!this.current) {
|
||||||
|
this.maybeFinishHardPhase();
|
||||||
|
if (!this.hardPhaseActive) {
|
||||||
|
this.current = this.pickNextItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!this.current) return;
|
if (!this.current) return;
|
||||||
const prompt = this.currentPrompt;
|
const prompt = this.currentPrompt;
|
||||||
this.acceptableAnswers = this.getAnswersForPrompt(prompt, this.direction);
|
this.acceptableAnswers = this.getAnswersForPrompt(prompt, this.direction);
|
||||||
|
|||||||
@@ -503,6 +503,13 @@
|
|||||||
"trainerProgressNewContent": "Bag-ong sulod: {current}/{target}",
|
"trainerProgressNewContent": "Bag-ong sulod: {current}/{target}",
|
||||||
"trainerProgressReview": "Balik-balik: {count}",
|
"trainerProgressReview": "Balik-balik: {count}",
|
||||||
"trainerProgressMixShare": "Nasagol nga bahin: {percent}%",
|
"trainerProgressMixShare": "Nasagol nga bahin: {percent}%",
|
||||||
|
"markVocabHard": "Mark as difficult",
|
||||||
|
"markVocabHardSaved": "Vocabulary marked as difficult.",
|
||||||
|
"unmarkVocabHard": "Remove from difficult list",
|
||||||
|
"unmarkVocabHardSaved": "Vocabulary removed from difficult list.",
|
||||||
|
"hardVocabModeActive": "Intensive block: difficult vocabulary",
|
||||||
|
"hardVocabRemaining": "Remaining until stable: {count}",
|
||||||
|
"startHardVocabTrainer": "Train difficult vocabulary ({count})",
|
||||||
"unknownExerciseTypeNotice": "Kini nga matang sa ehersisyo wala pa ipakita nga interaktibo sa kasamtangang view.",
|
"unknownExerciseTypeNotice": "Kini nga matang sa ehersisyo wala pa ipakita nga interaktibo sa kasamtangang view.",
|
||||||
"unknownExerciseTypeLabel": "Matang: {type}",
|
"unknownExerciseTypeLabel": "Matang: {type}",
|
||||||
"lessonReviewHeadlineDone": "Nakaabot na kini nga leksiyon sa libre nga pagpalalom.",
|
"lessonReviewHeadlineDone": "Nakaabot na kini nga leksiyon sa libre nga pagpalalom.",
|
||||||
@@ -806,6 +813,9 @@
|
|||||||
"remaining": "Nahibilin",
|
"remaining": "Nahibilin",
|
||||||
"success": "Malampuson",
|
"success": "Malampuson",
|
||||||
"fail": "Fail",
|
"fail": "Fail",
|
||||||
|
"hardCount": "Marked difficult",
|
||||||
|
"markHard": "Mark as difficult",
|
||||||
|
"unmarkHard": "Remove difficult mark",
|
||||||
"srsRateTitle": "Unsa ka lig-on sa imong pagbati?",
|
"srsRateTitle": "Unsa ka lig-on sa imong pagbati?",
|
||||||
"srsAgain": "Usab",
|
"srsAgain": "Usab",
|
||||||
"srsAgainHint": "balikon dayon",
|
"srsAgainHint": "balikon dayon",
|
||||||
|
|||||||
@@ -457,7 +457,10 @@
|
|||||||
"askedVocab": "Preguntado:",
|
"askedVocab": "Preguntado:",
|
||||||
"stats": "Estadísticas",
|
"stats": "Estadísticas",
|
||||||
"success": "Éxito",
|
"success": "Éxito",
|
||||||
"fail": "Fallo"
|
"fail": "Fallo",
|
||||||
|
"hardCount": "Marcadas como difíciles",
|
||||||
|
"markHard": "Marcar como difícil",
|
||||||
|
"unmarkHard": "Quitar marca de difícil"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"open": "Buscar",
|
"open": "Buscar",
|
||||||
@@ -809,6 +812,13 @@
|
|||||||
"trainerProgressNewContent": "Contenido nuevo: {current}/{target}",
|
"trainerProgressNewContent": "Contenido nuevo: {current}/{target}",
|
||||||
"trainerProgressReview": "Repaso: {count}",
|
"trainerProgressReview": "Repaso: {count}",
|
||||||
"trainerProgressMixShare": "Parte mezclada: {percent}%",
|
"trainerProgressMixShare": "Parte mezclada: {percent}%",
|
||||||
|
"markVocabHard": "Mark as difficult",
|
||||||
|
"markVocabHardSaved": "Vocabulary marked as difficult.",
|
||||||
|
"unmarkVocabHard": "Remove from difficult list",
|
||||||
|
"unmarkVocabHardSaved": "Vocabulary removed from difficult list.",
|
||||||
|
"hardVocabModeActive": "Intensive block: difficult vocabulary",
|
||||||
|
"hardVocabRemaining": "Remaining until stable: {count}",
|
||||||
|
"startHardVocabTrainer": "Train difficult vocabulary ({count})",
|
||||||
"unknownExerciseTypeNotice": "Este tipo de ejercicio todavía no se muestra de forma interactiva en la vista actual.",
|
"unknownExerciseTypeNotice": "Este tipo de ejercicio todavía no se muestra de forma interactiva en la vista actual.",
|
||||||
"unknownExerciseTypeLabel": "Tipo: {type}",
|
"unknownExerciseTypeLabel": "Tipo: {type}",
|
||||||
"lessonReviewHeadlineDone": "Esta lección ya ha llegado a la fase de práctica libre.",
|
"lessonReviewHeadlineDone": "Esta lección ya ha llegado a la fase de práctica libre.",
|
||||||
|
|||||||
@@ -457,7 +457,10 @@
|
|||||||
"askedVocab": "Demandé :",
|
"askedVocab": "Demandé :",
|
||||||
"stats": "statistiques",
|
"stats": "statistiques",
|
||||||
"success": "Succès",
|
"success": "Succès",
|
||||||
"fail": "échec"
|
"fail": "échec",
|
||||||
|
"hardCount": "Marqués difficiles",
|
||||||
|
"markHard": "Marquer comme difficile",
|
||||||
|
"unmarkHard": "Retirer le marquage difficile"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"open": "Recherche",
|
"open": "Recherche",
|
||||||
@@ -809,6 +812,13 @@
|
|||||||
"trainerProgressNewContent": "Nouveau contenu : {current}/{target}",
|
"trainerProgressNewContent": "Nouveau contenu : {current}/{target}",
|
||||||
"trainerProgressReview": "Répéter : {count}",
|
"trainerProgressReview": "Répéter : {count}",
|
||||||
"trainerProgressMixShare": "Proportion de mélange : {pourcentage} %",
|
"trainerProgressMixShare": "Proportion de mélange : {pourcentage} %",
|
||||||
|
"markVocabHard": "Mark as difficult",
|
||||||
|
"markVocabHardSaved": "Vocabulary marked as difficult.",
|
||||||
|
"unmarkVocabHard": "Remove from difficult list",
|
||||||
|
"unmarkVocabHardSaved": "Vocabulary removed from difficult list.",
|
||||||
|
"hardVocabModeActive": "Intensive block: difficult vocabulary",
|
||||||
|
"hardVocabRemaining": "Remaining until stable: {count}",
|
||||||
|
"startHardVocabTrainer": "Train difficult vocabulary ({count})",
|
||||||
"unknownExerciseTypeNotice": "Ce type d'exercice n'est pas encore affiché de manière interactive dans la vue actuelle.",
|
"unknownExerciseTypeNotice": "Ce type d'exercice n'est pas encore affiché de manière interactive dans la vue actuelle.",
|
||||||
"unknownExerciseTypeLabel": "Tapez : {type}",
|
"unknownExerciseTypeLabel": "Tapez : {type}",
|
||||||
"lessonReviewHeadlineDone": "Cette leçon a atteint le niveau d'immersion libre.",
|
"lessonReviewHeadlineDone": "Cette leçon a atteint le niveau d'immersion libre.",
|
||||||
|
|||||||
Reference in New Issue
Block a user