Skip to content

Kirby 3.9.8

Import and export translations

Kirby's Panel is the perfect place to work on translations for your content. But sometimes it is necessary to collaborate with external translators or use existing translation services instead of the Panel.

Being able to import and export translations quickly and beyond copy & paste is crucial in such situations. Especially for larger projects.

This article gives you a good foundation for your own import and export scripts. Every project is different and every translation service has its own requirements. But no matter if you need to work with JSON, XML or any other format, Kirby's PHP API is there to help you get the results you are looking for.

Exporting translations

Kirby can be loaded in any stand-alone PHP script by requiring the bootstrap.php file from the kirby folder. You might need to fix the path to the bootstrap file if you use a different folder setup.

/scripts/translation-export
#!/usr/bin/env php
<?php

require __DIR__ . '/../kirby/bootstrap.php';

$language     = 'de';
$translations = [];

foreach (site()->index() as $page) {
    $translations[] = [
        'id'      => $page->id(),
        'type'    => 'page',
        'content' => $page->content($language)->toArray(),
    ];

    foreach ($page->files() as $file) {
        $translations[] = [
            'id'      => $file->id(),
            'type'    => 'file',
            'content' => $file->content($language)->toArray(),
        ];
    }
}

$json = json_encode($translations, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);

F::write($language . '.json', $json);

echo 'The language has been exported';
exit(0);

How to run the script?

Make the script executable with…

cd scripts
chmod +x translation-export

Afterwards you can run the script from your command line with…

./translation-export

What's happening here?

The script loops through every page of your site with site()->index(). Every page is added to the $translations array with a sub-array that contains the id of the page, a type field and the content. We need the type field to distinguish between pages and files later in our import script. After adding the content for the page, a nested foreach loop goes through all the files of the page (if there are any) and adds their content to the $translations array as well.

Finally, $translations is converted to JSON using json_encode(). I added the JSON_PRETTY_PRINT and JSON_UNESCAPED_SLASHES flags to get human-readable JSON for our export file. This is easier to debug and can theoretically be directly passed to a translator who has some technical skills.

The JSON is then written to an export file in the same folder.

Example JSON output

[
    {
        "id": "photography",
        "type": "page",
        "content": {
            "title": "Fotografie"
        }
    },
    {
        "id": "photography/animals",
        "type": "page",
        "content": {
            "title": "Tiere",
            "description": "Wie macht der Fuchs?"
        }
    },
    {
        "id": "photography/animals/bird-reynolds.jpg",
        "type": "file",
        "content": {
            "alt": "Ein bunter Vogel"
        }
    }
]

Next steps

This is a pretty raw example. You have to change the $language variable for each individual language and you might need another format or another place to store the export files. But it should serve as a good foundation to be adjusted according to your own requirements.


Importing translations

The import script will overwrite all existing content for the given language. Make sure to back up your data before you run this script.

/scripts/translation-import
#!/usr/bin/env php
<?php

require __DIR__ . '/../kirby/bootstrap.php';

$kirby        = kirby();
$language     = 'de';
$translations = Data::read($language . '.json');

$kirby->impersonate('kirby');

foreach ($translations as $data) {
    $id      = $data['id'];
    $content = $data['content'];
    $type    = $data['type'];

    if ($model = $kirby->$type($id)) {
        $model->update($content, $language);
    }
}

echo 'The translation has been imported';
exit(0);

Our import script expects the output from above and reads the JSON from the exported translation files in the scripts directory. You can make it executable with…

chmod +x translation-import

…and execute it with…

./translation-import

What's happening here?

The script reads the content of the JSON file with Data::read(). This helper method returns an array. With the id, type and content stored in each item of the array, we can look for the right page or file, and update the content of it with $model->update($content, $language). It's important to pass the correct language code as the second argument. Otherwise the default language would be overwritten.

That's it! Now you have a working import/export workflow that can be used to update all translations of your site within a few seconds.

A few final ideas

Exports could be automated whenever the default content has changed or a new page or file is created. This could be achieved with hooks. The JSON could then be sent to an API of a translation service immediately.

The import could also be triggered via a webhook, if supported by your translation service. You can create custom webhooks for your site with our router. The JSON does not necessarily have to come from a file. It could also be sent in the payload of such a hook or be fetched from the translation service API.

The command line is not very user-friendly for everyone. A small section plugin could be included in the panel to offer upload- and download buttons for translations.

There are a lot of options to customize this and build the perfect solution for your project.