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 --> Response

Trê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

Bibliotecas e Ferramentas

  • Flask-Babel - Documentação oficial

  • [DeepSeek](https://www