Life and Trials of a Multilingual Weblog [en]

Here is an explanation of how I set up WordPress to manage my bilingual weblog. I give all the code I used to do it, and announce some of the things I’d like to implement. A “Multilingual blogging” TopicExchange channel is now open.

[fr] J'explique ici quelles sont les modifications que j'ai faites à WordPress pour gérer le bilinguisme de mon weblog -- code php et css à l'appui. Je mentionne également quelques innovations que j'ai en tête pour rendre ce weblog plus sympathique à mes lecteurs monolingues (ce résumé en est une!) Un canal pour le weblogging multilingue a été ouvert sur TopicExchange, et vous y trouverez peut-être d'autres écrits sur le même sujet. Utilisez-le (en envoyant un trackback) si vous écrivez des billets sur le multinguisme dans les weblogs!

My weblog is bilingual, and has been since November 2000. Already then, I knew that I wouldn’t be capable of producing a site which duplicates every entry in two languages.

I think this would defeat the whole idea of weblogging: lowering the “publication barrier”. I feel like writing something, I quickly type it out, press “Publish”, and there we are. Imposing upon myself to translate everything just pushes it back up again. I have seen people try this, but I have never seen somebody keep it up for anything nearing four years (this weblog is turning four on July 13).

This weblog is therefore happily bilingual, as I am — sometimes in English, sometimes in French. This post is about how I have adapted the blogging tools I use to my bilingualism, and more importantly, how I can accommodate my monolingual readers so that they also feel comfortable here.

First thing to note: although weblogging tools are now ready to be used by people speaking a variety of languages (thanks to a process named “localization”), they remain monolingual. Language is determined at weblog-level.

With Movable Type, I used categories to emulate post-level language awareness. This wasn’t satisfying at all: I ended up with to monstrous categories, Français and English, which didn’t help keep rebuild times down.

With WordPress, the solution is far more satisfying: I store the language information as Post Meta, or “custom field”. No more category exploitation for something they shouldn’t be used for.

Before I really got started doing the exciting stuff, I made a quick change to the WordPress admin interface. If I was going to be adding a “language” custom field to each and every post of mine, I didn’t want to be doing it with the (imho) rather clumsy “Custom Fields” form.

In edit.php, just after the categorydiv fieldset, I inserted the following:

<fieldset id="languagediv">
      <legend>< ?php _e('Language') ?></legend>
	  <div><input type="text" name="language" size="7"
                     tabindex="2" value="en" id="language" /></div>
</fieldset>

(You’ll probably have to move around your tabindex values so that the tabbing order makes sense to you.)

I also tweaked the wp-admin.css file a bit to keep it looking reasonably pretty, adding the rule below:

#languagediv {
	height: 3.5em;
	width: 5em;
}

and adding #languagediv everywhere I could see #poststatusdiv, so that they obeyed the same rules.

In this way, I have a small text field to edit to set the language. I pre-set it to “en”, and have just to change it to “fr” if I am writing in French.

We just need to add a little piece of code in the form processing script, post.php, just after the line that says add_meta($post_ID):

 // add language
	if(isset($_POST['language']))
	{
	$_POST['metakeyselect'] = 'language';
        $_POST['metavalue'] = $_POST['language'];
        add_meta($post_ID);
        }

The first thing I do with this language information is styling posts differently depending on the language. I do this by adding a lang attribute to my post <div>:

<div class="post" lang="<?php $post_language=get_post_custom_values("language"); $the_language=$post_language['0']; print($the_language); ?>">

In the CSS, I add these rules:

div.post:lang(fr) h2.post-title:before {
  content: " [fr] ";
  font-weight: normal;
}
div.post:lang(en) h2.post-title:before {
  content: " [en] ";
  font-weight: normal;
}
div.post:lang(fr)
{
background-color: #FAECE7;
}

I also make sure the language of the date matches the language of the post. For this, I added a new function, the_time_lg(), to my-hacks.php. I then use the following code to print the date: <?php the_time_lg($the_language); ?>.

Can more be done? Yes! I know I have readers who are not bilingual in the two languages I use. I know that at times I write a lot in one language and less in another, and my “monolingual” readers can get frustrated about this. During a between-session conversation at BlogTalk, I suddenly had an idea: I would provide an “other language” excerpt for each of my posts.

I’ve been writing excerpts for each of my posts for the last six months now, and it’s not something that raises the publishing barrier for me. Quickly writing a sentence or two about my post in the “other language” is something I can easily do, and it will at least give my readers an indication about what is said in the posts they can’t understand. This is the first post I’m trying this with.

So, as I did for language above, I added another “custom field” to my admin interface (in edit-form.php). Actually, I didn’t stop there. I also added the field for the excerpt to the “simple controls” posting page that I use (set that in Options > Writing), and another field for keywords, which I also store for each post as meta data. Use at your convenience:

