Sistema de Tradução: Abordagem Híbrida de Três Tecnologias
Este artigo explica nosso sistema de tradução que combina três tecnologias — Flask-Babel para templates, TranslationManager para tabelas de frases e DeepSeek AI para tradução de conteúdo — para fornecer suporte multilíngue abrangente.
O Problema: Traduzir um Site Complexo
Nosso site tem múltiplos tipos de conteúdo que precisam de tradução:
-
Templates estáticos: Navegação, botões, rótulos (500+ strings)
-
Conteúdo dinâmico: Nomes de produtos, descrições, características (10.000+ strings)
-
Conteúdo gerado pelo usuário: Avaliações, comentários, tickets de suporte
-
Termos técnicos: Nomes de marcas, SKUs, URLs (NÃO devem ser traduzidos)
Uma única abordagem de tradução não funciona:
-
Tradução automática: Rápida, mas imprecisa para termos técnicos
-
Tradução manual: Preciso, mas lento e caro
-
Apenas template: Não lida com conteúdo dinâmico
Precisamos de uma abordagem híbrida que combine o melhor das três.
Arquitetura de Tradução
graph TD
Request[Solicitação do Usuário
lang=hi]
subgraph Templates
Babel[Flask-Babel
arquivos .po]
Static[Strings estáticas
botões, rótulos]
end
subgraph Dynamic
TM[TranslationManager
tabelas de frases]
Phrases[Nomes de produtos
características, termos]
end
subgraph AI
DS[API DeepSeek
tradução por IA]
Content[Descrições
artigos, texto longo]
end
Request --> Babel
Request --> TM
Request --> DS
Babel --> Static
TM --> Phrases
DS --> Content
Static --> Response[Página Traduzida]
Phrases --> Response
Content --> ResponseTrês Tecnologias
1. Flask-Babel: Traduções de Template
Flask-Babel lida com strings estáticas de template usando arquivos gettext .po.
-
Caso de uso: Navegação, botões, rótulos, mensagens de erro
-
Exemplo:
# Template
{{ _('Add to Cart') }}
# Arquivo .po (Hindi)
msgid "Add to Cart"
msgstr "कार्ट में जोड़ें"
Benefícios:
-
✅ Abordagem padrão i18n
-
✅ Suporte a ferramentas de tradução (Poedit, Weblate)
-
✅ Validação em tempo de compilação
-
✅ Consulta rápida (arquivos .mo compilados)
Limitações:
-
❌ Funciona apenas para strings estáticas
-
❌ Requer alterações no código para adicionar novas strings
-
❌ Sem suporte a conteúdo dinâmico
2. TranslationManager: Tabelas de Frases
Nossa classe personalizada TranslationManager lida com conteúdo dinâmico usando tabelas de frases JSON.
-
Caso de uso: Nomes de produtos, características, especificações, descrições
-
Exemplo:
{
"Mini PC": "मिनी पीसी",
"Intel N100": "Intel N100",
"8GB RAM": "8GB RAM",
"256GB SSD": "256GB SSD"
}
Benefícios:
-
✅ Suporte a conteúdo dinâmico
-
✅ Atualizações em tempo de execução (sem necessidade de reinício)
-
✅ Regras de preservação (nomes de marcas, SKUs)
-
✅ Formatação regional (separadores decimais)
-
✅ Fila de tradução (strings faltantes)
Limitações:
-
❌ Gerenciamento manual de frases
-
❌ Sem contexto para tradutores
-
❌ Requer tradução inicial
3. DeepSeek AI: Tradução de Conteúdo
O modelo de IA DeepSeek lida com tradução de conteúdo longo.
-
Caso de uso: Artigos de blog, descrições de produtos, textos de marketing
-
Exemplo:
prompt = f"Traduza para {lang}: {text}"
response = deepseek.generate(prompt)
Benefícios:
-
✅ Lida com conteúdo longo (1000+ palavras)
-
✅ Tradução consciente do contexto
-
✅ Saída em linguagem natural
-
✅ Processamento em lote
Limitações:
-
❌ Custo de API por tradução
-
❌ Requer conexão com a internet
-
❌ Pode traduzir termos técnicos incorretamente
Arquitetura do TranslationManager
Estrutura da Tabela de Frases
As tabelas de frases são armazenadas como arquivos JSON por idioma:
app/shared/translation/phrase_tables/
Cada arquivo mapeia frases em inglês para traduções:
{
"Mini PC": "मिनी पीसी",
"Thin Client": "थिन क्लाइंट",
"Industrial PC": "औद्योगिक पीसी",
"All-in-One": "ऑल-इन-वन"
}
Consulta de Tradução
O método get() realiza uma consulta de múltiplos níveis:
1. Verificação de Preservação:
if self._should_skip_translation(text, lang):
return text # Não traduzir nomes de marcas, SKUs, URLs
2. Divisão por Vírgula (para strings longas):
if len(text) > 150 and "," in text:
parts = text.split(",")
return ", ".join(self.get(p, lang) for p in parts)
3. Consulta na Tabela de Frases:
if text in self.phrase_tables[lang]:
return self.phrase_tables[lang][text]
4. Promoção de Cache Legado:
if text_hash in self.legacy_caches[lang]:
translation = self.legacy_caches[lang][text_hash]
self.set(text, lang, translation) # Promover para tabela de frases
return translation
5. Enfileirar se Faltar:
if queue_if_missing:
self.add_to_queue(text, lang)
return None # Nenhuma tradução encontrada
Regras de Preservação
Preservamos tipos específicos de conteúdo:
Nomes de Marcas:
BRAND_NAMES_PRESERVE = [
"Thinvent", "Intel", "AMD", "Microsoft", "Ubuntu",
"Windows", "Linux", "WiFi", "Bluetooth"
]
Termos Técnicos:
TECHNICAL_TERMS_PRESERVE = [
"HDMI", "DisplayPort", "VGA", "USB", "Ethernet",
"DDR4", "SSD", "NVMe", "PCIe", "SATA"
]
SKUs (baseado em padrão):
if not " " in text and text.count("-") >= 2:
return True # Preservar "Treo-N100-8-256-2H-W6-11P"
URLs:
if text.startswith(("/", "http://", "https://")):
return True
Formatação Regional
Para localidades europeias (alemão, francês, espanhol), aplicamos formatação regional:
Separador Decimal:
# Inglês: 34.00
# Alemão: 34,00
text = text.replace(".", ",")
Tensão/Corrente:
# Inglês: 12.5V
# Alemão: 12,5V
text = re.sub(r"(\d+)\.(\d+)V", r"\1,\2V", text)
Isso garante que os números sejam exibidos corretamente para cada localidade.
Fila de Tradução
Traduções faltantes são enfileiradas para processamento em lote:
[
{
"text": "Compact Desktop",
"lang": "hi",
"hash": "a1b2c3d4e5f6"
},
{
"text": "Fanless Design",
"lang": "hi",
"hash": "f6e5d4c3b2a1"
}
]
Um script em segundo plano processa a fila usando DeepSeek AI:
for item in queue:
translation = deepseek.translate(item["text"], item["lang"])
translation_manager.set(item["text"], item["lang"], translation)
Bloqueio de Arquivo
Usamos bloqueio de arquivo fcntl para evitar gravações simultâneas:
with open(lock_path, "w") as lock_file:
fcntl.flock(lock_file, fcntl.LOCK_EX)
try:
# Ler, modificar, escrever tabela de frases
with open(table_path, "r+") as f:
data = json.load(f)
data.update(new_translations)
f.seek(0)
json.dump(data, f)
f.truncate()
finally:
fcntl.flock(lock_file, fcntl.LOCK_UN)
Isso garante que múltiplos processos não corrompam as tabelas de frases.
Detecção de Idioma
Detectamos o idioma da consulta usando intervalos Unicode e palavras de parada:
Detecção por Intervalo Unicode
Devanagari (Hindi, Marathi):
if 0x0900 <= ord(char) <= 0x097F:
return "hi"
Bengali:
if 0x0980 <= ord(char) <= 0x09FF:
return "bn"
Árabe:
if 0x0600 <= ord(char) <= 0x06FF:
return "ar"
Cirílico (Russo):
if 0x0400 <= ord(char) <= 0x04FF:
return "ru"
CJK (Chinês, Japonês, Coreano):
if 0x4E00 <= ord(char) <= 0x9FFF: # Kanji/Hanzi
has_cjk = True
if 0x3040 <= ord(char) <= 0x30FF: # Hiragana/Katakana
return "ja"
if 0xAC00 <= ord(char) <= 0xD7AF: # Hangul
return "ko"
Detecção por Palavras de Parada
Para idiomas baseados no latim, usamos palavras de parada:
Espanhol:
SPANISH_STOPWORDS = {
"el", "la", "los", "las", "un", "una", "para", "con",
"en", "por", "que", "es", "su", "y", "del", "al"
}
Francês:
FRENCH_STOPWORDS = {
"le", "la", "les", "des", "du", "un", "une", "pour",
"avec", "en", "est", "sur", "et", "au", "dans"
}
Alemão:
GERMAN_STOPWORDS = {
"der", "die", "das", "ein", "eine", "für", "mit",
"ist", "und", "auf", "den", "dem", "bei", "von"
}
Se alguma palavra de parada corresponder, retornamos aquele idioma.
Detecção Dinâmica de Idioma
Os usuários podem especificar o idioma via:
1. Parâmetro de URL
/p/Treo-N100-8-256-2H-W6-11P?lang=hi
2. Cookie
response.set_cookie("lang", "hi", max_age=31536000)
3. Cabeçalho Accept-Language do Navegador
lang = request.accept_languages.best_match(["en", "hi", "es", "fr", "de"])
Prioridade: Parâmetro de URL > Cookie > Accept-Language > Padrão (en)
Tradução Recursiva
Traduzimos recursivamente estruturas de dados aninhadas:
def translate_data(obj, lang):
if isinstance(obj, dict):
return {k: translate_data(v, lang) for k, v in obj.items()}
elif isinstance(obj, list):
return [translate_data(item, lang) for item in obj]
elif isinstance(obj, str):
return self.get(obj, lang) or obj
return obj
Exemplo:
data = {
"title": "Mini PC",
"features": {
"RAM": "8GB",
"Storage": "256GB SSD"
},
"price": 25000
}
translated = translate_data(data, "hi")
# Resultado:
# {
# "title": "मिनी पीसी",
# "features": {
# "RAM": "8GB",
# "Storage": "256GB SSD"
# },
# "price": 25000
# }
Tradução de Características
Características de produtos requerem tratamento especial:
def translate_features(features, lang):
translated = {}
for heading, feature_dict in features.items():
# Traduzir cabeçalho
translated_heading = self.get(heading, lang) or heading
# Traduzir nomes e valores das características
translated_features = {}
for name, value in feature_dict.items():
translated_name = self.get(name, lang) or name
translated_value = self.get(value, lang) or value
translated_features[translated_name] = translated_value
translated[translated_heading] = translated_features
return translated
Exemplo:
features = {
"Processing": {
"Processor": "Intel N100",
"Cores": "4",
"RAM": "8GB"
}
}
translated = translate_features(features, "hi")
# Resultado:
# {
# "प्रोसेसिंग": {
# "प्रोसेसर": "Intel N100",
# "कोर": "4",
# "RAM": "8GB"
# }
# }
Integração com o Pipeline de SEO
O sistema de tradução se integra ao pipeline de SEO:
Propagação de Idioma da Consulta
Quando um usuário pesquisa em Hindi, propagamos o idioma para pesquisas relacionadas:
if SEO_ENABLE_LANGUAGE_PROPAGATION:
lang = detect_query_language(query)
if lang != "en":
url = f"{url}?lang={lang}"
Isso garante que os usuários permaneçam em seu idioma preferido ao clicar em pesquisas relacionadas.
Páginas de Consulta Traduzidas
As páginas de consulta são geradas em múltiplos idiomas:
/q/mini-pc (Inglês)
/q/mini-pc?lang=hi (Hindi)
/q/mini-pc?lang=es (Espanhol)
O conteúdo é traduzido usando o TranslationManager.
Características de Desempenho
Consulta na Tabela de Frases:
-
Tempo: O(1) consulta de dicionário (~1μs)
-
Memória: ~5 MB por idioma (10.000 frases)
Detecção de Idioma:
-
Tempo: O(n) onde n = comprimento da string (~10μs para 100 caracteres)
-
Memória: Negligenciável
Tradução Recursiva:
-
Tempo: O(n) onde n = número de strings (~1ms para 100 strings)
-
Memória: Proporcional ao tamanho da estrutura de dados
Bloqueio de Arquivo:
-
Tempo: ~1ms por aquisição de bloqueio
-
Contenção: Rara (gravações são infrequentes)
Referências
Conceitos Técnicos
-
Internacionalização (i18n) - Wikipedia
-
Bloco Unicode - Wikipedia
-
Gettext - Documentação GNU
-
fcntl - Documentação Python
Bibliotecas e Ferramentas
-
Flask-Babel - Documentação oficial
-
[DeepSeek](https://www