DEV Community

shashank ms
shashank ms

Posted on

Building a Language Translation App with LLM, Speech Recognition, and Computer Vision

We are building a language translation assistant that handles text, voice, and images in a single pass. It is useful for travelers, field technicians, or logistics teams who need to translate signs, menus, or spoken instructions without switching apps.

What you'll need

Oxlo.ai runs fully OpenAI-compatible endpoints for chat, vision, and audio, so the SDK is the only client we need.

1. Scaffold text translation

First, I set up the Oxlo.ai client and test a plain text translation using Qwen 3 32B, which handles multilingual reasoning well. I create translator.py and add a helper that takes a string and returns the translated text.

from openai import OpenAI

client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")

def translate_text(text: str, target_lang: str = "English") -> str:
    response = client.chat.completions.create(
        model="qwen-3-32b",
        messages=[
            {"role": "system", "content": f"You are a precise translator. Translate the user's text to {target_lang}. Respond with only the translation, no explanations."},
            {"role": "user", "content": text},
        ],
    )
    return response.choices[0].message.content.strip()

if __name__ == "__main__":
    print(translate_text("¿Dónde está la estación de tren?", "English"))

Running this should return a direct English translation. Because Oxlo.ai charges a flat rate per request, a long paragraph costs the same as a short sentence, which keeps costs predictable when users paste entire documents.

2. Add speech recognition

Next, I add audio support. Oxlo.ai hosts Whisper Large v3, so I can transcribe voice memos or recordings before passing the text to the translator. I use the SDK's audio.transcriptions method, which routes to Oxlo.ai's audio/transcriptions endpoint.

def transcribe_audio(audio_path: str) -> str:
    with open(audio_path, "rb") as audio_file:
        transcript = client.audio.transcriptions.create(
            model="whisper-large-v3",
            file=audio_file,
        )
    return transcript.text

# Quick test
# print(transcribe_audio("phrase.mp3"))

This returns raw text in the source language. I keep transcription separate from translation so the agent can preserve context and handle code-switching later.

3. Add vision for images

For signs, menus, or labels, I use Kimi K2.6 to extract visible text from an image. I base64-encode the file and send it through the chat completions endpoint with an image payload. This gives me clean text I can feed into the translation layer.

import base64

def encode_image(image_path: str) -> str:
    with open(image_path, "rb") as image_file:
        return base64.b64encode(image_file.read()).decode("utf-8")

def extract_text_from_image(image_path: str) -> str:
    b64_image = encode_image(image_path)
    data_url = f"data:image/jpeg;base64,{b64_image}"

    response = client.chat.completions.create(
        model="kimi-k2.6",
        messages=[
            {
                "role": "user",
                "content": [
                    {"type": "text", "text": "Extract all text visible in this image. Preserve line breaks and formatting. Respond with only the extracted text."},
                    {"type": "image_url", "image_url": {"url": data_url}},
                ],
            },
        ],
    )
    return response.choices[0].message.content.strip()

# Quick test
# print(extract_text_from_image("menu.jpg"))

Kimi K2.6 handles both the vision and the reasoning required to preserve structure, which is important for forms or tabulated menus.

4. Unify the agent

Now I wire the three inputs into one router. The agent guesses the media type from the file extension, runs the appropriate preprocessor, and sends the result to Llama 3.3 70B with a shared system prompt. I define the system prompt first so it is easy to edit later.

SYSTEM_PROMPT = """You are a field translation assistant.
Translate the provided content accurately into the requested target language.
Preserve meaning, tone, and formatting such as line breaks or bullet points.
Respond with only the translation, no commentary."""

Then I add the main router and translation call.

import mimetypes

def translate(input_path: str, target_lang: str = "English") -> str:
    mime, _ = mimetypes.guess_type(input_path)

    if mime and mime.startswith("audio"):
        raw_text = transcribe_audio(input_path)
    elif mime and mime.startswith("image"):
        raw_text = extract_text_from_image(input_path)
    else:
        with open(input_path, "r", encoding="utf-8") as f:
            raw_text = f.read()

    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": f"Translate to {target_lang}:\n{raw_text}"},
        ],
    )
    return response.choices[0].message.content.strip()

The pipeline is now unified. Text files skip preprocessing, audio flows through Whisper, and images flow through Kimi K2.6 OCR, but every path ends with a consistent LLM translation step.

Run it

I create three sample inputs in the project root and call the agent from __main__.

if __name__ == "__main__":
    print("=== Text ===")
    print(translate("phrase.txt", "English"))

    print("\n=== Audio ===")
    print(translate("question.mp3", "English"))

    print("\n=== Image ===")
    print(translate("sign.jpg", "English"))

Example output:

=== Text ===
Where is the train station?

=== Audio ===
How much does this cost?

=== Image ===
Please do not enter.
Authorized personnel only.

Because Oxlo.ai has no cold starts on popular models, the first request after idle time returns just as quickly as subsequent ones, which matters for a user-facing translation app.

Next steps

Wrap the translate function in a FastAPI endpoint so mobile clients can upload photos or voice memos directly. You can also switch the translation model to DeepSeek V3.2 for heavier code or reasoning contexts, or enable streaming responses for long document translation so users see partial results immediately.

Top comments (0)