An approach for multilingual sites in Perch Runway

[Edit: this is not an approach that I use now, and instead favour something like this which feels easier to maintain and edit]

Many of my projects require support for multiple languages and that brings challenges in terms of allowing site owners to manage their content in an easy, intuitive manner.

I recently completed a small multilingual, real estate project and I used a nice, clean approach to the content management using my CMS of choice, the delightful Perch. The project shows a number of property developments using a simple list and detail view.

My own requirements for this project were that:

  • The edit forms for any property item are kept to a manageable length
  • The edit forms for any property item are easily understood
  • The user should never have to duplicate any content input
  • More languages can be added relatively easily and quickly
  • My own code and templates should be as clean and language independent as possible

Structuring the content

The content for each development can be split into Facts (e.g. completion date, number of units available, location lat/long coordinates etc.) and descriptive Content (e.g. summary, body content, meta info). The important thing here is that Facts are language independent, whereas the Content is specific to a language.

My approach is to store this data in Perch using collections and relationships. On this project there is a collection for the Facts, and then another collection for the Content in each language.

This gives me a collection called Properties Facts to hold the language independent property data, and a collection for each language to hold the language specific content (Properties Content EN, Properties Content ES etc).

The Properties Facts collection uses a properties-facts.html template, setting the title, slug, location, price, sizes etc. — everything that is language independent.

<!--* Assign title *-->
<perch:content id="property_title" type="text" size="l" label="Title" editor="markitup" textile="false" html="false" title="true" required="true" />
<perch:content id="property_slug" for="property_title" type="slug" suppress="true" />

<!--* Assign location category *-->
<perch:categories id="property_location" label="Location" set="locations" required="true" max="1">

<!--* Assign the lead image *-->
<perch:content type="image" id="property_lead_image" label="Image" width="640" height="480" crop="true" bucket="properties" />
<perch:content type="image" id="property_lead_image" label="Image" width="480" height="360" crop="true" bucket="properties" />

<!--* Assign some facts *-->
<perch:content id="property_price_from" type="number" format="#:0|.|," size="m" label="Price from" />
<perch:content id="property_units_available" type="number" size="s" format="#:0|.|," label="Units available"/>
<perch:content id="property_size_from" type="number" size="s" format="#:0|.|," label="Size from m2" />

etc.

Once a property item has been created within the Property Facts collection, this item is available as related content in other collections.

So to set up the Properties Content EN collection, I can use a template properties-content.html as follows:

<!--* Assign facts relationship *-->
<perch:related id="properties_facts" collection="Properties Facts" label="Facts" max="1" required="true">
</perch:related>

<!--* Descriptions *-->
<perch:content id="property_summary" type="textarea" size="s" label="Summary" editor="markitup" textile="true" html="false" divider-before="Summary" />
<perch:content id="property_description" type="textarea" size="l" textile="true" editor="markitup" label="Description" divider-before="Description" />

<!--* Detail view meta info *-->
<perch:content id="property_meta_title" label="Meta title" type="text" size="l" escape="true" count="chars" divider-before="Meta information" suppress="true" />
<perch:content id="property_meta_description" label="Meta description" type="textarea" size="m" escape="true" count="chars" suppress="true" />

etc.

The key thing above is the <perch:related> … </perch:related> tag which allows editors to select the appropriate item from the Properties Facts collection to use as related content. It’s set to only allow one item to be selected so that users can’t accidentally try to select multiple items.

Detecting the language

In this project, we’re using ISO language code references (e.g. /en, /es etc.) as the first part of the url paths. It’s trivial to grab this from the url and set a $lang variable that we can use at anytime to let the pages and content templates know which language branch is in use.

$lang should really never have a value that isn’t one of the language paths in use on the site. We can fairly easily get an array of all paths in the current site structure and then check that our $lang value matches one of the items in this array. If it does, then we can go ahead and use it. If it doesn’t match anything, then we can set it to the default language.

Something like the following will do that for us:

<?php 

    /* Check which langs we have in Perch navigation (the first part of the path e.g /en, /es etc) */

    $langs = perch_pages_navigation(array(
        'from-path'            => '/',
        'levels'               => 1,
        'hide-extensions'      => false,
        'hide-default-doc'     => true,
        'flat'                 => true,
        'template'             => 'page-path.html',
        'include-parent'       => true,
        'siblings'             => false,
        'only-expand-selected' => false,
        'add-trailing-slash'   => false,
        'navgroup'             => false,
        'include-hidden'       => false,
    ), true);


    $langs = explode("/",$langs);

    /* Check the first part of the current URL path and assign it to $lang variable */
	
    $lang = explode('/', $_SERVER["REQUEST_URI"]);

	if (count($lang) > 1) {
    	$lang = $lang[1];
	} else {
    	$lang = 'en';
	}

	/*
	  Check to see if $lang is in our array $langs
	  If not in our array, set it to value of 'en' i.e. the default language
	*/
	

	if (!in_array($lang, $langs) ){	
	    $lang = 'en';
	}
	
?>

Setting 'levels' => 1 means I’m just looking for the top level in the site structure which will give me the language branching, and the template file page-path.html just outputs the pagePath. There’s probably a cleaner way to get the same using skip-template and manipulating the result, but this works too.

<perch:pages id="pagePath" />

If the page url is /en/path/to/the/property/number, we should get a variable $lang set to en.

Perch allows us easily to pass variables into templates. In a master page template we can use the following to make the current value of $lang available for use in content templates:

PerchSystem::set_var('lang',$lang);

And then in any content template we can output the value of $lang using:

<perch:content id="lang" />

Displaying the detail page

Calling the property detail page from the correct Properties Content collection is now quite straightforward. It’s a question of using $lang to build the name of the collection we want to call and then filter on the slug of our current page url.

<?php

$collection_key = 'Properties ' . $lang;

perch_collection($collection_key, [
  'template' => 'collections/_property-detail.html',
  'count' => 1,
  'filter' => 'properties_facts.property_slug',
  'match' => 'eq',
  'value' => perch_get('s'),
]);

?>

(I like to keep my main collection templates free of any markup so that it’s really clear and focused just on the Perch template tags. Then I’ll use another template such as /_property-detail.html in the example above to actually put it all together with the markup I need on the front-end.)

The important thing above is the 'filter' => 'properties_facts.property_slug' which filters on the property slug from the related Properties Facts collection.

Keeping the templates language independent

The content templates to output the list and detail views have a fair amount of vocabulary items that I need to show alongside the Facts and Content data. For example, a property may have 3 beds, or 10 images available, or we might need to show a request info button. On a single language site I’d be happy to include those items directly in the templates. But to do that with multiple languages would require a set of conditionals to cater for each language possibility. That can lead to template bloat and it doesn’t feel particularly elegant.

Instead I used a great, lightweight translations app that very easily allows me to maintain and display a set of language strings within both page templates and content templates. The documentation is very clear and the app ties in perfectly with the multilingual approach I’m using here with a dynamic $lang variable.

Requirements met and easy editing for site admins

This set up allows the site owners to manage multilingual content in a scalable and intuitive manner, and it allows me to keep my templates clean and lean. It does mean that each property requires two Collection items (one in Facts and another in Content) but it’s a logical separation and I don’t think that it asks too much of the site editor.

Perch is flexible and there are other multilingual approaches, but this works well for me on this project. Your mileage may vary.

The project is online now, with the listings and detail view in place using this approach.