Como traduzir temas e plugins?

Quem lida com o WordPress provavelmente já traduziu um tema, simplesmente buscando por strings nos arquivos e substituindo pelo equivalente em português. Isso é simples (até certo ponto) e eficaz, mas o WP oferece um sistema de tradução nativo, baseado no GNU Gettext. Verdade que muitos desenvolvedores não dão a menor bola para isso, mas alguns sim! Um brinde a esses!

Nas próximas linhas vamos tentar mostrar como “internacionalizar” um tema ou plugin – usarei daqui em diante o termo genérico “addon” para ambos.

O modo grosseiro

O primeiro caso (citado acima) não requer muito conhecimento, apenas paciência. Onde achar uma frase traduzível, traduz e salva. Se eu fosse você, não apagaria as frases originais nesse caso. Mas isso só se aplica a temas e plugins que foram desenvolvidos sem essa preocupação – nos que usam as funções de tradução, o trabalho é bem mais fácil.

Se o addon que deseja traduzir tem suporte a tradução…

… as strings serão tratadas com as funções _e() e __(). Ou seja, o que você esperava que fosse assim:

<h2>Plugin title</h2>

aparece assim no código:

<h2><?php _e("Plugin title", "text_domain"); ?></h2>

Aqui “text_domain" é uma string definida pelo desenvolvedor para identificar o plugin no sistema de tradução e é definida pelas funções load_theme_textdomain() e load_plugin_textdomain() que associam o textdomain ao arquivo .mo, que é gerado a partir do catálogo PO.

Deve haver um ou mais arquivos com extensão .PO incluídos no pacote ou, de preferência, um arquivo .POT – que nada mais é que um .PO apenas com as strings originais, sem tradução, para servir de base para criação dos arquivos .PO. Para editar esses catálogos (PO ou POT), você pode usar o poEdit que é grátis e simples de usar.

Nesse caso, você não deve modificar os arquivos do addon – a tradução acontecerá apenas no catálogo, que deverá ser salvo seguindo o padrão definido pelo WP:

textdomain + ‘-‘ + código da linguagem + ‘.po’ -> no nosso exemplo seria assim -> text_domain-pt_BR.po

Ao finalizar a tradução, salve o catálogo. Neste momento o poEdit gera automaticamente um arquivo de mesmo nome com extensão .MO – esse é que será usado pelo WordPress, o .PO é apenas para nós, humanos, conseguirmos ler e editar as strings.

Se o arquivo text_domain-pt_BR.mo estiver presente no diretório informado ao declarar o textdomain, basta configurar a constante WP_LANG (em wp-config.php) para “pt_BR” e pronto, se tudo deu certo, o sistema aparecerá em português.

Mas você pode querer internacionalizar um addon que não oferece suporte à tradução…

Nesse caso teremos que fazer o que o desenvolvedor não fez, editando o código – portanto só é recomendado para quem tem alguma intimidade com o PHP.

A primeira coisa é declarar o textdomain e informar a localização dos arquivos .MO, usando uma das funções citada acima. A linha abaixo pode ser inserida em qualquer parte do arquivo principal do plugin, ou do arquivo functions.php, se estamos traduzindo um tema

// para plugins:
load_plugin_textdomain("mytextdomain", dirname(__FILE__)."/languages");
// para temas:
load_theme_textdomain("mytextdomain", dirname(__FILE__)."/languages");

Aqui vamos criar o textdomain “mytextdomain” e salvar nossos arquivos no diretório languages, sob a raiz do addon. Agora é que começa a parte chata. Teremos que editar os arquivos, um por um em busca das strings que precisam ser traduzidas. Vamos então entender a diferença entre as funções que iremos usar:

  • <?php _e('string', 'textdomain'); ?>
    a função _e() imprime a string traduzida na tela
  • <?php $str = __('string', 'textdomain'); ?>
    a função __() retorna a string traduzida sem imprimir

Vamos ver um trecho de código a ser traduzido, como exemplo:

<?php
  if(!empty($_POST['act'])) {
  ?><div class="updated fade"><p><?php
  if($_POST['act'] = 'remove')
  print deleteThing($_POST['mythings']) // função imaginária...
        ? "Your '{$_POST['mythings']}' was successfully removed."
        : "Your '{$_POST['mythings']}' could not be removed!";
  if($_POST['act'] = 'edit')
  print editThing($_POST['mythings'])// função imaginária...
        ? "Your '{$_POST['mythings']}' was successfully edited."
        : "Your '{$_POST['mythings']}' could not be edited!";
  }
  ?></p></div><?php
