번역기, AI로 10분 만에 직접 만들어 쓰기
API 키 없이 7개 언어를 지원하는 번역기를 직접 만들었습니다. MyMemory 무료 API와 AI 프롬프트 3단계로 완성한 과정을 공유합니다.
위 화면은 이 포스트의 프롬프트를 AI에 입력해서 생성한 샘플입니다
왜 번역기를 직접 만들었나?
구글 번역이나 파파고도 충분히 좋습니다. 그런데 저는 직접 만들고 싶었습니다.
블로그에 번역기를 붙여두면, 독자들이 포스트를 읽다가 모르는 표현을 바로 번역할 수 있습니다. 외부 사이트로 이동할 필요가 없죠. 게다가 내 블로그 디자인과 딱 맞게 스타일을 맞출 수 있습니다.
무엇보다, AI 프롬프트 몇 줄로 만들 수 있다는 것을 직접 확인하고 싶었습니다.
라이브 데모
위의 번역기를 직접 사용해보세요. 7개 언어(한국어, 영어, 일본어, 중국어, 스페인어, 프랑스어, 독일어)를 지원합니다.
- 왼쪽 영역에 텍스트를 입력합니다
- 원본 언어와 번역 언어를 선택합니다
- 번역하기 버튼을 누르거나 Ctrl+Enter를 눌러 번역합니다
- ↔ 버튼으로 언어를 바꿀 수 있습니다
API는 MyMemory의 무료 공개 API를 사용합니다. 별도 API 키가 필요 없습니다.
코드 핵심 포인트
1. MyMemory API 호출
GET https://api.mymemory.translated.net/get?q={텍스트}&langpair={from}|{to}
응답 구조:
{
"responseStatus": 200,
"responseData": {
"translatedText": "번역된 텍스트"
}
}
무료 플랜은 일일 5,000자까지 지원합니다. 개인 블로그 용도로는 충분합니다.
2. 언어 Swap 로직
단순히 from/to를 바꾸면 입력값은 그대로 남습니다. 더 자연스러운 UX를 위해, swap 시에 번역 결과가 새 입력값이 되도록 처리했습니다.
const handleSwap = () => {
setFromLang(prevTo) // from ↔ to 교환
setToLang(prevFrom)
setInputText(result || inputText) // 결과값을 새 입력으로
setResult(result ? inputText : '') // 원본을 새 결과로
}
3. Ctrl+Enter 단축키
긴 텍스트를 번역할 때 버튼까지 이동하기 귀찮습니다. textarea에서 Ctrl+Enter를 감지해 바로 번역 함수를 호출합니다.
onKeyDown={e => {
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) translate()
}}
마무리
API 키 없이 7개 언어를 지원하는 번역기를 10분 만에 완성했습니다.
바이브코딩의 핵심은 "어떻게 만드는지"보다 "무엇을 만들지"에 집중하는 것입니다. API 문서를 찾고, 컴포넌트 구조를 설계하는 시간을 AI에게 맡기면, 정작 중요한 기능 기획과 UX 설계에 더 많은 시간을 쓸 수 있습니다.
다음 편 예고
다음 편은 환율 계산기입니다. 실시간 환율 API를 연결하는 방법도 함께 다룹니다.
'use client'
import { useState, useCallback } from 'react'
import { ArrowLeftRight, Copy, Check } from 'lucide-react'
const LANGUAGES = [
{ label: '한국어', code: 'ko', flag: '🇰🇷' },
{ label: '영어', code: 'en', flag: '🇺🇸' },
{ label: '일본어', code: 'ja', flag: '🇯🇵' },
// ... 나머지 언어
]
export function Translator() {
const [fromLang, setFromLang] = useState('ko')
const [toLang, setToLang] = useState('en')
const [inputText, setInputText] = useState('')
const [result, setResult] = useState('')
const [isLoading, setIsLoading] = useState(false)
const translate = useCallback(async () => {
if (!inputText.trim()) return
setIsLoading(true)
try {
const url = `https://api.mymemory.translated.net/get?q=${encodeURIComponent(inputText)}&langpair=${fromLang}|${toLang}`
const res = await fetch(url)
const data = await res.json()
if (data.responseStatus !== 200) throw new Error(data.responseDetails)
setResult(data.responseData.translatedText)
} catch (e) {
console.error(e)
} finally {
setIsLoading(false)
}
}, [inputText, fromLang, toLang])
const handleSwap = () => {
setFromLang(toLang)
setToLang(fromLang)
setInputText(result || inputText)
setResult(result ? inputText : '')
}
return (
<div>
{/* 언어 선택 */}
<select value={fromLang} onChange={e => setFromLang(e.target.value)}>
{LANGUAGES.map(l => (
<option key={l.code} value={l.code}>{l.flag} {l.label}</option>
))}
</select>
<button onClick={handleSwap}>
<ArrowLeftRight /> 바꾸기
</button>
<select value={toLang} onChange={e => setToLang(e.target.value)}>
{LANGUAGES.map(l => (
<option key={l.code} value={l.code}>{l.flag} {l.label}</option>
))}
</select>
{/* 입력 */}
<textarea
value={inputText}
onChange={e => setInputText(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter' && e.ctrlKey) translate() }}
placeholder="번역할 텍스트..."
/>
{/* 번역 버튼 */}
<button onClick={translate} disabled={isLoading}>
{isLoading ? '번역 중...' : '번역하기'}
</button>
{/* 결과 */}
<div>{result}</div>
</div>
)
}