WordPress is a well known content management system. Clients often contact us and ask whether we could build a website for them using WordPress. While it is a good solution for simple projects, we often recognize that our clients have very custom requirements that require another, more robust solution.
That's where Wagtail comes into play. Wagtail is a Python/Django powered open source content management system to build high-quality websites and web applications.
Websites often have an international audience. The process of serving translated content to the reader is often referred to as "Internationalization" (i18n) or "multi-language" support. In this blog post we want to showcase how we are approaching multi-language in our web projects using Wagtail.
This blog post is targeted at developers, and especially developers interested in Wagtail, Python and Django. You probably need some basic Wagtail knowledge to make the most out of reading this article. We will showcase how you can create a Wagtail multi-language setup using separate page trees.
The solution outlined in this article has been heavily inspired by the official documentation and this blog post. It's used in production on multiple projects we have built, so it has been battle-tested and is a proven solution. The official blog of Austrian Airlines and codista.com (yes, the website you are just looking at) are two examples using this solution.
When approaching multi-language support you mainly have two options:
- Duplicating the fields on your model
- Duplicating your page tree
As for option 1, the code snippet below shows an example of how you could duplicate the fields on your models:
class BlogPostPage(Page): title_de = models.CharField(max_length=255) title_en = models.CharField(max_length=255) body_en = StreamField(...) body_de = StreamField(...) # Language-independent fields don't need to be duplicated hero_image = models.ForeignKey( "wagtailimages.Image", null=True, blank=True, on_delete=models.SET_NULL, related_name="+", )
We tried this approach and have not been very happy with it. Mainly because of three reasons:
- You need some logic to access the correct field in the template based upon the requested language. More logic, means potentially more bugs.
- The page will be served under the same slug, independent from the language. Mostly you want the slug to reflect the language of the content, because it will give you some extra SEO bonus points.
- The page models become quite cumbersome to maintain. Having to duplicate each field for which you want to have a translation basically doubles the count of your page variables. More code, means potentially more bugs.
The first two topics are solvable. Especially the first argument is easily solvable with some custom logic as defined in the docs. But the second argument is a really tough one to solve, which is only solvable if you dig deep into Wagtails way of serving pages.
Therefore we decided to go the route of duplicating the page tree, which works really well for us. Duplicating the page tree has some very compelling advantages:
- You get nicely routed internationalized urls + slugs out of the (wagtail) box
- You keep your page models and your backend logic slim and tidy
- No need for custom logic to access the correct field translation, since you are just doing what you are always doing: rendering a page template with exactly the page fields you want to render
- Editing a page is easier for the content editors, since they have less fields in the admin view to fill and check.
- You have to duplicate the page tree.
Duplicating the page tree requires a bit more work, but from our point of view, it's worth it. Especially since that can be easily automated if your use case requires you to do so, or you do not want to put that burden on the content editors.
The Root Page
So let’s get to the point. What is needed to achieve this duplicate page tree goodness? Basically not very much. In order to implement basic multi-language support, the only thing you need is a root page that detects the requested language and redirects the user to the appropriate page.
class LanguageRedirectionPage(Page): """Redirects the user to the language specfic home page""" parent_page_types =  def serve(self, request): # This will only return a language that is in the LANGUAGES Django setting language = translation.get_language_from_request(request) home_page = HomePage.objects.get(language=language) return HttpResponseRedirect(home_page.get_url())
Note that you have to set Django’s LANGUAGES setting so we don’t redirect non English/German users to pages that don’t exist.
HomePage needs to know its own language. Here's an example for defining the
class HomePage(Page): language = models.CharField( max_length=5, choices=getattr(settings, "LANGUAGES"), help_text=_("indicates the language this page serves as the main home page"), unique=True, ) hero_title = models.CharField(max_length=250, blank=True, null=True) hero_intro = models.TextField(_("Hero Intro"), blank=True, null=True) parent_page_types = ["cms.LanguageRedirectionPage"] class Meta: verbose_name = _("Home Page") verbose_name_plural = _("Home Pages")
It is important to note that only the
HomePage needs to know its language. All pages below know it implicitly, because they are children of a language-specific
HomePage. That is basically everything to get started for having multi-language support in your next Wagtail project.
Duplicated page trees are your friend because:
- they solve a specific SEO question for you: providing unique slugs for unique content
- you get almost everything out of the box for free if you duplicate the page tree!