Skip to content

[6.x] Fix duplicate localizations - #15732

Merged
jasonvarga merged 4 commits into
statamic:6.xfrom
qmeister:fix/localize-existing-entry
Oct 28, 2026
Merged

jasonvarga merged 4 commits into
statamic:6.xfrom
qmeister:fix/localize-existing-entry

Conversation

@qmeister

@qmeister qmeister commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Description of the Problem

LocalizeEntryController always creates a new localization and never checks whether one already exists for that site. Each request therefore adds another entry with the same origin, so one site can end up with several translations of the same entry.

This happens in two common ways:

  • The Sites panel doesn't block the localization request while it's in flight, so clicking a site several times in a row sends several requests.
  • A stale browser tab still shows a site as not localized. Localizing from it creates a duplicate of a translation that already exists.

Once duplicates exist, $entry->in($site) returns one of them arbitrarily. The CP, the language switcher and the front-end can then show different copies of the same entry.

How to Reproduce

  1. Set up a multisite install with a collection available in two sites.
  2. Create and save an entry in the first site.
  3. In the Sites panel, click the second site several times quickly.
  4. Filter the entry listing by the second site. The entry is listed once per click.

What this fix Does

  • If a localization already exists for the requested site, the controller returns its edit URL instead of creating another. The response shape (handle, url) is unchanged, so the existing CP code opens the existing localization.
  • Adds a test for this case.

The lookup and the creation run inside a Cache::lock per entry and site, so two requests arriving at the same moment can't both create one. The lock is a separate commit, easy to drop if you'd rather not have it.

@qmeister qmeister changed the title [6.x] Return existing localization instead of creating a duplicate [6.x] Fix duplicate localisations for the same site Oct 27, 2026
@qmeister qmeister changed the title [6.x] Fix duplicate localisations for the same site [6.x] Fix duplicate localizations for the same site Oct 27, 2026
@qmeister qmeister changed the title [6.x] Fix duplicate localizations for the same site [6.x] Fix duplicate localizations Oct 27, 2026
@duncanmcclean

Copy link
Copy Markdown
Member

Thanks for this! One gap: the existence check only matches entries whose origin is the entry being localized from. In the CP you can choose any existing localization as the origin, so if de already exists (from en) and someone localizes fr → de, the query finds nothing and a duplicate is still created.

Could you look the existing localization up through the whole origin chain instead, e.g. $entry->in($site) (which resolves via root()->descendants()), and key the lock on $entry->root()->id() so concurrent requests from different entries in the chain are serialised too? A test for localizing from a non-root localization would cover it.

@qmeister

Copy link
Copy Markdown
Contributor Author

Thanks for this! One gap: the existence check only matches entries whose origin is the entry being localized from. In the CP you can choose any existing localization as the origin, so if de already exists (from en) and someone localizes fr → de, the query finds nothing and a duplicate is still created.

@duncanmcclean Good catch, thanks! You’re right: the query only matched origin = <entry being localized>, so localizing fr from de slipped past an fr that already existed from en indeed.

Could you look the existing localization up through the whole origin chain instead, e.g. $entry->in($site) (which resolves via root()->descendants()), and key the lock on $entry->root()->id() so concurrent requests from different entries in the chain are serialised too? A test for localizing from a non-root localization would cover it.

I've changed it as suggested, and added a test to cover it. See: 48097f6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jasonvarga
jasonvarga merged commit c9cba48 into statamic:6.x Oct 28, 2026
61 checks passed
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.

3 participants