Building a Simple from English to German Translator using NextJS and ChatGPT API
Tags: nextjs, react, tutorial, openai
Published: 2023-03-25 12:31:29

I arrived in Germany about a week ago, and my current goal is to secure a development position in Frankfurt, I have been trying to learn German. Since I'm not the type of person who studies with books, I plan to study German by texting with German-speaking friends until I reach a certain level. In the mean time, I have been using Google Translator and ChatGPT to translate and learn from the translated sentences. The keywords that I often use with ChatGPT are following.

- Correct this ""
- "" in German and break it down

I use first one to correct my English sentences and the second one to translate my sentence into German and get some explanation. However, I found it bothersome that I have to type the same thing every time. So, I thought it would be good to create my own German translator to make the process easier. Additionally, if I add a TTS(Text-to-Speech) feature to the app, it would definitely save me a lot of time because I use TTS all the time to listen to the pronunciation of German words. 

For the first attempt, I decided to create a simple example using OpenAI API. I also chose NextJS and Vercel, thinking it would save me a lot of time too since they both use one language(javascript) and there's no need for a separate server with NextJS. Plus, deploying with Vercel is just a one-click process.

---

[Try it out](https://openai-simple-translator.vercel.app/)

![Translator Main Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sth4onke6rftizy37i90.png)

You can obtain your own API KEY from [OpenAI](https://openai.com/), then enter the key in the input box and click on `Register`. If you have entered the correct key, the translate button will be enabled after refreshing the page. 

![Translator Result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ibkb5hu5yggylf4a2x6q.png)

Enter any text you want to translate into German, then click the `Translate` button. The result will be displayed as shown above.

---

[pages/api/key/register.ts]
```typescript
import type { NextApiRequest, NextApiResponse } from "next";
import { setCookie } from "cookies-next";

type Param = {
  key: string;
};

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== "POST") {
    return res.status(405).end();
  }

  const param: Param = req.body;
  setCookie("key", param.key, { req, res });

  res.status(200).end();
}
```

I used the library `cookies-next` to handle cookies. `Register` API saves an API key in cookies that it obtained from the request, and it will allow you to use the same key even after closing the tab.

[pages/api/key/unregister.ts]
```typescript
import type { NextApiRequest, NextApiResponse } from "next";
import { deleteCookie } from "cookies-next";

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  deleteCookie("key", { req, res });
  res.status(200).end();
}
```

`Unregister` API deletes an API KEY from cookies.

[pages/api/translate/german.ts]
```typescript
import type { NextApiRequest, NextApiResponse } from "next";
import { getCookie } from "cookies-next";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const key = getCookie("key", { req, res });
  const content = `"${req.body.message}" in German and break it down`;

  const apiRes = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "post",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${key}`,
    },
    body: JSON.stringify({
      model: "gpt-3.5-turbo",
      messages: [{ role: "user", content }],
    }),
    // body: JSON.stringify({
    //   model: "gpt-3.5-turbo",
    //   messages: [
    //     { role: "system", content: "You are a great German teacher." },
    //     {
    //       role: "assistant",
    //       content:
    //         "Yes, I am. I will translate your German sentence and explain the detail about the translated sentence. ",
    //     },
    //     { role: "user", content },
    //   ],
    // }),
  });

  if (apiRes.status !== 200) {
    return res.status(500).send(`open ai API status code: ${apiRes.status}`);
  }

  const json = await apiRes.json();

  res.status(200).json({
    message: !json.choices?.length ? "" : json.choices[0].message.content,
  });
}
```

`German` API receives text from a client, sends a request to the OpenAI API, and then sends the response of the API back to the client.

You can create your own conversation and use it to generate chat completions, like the following.

```typescript
...

     body: JSON.stringify({
       model: "gpt-3.5-turbo",
       messages: [
         { role: "system", content: "You are a great German teacher." },
         {
           role: "assistant",
           content:
             "Yes, I am. I will translate your German sentence and explain the detail about the translated sentence. ",
         },
         { role: "user", content },
       ],
     }),

