diff --git a/docs/how-to/field-configuration.md b/docs/how-to/field-configuration.md index 7ae543c00..e77286ccb 100644 --- a/docs/how-to/field-configuration.md +++ b/docs/how-to/field-configuration.md @@ -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. @@ -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. + +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. + +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. diff --git a/wagtail_localize/segments/extract.py b/wagtail_localize/segments/extract.py index c7bf532d5..10287cc6f 100644 --- a/wagtail_localize/segments/extract.py +++ b/wagtail_localize/segments/extract.py @@ -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) diff --git a/wagtail_localize/segments/tests/test_segment_extraction.py b/wagtail_localize/segments/tests/test_segment_extraction.py index c19a068bb..087b38940 100644 --- a/wagtail_localize/segments/tests/test_segment_extraction.py +++ b/wagtail_localize/segments/tests/test_segment_extraction.py @@ -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( diff --git a/wagtail_localize/test/models.py b/wagtail_localize/test/models.py index 4ff6fc9d8..790ff99d5 100644 --- a/wagtail_localize/test/models.py +++ b/wagtail_localize/test/models.py @@ -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() @@ -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(