--- name: wp-translate description: Tradução de plugins WordPress para PT-PT com DeepL Pro, glossário próprio e scripts de automação. Gere ficheiros .po/.mo/.json, corrige traduções PT-BR e faz deploy para starter.descomplicar.pt. --- # /wp-translate — Traduções WordPress PT-PT ## Visão Geral Skill para traduzir plugins WordPress para Português Europeu (PT-PT) usando DeepL Pro com glossário, scripts de automação próprios e deploy directo para starter.descomplicar.pt. --- ## Scripts Disponíveis **Localização local:** `/media/ealmeida/Dados/Dev/Scripts/translate-wp-plugin/` **No servidor (tmp):** `/tmp/translate_missing.py`, `/tmp/fix_ptbr_safe.py` | Script | Função | |--------|--------| | `translate_missing.py` | Traduz strings vazias via DeepL Pro + glossário. Suporta batch 50 strings/pedido | | `fix_ptbr_safe.py` | Corrige ~100 padrões PT-BR → PT-PT | | `fix_malformed.py` | Corrige sintaxe .po mal formada | | `batch_process_library.py` | Orquestra fix_malformed → fix_ptbr → translate → compile .mo para toda a biblioteca | | `setup_glossary.py` | Cria/actualiza glossário DeepL (175 entradas WP PT-PT) | **Configuração (.env):** ``` DEEPL_API_KEY=3c74e27a-f688-4de2-927b-e91da46cf9cd DEEPL_GLOSSARY_ID=b9db5639-c201-479f-87af-adb81f112067 ``` --- ## DeepL Pro - **Endpoint:** `api.deepl.com` (Pro — sem `:fx`) - **Quota:** ~1,6M / 1 trilião de chars - **Glossário:** 160 entradas brand names + termos WordPress (EN→PT) - **Atenção:** DeepL também gera padrões PT-BR — sempre correr `fix_ptbr` ANTES E DEPOIS da tradução --- ## Workflow: Plugin Individual ```bash # 1. No servidor — gerar .pot (se não existir) wp --path=/home/ealmeida/starter.descomplicar.pt i18n make-pot \ wp-content/plugins/PLUGIN/ \ /tmp/PLUGIN-pt_PT.po \ --domain=TEXT_DOMAIN --allow-root # 2. Traduzir com DeepL Pro python3 /tmp/translate_missing.py /tmp/PLUGIN-pt_PT.po # 3. Corrigir PT-BR pós-tradução python3 /tmp/fix_ptbr_safe.py /tmp/PLUGIN-pt_PT.po # 4. Para plugins React — gerar JSON para strings JS wp --path=/home/ealmeida/starter.descomplicar.pt i18n make-json \ /tmp/PLUGIN-pt_PT.po /path/to/languages/ --no-purge --allow-root # 5. Copiar para servidor cp /tmp/PLUGIN-pt_PT.{po,mo} /home/ealmeida/starter.descomplicar.pt/wp-content/languages/plugins/ chown ealmeida:ealmeida /home/ealmeida/starter.descomplicar.pt/wp-content/languages/plugins/PLUGIN-pt_PT.* ``` ## Workflow: Biblioteca Completa ```bash python3 /media/ealmeida/Dados/Dev/Scripts/translate-wp-plugin/batch_process_library.py \ /path/to/library # Opções: --skip-translate, --dry-run, --only-ptbr ``` --- ## Encontrar o Text Domain **Crítico:** O nome da pasta do plugin ≠ text domain. Sempre verificar: ```bash grep "Text Domain" /path/to/plugin/plugin-main.php ``` **Casos conhecidos de divergência:** | Pasta | Text Domain | Ficheiro .po | |-------|-------------|--------------| | `pro-elements` | `elementor-pro` | `elementor-pro-pt_PT.po` | | `branda-white-labeling` | `ub` | `ub-pt_PT.po` | | `seo-by-rank-math` | `rank-math` | `rank-math-pt_PT.po` | --- ## Plugins React/JS — Ficheiros JSON Plugins com `wp_set_script_translations()` precisam de um `.json` além do `.mo`. **Detectar:** ```bash grep -r "wp_set_script_translations" /path/plugin/ --include="*.php" ``` **Gerar JSON:** ```bash wp i18n make-json PLUGIN-pt_PT.po /path/languages/ --no-purge --allow-root ``` **Atenção ao MD5:** WordPress calcula `md5(relative_path_of_script)` para o nome do JSON. Se o `source` no JSON não bater com o script registado, copiar o JSON com o MD5 correcto: ```bash # Encontrar qual script está registado grep "wp_register_script\|wp_enqueue_script" plugin.php | grep "HANDLE" # Calcular MD5 do caminho relativo python3 -c "import hashlib; print(hashlib.md5('app/index.js'.encode()).hexdigest())" # Copiar com nome correcto cp PLUGIN-pt_PT-{md5_errado}.json PLUGIN-pt_PT-{md5_correcto}.json ``` **Exemplo real — ai-engine:** - Handle `mwai` → script `app/index.js` → MD5: `2018087f584c4398b5c3a23fc0e5f9db` - JSON gerado com source `app/i18n.js` → MD5: `0bdc92e05d8f3ab638aa855679db059e` - Solução: copiar JSON com o MD5 de `app/index.js` --- ## Padrões PT-BR Comuns (fix_ptbr) DeepL introduz PT-BR mesmo com `target_lang=PT-PT`. Correr fix_ptbr **depois** da tradução: | PT-BR | PT-PT | |-------|-------| | atualiz* | actualiz* | | ativar/desativar | activar/desactivar | | você/vocês | o utilizador/os utilizadores | | excluir/excluído | eliminar/eliminado | | acessar | aceder | | compartilhar | partilhar | | postagem/postagens | publicação/publicações | | ativo/inativo | activo/inactivo | | ativação/desativação | activação/desactivação | | digite | introduza | | clique | clique (OK) | --- ## Servidor starter.descomplicar.pt ``` SSH: mcp__ssh-unified__ssh_execute server:"server" Path traduções: /home/ealmeida/starter.descomplicar.pt/wp-content/languages/plugins/ Loco Translate: /home/ealmeida/starter.descomplicar.pt/wp-content/languages/loco/plugins/ WP-CLI: wp --path=/home/ealmeida/starter.descomplicar.pt [...] --allow-root ``` **Permissões obrigatórias após operações root:** ```bash chown ealmeida:ealmeida /home/ealmeida/starter.descomplicar.pt/wp-content/languages/plugins/PLUGIN-pt_PT.* ``` **Deploy completo:** ```bash # Copiar .po + .mo + .json cp /tmp/PLUGIN-pt_PT.{po,mo} /home/.../languages/plugins/ # Se React: copiar também os .json chown ealmeida:ealmeida /home/.../languages/plugins/PLUGIN-pt_PT* wp --path=... cache flush --allow-root ``` --- ## Verificação de Estado ```bash # Strings em falta em todos os plugins python3 -c " from pathlib import Path; import polib for f in sorted(Path('languages/plugins').glob('*-pt_PT.po')): po = polib.pofile(str(f)) empty = len(po.untranslated_entries()) if empty: print(f'{f.name}: {empty} em falta') " # PT-BR residual após tradução grep -rn "atualiz\|ativar\|você\b\|excluir\b" languages/plugins/*.po ``` --- ## Problemas Conhecidos ### Python 3.6 no servidor Scripts locais usam type hints Python 3.9+. Para correr no servidor: - Remover anotações `-> type` das funções - Não usar `from __future__ import annotations` (não funciona no 3.6) - `tuple[str, list]` → usar `Tuple` de typing ### msgfmt vs polib `msgfmt 0.19.8.1` recusa .po com strings mal formadas. Usar sempre **polib** para compilar: ```python po = polib.pofile('file.po') po.save_as_mofile('file.mo') ``` ### Glossário Pro vs Free Glossário criado com chave `:fx` (Free) não funciona na conta Pro e vice-versa. Fix: `python3 setup_glossary.py` após mudar de chave API. ### XML tag_handling quebra com & `tag_handling='xml'` falha com strings como "Settings & Tools". Fix actual: tokens Unicode `⟦0⟧` como placeholders (implementado em translate_missing.py v2.1+). ### Strings hardcoded em bundles JS compilados Plugins com React que não usam `__()` nas strings de UI (ex: `subtitle:"text"`) não podem ser traduzidos. Não é possível sem modificar o bundle compilado. Aceitar e documentar. ### Loco Translate "Write protected" Ficheiros criados por root via SSH ficam inacessíveis ao web server. Fix: `chown ealmeida:ealmeida` nos ficheiros de tradução. --- ## Compilar .mo após Edições Manuais ```python import polib po = polib.pofile('/path/to/file.po') po.save('/path/to/file.po') # salva o .po actualizado po.save_as_mofile('/path/to/file.mo') ``` ## Re-gerar JSON após Edições ao .po ```bash wp i18n make-json file-pt_PT.po /path/languages/ --no-purge --allow-root # Depois sempre copiar com MD5 correcto se necessário chown ealmeida:ealmeida /path/languages/file-pt_PT-*.json wp --path=... cache flush --allow-root ``` --- ## Estado da Biblioteca (starter.descomplicar.pt — 25-02-2026) - **21 plugins** com ficheiros .po activos - **Cobertura:** 100% em todos os plugins (excl. webp-express sem suporte i18n) - **Strings hardcoded** não traduzíveis: ~7 subtítulos no ai-engine (limitação do plugin) - **Loco Translate path:** `languages/loco/plugins/` para pro-elements (elementor-pro) --- *Skill v1.0.0 | 25-02-2026 | Descomplicar®*