번역기, AI로 10분 만에 직접 만들어 쓰기
API 키 없이 7개 언어를 지원하는 번역기를 직접 만들었습니다. MyMemory 무료 API와 AI 프롬프트 3단계로 완성한 과정을 공유합니다.
위 화면은 이 포스트의 프롬프트를 AI에 입력해서 생성한 샘플입니다
번역기, 왜 직접 만드나?
구글 번역은 훌륭합니다. 하지만 막상 쓰다 보면 아쉬운 점이 생깁니다.
- 매번 탭을 새로 열거나 구글 번역 사이트로 이동해야 합니다
- 긴 텍스트를 번역하고 나서 다른 언어로도 보고 싶을 때 다시 복붙해야 합니다
- 내 블로그나 앱에 자연스럽게 통합된 번역 도구가 있으면 좋겠다 싶습니다
이런 불편함을 느꼈다면, 직접 만드는 게 답입니다. 실제로 만들어보니 무료 API 연결까지 포함해서 약 15분이 걸렸습니다.
완성된 번역기 살펴보기
위의 라이브 데모에서 바로 써보세요.
지원 언어: 한국어, 영어, 일본어, 중국어(간체), 스페인어, 프랑스어, 독일어
왼쪽에 번역할 텍스트를 입력하고 언어를 선택한 뒤 번역하기 버튼을 누르면 오른쪽에 결과가 나옵니다. ↔ 버튼으로 언어를 바꿀 수 있고, 결과 텍스트는 복사 버튼 한 번으로 클립보드에 저장됩니다.
단축키 Ctrl+Enter (Mac은 Cmd+Enter)로도 번역을 실행할 수 있습니다.
무료 번역 API — MyMemory
이 번역기는 MyMemory API를 사용합니다. 완전 무료에 API 키가 필요 없습니다.
https://api.mymemory.translated.net/get?q={텍스트}&langpair={from}|{to}
예시: 한국어 → 영어
https://api.mymemory.translated.net/get?q=안녕하세요&langpair=ko|en
응답 예시:
{
"responseData": {
"translatedText": "Hello",
"match": 1
},
"responseStatus": 200
}
무료 티어는 하루 5,000자까지 번역이 가능합니다. 개인 사용이나 소규모 프로젝트에는 충분한 용량입니다.
AI에게 어떻게 요청했나?
번역기를 처음부터 설계하려면 API 연동, 언어 코드, UI 레이아웃 등 고려할 게 많습니다. AI에게 명확하게 요청하면 이 모든 걸 한 번에 해결할 수 있습니다.
1단계 프롬프트 — 기본 번역기
Next.js 14 + TypeScript + Tailwind CSS로 번역기 컴포넌트를 만들어줘.
요구사항:
- 'use client' 컴포넌트
- 지원 언어: 한국어(ko), 영어(en), 일본어(ja), 중국어 간체(zh-CN), 스페인어(es), 프랑스어(fr), 독일어(de)
- 왼쪽: 입력 textarea + 언어 선택
- 가운데: ↔ swap 버튼
- 오른쪽: 번역 결과 + 복사 버튼
- MyMemory 무료 API 사용 (키 없음):
fetch('https://api.mymemory.translated.net/get?q=TEXT&langpair=FROM|TO')
- 번역 버튼 클릭 시 API 호출
- 로딩 스피너 표시
- 에러 처리
2단계 프롬프트 — 기능 개선
다음을 추가해줘:
1. Ctrl+Enter (Mac: Cmd+Enter) 단축키로 번역 실행
2. swap 버튼: 언어 교체 + 번역 결과를 입력창으로 이동
3. 번역 결과 복사 후 "복사됨!" 피드백 (2초 후 원복)
4. 입력 글자 수 / 5000 표시
3단계 프롬프트 — UI 완성
모바일 대응 반응형으로 수정해줘:
- 모바일: 세로 레이아웃 (입력 → 버튼 → 결과)
- 데스크톱: 가로 레이아웃 (입력 | 버튼 | 결과)
- 결과 영역은 수정 불가 (readOnly)
- 각 영역에 min-height: 160px 적용
코드 핵심 포인트
1. API 호출 함수
async function translate(text: string, from: string, to: string): Promise<string> {
const url = `https://api.mymemory.translated.net/get?q=${encodeURIComponent(text)}&langpair=${from}|${to}`
const res = await fetch(url)
const data = await res.json()
if (data.responseStatus !== 200) {
throw new Error(data.responseDetails || '번역에 실패했습니다')
}
return data.responseData.translatedText
}
encodeURIComponent가 핵심입니다. 한국어, 특수문자, 공백이 포함된 텍스트를 URL에 안전하게 담기 위해 반드시 필요합니다.
2. Swap 로직
단순히 언어만 바꾸는 게 아닙니다. 번역 결과를 새 입력값으로 넣어야 자연스럽습니다.
const handleSwap = () => {
setFromLang(toLang)
setToLang(fromLang)
setInputText(resultText) // 결과가 새 입력이 됨
setResultText(inputText) // 기존 입력이 새 결과 위치로
}
3. 키보드 단축키
const handleKeyDown = (e: React.KeyboardEvent) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault()
handleTranslate()
}
}
metaKey는 Mac의 Command 키입니다. ctrlKey와 함께 체크하면 Windows/Mac 모두 대응됩니다.
활용 예시 4가지
1. 블로그 제목 영문화 한국어로 쓴 포스트 제목을 영어로 번역해서 URL slug나 SEO 메타 태그에 활용합니다.
2. 이메일 초안 번역 영어 이메일을 받았을 때 한국어로 빠르게 이해하거나, 한국어로 쓴 답장을 영어로 바꿀 때 씁니다.
3. 다국어 콘텐츠 제작 블로그 포스트 요약을 여러 언어로 만들어 소셜 미디어에 올릴 때 유용합니다.
4. 개발 관련 영문 오류 번역 스택오버플로우의 영문 답변이나 GitHub 이슈를 빠르게 한국어로 확인합니다.
한계와 주의사항
무료 API의 한계 MyMemory API는 하루 5,000자 제한이 있습니다. 그 이상 사용하려면 이메일 주소를 쿼리 파라미터에 추가하면 하루 50,000자로 늘어납니다.
?q=TEXT&langpair=ko|en&de=your@email.com
번역 품질 구글 번역이나 DeepL보다 품질이 낮을 수 있습니다. 짧은 문장이나 간단한 텍스트에는 충분하지만, 전문 문서 번역에는 적합하지 않습니다.
이렇게 확장할 수 있습니다
- DeepL API 연동: 더 높은 번역 품질 (무료 50만자/월)
- 자동 언어 감지: 입력 언어를 자동으로 감지
- 번역 이력: 최근 번역 10개 로컬 저장
- 즐겨찾기 언어 쌍: 자주 쓰는 ko→en 같은 조합을 바로 선택
자주 묻는 질문
Q. API 키 없이 정말 무료인가요? 네, MyMemory는 API 키 없이 하루 5,000자까지 무료입니다.
Q. 번역 품질이 구글 번역과 다른가요? 다를 수 있습니다. 일반적인 문장은 비슷하지만, 전문 용어나 긴 문단은 차이가 날 수 있습니다.
Q. 지원 언어를 추가할 수 있나요? MyMemory는 한국어, 영어, 일본어, 중국어, 스페인어, 프랑스어, 독일어 외에도 수십 개 언어를 지원합니다. 코드에서 LANGUAGES 배열에 언어 코드를 추가하면 됩니다.
다음 편 예고
다음 편은 환율 계산기입니다. 실시간 환율 API를 연결하는 방법과 12개 통화 간 실시간 변환기를 만들어봅니다.
'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>
)
}