<!-- BEGIN BUNNY HACK -->
<fieldset style="clear:both">
<legend><a href="http://wordpress.org/docs/reference/post/#excerpt"
title="<?php _e('Help with excerpts') ?>"><?php _e('Excerpt') ?></a></legend>
<div><textarea rows="1" cols="40" name="excerpt" tabindex="5" id="excerpt">
<?php echo $excerpt ?></textarea></div>
</fieldset>
<fieldset style="clear:both">
<legend><?php _e('Other Language Excerpt') ?></legend>
<div><textarea rows="1" cols="40" name="other-excerpt"
tabindex="6" id="other-excerpt"></textarea></div>
</fieldset>
<fieldset style="clear:both">
<legend><?php _e('Keywords') ?></legend>
<div><textarea rows="1" cols="40" name="keywords" tabindex="7" id="keywords">
<?php echo $keywords ?></textarea></div>
</fieldset>
<!-- I moved around some tabindex values too -->
<!-- END BUNNY HACK -->

I inserted these fields just below the “content” fieldset, and styled the #keywords and #other-excerpt textarea fields in exactly the same way as #excerpt. Practical translation: open wp-admin.css, search for “excerpt”, and modify the rules so that they look like this:

#excerpt, #keywords, #other-excerpt {
	height: 1.8em;
	width: 98%;
}

instead of simply this:

#excerpt {
	height: 1.8em;
	width: 98%;
}

I’m sure by now you’re curious about what my posting screen looks like!

To make sure the data in these fields is processed, we need to add the following code to post.php (as we did for the “language” field above):

// add keywords
	if(isset($_POST['keywords']))
	{
	$_POST['metakeyselect'] = 'keywords';
        $_POST['metavalue'] = $_POST['keywords'];
        add_meta($post_ID);
        }
   // add other excerpt
	if(isset($_POST['other-excerpt']))
	{
	$_POST['metakeyselect'] = 'other-excerpt';
        $_POST['metavalue'] = $_POST['other-excerpt'];
        add_meta($post_ID);
        }

Displaying the “other language excerpt” is done in this simple-but-not-too-elegant way:

<?php
$post_other_excerpt=get_post_custom_values("other-excerpt");
$the_other_excerpt=$post_other_excerpt['0'];
if($the_other_excerpt!="")
{
	if($the_language=="fr")
	{
	$the_other_language="en";
	}

	if($the_language=="en")
	{
	$the_other_language="fr";
	}
?>
    <div class="other-excerpt" lang="<?php print($the_other_language); ?>">
    <?php print($the_other_excerpt); ?>
    </div>
  <?php
  }
  ?>

accompanied by the following CSS:

div.other-excerpt:lang(fr)
{
background-color: #FAECE7;
}
div.other-excerpt:lang(en)
{
background-color: #FFF;
}
div.other-excerpt:before {
  content: " [" attr(lang) "] ";
  font-weight: normal;
}

Now that we’ve got the basics covered, what else can be done? Well, I’ve got some ideas. Mainly, I’d like visitors to be able to add “en” or “fr” at the end of any url to my weblog, and that would automatically filter out all the content which is not in that language — maybe using the trick Daniel describes? In addition to that, it would also change the language of what I call the “page furniture” — titles, footer, and even (let’s by ambitious) category names. Adding language sensitivity to trackbacks and comments could also be interesting.

A last thing I’ll mention in the multilingual department for this weblog is my styling of outgoing links if they are written in a language which is not my post language, using the hreflang attribute. It’s easy, and you should do it too!

Suw (who has just resumed blogging in Welsh) and I have just set up a “Multilingual blogging” channel on TopicExchange — please trackback it if you write about blogging in more than one language!

Batch Category Editing For WordPress [en]

I put together an admin screen for WordPress today which allows changing multiple categories of multiple posts at the same time. Code available, no guarantees.

[fr] J'ai codé une extension à  WordPress qui permet d'éditer les catégories de nombreux billets en un coup. L'écran liste par exemple tous les billets d'une catégorie, accompagnés d'un certain nombre de selects. On effectue les modifications que l'on désire et on soumet le formulaire entier en une fois.

Update 13.07: A more recent version is out!

I had planned to give you a write-up of the beginning of my WordPress experience today. Unfortunately, I decided to clean up my categories somewhat before I did that, and I managed to badly mess things up.

The result is that I spent most of my day writing a Batch Categories admin screen to help me clean things up. It was something I had planned to do, and I suppose it will also be useful to other people.

If you want to play around: copy the code above into a file named batch-categories.php in your wp-admin directory. I highly recommend that you back up your wp_post2cat table before you get going. This script works for me, but hasn’t been tested much, and comes with no guarantees. It is not optimised either, so depending on how many posts and categories you list, the screen can very well take over half a minute to load!

There are still a few functionalities I want to add, in particular: assigning all listed posts to a category in one go (or removing them).

If you want pretty integration with the other screens of the Edit menu, you’ll have to tweak the navigation bar in edit.php, edit-comments.php, and moderation.php.

Update 24.06.04: I’ve uploaded a screenshot of the admin screen so you can see what it could look like.