?>
<h2>My things</h2>
<div class="wrap">
  <form action="<?php print $PHP_SELF; ?>" method="post">
  <label>Your cool things:
  <select name="mythings">
  <?php foreach($coolthings as $things) { ?>
  <option value="<?php print sanitize_title($things['thing_title']); ?>"><?php print $things['thing_title']; ?></option>
  <?php } ?>
  </select>
  </label>
  <p class="butts">
  <input type="button" class="button auto"<?php if(count($coolthings) == 0) print ' disabled="disabled"'; ?>
value="Edit" onclick=" this.form.act.value = 'edit'; this.form.submit();" />
  <input class="auto button delete"<?php if(count($coolthings) == 0) print ' disabled="disabled"'; ?>
value="<?php _e("Edit", "mythings"); ?>"type="button" value="Delete" onclick="if(!confirm('Do you really want to delete this thing?'))
return; this.form.act.value = 'remove'; this.form.submit();" />
  </p>
  <input type="hidden" name="act" />
  </form>
</div>

Veja abaixo o mesmo código, já com o suporte à internacionalização:

<?php
// essa linha deve aparecer apenas uma vez, em qualquer lugar - fora
// de funções (ou não, se você sabe quando executá-la)
load_plugin_text_domain("mythings", dirname(__FILE__).'/languages');

// começamos usando __(), pois o valor está sendo atribuído à variável $msg, não impresso
  if(!empty($_POST['act'])) {
    if($_POST['act'] = 'remove')
    $msg = deleteThing($_POST['mythings']) // função imaginária...
          ? sprintf(__("Your '%s' was successfully removed.", "mythings"), $_POST['mythings'])
          : sprintf(__("Your '%s' could not be removed!", "mythings"), $_POST['mythings']);
    if($_POST['act'] = 'edit')
    $msg = editThing($_POST['mythings'])// função imaginária...
          ? sprintf(__("Your '%s' was successfully edited.", "mythings"), $_POST['mythings'])
          : sprintf(__("Your '%s' could not be edited!", "mythings"), $_POST['mythings']);
    ?>
    <div class="updated fade">
    <p><?php print $msg; ?></p>
    </div>
    <?php
  }
?>

<?php
// para os valores que estavam escritos diretamente na parte HTML do documento,
// usamos a função _e(), que imprime a tradução, sem precisar de echos ou prints
?>

<h2><?php _e("My things", "mythings"); ?></h2>
<div class="wrap">
  <form action="<?php print $PHP_SELF; ?>" method="post">
  <label><?php _e("Your cool things:", "mythings"); ?>
  <select name="mythings">
  <?php foreach($coolthings as $things) { ?>
  <option value="<?php print sanitize_title($things['thing_title']); ?>"><?php print $things['thing_title']; ?></option>
  <?php } ?>
  </select>
  </label>
  <p class="butts">
  <input type="button" class="button auto"<?php if(count($coolthings) == 0) print ' disabled="disabled"'; ?>
value="<?php _e("Edit", "mythings"); ?>" onclick=" this.form.act.value = 'edit'; this.form.submit();" />
  <input class="auto button delete"<?php if(count($coolthings) == 0) print ' disabled="disabled"'; ?>
type="button" value="<?php _e("Delete", "mythings"); ?>" onclick="if(!confirm('<?php _e("Do you really want to delete this thing?", "mythings"); ?>')) return; this.form.act.value = 'remove'; this.form.submit();" />
  </p>
  <input type="hidden" name="act" />
  </form>
</div>

Repare no uso da função sprintf() para trabalhar com strings que são mescladas com variáveis PHP. Isso simplifica a string evitando confusões na hora de traduzir – pode ser que outras pessoas tentem fazer isso, inclusive gente que não conhece PHP.

Não esqueça coisas que podem parecer detalhes, como rótulos dos botões, texto de alerts e conffirms Javascript, etc.

Bem, se não havia suporte… não existe nenhum arquivo .POT ou .PO…

Você pode usar o já citado poEdit para criá-lo ou copiar o texto abaixo para um arquivo de texto:

msgid ""
msgstr ""
"Project-Id-Version: n"
"Report-Msgid-Bugs-To: n"
"POT-Creation-Date: n"
"PO-Revision-Date: n"
"Last-Translator: seu nome <seu@email.com>n"
"Language-Team: n"
"MIME-Version: 1.0n"
"Content-Type: text/plain; charset=UTF-8n"
"Content-Transfer-Encoding: 8bitn"
"X-Poedit-KeywordsList: __;_en"
"X-Poedit-Basepath: .n"
"X-Poedit-SearchPath-0: .n"

