Recent Updates Toggle Comment Threads | Keyboard Shortcuts

  • Thomas Scholz 11:03 pm on 2014/07/14 Permalink | Reply  

    How to get a post from another site in the network 

    Let’s say we have a site id and a post id. The source doesn’t matter, it could be a database, user input, whatever. Now we want to get a WP_Post object from that site.

    A first idea might look like this:

    switch_to_blog( $site_id );
    $post = get_post( $post_id );

    But this is not necessary, there is already a function in WordPress for that:

    function get_blog_post( $blog_id, $post_id ) {
        switch_to_blog( $blog_id );
        $post = get_post( $post_id );
        return $post;

    Nice, but there is an important, subtle bug in that function: it is using get_post( $post_id ) without a check on the post id. If $post_id is 0, NULL or "", get_post() will use an existing global post object or id. But the current global post is from the source site, not from the target site! The id on site 1 references a completely different post than on site 2.
    WordPress doesn’t care. So we have to do that check:

    $post = NULL;
    if ( ! empty ( $post_id ) )
        $post = get_blog_post( $site_id, $post_id );
  • Thomas Scholz 6:17 am on 2014/04/02 Permalink | Reply
    Tags: , theme   

    Create a custom language switcher 

    We offer a widget and the “quick links” to show links to translations, but many users need a custom solution at other places or in a formatting we just don’t give them (yet).

    There is a handy helper function to get all the links as an array: mlp_get_interlinked_permalinks(). It returns an array with the text (human readable language name), the permalink to the other site’s translation and an entry lang (the language code) – for each single translation.

    You can use that to create your own language switcher. The following function creates a string that might look like this:

    EN | DE | RU

    Add it to your theme’s functions.php.

     * Create a navigation between translations
     * @param  string $between  Separator between items
     * @param  string $before   HTML before items
     * @param  string $after    HTML after items
     * @return string
    function mlp_navigation(
        $between = ' | ',
        $before  = '<p class="mlp-lang-nav">',
        $after   = '</p>'
        $links = (array) mlp_get_interlinked_permalinks();
        if ( empty ( $links ) )
            return '';
        $items = array ();
        foreach ( $links as $link ) {
            if ( isset ( $link['text'] ) ) {
                $text = $link['text'];
            else {
                // take just the main code
                $first = strtok( $link['lang'], '_' );
                $text  = mb_strtoupper( $first );
            $items[] = sprintf(
                '<a href="%1$s" hreflang="%2$s" rel="alternate">%3$s</a>',
                esc_url( $link['permalink'] ),
                esc_attr( $link['lang'] ),
        return $before . join( $between, $items ) . $after;

    Now you can call the function wherever you need it without any arguments:

    echo mlp_navigation();

    Don’t forget the closing semicolon!

    If you want to get a list, you can change the parameters:

    echo mlp_navigation(
        '</li><li>', // between items
        '<ul class="mlp-lang-nav"><li>', // before
        '</li></ul>' // after

    We will add support for custom menus in one of the next versions of Multilingual Press, so you can create your own menus with drag and drop.

    • Piet 4:31 am on 2014/07/02 Permalink | Reply

      Thanks for the tutorial, I will try to implement this. I noticed though that although Posts and Pages are perfectly linked together across languages, Categories are not. If I am looking at a Category Archive the language switcher links to the homepage of the resp. languages. Is there a way to fix this?

      • Thomas Scholz 5:47 am on 2014/07/02 Permalink | Reply

        This part is not completed – yet. We will add taxonomy support in version 2.1.

  • Thomas Scholz 3:31 am on 2014/03/30 Permalink | Reply
    Tags: http,   

    Language negotiation – how our redirect feature works 

    For our users, one of the most important features of Multilingual Press is the HTTP redirect. Each visitor should be sent to the page she understands best.

    This is hard.

    Example: Maryam (مري) visits your site for the first time. She understands Arabic, English and Latin. Arabic is her native language.
    You have three language versions of your content: English, Arabic and Russian. The original content is written in English, the other sites are translations.

    Which page should Maryam see? Arabic or English? It depends on two factors: The quality of your Arabic translation and Maryam’s capability to read English.

    Multilingual Press cannot judge the quality of your translation, but you can give each language a weight, or as we say: a priority. There is a field for that in our language manager.

    Screen shot: Language Manager priorities

    Maryam can do the same in her browser. If her English is not very good, she can add a weight to that preference, and her browser will send that in a q value:

    Accept-Language: ar,en;q=0.7,la;q=0.4

    We read that as:

    Arabic   ar = 1.0 (omitted values equal 1)
    English  en = 0.7
    Latin    la = 0.4

    Multilingual Press will now multiply your priority for each language with Maryam’s preference for that language. The highest number wins. This is called Language negotiation. If there are two highest numbers or the highest number matches the current language, or Maryam selects one language per widget (an URL with a noredirect parameter), she will not be redirected.

    Our algorithm is clever enough to handle subsets: If your only English content is in Belizean English (en-bz), but your visitor’s preference is Canadian English (en-ca), Multilingual Press will strip the second part and find the matching English content.

    What does that mean for you?

    You should always know the quality of your translations. Set the priority for the original language to the highest value, weight the translations lower.

    In our example: If you set the priority for English to 10 and for Arabic to 7, Maryam will not be redirected when she visits the English or the Arabic site. She will be sent to the Arabic site (her first preference) if she visits the Russian site.

    But if you set English to 10 and Arabic to 6, the result of Multilingual Press’ calculation will be:

    Arabic   ar = 1.0 x  6 = 6
    English  en = 0.7 x 10 = 7
    Latin    la = 0.4 (not calculated = 0)
    Russian  ru = 0.4 (not calculated = 0)

    Maryam will get the English site.

    We will also use your priorities to sort the languages in our switcher widget in one of the next releases.

    Conclusion: Set the language priorities carefully. They can decide how good your visitors will understand your message. But always provide a way for your visitors to override automatic content negotiation manually, because not everyone uses a browser which is configured to their needs, or the browser’s user interface is very poor. And sometimes your translation is not as good as you think it is.

    Screenshot: Google Chrome language settings

    Google Chrome language settings

    For developers

    You can override our selection per filter:

     * Change the URL for redirects. You might add support for other types
     * than singular posts and home here.
     * If you return an empty value, the redirect will not happen.
     * The result will be validated with esc_url().
     * @param string $url
     * @param array  $match 'blog_id'  (int)    target blog ID
     *                      'language' (string) target language
     * @param int    $current_blog_id
    $url = apply_filters( 'mlp_redirect_url', $url, $match, $current_blog_id );


  • Thomas Scholz 9:15 pm on 2014/03/28 Permalink | Reply  

    Multilingual Press 2.0, RC 4 

    This is the last release candidate before we let the dogs out build a final. At least, that’s what I hope.

    Most important changes:

    • I have disabled the relationship changer for auto-drafts for now. Handling this insanity was just too much work; I will tackle it later again. Maybe.
    • I also dropped support for post formats in the Advanced Translator. Since they are not “real” terms until you have used them, getting them is a little bit too much work. Plus, there is now way to detect the supported formats in other sites, because that is a theme setting, and the theme is not active.
    • The widget doesn’t throw a notice anymore when you save its settings. The same applies to the Quicklink feature.
    • The translation metabox contains the blog title now. That should help when you have two or more sites with the same language.
    • I did what you shouldn’t do in a release candidate: I have refactored the HTTP redirect feature.

      It does a real language negotiation now, it weights the user’s preferences against the priority you have set in the language manager and finds the best match. And if the user has excluded a language (language-code;q=0), that will be respected too.

      You can see your browser settings for the Accept-Language on my little private test page.

    Please test this RC. We will use exactly that code probably for the final version.


    If you own a Pro version, the ZIP archive is already in your download area: English and German. The free version is available on our GitHub repository.

  • Thomas Scholz 6:09 pm on 2014/03/26 Permalink | Reply  

    Multilingual Press 2.0, RC 3 

    We just released RC 3, with some fixes:

    • The feature relationship control (button Change relationship) works with multiple languages now.
    • We dropped support for a missing spl_autoload_register(). This can be turned off in PHP 5.2 only, and when you do that … well, then you are in trouble anyway. We cannot waste resource on such edge cases.
    • Our metaboxes are now surrounded with HTML comments to make it easier to debug the insane HTML in the editor page. Search for
      <!-- Multilingual Press Translation Box -->.
    • The taxonomy selector for tags, categories and custom taxonomies is now hidden per default. This saves some space when you have many terms.

    Enjoy, and send us bug reports.


    If you own a Pro version, the ZIP archive is already in your download area: English and German. The free version is available on our GitHub repository.

  • Thomas Scholz 11:45 pm on 2014/03/20 Permalink | Reply  

    Multilingual Press 2.0, RC 2 

    Today, we released the second release candidate for version 2.0.

    Changes since version 1.1.1

    Free and Pro version

    • Code refactoring. This is huge. Our former 18 classes are now 80. We moved away from inheritance to composition, and we added many useful classes for third-party developers. We will start writing documentation next week.
    • New Language Manager with editable languages.
    • Rename Multilingual Press Widget to Language Switcher.
    • Improved storage of site relationships.
    • Set attributes width and height for flags.
    • Fixed error on plugin deactivation.
    • Implement uninstall.php to clean up on deletion properly.
    • Simplify user interface in site settings.
    • Better keyboard accessibility for form fields.
    • Convert text domain calls to static strings.
    • Better label text
    • Missing translation does not prevent translating a post again anymore.
    • Post authors of translations are not overwritten anymore.
    • Show current site in widget works now.
    • Rework translation metaboxes, there is now one box for each language.
    •  Rename blog to site in the user interface.
    • Update German translation, remove outdated other translations.
    • Fixed missing trailing slash in subdirectory installations.

    Pro version only

    • Removed icon from settings page. This is obsolete since WordPress 3.8.
    • Better design for the settings page. Easier to understand and to use, less options, works without JavaScript.
    • The Advanced Translator works with any custom post type now. Please make sure the post type is registered in a network activated plugin. Post types in themes don’t work. And they don’t belong to themes anyway.
    • The Advanced Translator includes taxonomies, built-in and custom. For custom taxonomies applies the same as for custom post types. Register them in a network plugin.
    • You can change relationships between translation now any time: Remove a relationship, create a new draft post, or search live for existing posts.
    • The Quick link output is now keyboard accessible.
    • License check does not prevent bulk plugin deletion anymore.
    • Improved performance for site duplication.
    • You can write translations from auto-drafts now. Just go to New Post, and start writing. We removed the extra step to save the post once.

    There are many more minor improvements, too many to list them all.

    Screen shots

    Screen shot Language Manager

    The new Language Manager makes all 174 languages editable.

    Screen shot Site Settings

    The Site Settings are now easier to use, the tab has a fixed address.

    Screen shot General Settings

    The General Settings page is much simpler now, more compact, and the whole row is clickable.


    Screen shot relationship manager

    You can change the relationship between posts now any time, search for existing posts (including drafts), or just create a new draft automatically.


    If you own a Pro version, the ZIP archive is already in your download area: English and German. The free version is available on our GitHub repository.

    Reporting bugs

    As a Pro user, please our support forum for bug reports, users of our free version should use the support forum on

    Please include the exact steps to reproduce the problem. Disable all plugins and use a Twenty X theme or my Mini Theme to exclude bugs caused by external code.

    Run stress tests on your test environment, test the update with real data (but still on a test site), find everything we missed. Anything you don’t find will be shipped with the final version.

  • Thomas Scholz 11:31 pm on 2014/02/20 Permalink | Reply
    Tags: i18n, time   

    How to integrate get_post_time with date_i18n function?

  • Thomas Scholz 12:04 pm on 2014/02/20 Permalink | Reply
    Tags: .htaccess, installation, wp-config.php   

    How to install multi-site 

    This is meant as a very short form of the overly long Codex post.

    Start with a clean regular single-site installation in the server document root.
    Be an admin.
    Make sure your salts are complete in your wp-config.php.
    Give PHP at least 128MB memory limit. More is better, less might break your site.
    Add the constant that enables multi-site:

    define( 'WP_ALLOW_MULTISITE', TRUE );

    Then go to Tools/Network Setup, use Sub-directories or Sub-domains if your are prepared for that (see Multisite on Windows with wildcard subdomains).

    Now you get the new code for wp-config.php (remove WP_ALLOW_MULTISITE) …

    define( 'MULTISITE',            true       );
    // Set this to true for sub-domain installations.
    define( 'SUBDOMAIN_INSTALL',    false      );
    define( 'DOMAIN_CURRENT_SITE',  'mstest.wp');
    define( 'PATH_CURRENT_SITE',    '/'        );
    define( 'SITE_ID_CURRENT_SITE', 1          );
    define( 'BLOG_ID_CURRENT_SITE', 1          );

    … and .htaccess, replace the default WordPress rules with this:

    RewriteEngine On
    RewriteBase /
    RewriteRule ^index\.php$ - [L]
    # add a trailing slash to /wp-admin
    RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L]
    RewriteCond %{REQUEST_FILENAME} -f [OR]
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule ^ - [L]
    RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]
    RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L]
    RewriteRule . index.php [L]

    Log in again, and you are done.

Compose new post
Next post/Next comment
Previous post/Previous comment
Show/Hide comments
Go to top
Go to login
Show/Hide help
shift + esc