Update II 24.06.04: Instead of hacking the Edit menu bars, you can also access the Batch Categories screen from the Plugins page: create a file called batch-access.php (e.g.) in your plugins directory. (Beware not to leave any whitespace after the ?>, though, or you’ll get errors. Promised, zips and more detailed documentation will follow.

Update 04.07.04: I tried using the script this morning, and it seems nastily broken (removed all categories for some posts). Use with caution, and get back to me if ever you hack it or modify it, I’m interested! I’ll look into this once I get back home from Vienna.

Update 12.07.04: The script now works as it should! Thanks to Ben and MooKitty for helping me nail the big nasty bug which was driving me bonkers! Two improvements I’m working on right now: making the code more efficient by using the category cache, and adding a “add all listed posts to category X” option.

Trackback? Qu’est-ce que c’est? [en]

Une petite explication du trackback pour les néophytes. Un trackback, c’est un moyen automatique de mettre un commentaire sur le billet de quelqu’un d’autre, pour dire qu’on a écrit un billet qui en parle. Facile!

Bon, alors, ces fichus trackbacks qu’on voit partout et dont tout le monde parle et qu’on a toujours pas compris, c’est quoi à  la fin? A la demande générale (les lémaniques concernés se reconnaîtront), une petite explication bien basique comme je les aime.

Imaginez que j’écris un billet qui vous inspire réflexion, au point que vous fassiez un billet en rapport sur votre propre weblog, au lieu de bêtement laisser chez moi un commentaire que vos lecteurs à  vous risquent bien de ne jamais voir. Vous faites un lien vers mon billet pour donner le contexte. Mais depuis chez moi, aucun moyen de savoir que vous avez écrit une réaction très pertinente à  ce que j’avais pondu!

La méthode classique, c’est de laisser quand même un commentaire chez moi, disant “hé, j’ai écrit un truc à  propos de ce que tu racontes ici, voilà  le lien”, et hop, le tour est joué. Seulement, à  la longue, ça devient un peu lassant de faire ça, et on voudrait bien un machin automatique qui le fasse à  notre place: le trackback.

Vous l’aurez donc à  présent compris, le trackback, c’est un bidule qui vous permet de mettre automatiquement (sans vous lever de votre bureau) un commentaire chez moi qui dit “hé, j’ai écrit un truc en rapport, venez voir!”

OK, bon, mais comment ça marche? Ce qu’il faut savoir d’abord, c’est qu’en principe, c’est votre “machine à  trackback” qui va parler avec ma “machine à  trackback”, et ils vont régler tout ça entre eux. La “machine à  trackback”, elle est en général intégrée à  l’outil de blogging qu’on utilise: Movable Type bien sûr, ou bien encore DotClear et WordPress, sans oublier les solutions “hébergées” TypePad et U-blog. Depuis peu, le système de commentaires HaloScan inclut également les trackbacks. Et depuis belle lurette, Primitive, tout en français, vous permet d’ajouter des trackbacks n’importe où.

Votre “machine à  trackback” va envoyer un message à  la mienne (un “ping”) en indiquant le nom de votre weblog et son adresse, le permalien de votre billet, et un extrait de ce que vous avez écrit. Il envoie ce message à  une certaine adresse (le fameux “TrackBack URL for this entry” que vous avez surement déjà  croisé dans la blogosphère), qui est en fait celle de ma “machine à  trackback”, agrémenté du numéro de mon billet. Mais tout ça, vous n’avez pas besoin de le savoir ni de le comprendre, vu que nos “machines à  trackback” s’en occupent très bien toutes seules.

Bon alors, comment on fait, concrètement? On admettra que vous avez déjà  une “machine à  trackback” installée. (Si vous voulez en installer une, ça c’est les règles avancées, et ce n’est malheureusement pas couvert par ce petit billet.)

La première chose à  faire, donc, c’est de cliquer sur le lien “commentaires” de mon billet pour dénicher l’adresse de trackback à  utiliser. (Pour ce billet, c’est http://, vous l’avez trouvée?)

Ensuite, il faut dire à  votre “machine à  trackback” d’envoyer le ping à  la mienne. Avec Movable Type, par exemple, cela signifie simplement que lorsque vous publiez votre billet, vous devez coller mon adresse de trackback dans le champ “URLs to Ping”. C’est tout. Le trackback apparaîtra comme un commentaire associé à  ce billet, avec un lien vers votre propre billet. Essayez!

Une chose intéressante est que les parties “envoyer” et “recevoir” de la “machine à  trackback” sont indépendantes. On peut tout à  fait envoyer des trackbacks sur d’autres sites sans pour autant les avoir activés sur le sien. Les sites utilisant Primitive, par exemple, fournissent un formulaire permettant d’envoyer un trackback. Ce formulaire peut en fait être utilisé pour envoyer des trackbacks, même si vous ne possédez pas votre propre “machine à  trackbacks”.

En guise de conclusion, un sujet de réflexion: quand envoyer un trackback? Uniquement lorsque j’ai fait un billet qui lie un autre billet, ou bien déjà  quand j’en fais un qui a un lien avec d’autres billets? Par exemple, j’ai écrit un petit compte-rendu de la troisième LBN, comme l’ont fait d’autres, mais je n’ai pas mentionné les adresses de leurs comptes-rendus dans mon billet (certains n’étaient pas écrits).

To trackback or not to trackback? That is the question.