Skip to content

Commit ecebade

Browse files
edrplsclaude
andauthored
fix: preserve source locale when duplicating content (#2303)
duplicate() built the copy's slug against the source row's locale but inserted the row without one, so create()'s fallback landed every copy in 'en'. On locale-scoped admin lists the copy silently vanished, and the mis-scoped slug-uniqueness check could trip the UNIQUE(slug, locale) constraint when the generated slug already existed in 'en'. Pass the source locale through to create(). Duplicates keep their own fresh translation group — a copy is a new entry, not a translation — which create() already does by default; a test now locks that in. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent c1f6768 commit ecebade

3 files changed

Lines changed: 53 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"emdash": patch
3+
---
4+
5+
Fixes the Duplicate action creating the copy in the default locale instead of the source entry's locale. Duplicates of non-English entries now stay in their locale — so they appear in the locale-filtered admin list they were duplicated from — and no longer fail with a unique-constraint error when the generated slug already exists in the default locale.

packages/core/src/database/repositories/content.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ export class ContentRepository {
322322
data: newData,
323323
status: "draft",
324324
authorId: authorId || original.authorId || undefined,
325+
locale: original.locale ?? undefined,
325326
});
326327
}
327328

packages/core/tests/integration/i18n/i18n.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,53 @@ describe("i18n (Integration)", () => {
189189
).rejects.toThrow();
190190
});
191191

192+
// ── duplicate ─────────────────────────────────────────────────
193+
194+
it("duplicate() preserves the source item's locale", async () => {
195+
const original = await repo.create(
196+
createPostFixture({ slug: "hola-mundo", locale: "es", data: { title: "Hola Mundo" } }),
197+
);
198+
199+
const copy = await repo.duplicate("post", original.id);
200+
201+
expect(copy.locale).toBe("es");
202+
});
203+
204+
it("duplicate() assigns a fresh translation group", async () => {
205+
const enPost = await repo.create(createPostFixture({ slug: "hello", locale: "en" }));
206+
const esPost = await repo.create(
207+
createPostFixture({
208+
slug: "hola",
209+
locale: "es",
210+
translationOf: enPost.id,
211+
data: { title: "Hola" },
212+
}),
213+
);
214+
215+
const copy = await repo.duplicate("post", esPost.id);
216+
217+
expect(copy.translationGroup).toBe(copy.id);
218+
expect(copy.translationGroup).not.toBe(esPost.translationGroup);
219+
});
220+
221+
it("duplicate() does not collide with the generated slug in another locale", async () => {
222+
await repo.create(
223+
createPostFixture({
224+
slug: "hola-mundo-copy",
225+
locale: "en",
226+
data: { title: "Unrelated EN Post" },
227+
}),
228+
);
229+
const original = await repo.create(
230+
createPostFixture({ slug: "hola-mundo", locale: "es", data: { title: "Hola Mundo" } }),
231+
);
232+
233+
const copy = await repo.duplicate("post", original.id);
234+
235+
expect(copy.locale).toBe("es");
236+
expect(copy.slug).toBe("hola-mundo-copy");
237+
});
238+
192239
// ── findBySlug ────────────────────────────────────────────────
193240

194241
it("findBySlug() without locale returns any match", async () => {

0 commit comments

Comments
 (0)