import { useEffect, useReducer, FC, ChangeEvent } from "react"; import { GetStaticPaths, GetStaticProps, InferGetStaticPropsType } from "next"; import Router from "next/router"; import { Stack, VStack, HStack, IconButton } from "@chakra-ui/react"; import { FaExchangeAlt } from "react-icons/fa"; import { useHotkeys } from "react-hotkeys-hook"; import { CustomHead, LangSelect, TranslationArea } from "@components"; import { useToastOnLoad } from "@hooks"; import { googleScrape, extractSlug, textToSpeechScrape } from "@utils/translate"; import { retrieveFiltered, replaceBoth } from "@utils/language"; import langReducer, { Actions, initialState } from "@utils/reducer"; const Page: FC> = ({ home, translationRes, audio, errorMsg, initial }) => { const [{ source, target, query, delayedQuery, translation, isLoading }, dispatch] = useReducer(langReducer, initialState); const handleChange = (e: ChangeEvent) => { dispatch({ type: Actions.SET_FIELD, payload: { key: e.target.id, value: e.target.value } }); }; useEffect(() => { if (home) return dispatch({ type: Actions.SET_ALL, payload: { state: { ...initialState, isLoading: false } } }); if (!initial) return; dispatch({ type: Actions.SET_ALL, payload: { state: { ...initial, delayedQuery: initial.query, translation: translationRes, isLoading: false } } }); }, [initial, translationRes, home]); useEffect(() => { const timeout = setTimeout(() => dispatch({ type: Actions.SET_FIELD, payload: { key: "delayedQuery", value: query }} ), 1000); return () => clearTimeout(timeout); }, [query]); useEffect(() => { if (isLoading) return; if (!delayedQuery || delayedQuery === initialState.query) return; if (!home && !initial) return; if (!home && delayedQuery === initial.query && source === initial.source && target === initial.target) return; dispatch({ type: Actions.SET_FIELD, payload: { key: "isLoading", value: true }}); Router.push(`/${source}/${target}/${encodeURIComponent(delayedQuery)}`); }, [source, target, delayedQuery, initial, home, isLoading]); useEffect(() => { const handler = (url: string) => url === Router.asPath || dispatch({ type: Actions.SET_FIELD, payload: { key: "isLoading", value: true }}); Router.events.on("beforeHistoryChange", handler); return () => Router.events.off("beforeHistoryChange", handler); }, []); const { sourceLangs, targetLangs } = retrieveFiltered(); const { source: transLang, target: queryLang } = replaceBoth("exception", { source: target, target: source }); useToastOnLoad({ title: "Unexpected error", description: errorMsg, status: "error", updateDeps: initial }); const canSwitch = source !== "auto" && !isLoading; useHotkeys("ctrl+shift+s, command+shift+s, ctrl+shift+f, command+shift+f", () => ( canSwitch && dispatch({ type: Actions.SWITCH_LANGS }) ), [canSwitch]); return ( <> } colorScheme="lingva" variant="ghost" onClick={() => dispatch({ type: Actions.SWITCH_LANGS })} isDisabled={!canSwitch} /> isLoading || handleChange(e)} lang={queryLang} audio={audio?.source} /> ); }; export default Page; export const getStaticPaths: GetStaticPaths = async () => ({ paths: [ { params: { slug: [] } } ], fallback: true }); export const getStaticProps: GetStaticProps = async ({ params }) => { if (!params?.slug || !Array.isArray(params.slug)) return { props: { home: true } }; const { source, target, query } = extractSlug(params.slug); if (!query) return { notFound: true }; if (!source || !target) return { redirect: { destination: `/${source ?? "auto"}/${target ?? "en"}/${query}`, permanent: true } } const textScrape = await googleScrape(source, target, query); const [sourceAudio, targetAudio] = await Promise.all([ textToSpeechScrape(source, query), textToSpeechScrape(target, textScrape.translationRes) ]); return { props: { ...textScrape, audio: { source: sourceAudio, target: targetAudio }, initial: { source, target, query } }, revalidate: !textScrape.errorMsg ? 2 * 30 * 24 * 60 * 60 // 2 months : 1 }; };