How to build a translations application using DeepL API’s?
Translations matter as much as accessibility in modern day , already responsive web-apps.
How to build a translations application using DeepL API’s?

Translations matter as much as accessibility in modern day , already responsive web-apps.
Having multilingual support helps your product cross borders and connect with thousands of users worldwide
DeepL’s AI-powered translation service, which uses neural networks (a form of deep learning, hence the name “DeepL”) to provide very natural and accurate translations between multiple languages . It tends to better capture context, idioms, and subtle meanings. Whereas Google Translate covers many more languages but sometimes produces more literal, less fluent semantic translations.
Some may question why to choose DeepL over Google Translate or any other translation service. The choice basically boils down to your use case - whether you need Google’s broader language support and ecosystem integration, or prefer DeepL’s more focused, privacy-conscious approach with professional features.
What DeepL API’s offer?
With the DeepL API you can translate text across 30+ languages, upload entire documents like Word, PowerPoint, and PDF while preserving formatting, and even define custom glossaries to keep brand terminology consistent. Features like formality control and semantic improvements give you flexibility, while enterprise-grade encryption and GDPR compliance ensure your data stays secure.
Here we will cover for text translations with & without glossaries.

Our end-product : Handcrafted backend, vibe coded frontend 🚀
The backend is built with Python, while the frontend is developed using React.js and styled with Tailwind CSS v4
BACKEND
Intialize the python project with uv init .
Packages used :
"python" = ">=3.13",
"dotenv>=0.9.9",
"fastapi>=0.116.1", //define the api and it's logic
"python-dotenv>=1.1.1",
"requests>=2.32.5",
"uvicorn>=0.35.0", // run's the api server so that it's accessible
// over the internet
main.py
#imports
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Optional
from dotenv import load_dotenv
import os
import requests
import uvicorn
load_dotenv()
app = FastAPI(title="DeepL Translation API", version="1.0.0")
# Enable CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Configuration
DEEPL_API_KEY = os.getenv("DEEPL_API_KEY")
DEEPL_BASE_URL = "https://api-free.deepl.com/v2"
Pydantic models are Python classes that define the structure and validation rules for data in FastAPI (and other Python projects). They are created by inheriting from pydantic.BaseModel . Just like typescript interfaces and types.
# Pydantic models
class TranslationRequest(BaseModel):
text: str
target_lang: str = "ES"
source_lang: str = "EN"
glossary_id: Optional[str] = None
context: Optional[str] = None
split_sentences: str = "1"
preserve_formatting: str = "1"
formality: str = "default"
show_billed_characters: str = "1"
class GlossaryRequest(BaseModel):
name: str
source_lang: str
target_lang: str
entries: str
entries_format: str = "csv" # or "tsv"
We have a helper function, which builds the request URLs and headers, sends those requests to DeepL API depending on the method arg, returns responses in json/text format and handles errors and exceptions.
# Helper function for API calls
def call_deepl_api(endpoint: str, method: str = "GET", data: dict = None):1
"""Make API call to DeepL"""
headers = {
"Authorization": f"DeepL-Auth-Key {DEEPL_API_KEY}",
"Content-Type": "application/x-www-form-urlencoded",
}
url = f"{DEEPL_BASE_URL}/{endpoint}"
try:
if method == "GET":
response = requests.get(url, headers=headers, params=data)
elif method == "POST":
response = requests.post(url, headers=headers, data=data)
elif method == "DELETE":
response = requests.delete(url, headers=headers)
# Handle different response types
if response.status_code == 204:
return {"message": "Success"}
if not response.ok:
raise HTTPException(
status_code=response.status_code,
detail=f"DeepL API error: {response.text}",
)
# Try to return JSON, fallback to text
try:
return response.json()
except:
return {"message": response.text}
except requests.exceptions.RequestException as e:
raise HTTPException(status_code=500, detail=f"Request failed: {str(e)}")
Endpoints :
Check the usage limit (since we are using the free-tier, which is capped at 500000 characters)
@app.get("/usage")
async def get_usage():
"""Get API usage information"""
return call_deepl_api("usage")
Translate endpoint — We can pass some parameters which would help us to get translations as per our use case
glossary_id: Optional[str] = None
# Specifies a glossary to use for translation, if provided.
context: Optional[str] = None.
# Extra context to improve translation accuracy. (for eg: medical,law)
split_sentences: str = "1"
# Split sentences at new lines
preserve_formatting: str = "1"
# Keep the original formatting of the text being translated
formality: str = "default"
# Set the translation formality level between:
# more - for more formal lang
# less - for les formal lang
# prefer_more - for a more formal language if available,
# otherwise fallback to default formality
# prefer_less - for a more informal language if available,
# otherwise fallback to default formality
show_billed_characters: str = "1"
# show the usage limit in the API response
@app.post("/translate")
async def translate_text(request: TranslationRequest):
"""Translate text"""
params = {
"text": request.text,
"source_lang": request.source_lang,
"target_lang": request.target_lang,
# pass more parameters(listed above) as per our usecase
}
if request.glossary_id:
params["glossary_id"] = request.glossary_id
result = call_deepl_api("translate", "POST", params)
# Return in expected format for React app
return {
"translations": [
{
"text": result["translations"][0]["text"],
"detected_source_language": request.source_lang,
}
]
}
Glossaries:
Get a list of all glossaries.
@app.get("/glossaries")
async def get_glossaries():
"""Get all glossaries"""
return call_deepl_api("glossaries")
Create a new glossary with new entries in csv or tsv format
@app.post("/glossaries")
async def create_glossary(request: GlossaryRequest):
"""Create a new glossary"""
params = {
"name": request.name,
"source_lang": request.source_lang,
"target_lang": request.target_lang,
"entries": request.entries,
"entries_format": request.entries_format,
}
return call_deepl_api("glossaries", "POST", params)
Delete a particular glossary
@app.delete("/glossaries/{glossary_id}")
async def delete_glossary(glossary_id: str):
"""Delete a specific glossary"""
call_deepl_api(f"glossaries/{glossary_id}", "DELETE")
return {"message": f"Glossary {glossary_id} deleted successfully"}
Delete all the glossaries added
async def delete_all_glossaries():
"""Delete all glossaries"""
# Get all glossaries first
result = call_deepl_api("glossaries")
glossaries = result.get("glossaries", [])
if not glossaries:
return {"message": "No glossaries to delete", "deleted_count": 0}
# Delete each glossary
deleted_count = 0
for glossary in glossaries:
try:
call_deepl_api(f"glossaries/{glossary['glossary_id']}", "DELETE")
deleted_count += 1
except:
continue
return {
"message": f"Deleted {deleted_count} glossaries",
"deleted_count": deleted_count,
"total_glossaries": len(glossaries),
}
Languages :
List all the languages supported by DeepL
@app.get("/languages")
async def get_languages():
"""Get supported languages"""
return call_deepl_api("languages")
Start the server and API’s would be ready to be integrated in frontend and tested in postman
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=5001)
Frontend

