Sistema de tradução para o painel administrativo do EmDash CMS, traduzindo a interface do inglês para português brasileiro.
O sistema usa uma abordagem híbrida que combina:
- Middleware - Injeta um script JS nas páginas do admin
- Dicionário embedded - Traduções dentro do próprio JavaScript
- JSON externo - Traduções carregadas sob demanda (opcional)
- MutationObserver - Traduz elementos dinâmicos do React (SPA)
┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐
│ Navegador │────▶│ Middleware │────▶│ Script i18n │
│ (Admin UI) │ │ (Astro) │ │ (Client-side) │
└─────────────────┘ └──────────────┘ └─────────────────┘
│ │
▼ ▼
┌───────────┐ ┌─────────────┐
│ React │ │ Mutation │
│ SPA │ │ Observer │
└───────────┘ └─────────────┘
Arquivo principal contendo todas as traduções. Estrutura:
(function() {
'use strict';
const Lang = 'pt-BR';
const dict = {
// Sidebar
'Dashboard': 'Painel',
'Pages': 'Páginas',
'Posts': 'Publicações',
'Users': 'Usuários',
'Settings': 'Configurações',
// Ações
'Save': 'Salvar',
'Delete': 'Excluir',
'Edit': 'Editar',
'Add': 'Adicionar',
'Create': 'Criar',
// ... centenas de traduções
};
// Função de tradução com fallback
function translateKey(key) {
return dict[key] ?? key;
}
// Elementos HTML que serão traduzidos
const textOnlyTags = ['h1','h2','h3','h4','h5','h6','p','span','label','th','td','div','li','article','section','header','footer','aside','nav','main','button','a','input','textarea'];
// Traduz um elemento individual
function translateElement(el) {
if (!el || el.nodeType !== Node.ELEMENT_NODE) return;
if (el.dataset.translated === Lang) return;
const tag = el.tagName.toLowerCase();
if (!textOnlyTags.includes(tag) && tag !== 'select') return;
const raw = el.textContent?.trim();
const t = translateKey(raw || '');
if (raw && t && t !== raw) {
el.textContent = el.textContent.replace(raw, t);
el.dataset.translated = Lang;
}
// Traduz opções de select
if (tag === 'select') {
Array.from(el.options).forEach(opt => {
const optText = opt.textContent?.trim();
const translatedOpt = translateKey(optText || '');
if (optText && translatedOpt && translatedOpt !== optText) {
opt.textContent = translatedOpt;
}
});
}
}
// Traduz toda a página
function translate() {
const elements = document.querySelectorAll(textOnlyTags.join(', '));
elements.forEach(translateElement);
}
// Intercepta navegações SPA
const originalPushState = history.pushState;
history.pushState = function(...args) {
originalPushState.apply(this, args);
setTimeout(translate, 100);
setTimeout(translate, 500);
};
// Observa elementos dinâmicos
const observer = new MutationObserver(() => translate());
observer.observe(document.body, { childList: true, subtree: true });
// Define idioma
document.documentElement.lang = 'pt-BR';
// Traduz imediatamente
translate();
setTimeout(translate, 100);
setTimeout(translate, 500);
console.log('[i18n] Camada PT-BR ativa para Admin');
})();Middleware do Astro que injeta o script de tradução nas páginas do admin:
/**
* Middleware para injetar script de tradução PT-BR no Admin
*/
export const onRequest = async (context, next) => {
const response = await next();
// Verifica se é uma rota do admin
if (context.url.pathname.startsWith('/_emdash/admin')) {
const html = await response.text();
// Injeta o script antes do fechamento </body>
const injectedHtml = html.replace(
'</body>',
'<script src="/i18n-admin-pt-br.js" defer></script></body>'
);
return new Response(injectedHtml, {
status: response.status,
headers: response.headers
});
}
return response;
};Registra o middleware na configuração do Astro:
export default defineConfig({
// ... outras configurações
middleware: "./src/middleware.ts",
// ...
});Define o idioma no HTML do frontend:
<html lang="pt-BR">
<!-- ... -->
</html>Edite public/i18n-admin-pt-br.js e adicione no objeto dict:
const dict = {
// ... traduções existentes
// Nova tradução
'New Text': 'Novo Texto',
'Another Text': 'Outro Texto',
};- Organize por categoria (Sidebar, Botões, Status, etc.)
- Mantenha em ordem alfabética dentro de cada categoria
- Use texto em inglês como chave (o que aparece na UI)
Se o texto não estiver sendo traduzido:
- Verifique se o elemento está na lista
textOnlyTags - Adicione debug no console:
console.log('[i18n] Trying:', text, '->', translateKey(text));
- Verifique se a chave existe no dicionário
- Verifique o console do navegador por erros
- Confirme que o script está sendo carregado (Network tab)
- Verifique se o
data-translated="pt-BR"está nos elementos traduzidos
- Verifique a tag do elemento (pode precisar adicionar à lista)
- Confirme que a chave existe no dicionário
- Teste com texto exato (case-sensitive)
- O código já inclui tradução de selects
- Verifique o log no console
- Confirme que as opções existem no dicionário
- Teste após cada adição - faça refresh da página
- Use texto exato - a tradução é case-sensitive
- Evite duplicatas - verifique se a chave já existe
- Console logs - use para debug
const dict = {
// Sidebar - Principal
'Dashboard': 'Painel',
'Pages': 'Páginas',
// Ações - Botões
'Save': 'Salvar',
'Delete': 'Excluir',
// Status
'Published': 'Publicado',
'Draft': 'Rascunho',
// Campos de formulário
'Title': 'Título',
'Content': 'Conteúdo',
// Mensagens
'Loading': 'Carregando',
'Error': 'Erro',
};MIT License - Use freely in your projects.
Para contribuir com novas traduções:
- Faça fork do projeto
- Adicione suas traduções no dicionário
- Teste localmente
- Envie o PR