Skip to content

fix(translations): backfill post_parent on child translations created before their parent - #244

Merged
JohnRDOrazio merged 2 commits into
mainfrom
fix/reparent-orphaned-child-translations
Jul 10, 2026
Merged

fix(translations): backfill post_parent on child translations created before their parent#244
JohnRDOrazio merged 2 commits into
mainfrom
fix/reparent-orphaned-child-translations

Conversation

@JohnRDOrazio

@JohnRDOrazio JohnRDOrazio commented Jul 10, 2026

Copy link
Copy Markdown
Member

Problem

The translated /governance/research section pages rendered 0–1 research cards instead of 3 (IT/ES: none; FR/PT/DE: one). The section template lists child pages by post_parent — and 12 of the 15 translated research papers were parentless root-level pages (e.g. /it/infrastruttura-dati-affidabile-… instead of /it/governance-2/ricerca/…). The 5 logo-symbolism translations had the same defect (17 orphaned pages total).

Root cause

All three draft-creation paths (translate.php, translate-all.php, deploy-translation.php) propagate post_parent only when the parent's translation already exists at draft-creation time:

if ($source->post_parent) {
    $parent_translation = pll_get_post($source->post_parent, $target_lang);
    if ($parent_translation) {
        $insert_args['post_parent'] = $parent_translation;
    }
}

A child translated before its parent is created parentless — and nothing ever healed it afterwards. That's why exactly one FR/PT/DE paper had a parent: it happened to be translated after the section pages.

Fix

New helper cdcf_reparent_orphaned_child_translations() in includes/handlers/translate.php, called from all three creation paths right after the new translation exists: every existing target-language translation of the source's children that is still orphaned (post_parent = 0) is adopted under the new post.

Guards:

  • Hierarchical post types only — flat types have no child pages, and their post_parent children are attachments, which must not be re-parented
  • Child translations that already have a parent are left untouched (never clobbers a deliberate placement)
  • Failed wp_update_post calls are not reported as re-parented

Production data

The 17 affected pages were re-parented directly via the REST API (research papers → their language's research section page; logo pages → their language's About page) and all affected paths + sitemaps revalidated. All 5 translated research pages now render 3 cards. This PR prevents recurrence regardless of translation order.

Tests

  • New ReparentOrphanedChildTranslationsTest (6 cases: happy path, already-parented skip, untranslated-child skip, non-hierarchical no-op, Polylang-missing no-op, failed-update not reported)
  • New wiring test in TranslateHandlerTest pinning that the creation path actually invokes the sweep
  • is_post_type_hierarchical stubbed false in the three existing handler test classes (they exercise flat posts)

composer test: 595 tests, 1370 assertions, OK

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved translations for hierarchical content by automatically linking previously orphaned child translations when their parent translation is created.
    • Prevented incorrect parent assignments by skipping children that already have a parent or lack a matching target-language translation.
  • Tests

    • Added coverage for re-parenting behavior, unsupported content types, missing translation support, and update failures.

… before their parent

The draft-creation paths (translate, translate-all, deploy-translation)
copy post_parent onto a new translation only when the parent's translation
already exists at creation time — a child translated before its parent came
out parentless and was never healed. The /governance/research papers shipped
root-level in 5 languages this way, so the translated research section pages
rendered 0-1 cards instead of 3 (children are listed by post_parent).

Add cdcf_reparent_orphaned_child_translations(): after a translation post is
created, adopt any existing target-language translations of the source's
children that are still orphaned (post_parent = 0). Only hierarchical post
types are swept — flat types have no child pages, and their post_parent
children are attachments, which must not be re-parented. Child translations
that already have a parent are left untouched.

Wired into all three creation paths. The 17 affected production pages
(12 research papers + 5 logo-symbolism translations) were re-parented
directly; this prevents recurrence regardless of translation order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@JohnRDOrazio, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 39 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7c3dba7f-aa20-4249-8ab5-f0d023386a10

📥 Commits

Reviewing files that changed from the base of the PR and between 2eeb817 and 64410a1.

📒 Files selected for processing (4)
  • wordpress/themes/cdcf-headless/includes/handlers/deploy-translation.php
  • wordpress/themes/cdcf-headless/includes/handlers/translate-all.php
  • wordpress/themes/cdcf-headless/includes/handlers/translate.php
  • wordpress/themes/cdcf-headless/tests/ReparentOrphanedChildTranslationsTest.php
📝 Walkthrough

Walkthrough

Changes

Translation reparenting

