Skip to content

Commit 6fae716

Browse files
edrplsclaude
andcommitted
fix: preserve source locale when duplicating content
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> Claude-Session: https://claude.ai/code/session_017m3BaSx6e1DQcU77hBLTxU
1 parent be8e8da commit 6fae716

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)