PHP
Article

Building an Internationalized Blog with FigDice

By Lukas White

Introduction to FigDice

In part one of this two-part series I started looking at FigDice, a PHP templating system that takes a slightly different approach to most.

Figs fruits

So far we’ve put together a very simple example website using Figdice. We’ve implemented a couple of pages, a Twitter feed and some template partials.

In this second and final part we’re going to add a simple blog to our example site, which allows us to look in more detail at Figdice’s concept of data feeds. We’ll also look at internationalization, translating some of the site’s content into a couple of additional languages.

The Code

I’ve created a separate repository for the code for this second part of the series, which you’ll find on Github. It expands upon the code we wrote in Part One.

There’s also an online demo.

Building a Simple Blog

Now let’s create a more complex example of a data feed, by implementing a simple blog.

First, create another feed class – this time for a blog.

<?php namespace Sitepoint\Feed;

	use figdice\Feed;

	class BlogFeed extends Feed
	{

		public function run() {
			
			return array(
				array(
					'id'			=>	3,
					'slug'		=>	'post-three',
					'title' 	=> 	'Sample Blog Post Three',
					'body'		=>	'<p>Donec sed odio dui. Maecenas sed diam eget risus varius blandit sit amet non magna. Aenean lacinia bibendum nulla sed consectetur. Vestibulum id ligula porta felis euismod semper. Cras mattis consectetur purus sit amet fermentum. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>',
					'author'	=>	array(
						'id'		=>	1,
						'name'	=>	'Bob',
					),
					'created'	=>	12345,
				),
				
				// .. more posts here, omitted for brevity

			);
		}
	}

Again, we’re faking the data, but retrieving blog posts from sort of storage is the sort of thing which is covered extensively elsewhere and ought to be relatively straightforward.

Now modify the FeedFactory class, letting it know about our new feed:

// ...
use Sitepoint\Feed\BlogFeed;

class FeedFactory implements \figdice\FeedFactory
{
	// ...
		
	public function create($className, array $attributes) {

		if ($className == 'BlogFeed') {			
			return new BlogFeed();
		}

		if ($className == 'TwitterFeed') {			
			return new TwitterFeed();
		}
		
		// ... 
		
	} 
}

Now create a new view, which we’ll use for our blog listing:

<!--  file: blog.html -->
<?xml version="1.0" encoding="utf-8"?>
<fig:template xmlns:fig="http://figdice.org/">

	<fig:dictionary file="menu.xml" name="menu" />

	<fig:feed class="BlogFeed" target="posts" />

	<fig:include file="layout.html" />

	<title fig:plug="docTitle"><fig:trans dict="menu" key="blog" /></title>

	<h1 fig:plug="pageTitle"><fig:trans dict="menu" key="blog" /></h1>

	<div fig:plug="pageContent">
		<article fig:walk="posts">
			<h2>			 					
				<a href="/blog/post/{slug}" fig:text="title"></a>
			</h2>
			<p class="author">By <span fig:text="author/name" /></p>
			<div fig:text="body"></div>
			<hr fig:auto="true" />
		</article>
	</div>

</fig:template>

Much of this we’re already seen, but let’s look at the fig:walk bit. This time, instead of creating a simple list element and injecting a single property, we’re creating additional markup; in this case, for each post we’re creating an <article> element.

Once we’re in a “walk” loop – which is essentially an iterator – properties can be accessed by name; see the <div fig:text="body"></body> for example.

Within the <h2> tag, we create a link by using fig:text to insert the title of the blog post inside the <a> tag, and use curly brackets {slug} to incorporate the post’s slug into the URL (href) attribute.

The link within the <h2> tag is slightly more complicated, because we’re defining an expression which will get evaluated. It concatenates the string /blog/post/ with the value of the post’s slug attribute to generate a URL, e.g. /blog/post/post-three.

You can also access deeper properties using slashes. For the author information we want to extract the name property from the author; so we use <span fig:text="author/name" />. You can also use the double-dot (..) notation to go up a level.

<fig:dictionary> and <fig:trans> are used for internationalization, which we’ll look at shortly.

Feeds and Attributes

You might remember there was a second, optional parameter to the factory’s create method. If you add any addtional attributes to the fig:feed element that aren’t in the fig namespace, class or target, they get passed to the factory as that second attribute.

For example, you could modify blog.html as follows:

<fig:feed class="BlogFeed" target="posts" num-posts="5" sort="'date'" sort-direction="'asc'" />

Note the single quotes inside the double-quotes; this is because the values are evaluated.

Then, in your factory, you can use getParameter() – or if you know what type to expect, getParameterBool() / getParameterInt() / getParameterString(). Each of these methods takes a default as an optional second parameter.

So for this example, we could extend the factory like this:

