Just implemented the recipe at Alternative multilingual recipe on a SS 2.3.1 site, with 3 language versions, and it works fine.
Big thanks to the author(s)! Lack of exactly this functionality was the main roadblock which stopped me from using SS before. Now I'm quite excited, with this multilanguage feature in a CMS such as SS my life will be easier :)
2 hiccups though, here they are with workarounds:
1) The homepages in the different languages don't work, as described above by schellmax, http://domain.tld/en/home and http://domain.tld/de/home etc all jump back to http://domain.tld/ - which is not what I want and not what I understand as the desired behavior described in the recipe. Couldn't fix it in the code without applying the core code patch as described by schellmax, but as an alternative workaround I changed the homepage to http://domain.tld/index - the language versions are now at http://domain.tld/de/index and http://domain.tld/en/index etc. Disadvantage: The real homepage is now at http://domain.tld/index and not at http://domain.tld/ . Entering http://domain.tld gets redirected to http://domain.tld/index. But that could certainly be fixed somehow, haven't looked at it yet.
2) Whenever a translated field is a HtmlEditorField and the page has been opened and saved (without entering content in that field) at least once in the CMS admin area, then these fields are saved with "<p></p>" in them, thus isset() and even empty() will report them as set and not empty, resulting in NOT showing the (untranslated) original field if there is no translation entered yet.
Yes, that sucks. But here is a workaround, the function getField($field) needs to strip the tags and check then if the translated field is empty, before deciding to serve it or the untranslated one. Here's the function getField($field) modified accordingly:
function getField($field) {
$lang = MultiLingual::currentLang();
$langField = $field.'_'.$lang;
if(!empty($this->record[$langField]) ) {
$tempField = trim(strip_tags($this->record[$langField]));
if (!empty($tempField)) {
return $this->record[$langField];
}
}
return isset($this->record[$field]) ? $this->record[$field] : null;
}
... and then there are still the forms, as dab already mentions, or more precisely the untranslated labels of the form fields of the usual contact form, haven't looked at that yet.