...
```

But in my case, using one message was enough and looked better, so I passed only one message to the Chat API.

```typescript
const content = `"${req.body.message}" in German and break it down`;

...

    body: JSON.stringify({
      model: "gpt-3.5-turbo",
      messages: [{ role: "user", content }],
    }),
```

[page/index.tsx]
```typescript
import Head from "next/head";
import { useEffect, useRef, useState } from "react";
import { getCookies } from "cookies-next";
import { TmpCookiesObj } from "cookies-next/lib/types";
import { GetServerSideProps } from "next";

interface PageProps {
  cookies: TmpCookiesObj;
}

export const getServerSideProps: GetServerSideProps<PageProps> = async (
  context
) => {
  const cookies = getCookies({
    req: context.req,
    res: context.res,
  });
  return {
    props: {
      cookies,
    },
  };
};

export default function Home({ cookies }: PageProps) {
  const [translating, setTranslating] = useState(false);
  const keyInputRef = useRef<HTMLInputElement>(null);
  const messageInputRef = useRef<HTMLTextAreaElement>(null);
  const resultTextRef = useRef<HTMLDivElement>(null);
  const key = cookies["key"];
  const keyRegistered = key !== undefined;

  const handleRegisterClick = async () => {
    if (keyRegistered) {
      await fetch("api/key/unregister");
      location.reload();
    } else {
      const key = keyInputRef.current?.value.trim() || "";

      if (!key?.length) {
        alert("Enter your key");
        return;
      }

      await fetch("api/key/register", {
        method: "post",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          key,
        }),
      });
    }
    location.reload();
  };

  const translate = async () => {
    setTranslating(true);
    try {
      const res = await fetch("api/translate/german", {
        method: "post",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          message: messageInputRef.current?.value || "",
        }),
      });
      const json = await res.json();

      if (resultTextRef.current) {
        resultTextRef.current.innerText = json.message || "";
      }
    } finally {
      setTranslating(false);
    }
  };

  useEffect(() => {
    if (!keyInputRef.current) return;

    keyInputRef.current.value = key || "";
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <>
      <Head>
        <title>Translator</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className="box-border w-full px-4 mt-8 md:mx-auto md:w-3/6">
        <section className="border p-4 mb-4">
          <div className="flex items-center">
            <label className="w-44">OpenAI Secret Key</label>
            <input
              ref={keyInputRef}
              type="text"
              placeholder="Enter your key"
              className="border h-8 p-2 disabled:opacity-50"
              disabled={keyRegistered}
            />
          </div>
          <button
            type="button"
            className="border bg-sky-500 text-white p-2 rounded hover:opacity-70"
            onClick={handleRegisterClick}
          >
            {keyRegistered ? "Unregister" : "Register"}
          </button>
        </section>
        <section className="border p-4">
          <div className="flex flex-col gap-y-2">
            <label className="w-44">English</label>
            <textarea
              placeholder="Input"
              className="border h-32 p-4"
              ref={messageInputRef}
            ></textarea>
          </div>
          <button
            type="button"
            className="border bg-sky-500 text-white p-2 rounded hover:opacity-70 mt-4 disabled:opacity-25"
            disabled={!keyRegistered || translating}
            onClick={translate}
          >
            Translate
          </button>
          <div
            className="border w-100 h-60 mt-4 p-4 overflow-y-auto"
            ref={resultTextRef}
          >
            Result
          </div>
        </section>
      </main>
    </>
  );
}
```

---

There are many models, features and API options available. You can check in [OpenAI API Docs](https://platform.openai.com/docs/introduction). To create the example quickly, I just gave a quick view and wrote code. I recommend you to read the documentation thoroughly before getting started.

In the future, I plan to create a better version of this app that includes a TTS feature and creates several chat completions at once I want. I will use it as my German teacher. I will share about it later.

It's amazing how technology has made our lives easier. I'm grateful to be living in this generation.

Thank you for reading my article, and I hope you found it helpful.

Happy Coding!

---

[App](https://openai-simple-translator.vercel.app/)

[Github Source](https://github.com/hsk-kr/openai-simple-translator)