Demo: Tradutor Inglês→Português com IA Local (Prompt API)
inicianteTraduza textos do inglês para português brasileiro com streaming local e privacidade total. Inferência on-device via Prompt API — sem enviar dados para nuvem.
Visão Geral
Demo de tradução Inglês → Português Brasileiro executada 100% local no navegador. O texto traduzido aparece em streaming, sem enviar nada pra servidor nenhum. Um bom exemplo de como configurar expectedInputs e expectedOutputs com idiomas.
Pra quem: Desenvolvedores que querem entender a configuração de idiomas na Prompt API.
Técnica principal: promptStreaming() com expectedInputs: [{ type: "text", languages: ["en"] }] e expectedOutputs: [{ type: "text", languages: ["pt"] }].
Wireframe
┌─────────────────────────────────────────────────────────┐
│ 🌐 Tradutor EN → PT │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────┐ ┌────────────────────┐ │
│ │ 🇺🇸 Inglês │ │ 🇧🇷 Português │ │
│ ├────────────────────┤ ├────────────────────┤ │
│ │ │ │ │ │
│ │ The quick brown │ → │ A rápida raposa │ │
│ │ fox jumps over │ │ marrom pula sobre │ │
│ │ the lazy dog. │ │ o cão preguiçoso.█ │ │
│ │ │ │ │ │
│ └────────────────────┘ └────────────────────┘ │
│ │
│ [ 🌐 Traduzir ] [ 📋 Copiar Tradução ] │
│ │
│ ⚠️ Limitações: modelo local compacto; ideal para │
│ textos curtos e de uso geral. │
└─────────────────────────────────────────────────────────┘
HTML
<section class="demo-container" id="tradutor-local">
<h2>🌐 Tradutor EN → PT</h2>
<div id="status-bar" class="status" hidden>
<span id="status-message"></span>
</div>
<div class="translator-grid">
<div class="panel panel-source">
<div class="panel-header">
<span>🇺🇸 Inglês</span>
<span id="char-count">0 / 3000</span>
</div>
<textarea
id="input-text"
placeholder="Type or paste English text here..."
rows="8"
maxlength="3000"
></textarea>
</div>
<div class="panel-arrow">→</div>
<div class="panel panel-target">
<div class="panel-header">
<span>🇧🇷 Português</span>
</div>
<div id="output-text" class="output-area"></div>
</div>
</div>
<div class="actions">
<button id="btn-translate" disabled>🌐 Traduzir</button>
<button id="btn-stop" hidden>⏹️ Parar</button>
<button id="btn-copy" hidden>📋 Copiar Tradução</button>
</div>
<div class="limitations-note">
<p>⚠️ <strong>Limitações:</strong> Modelo local compacto (Gemini Nano). Ideal para textos curtos e de uso geral. Para documentos técnicos longos ou termos especializados, use serviços dedicados de tradução.</p>
</div>
</section>
Código JavaScript
class LocalTranslator {
constructor() {
this.session = null;
this.controller = null;
this.inputEl = document.getElementById("input-text");
this.outputEl = document.getElementById("output-text");
this.btnEl = document.getElementById("btn-translate");
this.btnStop = document.getElementById("btn-stop");
this.btnCopy = document.getElementById("btn-copy");
this.statusBar = document.getElementById("status-bar");
this.statusMessage = document.getElementById("status-message");
this.charCount = document.getElementById("char-count");
this.init();
}
async init() {
if (!("LanguageModel" in window)) {
this.showStatus("❌ Prompt API não disponível. Use Chrome 148+.", "error");
return;
}
const availability = await LanguageModel.availability({
expectedInputs: [{ type: "text", languages: ["en"] }],
expectedOutputs: [{ type: "text", languages: ["pt"] }]
});
if (availability === "unavailable") {
this.showStatus("❌ Modelo não disponível ou idioma não suportado.", "error");
return;
}
try {
this.session = await LanguageModel.create({
expectedInputs: [{ type: "text", languages: ["en", "pt"] }],
expectedOutputs: [{ type: "text", languages: ["pt"] }],
initialPrompts: [{
role: "system",
content: `Você é um tradutor profissional inglês→português brasileiro. Traduza o texto fornecido de forma natural e fluente. Mantenha a formatação original (parágrafos, listas). Não adicione explicações — retorne apenas a tradução.`
}],
monitor(m) {
m.addEventListener("downloadprogress", (e) => {
document.getElementById("status-message").textContent =
`⏳ Baixando modelo... ${Math.round(e.loaded * 100)}%`;
});
}
});
this.btnEl.disabled = false;
this.hideStatus();
} catch (err) {
this.showStatus(`❌ Erro: ${err.message}`, "error");
return;
}
this.btnEl.addEventListener("click", () => this.translate());
this.btnStop.addEventListener("click", () => this.stop());
this.btnCopy.addEventListener("click", () => this.copy());
this.inputEl.addEventListener("input", () => this.updateCharCount());
this.inputEl.addEventListener("keydown", (e) => {
if (e.key === "Enter" && e.ctrlKey) this.translate();
});
}
async translate() {
const text = this.inputEl.value.trim();
if (!text) return;
this.controller = new AbortController();
this.btnEl.hidden = true;
this.btnStop.hidden = false;
this.btnCopy.hidden = true;
this.outputEl.textContent = "";
this.outputEl.classList.add("streaming");
try {
const stream = this.session.promptStreaming(
`Traduza para português brasileiro:\n\n${text}`,
{ signal: this.controller.signal }
);
for await (const chunk of stream) {
this.outputEl.textContent = chunk;
}
this.btnCopy.hidden = false;
} catch (err) {
if (err.name !== "AbortError") {
this.showStatus(`❌ Erro: ${err.message}`, "error");
}
} finally {
this.outputEl.classList.remove("streaming");
this.btnEl.hidden = false;
this.btnStop.hidden = true;
}
}
stop() {
this.controller?.abort();
}
copy() {
navigator.clipboard.writeText(this.outputEl.textContent).then(() => {
this.btnCopy.textContent = "✅ Copiado!";
setTimeout(() => { this.btnCopy.textContent = "📋 Copiar Tradução"; }, 2000);
});
}
updateCharCount() {
const len = this.inputEl.value.length;
this.charCount.textContent = `${len} / 3000`;
}
showStatus(msg, type) {
this.statusBar.hidden = false;
this.statusBar.className = `status status-${type}`;
this.statusMessage.textContent = msg;
}
hideStatus() {
this.statusBar.hidden = true;
}
}
document.addEventListener("DOMContentLoaded", () => new LocalTranslator());
Fluxo UX
- Página carrega → verifica suporte para par de idiomas EN→PT
- Usuário digita/cola texto em inglês no painel esquerdo
- Clica “Traduzir” (ou Ctrl+Enter) → botão vira “Parar”
- Tradução em streaming aparece no painel direito com cursor piscante
- Streaming completo → cursor some, botão “Copiar” aparece
- Parar → cancela via AbortController; tradução parcial permanece
Edge Cases e Tratamento de Erros
| Cenário | Tratamento |
|---|---|
| Idioma EN não suportado pelo modelo | availability() com config de idioma detecta antes |
| Texto já em português | Modelo pode retornar o próprio texto (system prompt instrui traduzir) |
| Texto misto (EN + PT) | Modelo traduz partes em inglês, mantém português |
| Gírias/expressões idiomáticas | Modelo local pode não traduzir perfeitamente — nota de limitação visível |
| Texto muito longo | maxlength="3000" limita; context window protege contra overflow |
| Rede indisponível | Funciona normalmente após modelo baixado |
| Caracteres especiais | Preservados na tradução |
CSS Essencial
.translator-grid {
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: 1rem;
align-items: stretch;
}
.panel-arrow {
display: flex;
align-items: center;
font-size: 1.5rem;
color: #6b7280;
}
.panel-header {
display: flex;
justify-content: space-between;
padding: 0.5rem;
font-weight: 600;
border-bottom: 1px solid #e5e7eb;
}
.output-area {
min-height: 180px;
padding: 0.75rem;
white-space: pre-wrap;
line-height: 1.6;
}
.streaming::after {
content: "█";
animation: blink 0.7s steps(1) infinite;
}
@keyframes blink { 50% { opacity: 0; } }
.limitations-note {
margin-top: 1rem;
padding: 0.75rem;
background: #fefce8;
border: 1px solid #fde047;
border-radius: 0.5rem;
font-size: 0.875rem;
}
@media (max-width: 768px) {
.translator-grid {
grid-template-columns: 1fr;
}
.panel-arrow {
justify-content: center;
transform: rotate(90deg);
}
}
Notas sobre Limitações
O Gemini Nano é um modelo compacto. Para tradução:
- Funciona bem: textos curtos, linguagem cotidiana, conteúdo geral
- Pode falhar: terminologia técnica especializada, jogos de palavras, nuances culturais
- Idiomas suportados na Prompt API (2026):
"en","ja","es"— e progressivamente mais - A nota de limitações é exibida permanentemente na UI para transparência com o usuário
- Para necessidades profissionais de tradução, recomende a Translator API dedicada do Chrome