Files
LingvAI/pages/[[...slug]].tsx

183 lines
6.3 KiB
TypeScript
Raw Normal View History

2021-03-19 00:20:28 +01:00
import { useEffect, useReducer, FC, ChangeEvent } from "react";
import { GetStaticPaths, GetStaticProps, InferGetStaticPropsType } from "next";
2021-03-11 13:29:22 +01:00
import Router from "next/router";
2021-03-18 14:18:29 +01:00
import { Stack, VStack, HStack, IconButton } from "@chakra-ui/react";
2021-03-16 01:33:19 +01:00
import { FaExchangeAlt } from "react-icons/fa";
import { Layout, LangSelect, TranslationArea } from "../components";
import { useToastOnLoad } from "../hooks";
import { googleScrape, extractSlug, textToSpeechScrape } from "../utils/translate";
2021-03-19 00:20:28 +01:00
import { retrieveFiltered, replaceBoth } from "../utils/language";
2021-03-10 19:27:36 +01:00
import langReducer, { Actions, initialState } from "../utils/reducer";
const Page: FC<InferGetStaticPropsType<typeof getStaticProps>> = ({ home, translationRes, audio, errorMsg, initial }) => {
const [{ source, target, query, delayedQuery, translation, isLoading }, dispatch] = useReducer(langReducer, initialState);
2021-03-10 19:27:36 +01:00
2021-03-16 01:33:19 +01:00
const handleChange = (e: ChangeEvent<HTMLTextAreaElement | HTMLSelectElement>) => {
2021-03-10 19:27:36 +01:00
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(() => {
2021-03-18 23:47:12 +01:00
const timeout = setTimeout(() =>
dispatch({ type: Actions.SET_FIELD, payload: { key: "delayedQuery", value: query }}
), 1000);
return () => clearTimeout(timeout);
2021-03-13 12:00:41 +01:00
}, [query]);
2021-03-09 20:33:12 +01:00
2021-03-11 13:29:22 +01:00
useEffect(() => {
if (isLoading)
return;
2021-03-18 23:47:12 +01:00
if (!delayedQuery || delayedQuery === initialState.query)
return;
if (!home && !initial)
return;
if (!home && delayedQuery === initial.query && source === initial.source && target === initial.target)
return;
2021-03-13 12:00:41 +01:00
dispatch({ type: Actions.SET_FIELD, payload: { key: "isLoading", value: true }});
2021-03-18 23:47:12 +01:00
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);
}, []);
2021-03-10 18:15:57 +01:00
2021-03-10 21:31:50 +01:00
const { sourceLangs, targetLangs } = retrieveFiltered(source, target);
2021-03-19 00:20:28 +01:00
const { source: transLang, target: queryLang } = replaceBoth("exception", { source: target, target: source });
2021-03-10 18:15:57 +01:00
useToastOnLoad({
title: "Unexpected error",
description: errorMsg,
status: "error",
updateDeps: initial
});
return (
<Layout home={home}>
2021-03-18 14:18:29 +01:00
<VStack px={[8, null, 24, 40]} w="full">
<HStack px={[1, null, 3, 4]} w="full">
<LangSelect
id="source"
aria-label="Source language"
value={source}
onChange={handleChange}
langs={sourceLangs}
/>
<IconButton
aria-label="Switch languages"
icon={<FaExchangeAlt />}
colorScheme="lingva"
variant="ghost"
onClick={() => dispatch({ type: Actions.SWITCH_LANGS })}
isDisabled={source === "auto"}
/>
<LangSelect
id="target"
aria-label="Target language"
value={target}
onChange={handleChange}
langs={targetLangs}
/>
</HStack>
<Stack direction={["column", null, "row"]} w="full">
<TranslationArea
id="query"
aria-label="Translation query"
placeholder="Text"
value={query}
onChange={e => isLoading || handleChange(e)}
2021-03-19 00:20:28 +01:00
lang={queryLang}
audio={audio?.source}
2021-03-18 14:18:29 +01:00
/>
<TranslationArea
id="translation"
aria-label="Translation result"
placeholder="Translation"
value={translation ?? ""}
readOnly={true}
2021-03-19 00:20:28 +01:00
lang={transLang}
audio={audio?.target}
canCopy={true}
isLoading={isLoading}
2021-03-18 14:18:29 +01:00
/>
</Stack>
2021-03-16 01:33:19 +01:00
</VStack>
2021-03-18 14:18:29 +01:00
</Layout>
2021-03-09 20:33:12 +01:00
);
}
export default Page;
export const getStaticPaths: GetStaticPaths = async () => ({
paths: [
{
params: { slug: [] }
}
],
fallback: true
});
2021-03-11 13:29:22 +01:00
export const getStaticProps: GetStaticProps = async ({ params }) => {
if (!params?.slug || !Array.isArray(params.slug))
return {
2021-03-18 23:47:12 +01:00
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
};
}