DEV Community

Sakshi
Sakshi

Posted on

TONETWIST

TONE TWIST
I Built a Personality Rewriter — Here's What Broke (And What Didn't)
The problem
Translation tools are really good at translating words, but terrifying at translating personality.
If you write a joke in English and run it through a normal translator, the result usually sounds robotic or awkward. The meaning is actually present, but the vibe disappears.
Most translation tools solve language. Most tone tools solve style. Nobody solves both at same time.
That's the problem I wanted to solve.


Introducing ToneTwist
ToneTwist is a personality rewriter.
You give it any text — an email, caption, rant, or quote — and it rewrites it through a chosen vibe:
• Gen Z
• Pirate
• Shakespeare
• Corporate
• Boomer
• Aussie
Then it can translate that personality into another language without changing the vibe.
Most tools solve translation.
Some tools solve vibe.
ToneTwist solves both at once.
Live demo:
https://tonetwist-eight.vercel.app
GitHub:
https://github.com/sakshhii95/Tonetwist


Tech Stack
• Lingo.dev — multilingual localisation
• Groq — LLM inference
• Next.js 16 — framework
• Vercel — deployment
• Pure CSS — styling
Responses typically return in 1–2 seconds, which makes the typing animation feel real-time. architecture
The pipeline is intentionally simple.
User Input


Auto Tone Detection (Groq)


Personality Rewrite


Translation (Lingo.dev)


Output with typing animation
What problem does this solve?
I typed in sarcasm. The translator spits out a formal complaint."
A message written in corporate English might feel cold or rude in another language.
A joke written in Gen Z slang loses everything when translated.
ToneTwist combines both:
Language + Personality
So you don't need to be:
• a native speaker
• a copywriter
• a cultural translator
all at the same time.


Why Lingo.dev specifically
Most translation APIs are based on a bag of words. They translate meaning but ignore context, tone, and vibe.
Lingo.dev is different. The localizeText() function understands context. When I pass it a pirate-rewritten text, it doesn't just swap English words for Spanish ones — it carries the energy of the text into the translation.
That's the key insight behind ToneTwist.
Without Lingo.dev, the pipeline would be:
Groq rewrites personality → Google Translate kills it → flat output
With Lingo.dev, it becomes:
Groq rewrites personality → Lingo.dev carries it → personality intact

Where the magic begins:
The core pipeline lives in:
app/api/transform/route.ts
This function chains Groq + Lingo.dev together.
const vibePrompt = VIBE_PROMPTS[vibe] || VIBE_PROMPTS.genz;

const completion = await groq.chat.completions.create({
model: "llama-3.3-70b-versatile",
messages: [{
role: "user",
content: `${vibePrompt}

Original text: "${text}"

Respond with ONLY the rewritten text.`
}]
});

let result = completion.choices[0]?.message?.content || "Something went wrong!";

if (language && language !== "en") {
const lingo = new LingoDotDevEngine({
apiKey: process.env.LINGODOTDEV_API_KEY
});

const translated = await lingo.localizeText(result, {
sourceLocale: "en",
targetLocale: language,
});

result = translated || result;
}
Groq handles the personality rewrite.
Lingo.dev then preserves that personality across languages.
Neither step needs to know about the other — they're simply chained.


Auto tone detection
ToneTwist also detects the tone of your writing automatically.
A debounced request runs 1 second after typing stops:
useEffect(() => {
detectTimeout.current = setTimeout(async () => {
const res = await fetch("/api/detect", {
method: "POST",
body: JSON.stringify({ text: input })
});

const data = await res.json();
setDetectedTone(data.tone);
Enter fullscreen mode Exit fullscreen mode

}, 1000);
}, [input]);
If your message is sarcastic, serious, or enthusiastic, the system detects it and preserves it during the rewrite


The most frustrating bug
Vercel builds kept failing with:
Parsing ecmascript source code failed
Expected '</', got 'ident'
The cause?
A nested template literal inside JSX:
const content = Hello\n${"─".repeat(30)}\n${output};
Turbopack couldn't parse it.
The fix was replacing it with a simple array join:
const divider = Array(30).fill("─").join("");
const content = ["Hello", divider, output].join("\n");
It took 3 hours to isolate.


A feature I deleted
The original design included a Tone Sampler:

  1. Generate 5 tone examples
  2. Pick one
  3. Use it as input for transformation It technically worked. But everyone who tried it was confused. So I removed it and replaced it with automatic tone detection. The UX became dramatically simpler. ________________________________________ Trade-offs I accepted Some shortcuts were intentional. No streaming Groq supports streaming responses, but integrating it with the typing animation would have required major refactoring. Instead, the app fetches the full response and animates it client-side. No persistence Session history was removed to reduce UI clutter.

Run it yourself
bash

git clone https://github.com/sakshhii95/Tonetwist.git
cd Tonetwist
npm install


Create `.env.local`:
Enter fullscreen mode Exit fullscreen mode

GROQ_API_KEY=your_groq_key
LINGODOTDEV_API_KEY=your_lingo_key

bash
npm run dev

http://localhost:3000

Top comments (0)