Pass it on.

When your work and life excite you, it’s hard to keep it to yourself. Our blog is just part of a larger discussion we’re having on Twitter, Facebook, and in communities all over the web.

Jason Berg

The protests that have erupted following the murders of Ahmaud Arbery, George Floyd, and Breonna Taylor are an extraordinary show of support for a completely ordinary desire: that every person be treated with respect and dignity, free to live without the fear of being killed or having their lives suffocated by an oppressive system designed to maintain the status quo. This global outcry is a hopeful sign of change and a vital step forward on the long road toward racial justice.   At Pixo, we recognize that we can and must do more to take a stand against racism.

Tyler Edwards

For web designers, the chasm between static mockups and the final website has always been vast. It’s a lot like the path from concept art to a completed film; the artist creates a painted vision for what they hope a particular scene will look like, but it’s impossible to account for the actual motion that will eventually define it. Websites are living, moving, and ever-evolving entities, while design mockups are momentary, simple, static paintings.

Melinda Miller

It’s become a Pixo tradition to reflect on our values at the end of every year by honoring eight Pixonauts who embody those values. Which means every December, there is a mad rush to collect the awards from the last year’s winners, organize small groups of Pixonauts to choose among their colleagues for a category, and prep for the ceremony. In our last meeting of the year, we share reflections about the year — both funny and serious — and cap it off with the Core Value awards.

Making an international site is hard. Even increasing the supported languages from one to two is a large burden on a software project.

At Pixo, we recently took on a site project whose audience is located in Western Europe. We needed to support French, German, Italian, Spanish, and English (but not the familiar U.S. English … we’re talking tea and crumpets English).

Depending on your software stack, this can have different degrees of difficulty. The software packages you use can hinder you or help you. The PHP framework Symfony has a reputation of being flexible and friendly to developers who use it. We’re already big fans of Symfony; after doing some research, we moved forward with it on our international project, and it ended up paying off big for us.

Symfony Slack screenshot with tons of international flags
From the Symfony Slack. Look at all those international flags.

One benefit of using Symfony for international work is that the community of developers who use it spans the globe. The project leader and primary maintainer of Symfony is Fabien Potencier, affectionately nicknamed FabPot by the community. FabPot is a French developer. Using software built by a French developer when you’re building a website for French users turns out to be a great advantage.

Here are seven things our team learned while working on this project.

1. Translations in templates, forms, controllers

Our application has many forms, labels, buttons, and navigation text that appear to both admin users and anonymous site visitors. It turns out writing text for users who speak the same language as us is something we’ve taken for granted in the past.

Fortunately, Symfony has a translation module that makes it easier. You can define your translations in YAML files, a format easy to parse and edit by developers and non-developers. In your application code, assign each unique text an identifier that matches up in the translation files. Symfony automatically parses the translation and prints it in the template.

Screenshot of our YAML file

Symfony also sends information into the debug toolbar, which can notify you if translations are missing or a fallback is being used (such as en_GB falling back to en).

Symfony debut toolbar screenshot
We found a missing translation here.

We ended up having about 80 lines for the front end and 150 lines for the admin area of the site. All of this is extra burden on the developers and has dubious value if you only need to support one language, but a benefit is that all the text that appears in the application can be reviewed and audited in one place: those translation files. Now your application text is abstracted away and no longer acts like many magic strings in your code.

2. Localized URL patterns

Symfony has a nice feature that allows you to customize the URL for different locales. For example, if you have a news story with the id 1, the URL for the story might look something like this for an English speaker:

And if you were French, the URL would look like this without modifications:

The prefixed language has changed to “fr”, but the rest of the route pattern still has the English word “news”. That’s kind of icky for a French speaker who may copy and paste this URL to share somewhere else on the web.

In Symfony you can set up different route patterns for each locale, like:

Screenshot of routing
Localized routing is awesome.

3. DoctrineExtensionsBundle

One of the most common Symfony bundles that gets installed along with the base Symfony project is the DoctrineExtensionsBundle, which adds many boilerplate features that are useful for CMSes and web applications. For example, the Timestampable extension is one of my favorites that I reuse all the time.

The one I discovered in this project was the Translatable extension, which creates a separate translation table in the SQL database. This table holds alternate text for each of your entity’s fields that can be translated. It also comes with code for swapping out the entity’s data with the translated text.

This turned out to be invaluable in a lot of ways. We could design our database like we normally would. Our entity tables would hold our content in a standard schema and in the default locale for the site (in our case French).

Then, an admin would enter German translations for a piece of content. The Translatable extension automatically puts this content in the translations table, leaving the original default entity untouched.

Screenshot of translated text
A peek at the translation table. Here we see some translations for Research and NewsStory entities.

If a site visitor was looking at the German version (detectable by the /de_DE prefix), for example, we could use the Translatable extension to swap the entity’s content, then render like page as normal. The German speaker would see a page with the German translations shown.

4. Language, locale names with the Intl package

Symfony has an Intl package that can provide names for locales and languages. This was so useful for the developers in a lot of ways. We kept running into instances where we needed the French word for Germany and the German word for France and every other combination we could think of. All these different names were provided by the Intl package with static methods.

We made a “locale switcher” on the front end of the site for users to identify what locale they live in and language they wanted to read. Best practices told us that using text labels for locales and languages is better than using something visual like a flag. (A French flag might seem okay for the French language, but what about Switzerland or Canada, which both have a lot of French speakers?)