Layer / File(s) Summary
Orphaned child reparenting helper
wordpress/themes/cdcf-headless/includes/handlers/translate.php
Adds a helper that finds orphaned child translations for hierarchical posts and assigns them to a newly created parent translation.
Translation handler integration
wordpress/themes/cdcf-headless/includes/handlers/deploy-translation.php, wordpress/themes/cdcf-headless/includes/handlers/translate-all.php, wordpress/themes/cdcf-headless/includes/handlers/translate.php
Deploy, translate-all, and enqueue flows invoke the helper after creating or linking parent translations.
Reparenting and handler tests
wordpress/themes/cdcf-headless/tests/ReparentOrphanedChildTranslationsTest.php, wordpress/themes/cdcf-headless/tests/*HandlerTest.php
Tests cover successful reparenting, skipped cases, update failures, hierarchical creation, and flat-post behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TranslationHandler
  participant WordPress
  participant Polylang

  TranslationHandler->>WordPress: Find source post children
  TranslationHandler->>Polylang: Resolve target-language child translations
  TranslationHandler->>WordPress: Update orphaned child post_parent values
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main fix: backfilling parent links for child translations created before their parent existed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/reparent-orphaned-child-translations

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 9 complexity

Metric Results
Complexity 9

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
wordpress/themes/cdcf-headless/includes/handlers/translate.php (1)

79-107: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reparenting results are computed but never surfaced.

$reparented is returned but discarded at every production call site (here, and in deploy-translation.php/translate-all.php). Given this backfill exists specifically because 17 pages silently shipped root-level for months, logging or folding the reparented count/IDs into the handler's errors/response payload (the same pattern already used for attachment-meta copy failures) would give visibility if the sweep ever silently fails to find/fix an orphan going forward.

♻️ Example: surface the count via error_log or response payload
-            cdcf_reparent_orphaned_child_translations($source, (int) $post_id, $target_lang);
+            $reparented = cdcf_reparent_orphaned_child_translations($source, (int) $post_id, $target_lang);
+            if ($reparented) {
+                error_log(sprintf('cdcf_reparent_orphaned_child_translations: reparented %d children under post %d (%s)', count($reparented), $post_id, $target_lang));
+            }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wordpress/themes/cdcf-headless/includes/handlers/translate.php` around lines
79 - 107, Surface the results returned by
cdcf_reparent_orphaned_child_translations at every production call site,
including the handlers in deploy-translation.php and translate-all.php. Log the
reparented count or IDs, or incorporate them into the existing errors/response
payload using the same pattern as attachment-meta copy failures, so unsuccessful
or empty orphan-repair sweeps are observable.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@wordpress/themes/cdcf-headless/includes/handlers/translate.php`:
- Around line 79-107: Surface the results returned by
cdcf_reparent_orphaned_child_translations at every production call site,
including the handlers in deploy-translation.php and translate-all.php. Log the
reparented count or IDs, or incorporate them into the existing errors/response
payload using the same pattern as attachment-meta copy failures, so unsuccessful
or empty orphan-repair sweeps are observable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 673a41e6-2d84-4919-bf2b-3995bb6bfb82

📥 Commits

Reviewing files that changed from the base of the PR and between 116e36c and 2eeb817.

📒 Files selected for processing (7)
  • wordpress/themes/cdcf-headless/includes/handlers/deploy-translation.php
  • wordpress/themes/cdcf-headless/includes/handlers/translate-all.php
  • wordpress/themes/cdcf-headless/includes/handlers/translate.php
  • wordpress/themes/cdcf-headless/tests/DeployTranslationHandlerTest.php
  • wordpress/themes/cdcf-headless/tests/ReparentOrphanedChildTranslationsTest.php
  • wordpress/themes/cdcf-headless/tests/TranslateAllHandlerTest.php
  • wordpress/themes/cdcf-headless/tests/TranslateHandlerTest.php

…esponses

Per review: the reparent helper's result was discarded at every call site, so
a failed wp_update_post inside the sweep was silently swallowed. The helper
now returns {reparented, errors}; translate and translate-all merge the
errors into their existing best-effort errors array (same pattern as the
attachment-meta copy failures), and deploy-translation gains an errors field
in its response carrying them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@JohnRDOrazio
JohnRDOrazio merged commit 055208a into main Jul 10, 2026
13 checks passed
@JohnRDOrazio
JohnRDOrazio deleted the fix/reparent-orphaned-child-translations branch July 10, 2026 07:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants