feat(tender-details): enhance language translation handling in TenderDetails component

- Added useRef hooks to manage pending language changes and prevent retry loops during translation.
- Updated the language query parameter handling to ensure translations are only triggered when the user explicitly selects a language.
- Improved the useEffect logic to handle translation requests based on fetched data and user selections, enhancing user experience and performance.
This commit is contained in:
AmirReza Jamali
2026-05-20 07:27:00 +03:30
parent 7dbe1cc41c
commit d7a3db07ab
+52 -7
View File
@@ -10,7 +10,7 @@ import {
} from "@/hooks/queries";
import type { TTenderDetails } from "@/lib/api/types/Tenders";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { use, useCallback, useMemo } from "react";
import { use, useCallback, useEffect, useMemo, useRef } from "react";
import { TenderDetailsContent } from "./components/tender-details-content";
import { TenderDetailsError } from "./components/tender-details-error";
import { TenderDetailsHeader } from "./components/tender-details-header";
@@ -31,9 +31,10 @@ const TenderDetails = ({ params }: IProps) => {
const selectedLanguage = searchParams.get("lang") || "en";
const { mutate: translateTender, isPending: isTranslating } =
useTranslateTenderMutation();
const { data, isLoading, isError } = useTenderDetailQuery(details, {
lang: selectedLanguage,
});
const { data, isLoading, isError, isFetching } = useTenderDetailQuery(
details,
{ language: selectedLanguage },
);
const breadcrumbItems = [
{ name: "Dashboard", href: "/" },
@@ -63,6 +64,13 @@ const TenderDetails = ({ params }: IProps) => {
[tenderNavItems],
);
const activeSectionId = useTenderSectionScrollSpy(sectionIds);
// Only translate when the user explicitly picks a language from the dropdown.
// The ref is set inside handleLanguageChange and consumed by the effect once
// the GET refetch resolves; initial page loads never trigger a translate.
const userPendingLangRef = useRef<string | null>(null);
const attemptedRef = useRef<Set<string>>(new Set());
const handleLanguageChange = useCallback(
(event: React.ChangeEvent<HTMLSelectElement>) => {
const nextLanguage = event.target.value;
@@ -71,16 +79,53 @@ const TenderDetails = ({ params }: IProps) => {
const prevLanguage = searchParams.get("lang") || "en";
if (nextLanguage === prevLanguage) return;
userPendingLangRef.current = nextLanguage;
const nextParams = new URLSearchParams(searchParams.toString());
nextParams.set("lang", nextLanguage);
const queryString = nextParams.toString();
router.replace(queryString ? `${pathname}?${queryString}` : pathname);
translateTender({ id: details, language: nextLanguage });
},
[details, pathname, router, searchParams, translateTender],
[pathname, router, searchParams],
);
// ensureTranslation flow (per backend integration doc):
// 1) User picks a language → handleLanguageChange marks it as pending.
// 2) GET detail with ?language={selected} refetches.
// 3) If the refetched data.language doesn't match, POST ai-translate.
// The mutation's onSuccess invalidates the detail query → refetch binds title/description.
// attemptedRef prevents a retry loop if translation fails.
useEffect(() => {
const fetched = data?.data;
if (!fetched) return;
if (isFetching || isTranslating) return;
const pending = userPendingLangRef.current;
if (!pending || pending !== selectedLanguage) return;
if (fetched.language === selectedLanguage) {
userPendingLangRef.current = null;
return;
}
const key = `${details}::${selectedLanguage}`;
if (attemptedRef.current.has(key)) {
userPendingLangRef.current = null;
return;
}
attemptedRef.current.add(key);
userPendingLangRef.current = null;
translateTender({ id: details, language: selectedLanguage });
}, [
data,
details,
isFetching,
isTranslating,
selectedLanguage,
translateTender,
]);
if (isLoading) return <Loading />;
if (isError) {
return <TenderDetailsError breadcrumbItems={breadcrumbItems} />;