diff --git a/tools/translation/align.py b/tools/translation/align.py index d72160d..bc36b7e 100644 --- a/tools/translation/align.py +++ b/tools/translation/align.py @@ -880,6 +880,25 @@ def build_delivery( for alignment in alignments: paragraph = bundle_ids.get(alignment.paragraph_id) or {} sources = {source.source_id: source for source in source_utterances(paragraph, bundle)} + if alignment.method == "mismatch": + # The paragraph's translation could not be trusted - it + # renders another paragraph. The utterances still get a row, + # marked as not voiced and carrying the reason, so the gap is + # visible in the review lane and says why. Silence would be + # indistinguishable from "never translated". + reason = "; ".join(alignment.problems) or "the translation could not be verified" + for source in sources.values(): + if source.row_id is None: + continue + utterances.append( + { + "utterance_id": source.row_id, + "voiced": False, + "reason": reason, + "source_ref": source_ref or alignment.paragraph_id, + } + ) + continue for segment in alignment.segments: source = sources.get(segment.source_id) if source is None or source.row_id is None: diff --git a/tools/translation/tests/test_align.py b/tools/translation/tests/test_align.py index 5ff5157..cd59784 100644 --- a/tools/translation/tests/test_align.py +++ b/tools/translation/tests/test_align.py @@ -333,6 +333,28 @@ class DeliveryTests(unittest.TestCase): self.assertIn("absorbed", collapsed[0]["reason"]) self.assertNotIn("text", collapsed[0]) + def test_an_unverifiable_paragraph_is_delivered_as_a_visible_gap(self) -> None: + """The row is kept and says why, rather than vanishing. + + A delivery that simply omits the utterances is indistinguishable + from one that was never translated. The review lane needs to see the + gap and its reason. + """ + alignment = align_paragraph(paragraph_fixture(), "这艘船很大。设计师很用心。", bundle_fixture()) + alignment.method = "mismatch" + alignment.segments = [] + alignment.problems = ["translation belongs to another paragraph: it renders the next one"] + delivery = build_delivery( + {"target_language": "zh"}, + [({"id": "ch-1", "paragraphs": [paragraph_fixture()]}, [alignment])], + bundle_fixture(), + ) + self.assertEqual(len(delivery["utterances"]), 2) + self.assertTrue(all(not u["voiced"] for u in delivery["utterances"])) + self.assertEqual([u["utterance_id"] for u in delivery["utterances"]], [100, 101]) + self.assertIn("another paragraph", delivery["utterances"][0]["reason"]) + self.assertNotIn("text", delivery["utterances"][0]) + class CommandTests(unittest.TestCase): def test_the_cache_directory_is_created_before_the_first_call(self) -> None: