diff --git a/wagtail_localize/models.py b/wagtail_localize/models.py
index fa3fb40f..231b42a8 100644
--- a/wagtail_localize/models.py
+++ b/wagtail_localize/models.py
@@ -1242,6 +1242,9 @@ class RelatedObjectSegment(BaseSegment):
TranslatableObject, on_delete=models.CASCADE, related_name="references"
)
+ def get_source_instance(self):
+ return self.object.get_instance_or_none(self.source.locale)
+
@classmethod
def from_value(cls, source, value):
context, context_created = TranslationContext.objects.get_or_create(
diff --git a/wagtail_localize/static_src/editor/components/TranslationEditor/index.tsx b/wagtail_localize/static_src/editor/components/TranslationEditor/index.tsx
index 8062e184..71a90c6f 100644
--- a/wagtail_localize/static_src/editor/components/TranslationEditor/index.tsx
+++ b/wagtail_localize/static_src/editor/components/TranslationEditor/index.tsx
@@ -77,7 +77,31 @@ export interface SynchronisedValueSegment extends SegmentCommon {
editUrl: string;
}
-export type Segment = StringSegment | SynchronisedValueSegment;
+export interface RelatedObjectSegment extends SegmentCommon {
+ type: 'related_object';
+ source: {
+ title: string;
+ isLive: boolean;
+ liveUrl?: string;
+ editUrl?: string;
+ createTranslationRequestUrl?: string;
+ } | null;
+ dest: {
+ title: string;
+ isLive: boolean;
+ liveUrl?: string;
+ editUrl?: string;
+ } | null;
+ translationProgress: {
+ totalSegments: number;
+ translatedSegments: number;
+ } | null; // Null if translated without wagtail-localize
+}
+
+export type Segment =
+ | StringSegment
+ | SynchronisedValueSegment
+ | RelatedObjectSegment;
export interface StringTranslationAPI {
string_id: number;
diff --git a/wagtail_localize/static_src/editor/components/TranslationEditor/segments.tsx b/wagtail_localize/static_src/editor/components/TranslationEditor/segments.tsx
index 296a6638..4a5bfc1a 100644
--- a/wagtail_localize/static_src/editor/components/TranslationEditor/segments.tsx
+++ b/wagtail_localize/static_src/editor/components/TranslationEditor/segments.tsx
@@ -18,7 +18,8 @@ import {
StringTranslationAPI,
SegmentOverride,
SegmentOverrideAPI,
- Locale
+ Locale,
+ RelatedObjectSegment
} from '.';
import {
EditorState,
@@ -718,6 +719,92 @@ const EditorSynchronisedValueSegment: FunctionComponent<
);
};
+interface EditorRelatedObjectSegmentProps {
+ segment: RelatedObjectSegment;
+}
+
+const EditorRelatedObjectSegment: FunctionComponent<
+ EditorRelatedObjectSegmentProps
+> = ({ segment }) => {
+ const openEditUrl = () => {
+ if (segment.dest) {
+ window.open(segment.dest.editUrl);
+ }
+ };
+
+ const openCreateTranslationRequestUrl = () => {
+ if (!!segment.source && segment.source.createTranslationRequestUrl) {
+ window.open(segment.source.createTranslationRequestUrl);
+ }
+ };
+
+ let message = <>>;
+
+ if (segment.dest) {
+ if (segment.translationProgress !== null) {
+ // Translated with Wagtail localize. Show progress
+ message = (
+ <>
+ {segment.translationProgress.translatedSegments} /{' '}
+ {segment.translationProgress.totalSegments}{' '}
+ {gettext('segments translated')}
+ {segment.translationProgress.translatedSegments ==
+ segment.translationProgress.totalSegments && (
+
+ )}
+ >
+ );
+ } else {
+ // Segment translated without Wagtail localize. Just show a tick
+ message = ;
+ }
+ } else {
+ // Not translated
+ message = (
+ <>
+ {gettext('Not translated')}{' '}
+
+ >
+ );
+ }
+
+ return (
+
+ {segment.location.subField && (
+
+ {segment.location.subField}
+
+ )}
+
+
+ {segment.source
+ ? segment.source.title
+ : gettext('[DELETED]')}
+
+
+
+ {message}
+
+ {segment.dest && segment.dest.editUrl && (
+
+ {gettext('Edit')}
+
+ )}
+ {!segment.dest &&
+ !!segment.source &&
+ segment.source.createTranslationRequestUrl && (
+
+ {gettext('Translate')}
+
+ )}
+
+
+
+ );
+};
+
interface EditorSegmentListProps extends EditorProps, EditorState {
dispatch: React.Dispatch;
csrfToken: string;
@@ -777,6 +864,9 @@ const EditorSegmentList: FunctionComponent = ({
/>
);
}
+ case 'related_object': {
+ return ;
+ }
}
});
diff --git a/wagtail_localize/tests/test_edit_translation.py b/wagtail_localize/tests/test_edit_translation.py
index 57f83f00..afcbfa9a 100644
--- a/wagtail_localize/tests/test_edit_translation.py
+++ b/wagtail_localize/tests/test_edit_translation.py
@@ -187,6 +187,34 @@ def test_edit_page_translation(self):
self.assertEqual(props['segments'][9]['location'], {'tab': 'content', 'field': 'Text block', 'blockId': str(STREAM_BLOCK_ID), 'fieldHelpText': '', 'subField': None, 'widget': None})
# TODO: Examples that use fieldHelpText and subField
+ # Check related object
+ related_object_segment = props['segments'][10]
+ self.assertEqual(related_object_segment['type'], 'related_object')
+ self.assertEqual(related_object_segment['contentPath'], 'test_snippet')
+ self.assertEqual(related_object_segment['location'], {'tab': 'content', 'field': 'Test snippet', 'blockId': None, 'fieldHelpText': '', 'subField': None, 'widget': None})
+ self.assertEqual(related_object_segment['source']['title'], str(self.snippet))
+ self.assertEqual(related_object_segment['dest']['title'], str(self.fr_snippet))
+ self.assertEqual(related_object_segment['translationProgress'], {'totalSegments': 1, 'translatedSegments': 0})
+
+ def test_manually_translated_related_object(self):
+ # Related objects don't have to be translated by Wagtail localize so test with the snippet's translation record deleted
+ self.snippet_translation.delete()
+
+ response = self.client.get(reverse('wagtailadmin_pages:edit', args=[self.fr_page.id]))
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtail_localize/admin/edit_translation.html')
+
+ props = json.loads(response.context['props'])
+
+ # Check related object
+ related_object_segment = props['segments'][10]
+ self.assertEqual(related_object_segment['type'], 'related_object')
+ self.assertEqual(related_object_segment['contentPath'], 'test_snippet')
+ self.assertEqual(related_object_segment['location'], {'tab': 'content', 'field': 'Test snippet', 'blockId': None, 'fieldHelpText': '', 'subField': None, 'widget': None})
+ self.assertEqual(related_object_segment['source']['title'], str(self.snippet))
+ self.assertEqual(related_object_segment['dest']['title'], str(self.fr_snippet))
+ self.assertIsNone(related_object_segment['translationProgress'])
+
def test_override_types(self):
# Similar to above but adds some more overridable things to test with
self.page.test_synchronized_image = Image.objects.create(
diff --git a/wagtail_localize/views/edit_translation.py b/wagtail_localize/views/edit_translation.py
index 8086230e..c9808481 100644
--- a/wagtail_localize/views/edit_translation.py
+++ b/wagtail_localize/views/edit_translation.py
@@ -355,6 +355,7 @@ def edit_translation(request, translation, instance):
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)
@@ -407,7 +408,75 @@ def edit_translation(request, translation, instance):
for segment in overridable_segments
]
- segments = string_segment_data + syncronised_value_segment_data
+ 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', {