Example #1
0
def edit_translation(request, translation, instance):
    if isinstance(instance, Page):
        # Page
        # Note: Edit permission is already checked by the edit page view

        page_perms = instance.permissions_for_user(request.user)

        is_live = instance.live
        is_locked = instance.locked

        if instance.live_revision:
            last_published_at = instance.live_revision.created_at
            last_published_by = instance.live_revision.user
        else:
            last_published_at = instance.last_published_at
            last_published_by = None

        if instance.live:
            live_url = instance.full_url
        else:
            live_url = None

        can_publish = page_perms.can_publish()
        can_unpublish = page_perms.can_unpublish()
        can_lock = page_perms.can_lock()
        can_unlock = page_perms.can_unlock()
        can_delete = page_perms.can_delete()

    else:
        # Snippet
        # Note: Edit permission is already checked by the edit snippet view

        is_live = True
        is_locked = False
        last_published_at = None
        last_published_by = None
        live_url = None

        can_publish = True
        can_unpublish = False
        can_lock = False
        can_unlock = False
        can_delete = request.user.has_perm(
            get_permission_name('delete', instance.__class__))

    source_instance = translation.source.get_source_instance()

    if request.method == 'POST':
        if request.POST.get('action') == 'publish':
            if isinstance(instance, Page):
                if not page_perms.can_publish():
                    raise PermissionDenied

            try:
                translation.save_target(user=request.user, publish=True)

            except ValidationError:
                messages.error(
                    request,
                    _("New validation errors were found when publishing '{object}' in {locale}. Please fix them or click publish again to ignore these translations for now."
                      ).format(
                          object=str(instance),
                          locale=translation.target_locale.get_display_name()))

            else:
                # Refresh instance to title in success message is up to date
                instance.refresh_from_db()

                string_segments = translation.source.stringsegment_set.all(
                ).order_by('order')
                string_translations = string_segments.get_translations(
                    translation.target_locale)

                # Using annotate_translation as this ignores errors by default (so both errors and missing segments treated the same)
                if string_segments.annotate_translation(
                        translation.target_locale).filter(
                            translation__isnull=True).exists():
                    # One or more strings had an error
                    messages.warning(
                        request,
                        _("Published '{object}' in {locale} with missing translations - see below."
                          ).format(object=str(instance),
                                   locale=translation.target_locale.
                                   get_display_name()))

                else:
                    messages.success(
                        request,
                        _("Published '{object}' in {locale}.").format(
                            object=str(instance),
                            locale=translation.target_locale.get_display_name(
                            )))

        return redirect(request.path)

    string_segments = translation.source.stringsegment_set.all().order_by(
        'order')
    string_translations = string_segments.get_translations(
        translation.target_locale)

    overridable_segments = translation.source.overridablesegment_set.all(
    ).order_by('order')
    segment_overrides = overridable_segments.get_overrides(
        translation.target_locale)
    related_object_segments = translation.source.relatedobjectsegment_set.all(
    ).order_by('order')

    tab_helper = TabHelper(source_instance)

    breadcrumb = []
    title_segment_id = None
    if isinstance(instance, Page):
        # find the closest common ancestor of the pages that this user has direct explore permission
        # (i.e. add/edit/publish/lock) over; this will be the root of the breadcrumb
        cca = get_explorable_root_page(request.user)
        if cca:
            breadcrumb = [{
                'id':
                page.id,
                'isRoot':
                page.is_root(),
                'title':
                page.title,
                'exploreUrl':
                reverse('wagtailadmin_explore_root') if page.is_root() else
                reverse('wagtailadmin_explore', args=[page.id]),
            } for page in instance.get_ancestors(
                inclusive=False).descendant_of(cca, inclusive=True)]

        # Set to the ID of a string segment that represents the title.
        # If this segment has a translation, the title will be replaced with that translation.
        try:
            title_segment_id = string_segments.get(context__path='title').id
        except StringSegment.DoesNotExist:
            pass

    machine_translator = None
    translator = get_machine_translator()
    if translator and translator.can_translate(translation.source.locale,
                                               translation.target_locale):
        machine_translator = {
            'name':
            translator.display_name,
            'url':
            reverse('wagtail_localize:machine_translate',
                    args=[translation.id]),
        }

    string_segment_data = [{
        'type':
        'string',
        'id':
        segment.id,
        'contentPath':
        segment.context.path,
        'source':
        segment.string.data,
        'location':
        get_segment_location_info(source_instance, tab_helper,
                                  segment.context.path),
        'editUrl':
        reverse('wagtail_localize:edit_string_translation',
                kwargs={
                    'translation_id': translation.id,
                    'string_segment_id': segment.id
                }),
        'order':
        segment.order,
    } for segment in string_segments]
    syncronised_value_segment_data = [{
        'type':
        'synchronised_value',
        'id':
        segment.id,
        'contentPath':
        segment.context.path,
        'location':
        get_segment_location_info(source_instance,
                                  tab_helper,
                                  segment.context.path,
                                  widget=True),
        'value':
        segment.data,
        'editUrl':
        reverse('wagtail_localize:edit_override',
                kwargs={
                    'translation_id': translation.id,
                    'overridable_segment_id': segment.id
                }),
        'order':
        segment.order,
    } for segment in overridable_segments]

    def get_source_object_info(segment):
        instance = segment.get_source_instance()

        if isinstance(instance, Page):
            return {
                'title':
                str(instance),
                'isLive':
                instance.live,
                'liveUrl':
                instance.full_url,
                'editUrl':
                reverse('wagtailadmin_pages:edit', args=[instance.id]),
                'createTranslationRequestUrl':
                reverse('wagtail_localize:submit_page_translation',
                        args=[instance.id]),
            }

        else:
            return {
                'title':
                str(instance),
                'isLive':
                True,
                'editUrl':
                reverse('wagtailsnippets:edit',
                        args=[
                            instance._meta.app_label,
                            instance._meta.model_name,
                            quote(instance.id)
                        ]),
                'createTranslationRequestUrl':
                reverse('wagtail_localize:submit_snippet_translation',
                        args=[
                            instance._meta.app_label,
                            instance._meta.model_name,
                            quote(instance.id)
                        ]),
            }

    def get_dest_object_info(segment):
        instance = segment.object.get_instance_or_none(
            translation.target_locale)
        if not instance:
            return

        if isinstance(instance, Page):
            return {
                'title': str(instance),
                'isLive': instance.live,
                'liveUrl': instance.full_url,
                'editUrl': reverse('wagtailadmin_pages:edit',
                                   args=[instance.id]),
            }

        else:
            return {
                'title':
                str(instance),
                'isLive':
                True,
                'editUrl':
                reverse('wagtailsnippets:edit',
                        args=[
                            instance._meta.app_label,
                            instance._meta.model_name,
                            quote(instance.id)
                        ]),
            }

    def get_translation_progress(segment, locale):
        try:
            translation = Translation.objects.get(
                source__object_id=segment.object_id,
                target_locale=locale,
                enabled=True)

        except Translation.DoesNotExist:
            return None

        total_segments, translated_segments = translation.get_progress()

        return {
            'totalSegments': total_segments,
            'translatedSegments': translated_segments,
        }

    related_object_segment_data = [{
        'type':
        'related_object',
        'id':
        segment.id,
        'contentPath':
        segment.context.path,
        'location':
        get_segment_location_info(source_instance, tab_helper,
                                  segment.context.path),
        'order':
        segment.order,
        'source':
        get_source_object_info(segment),
        'dest':
        get_dest_object_info(segment),
        'translationProgress':
        get_translation_progress(segment, translation.target_locale),
    } for segment in related_object_segments]

    segments = string_segment_data + syncronised_value_segment_data + related_object_segment_data
    segments.sort(key=lambda segment: segment['order'])

    return render(
        request,
        'wagtail_localize/admin/edit_translation.html',
        {
            # These props are passed directly to the TranslationEditor react component
            'props':
            json.dumps(
                {
                    'object': {
                        'title':
                        str(instance),
                        'titleSegmentId':
                        title_segment_id,
                        'isLive':
                        is_live,
                        'isLocked':
                        is_locked,
                        'lastPublishedDate':
                        last_published_at.strftime(DATE_FORMAT)
                        if last_published_at is not None else None,
                        'lastPublishedBy':
                        UserSerializer(last_published_by).data
                        if last_published_by is not None else None,
                        'liveUrl':
                        live_url,
                    },
                    'breadcrumb':
                    breadcrumb,
                    'tabs':
                    tab_helper.tabs_with_slugs,
                    'sourceLocale': {
                        'code':
                        translation.source.locale.language_code,
                        'displayName':
                        translation.source.locale.get_display_name(),
                    },
                    'locale': {
                        'code':
                        translation.target_locale.language_code,
                        'displayName':
                        translation.target_locale.get_display_name(),
                    },
                    'translations': [{
                        'title':
                        str(translated_instance),
                        'locale': {
                            'code':
                            translated_instance.locale.language_code,
                            'displayName':
                            translated_instance.locale.get_display_name(),
                        },
                        'editUrl':
                        reverse('wagtailadmin_pages:edit',
                                args=[translated_instance.id]) if isinstance(
                                    translated_instance, Page) else
                        reverse('wagtailsnippets:edit',
                                args=[
                                    translated_instance._meta.app_label,
                                    translated_instance._meta.model_name,
                                    quote(translated_instance.id)
                                ]),
                    } for translated_instance in instance.get_translations().
                                     select_related('locale')],
                    'perms': {
                        'canPublish': can_publish,
                        'canUnpublish': can_unpublish,
                        'canLock': can_lock,
                        'canUnlock': can_unlock,
                        'canDelete': can_delete,
                    },
                    'links': {
                        'downloadPofile':
                        reverse('wagtail_localize:download_pofile',
                                args=[translation.id]),
                        'uploadPofile':
                        reverse('wagtail_localize:upload_pofile',
                                args=[translation.id]),
                        'unpublishUrl':
                        reverse('wagtailadmin_pages:unpublish',
                                args=[instance.id]) if isinstance(
                                    instance, Page) else None,
                        'lockUrl':
                        reverse('wagtailadmin_pages:lock', args=[instance.id])
                        if isinstance(instance, Page) else None,
                        'unlockUrl':
                        reverse('wagtailadmin_pages:unlock',
                                args=[instance.id]) if isinstance(
                                    instance, Page) else None,
                        'deleteUrl':
                        reverse('wagtailadmin_pages:delete',
                                args=[instance.id]) if isinstance(
                                    instance, Page) else reverse(
                                        'wagtailsnippets:delete',
                                        args=[
                                            instance._meta.app_label,
                                            instance._meta.model_name,
                                            quote(instance.pk)
                                        ]),
                        'stopTranslationUrl':
                        reverse('wagtail_localize:stop_translation',
                                args=[translation.id]),
                    },
                    'previewModes': [{
                        'mode':
                        mode,
                        'label':
                        label,
                        'url':
                        reverse('wagtail_localize:preview_translation',
                                args=[translation.id])
                        if mode == instance.default_preview_mode else reverse(
                            'wagtail_localize:preview_translation',
                            args=[translation.id, mode]),
                    } for mode, label in (instance.preview_modes if isinstance(
                        instance, Page) else [])],
                    'machineTranslator':
                    machine_translator,
                    'segments':
                    segments,

                    # We serialize the translation data using Django REST Framework.
                    # This gives us a consistent representation with the APIs so we
                    # can dynamically update translations in the view.
                    'initialStringTranslations':
                    StringTranslationSerializer(string_translations,
                                                many=True,
                                                context={
                                                    'translation_source':
                                                    translation.source
                                                }).data,
                    'initialOverrides':
                    SegmentOverrideSerializer(segment_overrides,
                                              many=True,
                                              context={
                                                  'translation_source':
                                                  translation.source
                                              }).data,
                },
                cls=DjangoJSONEncoder)
        })
