GCP Translate API를 이용해서 번역 앱 만들어보기

박경현·2024년 10월 8일

드디어 다시 GCP가 풀려서!! 다시 적극적으로 사용할 수 있게 되었습니다..ㅠ

사실 간단한 VM이나 몇가지만 해보았기 때문에 이번에는 GCP에 있는 기능들을 사용한 몇가지 프로젝트를 만들어보면서 익혀보았습니다.

Translate API는 과금이 비쌀까?
처음 100만 글자는 2만원이라 연습용으로 만드는 프로젝트에서는 거의 과금이 안든다고 생각해도 되서
부담없이 사용했습니다.^^

기본 틀 설계

위에 사진처럼 원하는 언어(영어, 중국어, 일본어, 한국어)를 선택하고 내가 친 단어를 번역해달라고 하면
왼쪽에 있는 박스에서 번역한 결과가 나옵니다.

파파고 처럼 보여기(?) 위해서 UI는 최대한 간단하게 만들었습니다

추가적으로 이번 프로젝트에서는 Next js가 가진 장점들을 사용해보려고 많이 노력했습니다

.env.local에 숨겨진 놀라운 사실...!

.env.local에 gcp의 TranslateAPI를 사용하기 위해 받은 키를 저장했습니다.

이때 Next js의 경우 CSR과 SSR방식으로 크게 나뉘는데 각각 변수명이 달라집니다.

SSR의 경우 -> 서버사이드렌더링이라 서버에서 API를 부르기 때문에 변수명은 딱히 중요하지 않습니다.
CSR의 경우 -> NEXTPUBLIC변수명 이런식으로 적어줘야 Client 컴포넌트에서 불러와서 사용할 수 있습니다.

not-found.tsx

Next js가 가진 강점 중 하나인, 잘못된 경로로 갔을때 not Found페이지로 컴포넌트를 만들 수 있습니다.

app안에 not-found.tsx라고해서 만들어주면 됩니다 -> 매우 간단

여기서는 잘못들어왔습니다... 라는 문구만 나오지만
나중에 더 발전시켜서 "번역페이지로 이동"이라는 버튼을 만들어서 바로 이동할 수 있게끔 만들 예정입니다.

app/api/translate/route.ts

Next js의 핵심 중 하나인 API를 폴더명으로 부르는 방식으로 작성했습니다.
이러면 page에서 api/translate로만 부르면 각 HTTP method로 가서 처리를 해줍니다.

NextResponse

API라우터나 미들웨어에서 HTTP 응답을 생성하고 반환할때 사용합니다.

import {NextResponse} from 'next/server';

export async function POST(req: Request) {
	const {text, targetLanguage} = await req.json();
    const apiKey = process.env.GOOGLE_TRANSLATE_API_KEY;
    
    try {
    	const response = await fetch(`https://translation.googleapis.com/language/translate/v2?key=${apiKey}`)
        {
        	method; 'POST',
            headers : {
            	'Content-Type' : 'application/json',
            },
            body: JSON.stringify({
            	q: text,
                target: targetLanguage,
            }
          )
          if (response.ok) {
          	const data = await response.json()
            return NextResponse.json({translatedText: data.data.traslations[0].translatedText})
          } else {
          	return NextResponse.json(
        { error: 'Translation API request failed', details: response.statusText },
        { status: response.status }
      );
          }
        }
        
    } catch (error) {
    	return NextResponse.json({ error: 'Internal server error', details: error instanceof Error ? error.message : String(error) }, { status: 500 });
    }
}

page.tsx

이제 이부분이 UI이기 때문에 여기서 유저가 글을 작성하고 언어를 선택 후 번역하기를 누르면 왼쪽 박스에 나오게 해주면 됩니다.

추가로 React Hook을 사용해 동적으로 사용자와 대화하기 때문에 CSR로 만들었습니다.

"use client"

import {useState{ from 'react';

export default function Home() {
	const [text, setText] = useState("")
    const [target, setTarget] = useState('en')
    const [translatedText, setTranslatedText] = useState("")
    
    const handlerTranslate = async () => {
    	if (!text || !targetLanguage) {
        	console.error('Text or target Language is missing!!')
            return;
        }
        
        try {
        	const response = await fetch('api/translate', {
            	method: 'POST',
                headers : {
                	'Content-Type': 'application/json'
                },
                body : JSON.stringify({
                	text,
                    targetLanguage,
                })
            })
        if (response.ok) {
        	const {translatedText} = await.response.json();
            setTranslatedText(translatedText);
        }else {
        	 console.error('Translation API error:', response.status, response.statusText);
      	}
        } catch(error) {
        	console.error('Error during translation', error);
        }
    };
    
    
    return (
    <div className="h-screen p-4 bg-black">
      <h1 className="w-full my-8 font-extrabold text-center text-transparent text-8xl bg-clip-text bg-gradient-to-r from-blue-600 to-red-600">
  번역 서비스
</h1>

      <div className="flex flex-col items-center justify-center gap-8 md:flex-row">
        <div className="flex flex-col p-4 gap-2 border-[3px] border-yellow-500 rounded-lg md:w-[600px] md:h-[500px] lg:w-[700px] lg:h-[600px] ">

          <select className="py-4 pl-2 text-2xl" value={targetLanguage} onChange={(e) => setTargetLanguage(e.target.value)}>
              <option value="en">영어</option>
              <option value="ko">한국어</option>
              <option value="ja">일본어</option>
              <option value="zh-CN">중국어(간체)</option>
          </select>
          <textarea  className="flex-1 px-4 py-8 text-3xl" value={text} onChange={(e) => setText(e.target.value)} />

          <button className="py-8 text-white bg-green-500 rounded-lg" onClick={handleTranslate}>번역하기</button>
       </div>
        {
          <div className="flex flex-col p-2 gap-2 border-[3px] border-yellow-500 rounded-lg md:w-[600px] md:h-[500px] lg:w-[700px] lg:h-[600px]  ">
            <h2 className="p-4 mb-4 text-4xl font-extrabold text-white border-b border-white">변역 결과</h2>
            <p className="flex-1 pl-2 text-3xl text-white">{translatedText}</p>
          </div>
        }
      </div>
    </div>
  );
}

Dockerfile과 .dockerignore

깃허브액션을 이용해서 이미지를 Google Cloud Run을 이용해서 서버리스로 만들어보기 위해 일단
docker image를 만들었습니다.

FROM node:18-alpine

WORKDIR /app

COPY package.json .
COPY package-lock.json .

RUN npm install

COPY . .
RUN num build

CMD ["npm" , "start"]

이때 .dockerignore에 .env.local이 있으면 도커 이미지가 만들어졌을때 키가 없이 만들게 되기 때문에
여기에 포함시키면 안됩니다.

피드백

API가 너무 사용하기 쉽고 편하고 돈도 크게 걱정 안되서 번역관련된걸 만든다면 많이 사용할 예정입니다.

Cloud Run의 경우 인증 문제로 실패했으니 이것또한 다시 해보면서 해결해보자...

profile
SW로 문제를 해결하려는 열정만 있는 대학생

0개의 댓글