Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/api/translations.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)}`);
};
36 changes: 28 additions & 8 deletions src/translate/Translate.js
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
90 changes: 78 additions & 12 deletions src/translate/TranslateHistory.js
Original file line number Diff line number Diff line change
@@ -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 <LoadingAnimation />;
}

if (error) {
return <s.NoResults>{error}</s.NoResults>;
}

if (historyItems.length === 0) {
return (
<s.NoResults>
No translation history yet. Search for words in the Translate tab to see them here.
</s.NoResults>
);
}

return (
<ComingSoon>
Translation history coming soon...
</ComingSoon>
<s.ResultsContainer>
<s.ResultsHeader>Recent Searches</s.ResultsHeader>
{historyItems.map((item) => (
<s.TranslationCard
key={item.id}
onClick={() => handleItemClick(item)}
style={{ cursor: "pointer" }}
>
<s.TranslationHeader>
<s.TranslationInfo>
<s.TranslationText>{item.search_word}</s.TranslationText>
</s.TranslationInfo>
<s.TranslationSource>{formatTime(item.search_time)}</s.TranslationSource>
</s.TranslationHeader>
</s.TranslationCard>
))}
</s.ResultsContainer>
);
}