Example #2
0
def machine_translate(request, translation_id):
    translation = get_object_or_404(Translation, id=translation_id)

    instance = translation.get_target_instance()
    if not user_can_edit_instance(request.user, instance):
        raise PermissionDenied

    translator = get_machine_translator()
    if translator is None:
        raise Http404

    if not translator.can_translate(translation.source.locale,
                                    translation.target_locale):
        raise Http404

    # Get segments
    segments = defaultdict(list)
    for string_segment in translation.source.stringsegment_set.all(
    ).select_related("context", "string"):
        segment = StringSegmentValue(
            string_segment.context.path,
            string_segment.string.as_value()).with_order(string_segment.order)
        if string_segment.attrs:
            segment.attrs = json.loads(string_segment.attrs)

        # Don't translate if there already is a translation
        if StringTranslation.objects.filter(
                translation_of_id=string_segment.string_id,
                locale=translation.target_locale,
                context_id=string_segment.context_id,
        ).exists():
            continue

        segments[segment.string].append(
            (string_segment.string_id, string_segment.context_id))

    if segments:
        translations = translator.translate(translation.source.locale,
                                            translation.target_locale,
                                            segments.keys())

        with transaction.atomic():
            for string, contexts in segments.items():
                for string_id, context_id in contexts:
                    StringTranslation.objects.get_or_create(
                        translation_of_id=string_id,
                        locale=translation.target_locale,
                        context_id=context_id,
                        defaults={
                            'data': translations[string].data,
                            'translation_type':
                            StringTranslation.TRANSLATION_TYPE_MACHINE,
                            'tool_name': translator.display_name,
                            'last_translated_by': request.user,
                            'has_error': False,
                            'field_error': "",
                        })

        messages.success(
            request,
            _("Successfully translated with {}.").format(
                translator.display_name))

    else:
        messages.warning(request, _("There isn't anything left to translate."))

    # Work out where to redirect to
    next_url = get_valid_next_url_from_request(request)
    if not next_url:
        # Note: You should always provide a next URL when using this view!
        next_url = reverse('wagtailadmin_home')

    return redirect(next_url)
