π How qz-l.com Delivers Multi-Language Blog Posts with Only One Markdown File
At qz-l.com, we believe great developer experiences should be fast, simple, and intelligent.
When building our new blog system, we wanted a solution that:
- Keeps a single English Markdown file as the source of truth
- Automatically serves blog content in English, Chinese, or French
- Avoids manually maintaining multiple
.mdtranslations - Uses modern AI responsibly and efficiently
- Integrates seamlessly with our Next.js App Router
This post covers exactly how we implemented automatic real-time translations using Google GenAI, while preserving all Markdown formatting perfectly.
π― Problem: Multi-Language Blog Without Multi-File Headaches
Traditionally, multi-language blogs maintain:
/posts/my-post.en.md
/posts/my-post.zh.md
/posts/my-post.fr.md
This quickly becomes:
- β Hard to maintain
- β Error-prone
- β Difficult to update consistently
- β Expensive (writer/translator cost)
We wanted:
β Only one file
β Translated automatically
β SEO-friendly
β Fully Markdown-preserving
β No hallucinated structure changes
β No risk of broken code blocks or formatting
So we built a translator API endpoint around:
@google/genai
gemini-2.5-flash-lite
π§ The Core Idea
When a user views a blog post:
- We detect the requested language
- If it's
"en"β deliver the original.md - If it's
"zh"or"fr"β call our translation API - The API asks Google GenAI to translate:
markdowntitledescription
- Returned content is pure Markdown, structure preserved
- We render it exactly as if it were a local
.mdfile
π‘ API Design
Our translation endpoint supports three fields:
type TranslateRequest = {
markdown?: string;
targetLanguage: string;
title?: string;
description?: string;
};
And returns:
type TranslateResponse = {
translatedMarkdown?: string;
translatedTitle?: string;
translatedDescription?: string;
};
π€ System Prompt (The Secret Sauce)
We built a robust system prompt to guarantee:
- No broken Markdown
- Table, lists, headings, inline code preserved
- Only the text is translated
- JSON response guaranteed
Here is the exact SYSTEM_PROMPT:
You are a professional translator and Markdown expert.
Your task is to translate the input content into the TARGET_LANGUAGE specified in the user request.
Preserve all Markdown formatting, code blocks, headings, lists, links, tables, emojis, and any other Markdown syntax exactly for the 'markdown' field.
Also translate 'title' and 'description' text accurately.
Return only the translated content in the same structure as input.
If the TARGET_LANGUAGE is English, return the original content unmodified.
π Implementation (Next.js API Route)
Below is the core logic powering our blog translation:
import { GoogleGenAI } from "@google/genai";
const apiKey = process.env.GOOGLE_AI_KEY!;
const ai = new GoogleGenAI({ apiKey });
export async function POST(req: NextRequest) {
const { markdown, targetLanguage, title, description } = await req.json();
const SYSTEM_PROMPT = `...`;
const userContent =
`TARGET_LANGUAGE: ${targetLanguage}\n\n` +
`markdown:\n${markdown}\n` +
(title ? `\ntitle:\n${title}\n` : "") +
(description ? `\ndescription:\n${description}\n` : "");
const aiRes = await ai.models.generateContent({
model: "gemini-2.5-flash-lite",
contents: [{ role: "user", parts: [{ text: userContent }] }],
config: {
temperature: 0,
systemInstruction: {
role: "system",
parts: [{ text: SYSTEM_PROMPT }],
},
},
});
const textOutput = aiRes.candidates?.[0]?.content?.parts?.[0]?.text ?? "";
let cleanText = textOutput
.trim()
.replace(/^```json\s*/i, "")
.replace(/```$/, "");
const aiJson = JSON.parse(cleanText);
return NextResponse.json({
translatedMarkdown: aiJson.markdown || aiJson.markdown?.content,
translatedTitle: aiJson.title,
translatedDescription: aiJson.description,
});
}
π¦ When Does Translation Happen?
Inside the blog loader:
let posts = await getBlogPostSummaries();
const lang = await getCurrentLanguage();
if (lang !== "en") {
posts = await Promise.all(
posts.map((post) => translatePost(post, lang))
);
}
This keeps everything fast and idiomatic in Next.js.
πΎ Should We Cache Translations?
Yes β and we will.
Google GenAI is fast but not free.
Planned improvements:
- Redis caching
- File-based
.cache.json - ETag header invalidation
Blog content doesn't change often, so per-post caching is ideal.
π Result: True Multi-Language Without Multi-File
Our system now supports:
- π¬π§ English
- π¨π³ Chinese
- π«π· French
All from one single Markdown file.
No duplication.
No editing in multiple languages.
No human translation cost.
Just:
- Great UX
- Clean architecture
- Low maintenance
- Perfect Markdown fidelity
π Whatβs Next?
We are expanding internationalization with:
- Per-paragraph caching
- Translator cost analytics
- Inline code fence safety mode
- Translated SEO fields
- Automatic
hreflangsitemap support
π Final Thoughts
This system lets us write once and publish globally.
If you're building a multilingual blog without translator overhead, this architecture is clean, scalable, and future-proof.
If youβd like the full example repo or template, just ask!