La manera más cómoda es hacerlo a través de la API de wordpress, usando un script de WP-CLI que use CURL para hacer el get y expresiones regulares para obtener las traducciones independientes de cada campo. Este es un ejemplo, basado en la migración de la web de novact.org que lee una web en tres idiomas: español, catalán e inglés.

## Ajustando la API remota

En este caso creamos un endpoint a través del archivo `functions.php` del tema. Para ello sólo creamos un endpoint y le asociamos una función callback. En este caso concreto nos interesaba que el mismo endpoint pudiera hacer dos cosas, para lo que usamos parámetros GET: poder seleccionar sólo los posts de categorías concretas (que se iban a asociar a nuevos tipos de contenido en el nuevo wordpress) y poder leer o todos los posts o sólo un número reducido (para hacer pruebas o para hacer la migración definitiva). IMPORTANTE: no olvidar en la migración los slugs de los posts (`post_name`), para poder crear redirects que salven los enlaces antiguos en la web nueva.

    #!php

    \<?php
    /**
     *  Custom API hook to display posts
     */

    function custom_api_get_posts(){
        register_rest_route( 'posts', '/get', array(
           'methods' => 'GET',
           'callback' => 'custom_api_get_posts_callback'
        ));
    }

    /**
     *   Custom callback
     */
    function custom_api_get_posts_callback(WP_REST_Request $request)
    {
      	$args = [
            'post_type'      => 'post',
            'status'         => 'publish',	
            'posts_per_page' => -1,
      	];
      	// Set number of posts in case (all by default)
      	$np = $_GET["np"];
      	if($np)  $args['posts_per_page'] = $np;
        // Set post category (all by default)
      	$cat = $_GET["cat"];
      	if($cat) $args['category'] = $cat;
      	// Set post language (all by default)
      	$lang =	$_GET["lang"];
         	
      	return array_map(function($i)
      	{
            $id    = $i->ID;
            $title = $i->post_title;
            $content = $i->post_content;
            $image = get_the_post_thumbnail_url($id, 'full');
            return [ 
                'id' => $i->ID,
        	'title'   => qtrans_use($lang, $title),
        	'content' => $content,
        	'image'   => $image,
        	'date'    => $i->post_date,
        	'slug'    => $i->post_name
            ];
      	}, get_posts($args));
    }

    add_action('rest_api_init', 'custom_api_get_posts');


# Importar los posts

Para ello creamos primero una función genérica, siguiendo el principio DRY. En este caso se puso en un plugin, en la carpeta _inc_, para ser llamada usando WP-CLI. Usa la función para importar imágenes descrita en otro artículo de la wiki. Como peculiaridad el sitio añadía HTML y determinadas palabras que distinguían los títulos unos de otros en función de la categoría, de manera redundante. Se configuró la importancia para que eliminara ambas cosas. 

    #!php

    \<?php
    /**
     *   PHP Script to import posts from the API of the old site
     *   translating them from qTranslate to Polylang
     *
     *   usage: wp eval-file import-from-api.php path_to_api
     */

    require_once(ABSPATH . 'wp-content/plugins/nombre-plugin/inc/insert-attachment-from.php');

    /**
     *  Imports posts from a given url
     *
     *  @param String $url              Complete URL to the WP endpoint with the posts to be imported
     *  @param String $post_type        Post type to be set on the imported content
     *  @param Array  $title_blacklist  An array of words to be removed from titles
     *  @param Array  $post_extra_args  An array of extra arguments to be assigned to posts during import
     *  @param String $author           Name of the default post author username
     *  @param String $domain_name      Domain name to replace local links, for instance 'example.org'
     *  @param String $status           Post status of the umported content
     *
     */

    function import_posts(
        $url,
        $post_type='post',
        $title_blacklist=null,
        $post_extra_args=null,
        $author='novact',
        $domain_name='/',
        $status='publish'
    ){
        // Set-up CURL
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_FAILONERROR, 0);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6');
        $result = curl_exec($curl);
        curl_close($curl);
        $posts = json_decode($result, true);

        $langs = ['es', 'ca', 'en'];