def edit_translation(request, translation, instance):
    if isinstance(instance, Page):
        # Page
        # Note: Edit permission is already checked by the edit page view

        page_perms = instance.permissions_for_user(request.user)

        is_live = instance.live
        is_locked = instance.locked

        if instance.live_revision:
            last_published_at = instance.live_revision.created_at
            last_published_by = instance.live_revision.user
        else:
            last_published_at = instance.last_published_at
            last_published_by = None

        if instance.live:
            live_url = instance.full_url
        else:
            live_url = None

        can_publish = page_perms.can_publish()
        can_unpublish = page_perms.can_unpublish()
        can_lock = page_perms.can_lock()
        can_unlock = page_perms.can_unlock()
        can_delete = page_perms.can_delete()

    else:
        # Snippet
        # Note: Edit permission is already checked by the edit snippet view

        is_live = True
        is_locked = False
        last_published_at = None
        last_published_by = None
        live_url = None

        can_publish = True
        can_unpublish = False
        can_lock = False
        can_unlock = False
        can_delete = request.user.has_perm(
            get_permission_name("delete", instance.__class__)
        )

    source_instance = translation.source.get_source_instance()

    if request.method == "POST":
        if request.POST.get("action") == "publish":
            if isinstance(instance, Page):
                if not page_perms.can_publish():
                    raise PermissionDenied

            try:
                translation.save_target(user=request.user, publish=True)

            except ValidationError:
                messages.error(
                    request,
                    _(
                        "New validation errors were found when publishing '{object}' in {locale}. Please fix them or click publish again to ignore these translations for now."
                    ).format(
                        object=str(instance),
                        locale=translation.target_locale.get_display_name(),
                    ),
                )

            else:
                # Refresh instance to title in success message is up to date
                instance.refresh_from_db()

                string_segments = translation.source.stringsegment_set.all().order_by(
                    "order"
                )
                string_translations = string_segments.get_translations(
                    translation.target_locale
                )

                # Using annotate_translation as this ignores errors by default (so both errors and missing segments treated the same)
                if (
                    string_segments.annotate_translation(translation.target_locale)
                    .filter(translation__isnull=True)
                    .exists()
                ):
                    # One or more strings had an error
                    messages.warning(
                        request,
                        _(
                            "Published '{object}' in {locale} with missing translations - see below."
                        ).format(
                            object=str(instance),
                            locale=translation.target_locale.get_display_name(),
                        ),
                    )

                else:
                    messages.success(
                        request,
                        _("Published '{object}' in {locale}.").format(
                            object=str(instance),
                            locale=translation.target_locale.get_display_name(),
                        ),
                    )

        return redirect(request.path)

    string_segments = translation.source.stringsegment_set.all().order_by("order")
    string_translations = string_segments.get_translations(translation.target_locale)

    overridable_segments = translation.source.overridablesegment_set.all().order_by(
        "order"
    )
    segment_overrides = overridable_segments.get_overrides(translation.target_locale)
    related_object_segments = (
        translation.source.relatedobjectsegment_set.all().order_by("order")
    )

    tab_helper = TabHelper(source_instance)

    breadcrumb = []
    title_segment_id = None
    if isinstance(instance, Page):
        # find the closest common ancestor of the pages that this user has direct explore permission
        # (i.e. add/edit/publish/lock) over; this will be the root of the breadcrumb
        cca = get_explorable_root_page(request.user)
        if cca:
            breadcrumb = [
                {
                    "id": page.id,
                    "isRoot": page.is_root(),
                    "title": page.title,
                    "exploreUrl": reverse("wagtailadmin_explore_root")
                    if page.is_root()
                    else reverse("wagtailadmin_explore", args=[page.id]),
                }
                for page in instance.get_ancestors(inclusive=False).descendant_of(
                    cca, inclusive=True
                )
            ]

        # Set to the ID of a string segment that represents the title.
        # If this segment has a translation, the title will be replaced with that translation.
        try:
            title_segment_id = string_segments.get(context__path="title").id
        except StringSegment.DoesNotExist:
            pass

    machine_translator = None
    translator = get_machine_translator()
    if translator and translator.can_translate(
        translation.source.locale, translation.target_locale
    ):
        machine_translator = {
            "name": translator.display_name,
            "url": reverse("wagtail_localize:machine_translate", args=[translation.id]),
        }

    segments = []

    for segment in string_segments:
        try:
            location_info = get_segment_location_info(
                source_instance,
                tab_helper,
                segment.context.path,
                segment.context.get_field_path(source_instance),
            )
        except FieldHasNoEditPanelError:
            continue

        segments.append(
            {
                "type": "string",
                "id": segment.id,
                "contentPath": segment.context.path,
                "source": segment.string.data,
                "location": location_info,
                "editUrl": reverse(
                    "wagtail_localize:edit_string_translation",
                    kwargs={
                        "translation_id": translation.id,
                        "string_segment_id": segment.id,
                    },
                ),
                "order": segment.order,
            }
        )

    for segment in overridable_segments:
        try:
            location_info = get_segment_location_info(
                source_instance,
                tab_helper,
                segment.context.path,
                segment.context.get_field_path(source_instance),
                widget=True,
            )
        except FieldHasNoEditPanelError:
            continue

        segments.append(
            {
                "type": "synchronised_value",
                "id": segment.id,
                "contentPath": segment.context.path,
                "location": location_info,
                "value": segment.data,
                "editUrl": reverse(
                    "wagtail_localize:edit_override",
                    kwargs={
                        "translation_id": translation.id,
                        "overridable_segment_id": segment.id,
                    },
                ),
                "order": segment.order,
            }
        )

    def get_source_object_info(segment):
        instance = segment.get_source_instance()

        if isinstance(instance, Page):
            return {
                "title": str(instance),
                "isLive": instance.live,
                "liveUrl": instance.full_url,
                "editUrl": reverse("wagtailadmin_pages:edit", args=[instance.id]),
                "createTranslationRequestUrl": reverse(
                    "wagtail_localize:submit_page_translation", args=[instance.id]
                ),
            }

        else:
            return {
                "title": str(instance),
                "isLive": True,
                "editUrl": reverse(
                    "wagtailsnippets:edit",
                    args=[
                        instance._meta.app_label,
                        instance._meta.model_name,
                        quote(instance.id),
                    ],
                ),
                "createTranslationRequestUrl": reverse(
                    "wagtail_localize:submit_snippet_translation",
                    args=[
                        instance._meta.app_label,
                        instance._meta.model_name,
                        quote(instance.id),
                    ],
                ),
            }

    def get_dest_object_info(segment):
        instance = segment.object.get_instance_or_none(translation.target_locale)
        if not instance:
            return

        if isinstance(instance, Page):
            return {
                "title": str(instance),
                "isLive": instance.live,
                "liveUrl": instance.full_url,
                "editUrl": reverse("wagtailadmin_pages:edit", args=[instance.id]),
            }

        else:
            return {
                "title": str(instance),
                "isLive": True,
                "editUrl": reverse(
                    "wagtailsnippets:edit",
                    args=[
                        instance._meta.app_label,
                        instance._meta.model_name,
                        quote(instance.id),
                    ],
                ),
            }

    def get_translation_progress(segment, locale):
        try:
            translation = Translation.objects.get(
                source__object_id=segment.object_id, target_locale=locale, enabled=True
            )

        except Translation.DoesNotExist:
            return None

        total_segments, translated_segments = translation.get_progress()

        return {
            "totalSegments": total_segments,
            "translatedSegments": translated_segments,
        }

    for segment in related_object_segments:
        try:
            location_info = get_segment_location_info(
                source_instance,
                tab_helper,
                segment.context.path,
                segment.context.get_field_path(source_instance),
            )
        except FieldHasNoEditPanelError:
            continue

        segments.append(
            {
                "type": "related_object",
                "id": segment.id,
                "contentPath": segment.context.path,
                "location": location_info,
                "order": segment.order,
                "source": get_source_object_info(segment),
                "dest": get_dest_object_info(segment),
                "translationProgress": get_translation_progress(
                    segment, translation.target_locale
                ),
            }
        )

    # Order segments by how they appear in the content panels
    # segment['location']['order'] is the content panel ordering
    # segment['order'] is the model field ordering
    # User's expect segments to follow the panel ordering as that's the ordering
    # that is used in the page editor of the source page. However, segments that
    # come from the same streamfield/inline panel are given the same value for
    # panel ordering, so we need to order by model field ordering as well (all
    # segments have a unique value for model field ordering)
    segments.sort(key=lambda segment: (segment["location"]["order"], segment["order"]))

    # Display a warning to the user if the schema of the source model has been updated since the source was last updated
    if translation.source.schema_out_of_date():
        messages.warning(
            request,
            _(
                "The data model for '{model_name}' has been changed since the last translation sync. "
                "If any new fields have been added recently, these may not be visible until the next translation sync."
            ).format(model_name=capfirst(source_instance._meta.verbose_name)),
        )

    return render(
        request,
        "wagtail_localize/admin/edit_translation.html",
        {
            "translation": translation,
            # These props are passed directly to the TranslationEditor react component
            "props": json.dumps(
                {
                    "adminBaseUrl": reverse("wagtailadmin_home"),
                    "object": {
                        "title": str(instance),
                        "titleSegmentId": title_segment_id,
                        "isLive": is_live,
                        "isLocked": is_locked,
                        "lastPublishedDate": last_published_at.strftime(DATE_FORMAT)
                        if last_published_at is not None
                        else None,
                        "lastPublishedBy": UserSerializer(last_published_by).data
                        if last_published_by is not None
                        else None,
                        "liveUrl": live_url,
                    },
                    "breadcrumb": breadcrumb,
                    "tabs": tab_helper.tabs_with_slugs,
                    "sourceLocale": {
                        "code": translation.source.locale.language_code,
                        "displayName": translation.source.locale.get_display_name(),
                    },
                    "locale": {
                        "code": translation.target_locale.language_code,
                        "displayName": translation.target_locale.get_display_name(),
                    },
                    "translations": [
                        {
                            "title": str(translated_instance),
                            "locale": {
                                "code": translated_instance.locale.language_code,
                                "displayName": translated_instance.locale.get_display_name(),
                            },
                            "editUrl": reverse(
                                "wagtailadmin_pages:edit", args=[translated_instance.id]
                            )
                            if isinstance(translated_instance, Page)
                            else reverse(
                                "wagtailsnippets:edit",
                                args=[
                                    translated_instance._meta.app_label,
                                    translated_instance._meta.model_name,
                                    quote(translated_instance.id),
                                ],
                            ),
                        }
                        for translated_instance in instance.get_translations().select_related(
                            "locale"
                        )
                    ],
                    "perms": {
                        "canPublish": can_publish,
                        "canUnpublish": can_unpublish,
                        "canLock": can_lock,
                        "canUnlock": can_unlock,
                        "canDelete": can_delete,
                    },
                    "links": {
                        "downloadPofile": reverse(
                            "wagtail_localize:download_pofile", args=[translation.id]
                        ),
                        "uploadPofile": reverse(
                            "wagtail_localize:upload_pofile", args=[translation.id]
                        ),
                        "unpublishUrl": reverse(
                            "wagtailadmin_pages:unpublish", args=[instance.id]
                        )
                        if isinstance(instance, Page)
                        else None,
                        "lockUrl": reverse(
                            "wagtailadmin_pages:lock", args=[instance.id]
                        )
                        if isinstance(instance, Page)
                        else None,
                        "unlockUrl": reverse(
                            "wagtailadmin_pages:unlock", args=[instance.id]
                        )
                        if isinstance(instance, Page)
                        else None,
                        "deleteUrl": reverse(
                            "wagtailadmin_pages:delete", args=[instance.id]
                        )
                        if isinstance(instance, Page)
                        else reverse(
                            "wagtailsnippets:delete",
                            args=[
                                instance._meta.app_label,
                                instance._meta.model_name,
                                quote(instance.pk),
                            ],
                        ),
                        "stopTranslationUrl": reverse(
                            "wagtail_localize:stop_translation", args=[translation.id]
                        ),
                    },
                    "previewModes": [
                        {
                            "mode": mode,
                            "label": label,
                            "url": reverse(
                                "wagtail_localize:preview_translation",
                                args=[translation.id],
                            )
                            if mode == instance.default_preview_mode
                            else reverse(
                                "wagtail_localize:preview_translation",
                                args=[translation.id, mode],
                            ),
                        }
                        for mode, label in (
                            instance.preview_modes if isinstance(instance, Page) else []
                        )
                    ],
                    "machineTranslator": machine_translator,
                    "segments": segments,
                    # We serialize the translation data using Django REST Framework.
                    # This gives us a consistent representation with the APIs so we
                    # can dynamically update translations in the view.
                    "initialStringTranslations": StringTranslationSerializer(
                        string_translations,
                        many=True,
                        context={"translation_source": translation.source},
                    ).data,
                    "initialOverrides": SegmentOverrideSerializer(
                        segment_overrides,
                        many=True,
                        context={"translation_source": translation.source},
                    ).data,
                },
                cls=DjangoJSONEncoder,
            ),
        },
    )