I'm currently working on the initial development phase of my latest project, a redesign and site migration for the charity Girls Not Brides.
The previous site is a pretty solid Wordpress build, and as it's multilingual (in English, French, and Spanish), it's using WPML for the translations.
Having been involved with a project recently that uses Wagtail Model Translation, we decided to go with the other major alternative for multilingual Wagtail sites, Wagtailtrans.
For me, this follows a better UX model of having an individual page per language (using synchronised page trees), rather than the model translation method of duplicating multiple fields on the same page.
It's also similar to the way that WPML deals with post translations - so that's a UX win, as the transition from Wordpress to Wagtail will retain some familiarity for the CMS users.
Nothing is perfect
There are some drawbacks to Wagtailtrans, notably its current inability to handle non-page items such as settings or snippets.
As such, these are being handled in the build by Django Model Translation (I would have used Wagtail Model Translation, but there's no option to disable its page translation handling, so that just causes a big fight with Wagtailtrans).
Another feature that's missing is the ability to edit translated sibling pages directly from the page editor. You can do so from the parent listing view using the Edit in action buttons, but these aren't available when editing a page directly.

"Edit in" buttons in the Wagtail page listing view. If the page isn't immediately visible in the listing, it can hard to find and edit sibling pages
This means finding and editing a sibling page can be a challenge, especially if the CMS user has not recently edited the page that they are looking for, as it won't necessarily be immediately visible in the parent page listing.
Wagtail hooks to rescue
We can, however, use the register_page_action_menu_item
Wagtail hook combined with the Wagtailtrans helper function edit_in_language_items
to add action buttons to the page editor itself.

The default page edit action button menu. Here's where we can add edit buttons for sibling languages
This hook needs an instance of wagtail.admin.action_menu.ActionMenuItem
returned from the function, so we'll need a separate class per language. Within that class, we'll need to:
- Check the page has a language
- Don't show if it's the current page language
- Return the edit URL if we're showing the button
As we're going to need to do that for multiple languages, I created the helper functions get_is_shown
and get_edit_url_for_lang
so that they could be reused and keep everything DRY.
Here's how that shapes up for adding an Edit English action button to translated pages:
from wagtailtrans.wagtail_hooks import edit_in_language_items
from wagtail.admin.action_menu import ActionMenuItem
from wagtail.core import hooks
def get_is_shown(context, lang):
page = context['page']
language = getattr(page, 'language', None)
if not language:
return False
elif lang == language.code:
return False
return True
def get_edit_url_for_lang(context, lang):
items = edit_in_language_items(context['page'], context['user_page_permissions'])
for item in items:
if self.lang_name == item.label:
return item.url
class EditEnMenuItem(ActionMenuItem):
name = 'action-edit-en'
label = 'Edit English'
icon_name = 'site'
def get_url(self, request, context):
return get_edit_url_for_lang(context, 'English')
def is_shown(self, request, context):
return get_is_shown(context, 'en')
@hooks.register('register_page_action_menu_item')
def register_en_menu_item():
return EditEnMenuItem(order=0)
So that's not too bad.
We get our action button on the page which is the most important thing, but I'm sure you'll see how this is going to become very boilerplatey once we add more languages, with each language needing its own class and register function:
from wagtailtrans.wagtail_hooks import edit_in_language_items
from wagtail.admin.action_menu import ActionMenuItem
from wagtail.core import hooks
def get_edit_url_for_lang(context, lang):
items = edit_in_language_items(context['page'], context['user_page_permissions'])
for item in items:
if self.lang_name == item.label:
return item.url
def get_is_shown(context, lang):
page = context['page']
language = getattr(page, 'language')
if not language:
return False
elif lang == language.code:
return False
return True
class EditEnMenuItem(ActionMenuItem):
name = 'action-edit-en'
label = 'Edit English'
icon_name = 'site'
def get_url(self, request, context):
return get_edit_url_for_lang(context, 'English')
def is_shown(self, request, context):
return get_is_shown(context, 'en')
class EditFrMenuItem(ActionMenuItem):
name = 'action-edit-fr'
label = "Edit French"
icon_name = 'site'
def get_url(self, request, context):
return get_edit_url_for_lang(context, 'French')
def is_shown(self, request, context):
return get_is_shown(context, 'fr')
class EditEsMenuItem(ActionMenuItem):
name = 'action-edit-es'
label = "Edit Spanish"
icon_name = 'site'
def get_url(self, request, context):
return get_edit_url_for_lang(context, 'Spanish')
def is_shown(self, request, context):
return get_is_shown(context, 'es')
@hooks.register('register_page_action_menu_item')
def register_en_menu_item():
return EditEnMenuItem(order=0)
@hooks.register('register_page_action_menu_item')
def register_fr_menu_item():
return EditFrMenuItem(order=1)
@hooks.register('register_page_action_menu_item')
def register_es_menu_item():
return EditEsMenuItem(order=2)
You gotta love a loop
So, let's refactor the above code to use dynamic classes, and keep everything nice and scalable.
Here's the finished version of the code:
from django.conf import settings
from wagtailtrans.wagtail_hooks import edit_in_language_items
from wagtail.admin.action_menu import ActionMenuItem
from wagtail.core.hooks import register
class EditLangMenuItem(ActionMenuItem):
name = 'action-edit-lang'
icon_name = 'site'
lang_code = ''
lang_name = ''
def get_url(self, request, context):
items = edit_in_language_items(context['page'], context['user_page_permissions'])
for item in items:
if self.lang_name == item.label:
return item.url
def is_shown(self, request, context):
page = context['page']
language = getattr(page, 'language', None)
if not language:
return False
elif self.lang_code == language.code:
return False
return True
# dynamically create and regisetr action menu classes for each language
for i, item in enumerate(settings.LANGUAGES):
cls = type(
'EditLangMenuItem' + item[0],
(EditLangMenuItem, ),
{
'lang_code': str(item[0]),
'lang_name': str(item[1]),
'label': 'Edit ' + str(item[1]),
'order': i,
}
)
register('register_page_action_menu_item', lambda cls=cls: cls())
And here's how our modified menu looks on an English version of a translated page:

Modified page editor action button menu, with additional sibling page edit buttons