Complex Multi-Lingual Site Setup

Hi all,

I have a customer that needs a site setup in 3 languages. The problem is, that the content of the site is going to be vastly different between the 3 languages - for instance: the site has a blog, but it will use different article categories, tags, and will have totally different articles in the 3 languages. So, what is needed is not just a straight-forward translation of every page in the site. In each language, there will be lots of pages that the other languages won’t have, intentionally.

It seems to me, therefore, that Kirby’s built-in multi-lingual setup may not be the best solution in this scenario Please correct me if I’m wrong, as I’d love to be able to do this with the built-in tools…

The client has asked us, to have the site’s URLs like this:

The main site, in English:
The French site:
The German site:

…and so on. I was wondering whether the following would work:

  1. copy the entire site into de and fr subfolders within the parent English site, so that each subfolder is an entire site by itself.
  2. edit the .htaccess file at the root of the parent site, so that /fr and /de requests don’t get routed - get passed straight through to those folders.

As this is the first time I’ve ever had to do a setup like this, I thought I’d run it by you guys first, to get some feedback, and check whether this is indeed the proper way to do it, of whether I’m missing something obvious.

All guidance is greatly appreciated!

I’m currently working on a similar site. We are using the customized folder setup for this so that every language has its own content folder. Then we use domain-specific configuration files to change the default language per domain.

@lukasbestle That sounds IDEAL!!! But doesn’t this kind of setup require me to have separate domains? In my case, it’s all in the same domain, with sub-directories/sub-folders - i.e., “” rather than “” (I tried suggesting that to the client, and they didn’t want it)…

Can I use “customised folder setup” and “domain specific configuration files” with this setup, if I’m using sub-folders in the url? i.e.- how can I load specific configurations for “”, “” and “”?

Would you mind sharing some of your setup, as an example?

Ah, sorry. I forgot about that part.

Your “customized folder setup” can use any logic you need. So you could also extract the language code from url::path() and use that to determine the language. Maybe something like this in the site.php:

// Initiate Kirby
$kirby   = kirby();
$domain  = server::get('server_name');

// Manually set the index URL
$kirby->urls->index = url::scheme() . '://' . $domain;

// Get the current language code
$code = explode('/', url::path())[0];
if(!in_array($code, array('en', 'de', 'fr'))) $code = 'en';

// Set the content root and URL
$kirby->roots->content = __DIR__ . DS . 'content' . DS . $code;
$kirby->urls->content  = $kirby->urls->index . '/content/' . $code;

// Set a configuration var so that you can later set the default language
// Note: You have to do this manually in the config, Kirby won't do it automatically
c::set('language.default', $code);

How will the Panel know which ‘content’ folder to access? - i.e., if I use “”, will it know that it needs to read from the ‘de’ content folder?

The Panel also supports the site.php configuration. :slight_smile:

@lukasbestle do I also have to setup custom routes? At the moment, if I try to access “” or “” I get the error page…

I have done the following, as per your instructions:

  1. put a folder “en” inside the content folder, and moved all the content into it.
  2. duplicated the ‘en’ folder to make ‘fr’ and ‘de’ folders.
  3. at the root level, next to index.php, created a ‘site.php’ with the code you provided above.
  4. at the end of the file, I added the following code, to try and set the locale - I don’t know whether this will work or not, so please feel free to guide me here, too:
switch ($code){
  case 'fr':
  case 'de':

If I simply try to access “”, it goes fine - it finds the content that is inside “content/en”. But if I try to access “” or “”, it fails - it takes me to the error page…

I’ve got it almost working… Everything works now, except for the panel.

What I’ve done is:

  1. added routes for anything going to ‘/de’ or ‘/fr’ - like this:
c::set('routes', array(
    // trapping DE language-specific routes
    'pattern' => array('de','de/(:all)'),
    'action' => function($uri='') {
      return page($uri);
  1. set the base url for each language, like this:
case 'de':
    c::set('url', $domain . '/de');

As mentioned, this means that everything works, except for the panel: if I try to access “” or “” I get a blank page. No php errors, no html code on the page at all, it simply returns blank…

EDIT: setting the base url via c::set('url', $domain . '/de') doesn’t actually work - I’m now getting blank pages everywhere, and it seems that the browser is trying to load assets from ‘de/assets’, thumbs from ‘de/thumbs’, etc. It’s not just a panel issue, and I can’t see a way around this…

I think it might be easier to just have sub-folders inside the parent site, as I described in my initial post. Not elegant, but probably the easiest solution in this scenario, where we must use sub-folders, instead of sub-domains… :disappointed:

That’s all correct.

My solution requires proper language configuration using the languages option. So it would be something like this instead of your switch:

c::set('languages', array(
    'code'    => 'en',
    'name'    => 'English',
    'default' => $code == 'en',
    'locale'  => 'en_US',
    'url'     => '/',
    'code'    => 'de',
    'name'    => 'Deutsch',
    'default' => $code == 'de',
    'locale'  => 'de_DE',
    'url'     => '/de',
    'code'    => 'fr',
    'name'    => 'Français',
    'default' => $code == 'fr',
    'locale'  => 'fr_FR',
    'url'     => '/fr',

The advantage: You can use language variables (l::set and l::get) and language selectors and Kirby will do all the routing for you.
But please note that you will have to rename all content files from e.g. project.txt to project.en.txt, and respectively.

I haven’t tested this code specifically, so please let me know if that works.