Hasta aquí nada del otro mundo. Hemos configurado CURL con la url que le pasamos a la función, llamamos a la API y nos devuelve un array con posts. Ahora los procesamos:

        // Fetch each post of the result
        foreach($posts as $post)
        {
            // Set of related translations
            $translations = [];
            // Variable to hold different inline regex matches as we need them
            $inline_matches = [[]];
            // Get all local files referenced inline
            $m = preg_match_all(
                '#"https\:\/\/example\.com\/wp-content\/uploads\/(\d{4})/(\d{2})/(.*?)"#',
                $post['content'],
                $inline_matches
            );
            for($i = 0; $i < $m; $i++){
                insert_attachment_from(
                    str_replace('"', '', $inline_matches[0][$i]),
                    $inline_matches[1][$i].'/'.$inline_matches[2][$i].'/01 00:00:00',
                    $author
                );
            }
            $root = '';
            // For each language
            // Variable to hold different regex matches as we need them
            $matches = [];

Empezamos por procesar todas las referencias en línea a archivos e imágenes del wordpress, que procesamos usando el importador de imágenes y 'capturando' la fecha de subida de su url, para crear los metadatos correctamente. Para ello usamos la expresión regular: 

    '#"https\:\/\/example\.com\/wp-content\/uploads\/(\d{4})/(\d{2})/(.*?)"#

Donde se generan tres atoms: año (\d{4}), mes (\d{2}) y nombre del archivo (.*?). Con ello ya podemos importar todas las referencias en línea.

Una vez hecho esto, nos encontramos con el principal escollo, que es la manera en que qTranslate guarda todas las traducciones un un mismo post. Hay que separarlas para crear posts independientes por cada traducción, vinculados entre ellos. Para ello usamos regex nuevamente:

    #!php

    foreach($langs as $lang){
          // Get translated title
          preg_match('#\<\!\-\-\:'.$lang.'\-\-\>(.*?)(\<\!|$)#s', $post['title'], $matches);
          $translated_title = strip_tags( $matches[1] ); // Remove html tags if any in the title
          // Get the translated body
          preg_match('#\<\!\-\-\:'.$lang.'\-\-\>(.*?)(\<\!|$)#s', $post['content'], $matches);
          $translated_content = $matches[1];

          // Only if we have translated title and body create the post
          if( isset($translated_title) && isset($translated_content) )
          {
              // Remove items in the blacklist from the title
              if($title_blacklist)
                  $translated_title = str_replace($title_blacklist, '', $translated_title);
              // Trim title
              $translated_title = trim( $translated_title );
              // If a new domain name is provided replace local links from content
              if($domain_name) {
                  $base_url = explode('/', $url)[2];
                  $content = str_replace([
                    'https://' . $base_url . '/',
                    'http://'  . $base_url . '/'
                  ], $domain_name, $translated_content);
              }

              $post_status = $status ? $status : 'publish';

              $post_default_args = [
                  'post_title'   => $translated_title,
                  'post_content' => $content,
                  'post_type'    => $post_type,
                  'post_name'    => $post['slug'],
                  'post_author'  => $author,
                  'post_status'  => $post_status,
                  'post_date'    => $post['date'],
              ];

              // Insert post
              $id = wp_insert_post(
                  $post_extra_args ?
                  array_merge($post_extra_args, $post_default_args) :
                  $post_default_args
              );
              // Set the language of the post using polylang
              pll_set_post_language($id, $lang);
              WP_CLI::success('Saved with success post with id: ' . $id . ' and title: ' . $translated_title);
              // Add to set of related translations
              $translations[$lang] = $id;
              // Attached
              $thumbnail = insert_attachment_from(
                  $post['image'],
                  $post['date'],
                  'novact'
              );
              WP_CLI::log('Setting featured image: ' . $thumbnail);
              set_post_thumbnail($id, $thumbnail);
          }
      };