diff --git a/src/api/translations.js b/src/api/translations.js
index 45ca1c5b5..efbee921e 100644
--- a/src/api/translations.js
+++ b/src/api/translations.js
@@ -244,3 +244,11 @@ Zeeguu_API.prototype.basicTranlsate = function (from_lang, to_lang, phrase) {
body: `phrase=${phrase}`,
});
};
+
+Zeeguu_API.prototype.getTranslationHistory = function (limit = 50) {
+ return this.apiGet(`/translation_history?limit=${limit}`).then((res) => res.data);
+};
+
+Zeeguu_API.prototype.logTranslationSearch = function (searchWord) {
+ return this._post("log_translation_search", `search_word=${encodeURIComponent(searchWord)}`);
+};
diff --git a/src/translate/Translate.js b/src/translate/Translate.js
index f6a2f242e..e9c28bd2c 100644
--- a/src/translate/Translate.js
+++ b/src/translate/Translate.js
@@ -1,4 +1,5 @@
import React, { useContext, useState, useEffect, useRef } from "react";
+import { useLocation } from "react-router-dom";
import { toast } from "react-toastify";
import { APIContext } from "../contexts/APIContext";
import { UserContext } from "../contexts/UserContext";
@@ -51,6 +52,7 @@ export default function Translate() {
const api = useContext(APIContext);
const { userDetails } = useContext(UserContext);
const { updateExercisesCounter } = useContext(ExercisesCounterContext);
+ const location = useLocation();
const learnedLang = userDetails?.learned_language;
const nativeLang = userDetails?.native_language;
@@ -87,6 +89,18 @@ export default function Translate() {
setTitle("Translate");
}, []);
+ // Handle searchWord passed from history navigation
+ useEffect(() => {
+ if (location.state?.searchWord) {
+ const word = location.state.searchWord;
+ setSearchWord(word);
+ performSearch(word, true); // Skip logging - already in history
+ // Clear the state so refreshing doesn't re-trigger
+ window.history.replaceState({}, document.title);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [location.state]);
+
function getTranslationKey(translation) {
return translation.toLowerCase();
}
@@ -156,14 +170,8 @@ export default function Translate() {
return true;
}
- function handleSearch(e) {
- e.preventDefault();
- if (!searchWord.trim()) return;
-
- const word = searchWord.trim();
-
- // Validate input
- if (!isValidWord(word)) {
+ function performSearch(word, skipHistory = false) {
+ if (!word || !isValidWord(word)) {
setError("Please enter a valid word or phrase");
return;
}
@@ -230,6 +238,11 @@ export default function Translate() {
setTranslations(finalTranslations);
setActiveDirection(direction);
+ // Log to history (only for new searches, not from history navigation)
+ if (!skipHistory && finalTranslations.length > 0) {
+ api.logTranslationSearch(word);
+ }
+
// Auto-fetch examples for each translation (skip for long phrases)
const wordCount = word.split(/\s+/).length;
if (wordCount <= 3 && finalTranslations.length > 0) {
@@ -258,6 +271,13 @@ export default function Translate() {
});
}
+ function handleSearch(e) {
+ e.preventDefault();
+ const word = searchWord.trim();
+ if (!word) return;
+ performSearch(word);
+ }
+
// displayKey: the key used for caching (based on what's displayed in UI)
// word: the word in learned language (for generating examples)
// translation: the translation in native language
diff --git a/src/translate/TranslateHistory.js b/src/translate/TranslateHistory.js
index 016b772b8..ce7f1c369 100644
--- a/src/translate/TranslateHistory.js
+++ b/src/translate/TranslateHistory.js
@@ -1,17 +1,83 @@
-import React from "react";
-import styled from "styled-components";
-
-const ComingSoon = styled.div`
- text-align: center;
- padding: 3rem 1rem;
- color: #666;
- font-style: italic;
-`;
+import React, { useContext, useState, useEffect } from "react";
+import { useHistory } from "react-router-dom";
+import { formatDistanceToNow } from "date-fns";
+import { APIContext } from "../contexts/APIContext";
+import LoadingAnimation from "../components/LoadingAnimation";
+import { setTitle } from "../assorted/setTitle";
+import * as s from "./Translate.sc";
export default function TranslateHistory() {
+ const api = useContext(APIContext);
+ const history = useHistory();
+
+ const [historyItems, setHistoryItems] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState("");
+
+ useEffect(() => {
+ setTitle("Translation History");
+ loadHistory();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ function loadHistory() {
+ setIsLoading(true);
+ setError("");
+
+ api.getTranslationHistory(50)
+ .then((data) => {
+ setHistoryItems(data);
+ setIsLoading(false);
+ })
+ .catch((err) => {
+ console.error("Failed to load translation history:", err);
+ setError("Failed to load history");
+ setIsLoading(false);
+ });
+ }
+
+ function handleItemClick(item) {
+ history.push("/translate", { searchWord: item.search_word });
+ }
+
+ function formatTime(isoString) {
+ return formatDistanceToNow(new Date(isoString), { addSuffix: true })
+ .replace("about ", "");
+ }
+
+ if (isLoading) {
+ return ;
+ }
+
+ if (error) {
+ return {error};
+ }
+
+ if (historyItems.length === 0) {
+ return (
+
+ No translation history yet. Search for words in the Translate tab to see them here.
+
+ );
+ }
+
return (
-
- Translation history coming soon...
-
+
+ Recent Searches
+ {historyItems.map((item) => (
+ handleItemClick(item)}
+ style={{ cursor: "pointer" }}
+ >
+
+
+ {item.search_word}
+
+ {formatTime(item.search_time)}
+
+
+ ))}
+
);
}