Skip to content

Commit

Permalink
Show dependencies on edit translation view
Browse files Browse the repository at this point in the history
  • Loading branch information
kaedroho committed Oct 8, 2020
1 parent b7bdb5a commit 5ae70ae
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 27 deletions.
3 changes: 3 additions & 0 deletions wagtail_localize/models.py
Expand Up @@ -1158,6 +1158,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(
Expand Down
Expand Up @@ -43,20 +43,47 @@ export interface PreviewMode {
url: string;
}

export interface StringSegment {
export interface SegmentCommon {
id: number;
contentPath: string;
source: string;
location: {
tab: string;
field: string;
blockId: string | null;
subField: string | null;
helpText: string;
};
}

export interface StringSegment extends SegmentCommon {
type: 'string';
source: string;
editUrl: string;
}

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;
};
}

export type Segment = StringSegment | RelatedObjectSegment;

export interface StringTranslationAPI {
string_id: number;
segment_id: number;
Expand Down Expand Up @@ -111,7 +138,7 @@ export interface EditorProps {
name: string;
url: string;
} | null;
segments: StringSegment[];
segments: Segment[];
initialStringTranslations: StringTranslationAPI[];
}

Expand Down
Expand Up @@ -9,7 +9,9 @@ import {
EditorProps,
StringSegment,
StringTranslation,
StringTranslationAPI
StringTranslationAPI,
RelatedObjectSegment,
Segment
} from '.';
import {
EditorState,
Expand Down Expand Up @@ -266,15 +268,15 @@ const SegmentList = styled.ul`
padding-right: 50px;
`;

interface EditorSegmentProps {
interface EditorStringSegmentProps {
segment: StringSegment;
translation?: StringTranslation;
isLocked: boolean;
dispatch: React.Dispatch<EditorAction>;
csrfToken: string;
}

const EditorSegment: FunctionComponent<EditorSegmentProps> = ({
const EditorStringSegment: FunctionComponent<EditorStringSegmentProps> = ({
segment,
translation,
isLocked,
Expand Down Expand Up @@ -392,6 +394,79 @@ const EditorSegment: FunctionComponent<EditorSegmentProps> = ({
);
};

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);
}
};

return (
<li>
{segment.location.subField && (
<SegmentFieldLabel>
{segment.location.subField}
</SegmentFieldLabel>
)}
<SegmentValue>
<p>
{segment.source
? segment.source.title
: gettext('[DELETED]')}
</p>
</SegmentValue>
<SegmentToolbar>
<li>
{!!segment.dest ? (
<>
{segment.translationProgress.translatedSegments} /{' '}
{segment.translationProgress.totalSegments}{' '}
{gettext('segments translated')}
{segment.translationProgress.translatedSegments ==
segment.translationProgress.totalSegments && (
<Icon name="tick" className="icon--green" />
)}
</>
) : (
<>
{gettext('Not translated')}{' '}
<Icon name="warning" className="icon--red" />
</>
)}
</li>
<li>
{segment.dest && segment.dest.editUrl && (
<ActionButton onClick={openEditUrl}>
{gettext('Edit')}
</ActionButton>
)}
{!segment.dest &&
!!segment.source &&
segment.source.createTranslationRequestUrl && (
<ActionButton
onClick={openCreateTranslationRequestUrl}
>
{gettext('Translate')}
</ActionButton>
)}
</li>
</SegmentToolbar>
</li>
);
};

interface EditorSegmentListProps extends EditorProps, EditorState {
dispatch: React.Dispatch<EditorAction>;
csrfToken: string;
Expand All @@ -405,7 +480,7 @@ const EditorSegmentList: FunctionComponent<EditorSegmentListProps> = ({
csrfToken
}) => {
// Group segments by field/block
const segmentsByFieldBlock: Map<string, StringSegment[]> = new Map();
const segmentsByFieldBlock: Map<string, Segment[]> = new Map();
segments.forEach(segment => {
const field = segment.location.field;
const blockId = segment.location.blockId || 'null';
Expand All @@ -424,16 +499,23 @@ const EditorSegmentList: FunctionComponent<EditorSegmentListProps> = ({
([fieldBlock, segments]) => {
// Render segments in field/block
const segmentsRendered = segments.map(segment => {
return (
<EditorSegment
key={segment.id}
segment={segment}
translation={stringTranslations.get(segment.id)}
isLocked={isLocked}
dispatch={dispatch}
csrfToken={csrfToken}
/>
);
switch (segment.type) {
case 'string': {
return (
<EditorStringSegment
key={segment.id}
segment={segment}
translation={stringTranslations.get(segment.id)}
isLocked={isLocked}
dispatch={dispatch}
csrfToken={csrfToken}
/>
);
}
case 'related_object': {
return <EditorRelatedObjectSegment segment={segment} />;
}
}
});

return (
Expand Down
98 changes: 88 additions & 10 deletions wagtail_localize/views/edit_translation.py
Expand Up @@ -266,6 +266,8 @@ def edit_translation(request, translation, instance):
string_segments = translation.source.stringsegment_set.all().order_by('order')
string_translations = string_segments.get_translations(translation.target_locale)

related_object_segments = translation.source.relatedobjectsegment_set.all().order_by('order')

tab_helper = TabHelper(source_instance)

breadcrumb = []
Expand All @@ -292,6 +294,91 @@ def edit_translation(request, translation, instance):
'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),
'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
]

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):
# TODO: handle manually translated things
try:
translation = Translation.objects.get(source__object_id=segment.object_id, target_locale=locale, enabled=True)

except Translation.DoesNotExist:
return

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),
'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 + 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({
Expand Down Expand Up @@ -349,16 +436,7 @@ def edit_translation(request, translation, instance):
for mode, label in (instance.preview_modes if isinstance(instance, Page) else [])
],
'machineTranslator': machine_translator,
'segments': [
{
'id': segment.id,
'contentPath': segment.context.path,
'source': segment.string.data,
'location': get_segment_location_info(source_instance, tab_helper, segment),
'editUrl': reverse('wagtail_localize:edit_string_translation', kwargs={'translation_id': translation.id, 'string_segment_id': segment.id}),
}
for segment in string_segments
],
'segments': segments,

# We serialize the translation data using Django REST Framework.
# This gives us a consistent representation with the APIs so we
Expand Down

0 comments on commit 5ae70ae

Please sign in to comment.