// @file FeedFactory.php
	public function create($className, array $attributes) {
		
		$num_posts = $this->getParameterInt('num-posts', 10);
		$sort_by = $this->getParameterString('sort', 'date');
		$sort_dir = $this->getParameterString('sort-direction', 'desc');
		
		// now you can use these values, for example when you instantiate the feed

See the feeds section of the manual for more details.

Wrapping up the Blog

Now that we’ve implemented a simple blog listing, we need to create a page to display an individual blog post.

First, let’s define the route:

// Individual blog posts.
	$app->get('/blog/post/{slug}', function($slug) use ($view) {

		$view->loadFile( '../templates/post.html' );

		// We use mount to "inject" the slug into the view, which it can then use to "pull" the appropriate post.
		$view->mount('slug', $slug);

		return $view->render();

	});

Notice how we’re injecting the slug – which is a URL parameter – into the view using the mount() method.

Now let’s create the view:

// post.html
	<xml fig:mute="true"> <!-- Mute because this tag should not end up in the HTML document. But the FigDice template must have an XML root node. -->

		<fig:dictionary file="menu.xml" name="menu" />

		<!-- Load the page layout -->
		<fig:include file="layout.html" />

		<fig:feed class="BlogFeed" target="post" post-slug=" /slug " />

		<!-- Set the <title> tag -->
		<title fig:plug="docTitle"><fig:trans dict="menu" key="about" /></title>

		<!-- Set the <h1> tag -->
		<h1 fig:plug="pageTitle" fig:text="/post/title" />

		<!-- "Plug in" the page content -->
		<div fig:plug="pageContent">

			<p class="back"><a href="/blog">&laquo; <fig:trans dict="menu" key="blog" /></a></p>

			<p class="author">By <span fig:text="/post/author/name" /></p>
			<div fig:text="/post/body"></div>	
		</div>
	</xml>

Much of this should be familiar, but let’s look at the <fig:feed> element more closely:

<fig:feed class="BlogFeed" target="post" post-slug=" /slug " />

Notice how we’re feeding the “slug” variable, which we’ve made available to the view using the mount() method, back to the BlogFeed class by using the post-slug attribute.

Let’s modify the BlogFeed class to retrieve a single blog post. Because we’re defining posts as a simple array, place this after we instantiate it:

$posts = array( .... );

// Get the slug, if provided
$slug = $this->getParameterString('post-slug');

if ($slug) {
			
	// Use a little Underscore magic to retrieve the appropriate post
	$post = Arrays::find($posts, function($post) use ($slug) {
		return ($post['slug'] == $slug);
	});

	return $post;

} else {

	// No slug, we want the lot.
	return $posts;	

}

Obviously in a “real” application you’d do some sort of database lookup, but this example should give you an idea of how to set up a data feed for your views.

Conditions

A quick note on conditionals, which you can achieve using the fig:cond attribute.

For example, to show some content given a certain condition:

<span fig:cond="logged_in == true">You are logged in</span>

To add a class to an <article> if it’s published:

<article>
		<fig:attr fig:cond="status == 1" name="class">published</fig:attr>
		...
	</article>

If status is set to one, the resulting HTML will be as follows:

<article class="published">
		...
	</article>

Internationalization

FigDice makes translating text in your templates easy. Let’s start by creating some language files to hold our translatable strings. In a large application you might want to create a file per module, making it easy to re-use them across multiple applications.

A typical structure might look like this:

lang
		en
			menu.xml
			products.xml
			checkout.xml
		es
			menu.xml
			products.xml
			checkout.xml
		fr
			menu.xml
			products.xml
			checkout.xml

Let’s create a concrete example, by translating our example site’s menu:

<!-- lang/en/menu.xml -->
<fig:dictionary xmlns:fig="http://www.figdice.org/" language="en">
	<entry key="home">Home</entry>
	<entry key="about">About</entry>
	<entry key="blog">Blog</entry>
</fig:dictionary>

Then, the Spanish version of this file:

<!-- lang/es/menu.xml -->
<fig:dictionary xmlns:fig="http://www.figdice.org/" language="es">
		<entry key="home">Inicio</entry>
		<entry key="about">Acerca</entry>
	<entry key="blog">Blog</entry>	
</fig:dictionary>

When you want to use translated text, first reference the dictionary file and give it an identifier somewhere in your template:

<fig:dictionary file="menu.xml" name="menu" />

Now you can insert the value of an item using the dictionary’s key – taken from the name attribute in the fig:dictionary declaration – and the text item’s key:

// file: templates/menu.html
	<ul class="nav nav-pills pull-right">
		<li><a href="/"><fig:trans dict="menu" key="home" /></a></li>
		<li><a href="/about"><fig:trans dict="menu" key="about" /></a></li>
		<li><a href="/blog"><fig:trans dict="menu" key="blog" /></a></li>
	</ul>

Now, somewhere in your application code, tell the view where to find your language files:

$view->setTranslationPath('../lang');

Then set the language:

$view->setLanguage('es');
	// or $view->setLanguage('en'), $view->setLanguage('fr'), etc

Let’s implement language-switching in our sample application, albeit keeping it really simple.

First, we’ll add some links in the header of the layout (templates/layout.html):

<a href="/lang/en"><img src="/img/en.png" width="16" height="16" /></a> | <a href="/lang/es"><img src="/img/es.png" width="16" height="16" /></a>

You’ll find the flag icons we’re using here in the sample repository.

Now implement the corresponding route, which simply pops the appropriate language code in a session variable:

$app->get('/lang/{lang}', function ($lang) use ($app) {

	$app['session']->set('lang', $lang);

	return $app->redirect('/');

});

Finally, near the top of index.php:

// Get the language from the session, if it's set - default to English.
$language = $app['session']->get('lang', 'en');

// ...and set it.
$view->setLanguage($language);

Now if you browse the site, you should find that clicking one of the flags will switch the menu labels to the appropriate language.

Summary

In these two articles I’ve introduced FigDice, a templating system which takes a very different approach to most. There’s more to it than I’ve been able to cover, so check out the manual, tutorials, examples or reference.

I expect that while some people will like the approach that FigDice takes, equally some won’t – so I’d be interested to hear your thoughts the comments.

  • Antonio Salvador

    I really like the fact that, finally, there’s a template engine that doesn’t change your html markup in a way that you need have to guess, in irder to apply css on elements, so I was really impressed with this approach! Thanks for sharing this.

    I am just concern with one thing: how will this look like, when you have *much more* complex conditionals, to show? Ie: “Show this ul, li, only if, price is lower then x, and user is not of type y, if date is between z and b, for all products of category Z, in companies of type are g” ?

    All this, just for “view” proposes.

    Will you reply that fig is meant to be simple, and those conditionals should be decided earlier on the model side of things?

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in PHP, once a week, for free.