Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow defining translatable_fields/override_translatable_fields on StructBlock #752

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 45 additions & 4 deletions docs/how-to/field-configuration.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
# Configuring translatable fields

!!! attention

It is not currently possible to configure `StructBlock` translatable fields. See [Issue #307](https://github.com/wagtail/wagtail-localize/issues/307) for more details.

By default, Wagtail Localize will decide for you which fields are translatable and which fields should just be synchronised.
This is decided by some simple rules described in the [Auto-generation of Translatable Fields](/concept/translatable-fields-autogen)
explanation.
Expand Down Expand Up @@ -105,3 +101,48 @@ class BlogPage(Page):
SynchronizedField("slug"),
]
```

## Specifying Translatable Fields within a StructBlock

**By default, all sub-fields within a StructBlock are included for translation.** However, there may be instances where certain fields within the StructBlock should be excluded from translation. To facilitate this, we've introduced the **`translatable_blocks`** parameter.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: no need to wrap inline code ** (bold)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: wrap StructBlock, CharBlock, TextBlock and YouTubeBlock in backticks (so they are treated as inline code)


The **`translatable_blocks`** parameter allows you to specify a list of block names that should be processed for translation. Any fields within the StructBlock that are not included in this list will be excluded from translation.

For instance, consider a YouTubeBlock that contains a CharBlock for a video ID and a TextBlock for a video description. The video ID is not something we'd want to send for translation, but the description is. To exclude the video ID from translation, we would use the **`translatable_blocks`** parameter as follows:

```python
class YouTubeBlock(blocks.StructBlock):
video_id = blocks.CharBlock(
required=True, help_text="Add a YouTube video ID. You can find this in the url."
)
description = blocks.TextBlock(
required=False, help_text="Add a description for the video."
)

translatable_blocks = ["description"]
```

In this example, only the description field will be included for translation. The video_id field will be excluded from both the translation UI in Wagtail and the exported PO file.

##### Managing Images and Overrideable Segments within a StructBlock

When dealing with overrideable segments such as images within a StructBlock, it's important to note that ignoring these segments could result in losing the ability to use a different image for different languages. If you want to maintain the ability to override an image, include it in the list of translatable_blocks to preserve the default behavior.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion:

Suggested change
When dealing with overrideable segments such as images within a StructBlock, it's important to note that ignoring these segments could result in losing the ability to use a different image for different languages. If you want to maintain the ability to override an image, include it in the list of translatable_blocks to preserve the default behavior.
It is important to note that ignoring overrideable segments such as images within a `StructBlock` could result in losing the ability to use a different image for different languages. If you want to maintain the ability to override an image, include it in the list of `translatable_blocks` to preserve the default behavior.


Consider the following example of a **`LocationImageBlock`** that contains an **`ImageChooserBlock`** for an image and a **`TextBlock`** for a caption, along with a **`TextBlock`** for an address:

```python
class LocationImageBlock(blocks.StructBlock):
"""Location image block with caption."""

image = ImageChooserBlock(required=True, help_text="Add a banner image.")
caption = blocks.TextBlock(
required=False, help_text="Add a description for the image."
)
address = blocks.TextBlock(
required=True, help_text="Enter the address for this location."
)

translatable_blocks = ["caption", "image"]
```

In this example, the image is still overrideable, but the address, which is unique to this location, is locked in by translatable_blocks. This allows you to maintain the flexibility of using different images for different languages, while ensuring that certain unique information remains consistent across all translations.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
In this example, the image is still overrideable, but the address, which is unique to this location, is locked in by translatable_blocks. This allows you to maintain the flexibility of using different images for different languages, while ensuring that certain unique information remains consistent across all translations.
In this example, the image is still overrideable. The address, nique to this location, is locked in by `translatable_blocks`. This allows you to maintain the flexibility of using different images for different languages, while ensuring that certain unique information remains consistent across all translations.

8 changes: 8 additions & 0 deletions wagtail_localize/segments/extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,15 @@ def handle_related_object_block(self, related_object):
def handle_struct_block(self, struct_block, raw_value=None):
segments = []

translatable_blocks = getattr(struct_block.block, "translatable_blocks", None)

for field_name, block_value in struct_block.items():
if (
translatable_blocks is not None
and field_name not in translatable_blocks
):
continue

block_type = struct_block.block.child_blocks[field_name]
try:
block_raw_value = raw_value["value"].get(field_name)
Expand Down
34 changes: 34 additions & 0 deletions wagtail_localize/segments/tests/test_segment_extraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,40 @@ def test_customstructblock(self):
],
)

def test_structblockwithoverrides(self):
block_id = uuid.uuid4()
page = make_test_page_with_streamfield_block(
str(block_id),
"test_structblockwithoverrides",
{"field_a": "Test content", "field_b": "Non-translatable content"},
)

segments = extract_segments(page)

self.assertEqual(
segments,
[
StringSegmentValue(
f"test_streamfield.{block_id}.field_a",
"Test content",
)
],
)

def test_structblockignoreall(self):
block_id = uuid.uuid4()
page = make_test_page_with_streamfield_block(
str(block_id),
"test_structblockignoreall",
{
"field_a": "Non-translatable content",
"field_b": "Non-translatable content",
},
)

segments = extract_segments(page)
self.assertEqual(segments, [])

def test_customblockwithoutextractmethod(self):
block_id = uuid.uuid4()
page = make_test_page_with_streamfield_block(
Expand Down
16 changes: 16 additions & 0 deletions wagtail_localize/test/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,20 @@ class TestNestedStreamBlock(blocks.StreamBlock):
chooser_in_list = blocks.ListBlock(blocks.PageChooserBlock())


class TestStructBlockOverride(blocks.StructBlock):
field_a = blocks.TextBlock()
field_b = blocks.TextBlock()

translatable_blocks = ["field_a"]


class TestStructBlockIgnoreAll(blocks.StructBlock):
field_a = blocks.TextBlock()
field_b = blocks.TextBlock()

translatable_blocks = []


class TestNestedChooserStructBlock(blocks.StructBlock):
nested_page = TestChooserStructBlock()

Expand Down Expand Up @@ -196,6 +210,8 @@ class TestStreamBlock(blocks.StreamBlock):
test_nestedstreamblock = TestNestedStreamBlock()
test_streamblock_in_structblock = TestStreamBlockInStructBlock()
test_customstructblock = CustomStructBlock()
test_structblockwithoverrides = TestStructBlockOverride()
test_structblockignoreall = TestStructBlockIgnoreAll()
test_customblockwithoutextractmethod = CustomBlockWithoutExtractMethod()
test_pagechooserblock = blocks.PageChooserBlock()
test_pagechooserblock_with_restricted_types = blocks.PageChooserBlock(
Expand Down