Salve este arquivo como mythings-pt_BR.po na pasta-mãe do plugin. A extensão .PO já associa o arquivo ao poEdit. Abra com um duplo clique. Clique então em “Atualizar catálogo” e as strings que estiverem dentro de chamadas às funções de tradução irão aparecer como num passe de mágica!

Clique em Ok e inicie sua tradução. Ao terminar, salve o arquivo e saia. Você verá que o arquivo mythings-pt_BR.mo foi gerado. Coloque este arquivo no diretório que associamos ao nosso textdomain (./languages). Pronto!

13 Comments

  1. Para aprender, tentei usar um tema (Default) do WordPress que, com certeza, está preparado para tradução (internacionalização). Entretanto, no Poedit, quando ciclo em “Atualizar Catálogo”, usando o arquivo publicado no final do post, apenas a opção “Atualizar catálogo pelo arquivo pot” está habilitada. A outra opção, “Atualizar a partir do código fonte” não fica disponível. Assim, o “passe de mágica” não acontece.Fiz algo errado? O que é necessário para criar um arquivo pot? Baixei o GetTex, mas não achei nenhum arquivo executável que me permitisse instalar o programa. Ficaria grato pela gentileza de um esclarecimento.

  2. Armando,
    não entendo por que não… fiquei na dúvida e refiz o processo, salvando o arquivo a partir do texto publicado aqui. Abri no poEdit e lá estava a opção habilitada! – Estou usando a versão 1.3.6.
    A propósito, “atualizar pelo arquivo POT” não lê as strings do código fonte, mas de um arquivo POT indicado…
    Se achar algo a respeito, publico aqui.

    • Ótimo o texto, mas também só consegui fazer depois das dicas do Armando nos comentários. Como seu texto está bem posicionado no Google, você já poderia incluir as dicas dele direto no seu texto (citando o nome dele, claro), para que seja ainda mais fácil do visitante encontrar a informação que deseja :)

  3. Para abrir a opção “Atualizar catálogo a partir do código fonte” no Poedit, há uma série de detalhes que precisam ser seguidos. Encontrei-os no Codex do WordPress. No caso, refere-se à tradução de plugins, mas funciona do mesmo jeito com temas. Os passos são os seguintes:
    1. Start POEdit
    2. Click File -> New Catalog
    3. Enter a project name (probably your plugin’s file name)
    4. Click the Paths tab at the top
    5. Click the New Item icon (second one, looks like a little square)
    6. Enter the path to the directory containing your plugin file (“.” tells POEdit to scan the directory that you will save the file to), press enter
    7. Click the Keywords tab at the top
    8. Click the New Item icon
    9. Enter __ (that’s underscore underscore), press enter
    10. Click the New Item icon
    11. Enter _e (that’s underscore e), press enter
    12. Click Okay
    13. Choose a name for your .po file (probably your plugin’s base filename)

    Seguindo essa sequência, não tem erro. Mas quero deixar claro que, de qualquer forma, seu post me foi muito útil. Obrigado.

  4. Parabéns pela iniciativa, de criar um post com ajuda, sobre tradução.
    estou começando iniciar pelo teu conteúdo e curti mto

    vlwss

  5. Muito obrigado por toda a explicação e obrigado pelo complemento Armando, vocês fizeram do meu dia de pesquisa um dia melhor rsrsr.

    Valeu.

  6. Muito bom este post, havia procurado em vários lugares como alterar o texto " Nenhum Comentário" para apenas "Comentários" já havia editado e salvo o arquivo de tradução do tema diversas vezes e não funcionava. Com a dica do PoEdit foi batata.
    Vlw

  7. Muito obrigado por compartilhar estas dicas, nem acredito que eu traduzia themes procurando pelas strings em cada arquivo e parseando todas as palavras com acento. Levava horas para traduzir um theme e agora acabo de traduzir um em 15 minutos

  8. Poedit e muito bom porem sempre temos que usar a codificação html? sempre mudar pé para p&eacute;

    • Não Renato, basta usar utf-8. Abra as configurações do catálogo e defina ‘Tabela de caracteres’ e ‘Conjunto de caracteres no codigo fonte’

  9. Cau, Muito bom! Obrigado por compartilhar a dica!

  10. Legal. Consegui traduzir um tema premium que veio sem suporte para arquivos .mo .po e pedia para comprar o plugin wpml para fazer a tradução. Boa dica.

Deixe uma resposta