UI
For the UI , we will have the option to choose source (EN) and target languages (DE), chat interface where for instance we type in “Hello” , the “bot” will reply with translated text — “Hallo” .
Also an area for uploading our own glossary in csv/tsv format, after which we can make use of our glossaries entries in translations.
const [messages, setMessages] = useState([]);
const [inputText, setInputText] = useState("");
const [sourceInput, setSourceInput] = useState("EN"); // source lang
const [targetInput, setTargetInput] = useState("DE"); // target lang
const [selectedFile, setSelectedFile] = useState(null); // Uploaded file
const [parsedData, setParsedData] = useState(""); // ParsedData for passing to /glossaries endpoint
const [glossaryName, setGlossaryName] = useState(""); // Set glossary name
const [glossaryId, setGlossaryId] = useState(""); // Set glossary Id
Set the source and target
const handleSetLanguages = () => {
// Show confirmation message in the chatbox
const confirmMessage = {
id: Date.now(),
type: "system",
text: `Languages set: ${sourceInput.toUpperCase()} → ${targetInput.toUpperCase()}`,
timestamp: new Date(),
};
setMessages((prev) => [...prev, confirmMessage]);
};
const handleKeyPress = (e) => {
if (e.key === "Enter") {
handleSendMessage();
}
};
Handling to and fro messages between user and translation-bot
const handleSendMessage = async () => {
if (inputText.trim() === "") return;
const userMessage = {
id: Date.now(),
type: "user",
text: inputText,
timestamp: new Date(),
};
// Add user message immediately
setMessages((prev) => [...prev, userMessage]);
setInputText("");
// Get translation and add bot message
const translation = await translateText(inputText);
const botMessage = {
id: Date.now() + 1,
type: "bot",
text: translation,
timestamp: new Date(),
};
setMessages((prev) => [...prev, botMessage]);
};
For translation, we can add more parameters to the body , if needed like- split_sentences, preserve_formatting, formality etc as listed above in the /translate endpoint
const translateText = async (text) => {
try {
const result = await fetch("http://localhost:5001/translate", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
text: text,
source_lang: sourceInput,
target_lang: targetInput,
glossary_id: glossaryId || "",
preserve_formatting: true,
split_sentences: true,
formality: "more",
// more parameters can be added
}),
});
const data = await result.json();
return data.translations[0].text;
} catch (error) {
console.error("Translation error:", error);
return "Translation failed";
}
};
For handling the file upload and parsing the csv file using PapaParser a well known parsing package in JS .
const handleFileUpload = (event) => {
const file = event.target.files[0];
if (!file) return;
setSelectedFile(file);
// Check file type
const fileExtension = file.name.split(".").pop().toLowerCase();
if (!["csv", "tsv"].includes(fileExtension)) {
alert("Please select a CSV or TSV file");
return;
}
// Parse the file using Papa Parse
Papa.parse(file, {
header: false, // Set to true if your file has headers
skipEmptyLines: true,
delimiter: fileExtension === "tsv" ? "\t" : ",", // Use tab for TSV, comma for CSV
complete: (results) => {
console.log("Parsed data:", results.data);
// Remove the first row : we are assuming that 1st row will have the
// source(EN) and target (DE)
let dataToProcess = results.data;
if (dataToProcess.length > 0 && dataToProcess[0].length >= 2) {
//slice 1st row as it contains language codes
dataToProcess = dataToProcess.slice(1);
}
// Convert parsed data to tab-separated format
const formattedData = dataToProcess
.filter((row) => row.length >= 2 && row[0] && row[1]) // Filter out incomplete rows
.map((row) => `${row[0].trim()}\t${row[1].trim()}`) // Format as "source\ttarget"
.join("\n"); // Join with newlines
setParsedData(formattedData);
console.log("Formatted data:", formattedData);
},
error: (error) => {
console.error("Error parsing file:", error);
},
});
};
handleGlossary( ) — it sends a POST request to the backend with the glossary name, source/target languages, and parsed glossary entries.
const handleGlossary = async () => {
if (!selectedFile || !parsedData || parsedData.trim().length === 0) {
alert("Please upload a file first");
return;
}
if (!glossaryName.trim()) {
alert("Please enter a glossary name");
return;
}
try {
// parsedData is already formatted as "source\ttarget\nsource2\ttarget2"
const result = await fetch("http://localhost:5001/glossaries", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: glossaryName,
source_lang: sourceInput,
target_lang: targetInput,
entries: parsedData,
entries_format: "tsv",
}),
});
const data = await result.json();
console.log("Glossary created:", data);
if (result.ok) {
// Show success message
setGlossaryId(data.glossary_id);
const entryCount = parsedData.split("\n").length;
const successMessage = {
id: Date.now(),
type: "system",
text: `Glossary "${glossaryName}" created successfully with ${entryCount} entries`,
timestamp: new Date(),
};
setMessages((prev) => [...prev, successMessage]);
// Reset form
setSelectedFile(null);
setParsedData("");
setGlossaryName("");
}
return result.ok && data;
} catch (error) {
console.error("Glossary upload error:", error);
const errorMessage = {
id: Date.now(),
type: "system",
text: `Error creating glossary: ${error.message}`,
timestamp: new Date(),
};
setMessages((prev) => [...prev, errorMessage]);
}
};
And then finally the code for the UI.
<div className="min-h-screen min-w-screen bg-gray-100 pt-8 dark:bg-gray-900">
<div className="max-w-4xl mx-auto">
{/* Header */}
<div className="text-center mb-8">
<h1 className="text-5xl font-bold text-gray-800 dark:text-white">
DeepL Translations
</h1>
<p className="text-gray-600 dark:text-gray-300 mt-2">
Type a message to get an instant translation
</p>
</div>
{/* Language Selection */}
<div className="flex justify-center items-center gap-4 mb-6 p-4 bg-white dark:bg-gray-800 rounded-lg shadow">
<div className="flex items-center gap-2">
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">
From:
</label>
<input
type="text"
value={sourceInput}
onChange={(e) => setSourceInput(e.target.value)}
placeholder="EN"
className="w-16 text-center border border-gray-300 dark:border-gray-600 rounded px-2 py-1 text-sm dark:bg-gray-700 dark:text-white"
/>
</div>
<div className="text-gray-500 dark:text-gray-400">→</div>
<div className="flex items-center gap-2">
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">
To:
</label>
<input
type="text"
value={targetInput}
onChange={(e) => setTargetInput(e.target.value)}
placeholder="DE"
className="w-16 text-center border border-gray-300 dark:border-gray-600 rounded px-2 py-1 text-sm dark:bg-gray-700 dark:text-white"
/>
</div>
<button
onClick={handleSetLanguages}
className="px-4 py-1 bg-green-500 text-white rounded hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-500 text-sm transition-colors"
>
Set Languages
</button>
</div>
{/* Current Language Display */}
<div className="text-center mb-4">
<span className="text-sm text-gray-600 dark:text-gray-400">
Current: {sourceInput} → {targetInput}
</span>
</div>
{/* Chat Container */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
{/* Messages Area */}
<div className="h-96 overflow-y-auto p-4 space-y-4">
{messages.length === 0 ? (
<div className="text-center text-gray-500 dark:text-gray-400 mt-20">
<p className="text-lg">Start a conversation!</p>
<p className="text-sm">
Type a message below to see the translation
</p>
</div>
) : (
messages.map((message) => (
<div
key={message.id}
className={`flex ${
message.type === "user"
? "justify-end"
: message.type === "system"
? "justify-center"
: "justify-start"
}`}
>
<div
className={`max-w-xs lg:max-w-md px-4 py-2 rounded-lg ${
message.type === "user"
? "bg-blue-500 text-white"
: message.type === "system"
? "bg-yellow-100 dark:bg-yellow-800 text-yellow-800 dark:text-yellow-200 text-xs"
: "bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-white"
}`}
>
<p className="text-sm">{message.text}</p>
<p className="text-xs opacity-70 mt-1">
{message.timestamp.toLocaleTimeString()}
</p>
</div>
</div>
))
)}
</div>
{/* Input Area */}
<div className="border-t border-gray-200 dark:border-gray-700 p-4">
<div className="flex space-x-4">
<input
type="text"
value={inputText}
onChange={(e) => setInputText(e.target.value)}
onKeyDown={handleKeyPress}
placeholder="Type your message here..."
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
/>
<button
onClick={handleSendMessage}
disabled={inputText.trim() === ""}
className="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
Send
</button>
</div>
</div>
</div>
{/* Glossary Upload Section */}
<div className="mt-8 p-6 bg-white dark:bg-gray-800 rounded-lg shadow">
<h2 className="text-2xl font-bold text-gray-800 dark:text-white mb-4">
Upload Glossary
</h2>
<div className="space-y-4">
{/* Glossary Name Input */}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Glossary Name
</label>
<input
type="text"
value={glossaryName}
onChange={(e) => setGlossaryName(e.target.value)}
placeholder="Enter glossary name..."
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 dark:bg-gray-700 dark:text-white"
/>
<p className="text-xs text-red-500 mt-1">
* This field is required
</p>
</div>
{/* File Upload */}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Select CSV or TSV File
</label>
<input
type="file"
name="file"
accept=".csv,.tsv"
onChange={handleFileUpload}
className="block w-full text-sm text-gray-500 dark:text-gray-400 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-purple-50 file:text-purple-700 hover:file:bg-purple-100 dark:file:bg-gray-700 dark:file:text-gray-300"
/>
</div>
{/* File Info */}
{selectedFile && (
<div className="p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
<p className="text-sm text-gray-600 dark:text-gray-400">
<strong>Selected file:</strong> {selectedFile.name}
</p>
<p className="text-sm text-gray-600 dark:text-gray-400">
<strong>Entries found:</strong>{" "}
{parsedData ? parsedData.split("\n").length : 0}
</p>
{parsedData && (
<div className="mt-2">
<p className="text-xs text-gray-500 dark:text-gray-400">
<strong>Sample data:</strong>
</p>
<code className="text-xs bg-gray-100 dark:bg-gray-600 p-1 rounded">
{parsedData.split("\n").slice(0, 2).join("\\n")}
</code>
</div>
)}
</div>
)}
{/* Upload Button */}
<button
onClick={() => {
console.log("Button clicked", {
selectedFile: !!selectedFile,
glossaryName: glossaryName.trim(),
parsedData: parsedData,
parsedDataLength: parsedData?.length || 0,
});
handleGlossary();
}}
disabled={!selectedFile || !glossaryName.trim() || !parsedData}
className="w-full mt-4 px-4 py-2 bg-purple-500 text-white rounded-lg hover:bg-purple-600 focus:outline-none focus:ring-2 focus:ring-purple-500 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
Create Glossary
</button>
</div>
</div>
</div>
</div>
Now that all the ends are tied up, we’ll see screenshots of the working application
Glossary Used :
EN DE
car Auto
computer Computer
friend Freund
good morning Guten Morgen

Translations according to glossaries and formality
That’s it for the tutorial of using DeepL API’s .If you’re building multilingual apps, try integrating DeepL into your stack — it’s easier than you think, and the payoff in user experience is huge
Thanks for tagging along with me! I hope you found this helpful.
Until next time — stay fearless in code and in life.
A message from our Founder
Hey, Sunil here. I wanted to take a moment to thank you for reading until the end and for being a part of this community.
Did you know that our team run these publications as a volunteer effort to over 3.5m monthly readers? We don’t receive any funding, we do this to support the community. ❤️
If you want to show some love, please take a moment to follow me on LinkedIn, TikTok, **Instagram. You can also subscribe to our [weekly newsletter](https://newsletter.plainenglish.io/)**.
And before you go, don’t forget to clap and follow the writer️!
메타데이터
- post_id
- b6251b5dfdf5
- slug
- how-to-build-a-translations-application-using-deepl-apis-b6251b5dfdf5
- url
- https://ai.plainenglish.io/how-to-build-a-translations-application-using-deepl-apis-b6251b5dfdf5
- canonical_url
- https://ai.plainenglish.io/how-to-build-a-translations-application-using-deepl-apis-b6251b5dfdf5
- author_url
- https://medium.com/@tishasoumya
- status
- ok
- fetched_at
- 2026-06-24 13:29:15