We could also print a locale’s name in the locale switcher in the language of that locale. This means that “German (Germany)” in the locale switcher always appears as “Deutsch (Deutschland)”. This will make sure that the menu option looks familiar to a German speaker even if they are on the French section of the site.

5. HttpFoundation, the Accept-Language header, and the Request object

Symfony’s HttpFoundation package is so useful for a lot of reasons but we have found another one while working on this project.

If it’s unclear what section of the site the visitor wants to see (for example, they hit the / route) we can use the header utilities provided in the HttpFoundation package to read the Accept-Language header and try to redirect them to matching section.

If not, we can set a default. For us, the default can be set by the admin. We can also do some smart checking, where a user looking for the English language can be redirected to English (Great Britain).

Another key thing about the Request object that might be kind of obscure: there is a method called “setLocale” on the object. If you set a locale here, then Symfony will treat that locale as the desired locale for the rest of the Request. The means rendering, translation, and other functions depending on a locale will read from whatever the developer has set in the Request object.

In the documentation, it’s carefully outlined how to set an Event Listener in your app to set the locale in the Request object early enough so that everything works as expected. Symfony provides a special attribute called “_locale” which you can use in your routes. We found it extremely useful.

public function onKernelRequest(RequestEvent $event)
    $request = $event->getRequest();

    if ($locale = $request->attributes->get('_locale'))

We strongly recommend not setting the locale in the user’s session, but instead to rely on URL patterns and the _locale attribute.

We wrote many custom services that relied on “knowing” what locale the visitor wanted — but it ended up being trivial. There are two ways to get the current locale for the request: RequestStack and RequestContext. It seems like RequestContext is a bit safer. Inject it like below, and pull the _locale out using getParameter.

public function __construct(RequestContext $requestContext)

6. SonataTranslationBundle

We used the popular SonataAdminBundle to create an admin dashboard for the maintainers of the site. Sonata has a plugin that integrates with the DoctrineExtensionsBundle to create forms for entering translations.

We found that it worked pretty well, but we wanted to make several changes to make the experience better for admins. The part we enjoy most about Sonata is how extensible and customizable it is. We changed the flags in the locale switcher to be bigger and use SVGs instead of bitmap images that come with Sonata. It was also easy to add some helpful text and tooltips when needed.

7. IntlDateFormatter

This is more of a PHP feature but we are including it anyway. Dates are tough. Each region and culture could have their own way of formatting dates. Did you know that months are rarely capitalized in France? As a bunch of Americans, we had no idea.

We relied on using standard tools for internationalization. One of them, provided by PHP’s Intl extension, is a date formatter. Give it a DateTime object and ask it to format it for the given locale, and it will give you the standard format for that locale. It removes the headache of us having to research, create, and debug different formats for each of our supported locales.

While not every feature of Symfony is specific to internalization, we did run into many cases where we needed to do something custom or configure the framework to handle a use case differently, and we generally had a much easier time doing that using Symfony.

There are lots of other little things that Symfony does that makes internationalization easier. In general, Symfony follows best object-oriented practices in their code, such as the SOLID Principles.

To learn more, we strongly recommend reading the docs for the Translation Component.

Melinda Miller

In what is now an annual tradition, Pixonauts gathered for our last staff meeting of the year and celebrated 2018. In addition to a slideshow recapping our year with both laughter (oh, the GIFs and Slack spoofs!) and serious reflections (Kleenex, anyone?), we recognized members of the Pixo team who exemplify our values. .

Lindsey Gates-Markel

How we replaced our print holiday card with a website, 1,400 LED lights, and support for an invaluable community resource The holidays and New Year inevitably get everyone reflecting on what went well, what didn’t, and how we can improve. So it’s no surprise that when a few Pixo colleagues went out to lunch in early December, the conversation turned not only to ways we can improve Pixo, but to ways we can send big love to our community as well. They decided to focus on the traditional holiday card — something we’ve sent out as a signal of goodwill for years — and turn it on its head.

Lindsey Gates-Markel

Online election guide — We Choose Our Future — won for best UX The second annual PygHack — a 24-hour hackathon at Research Park — took place in September. Just like last year, Pixo had a few hands in the hack. We staffed a table with consultants (and candy!) while the hack took place, and afterward, we judged the final hacks and awarded a Pixo Prize for best user experience (UX).

Lindsey Gates-Markel

And maybe, IDK, transform your entire organization! No problems whatsoever and no missing pieces at all Recently, visual designer Tyler and I sat down with our CEO, Lori. She’d asked for an update on a web redesign project he and I had been blissfully working on. I had done weeks of discovery full of interviews and visits and had filled a wireframe with content design.

Brian Walters

Symfony moved into version 4 last November. One of the best patterns embraced by the newest major version of this venerable framework is Autowiring — basically a term for Dependency Injection that uses PHP type-hinting. What is Dependency Injection? A fine place to start! To summarize Dependency Injection as simply as possible: When a class needs another class, the framework should provide it.

Jason Berg

“The only thing professionally better than being blessed to lead Pixo for 20 years is being able to confidently hand Pixo over to competent, like-minded colleagues. ” — Lori Gold Patterson With that, Lori Gold Patterson, Pixo’s co-founder and CEO, announced to our team that she will be transitioning leadership at Pixo to directors Jason Berg and Melinda Miller. Melinda and Jason work closely with our clients from the very beginning to the very end of every major project so, while they have a lot to learn, they get our core business.

Interested in working with us?