I think I've hit a real regression here and want to flag it before someone else burns a few hours chasing it like I just did.
I'm on decap-cms@3.14.1 (bundles decap-cms-core@3.16.0), GitHub backend, editorial_workflow: true, and a collection with i18n: true using the multiple_folders structure (each locale lives in its own top-level folder, e.g. content/kb-articles/en/slug.md, content/kb-articles/pt-br/slug.md). I have a preSave handler that writes AI-translated content into another locale, using exactly the pattern described in the fix for #4729:
CMS.registerEventListener({
name: 'preSave',
handler: async ({ entry }) => {
return entry.setIn(['i18n', 'pt-br', 'data', 'title'], translatedTitle);
// ...a handful more setIn calls for other fields
},
});
That's the exact pattern from the #7227 PR description. On my version this does not write to the pt-br file at all. Instead:
- the pt-br file is left completely untouched (whatever was already there before my handler ran)
- the EN (default locale) file's frontmatter gets clobbered with what looks like the entire internal entry object --
data, i18n, path, meta, raw, slug, mediaFiles, collection, all of it, dumped straight into the file as YAML
Took me a while to track down but I'm fairly confident I found it. invokeEvent in registry.js:
let _data = { ...data };
for (const { handler, options } of handlers) {
const result = await handler(_data, options);
if (result !== undefined) {
const entry = _data.entry.set('data', result);
_data = { ...data, entry };
}
}
return _data.entry;
Whatever the handler returns gets set as entry.data (not swapped in as the new entry), and then the whole entry -- now with my full returned entry nested one level inside its own .data -- gets returned. persistEntry in backend.ts sees updatedEntity.get('data') is defined and swaps the whole thing in as the new entry draft, and entryToRaw dumps entry.get('data').toJS() straight into the file. Since my handler's setIn(['i18n', 'pt-br', ...]) only touched the top-level i18n branch and never the one that actually gets read afterwards (now one level deeper because of the extra nesting), the pt-br write never lands anywhere real.
I bisected it to #7667 (merged 2026-04-22). Before that, invokeEvent returned _data.entry.get('data'), which combined with the .set('data', result) line above made it a round trip back to exactly result -- i.e. the handler's own return value, unwrapped again -- so the #7227 pattern worked as documented. #7667 changed the final line to return _data.entry, which broke that round trip for anyone using the i18n setIn pattern, even though it was fixing a real and completely separate bug for file collections. I checked main just now (2026-09-01) and it's still there.
I confirmed decap-cms@3.12.2 (published 2026-04-17, just before #7667 landed) still has the old, working return _data.entry.get('data'); line, so I'm pinning to that as a stopgap for now.
Not sure what the right fix looks like without reintroducing the bug #7667 was fixing -- maybe persistEntry needs to detect and unwrap this specific shape, or invokeEvent needs to treat i18n entries differently. Happy to put together a minimal repro repo if that's useful, and I can take a swing at a PR if someone more familiar with this code can point me at which behavior is actually meant to be correct here.
Versions: decap-cms@3.14.1 (core 3.16.0), GitHub backend, editorial_workflow: true, i18n structure multiple_folders.
-- Marc
I think I've hit a real regression here and want to flag it before someone else burns a few hours chasing it like I just did.
I'm on
decap-cms@3.14.1(bundlesdecap-cms-core@3.16.0), GitHub backend,editorial_workflow: true, and a collection withi18n: trueusing themultiple_foldersstructure (each locale lives in its own top-level folder, e.g.content/kb-articles/en/slug.md,content/kb-articles/pt-br/slug.md). I have apreSavehandler that writes AI-translated content into another locale, using exactly the pattern described in the fix for #4729:That's the exact pattern from the #7227 PR description. On my version this does not write to the pt-br file at all. Instead:
data,i18n,path,meta,raw,slug,mediaFiles,collection, all of it, dumped straight into the file as YAMLTook me a while to track down but I'm fairly confident I found it.
invokeEventinregistry.js:Whatever the handler returns gets set as
entry.data(not swapped in as the new entry), and then the whole entry -- now with my full returned entry nested one level inside its own.data-- gets returned.persistEntryinbackend.tsseesupdatedEntity.get('data')is defined and swaps the whole thing in as the new entry draft, andentryToRawdumpsentry.get('data').toJS()straight into the file. Since my handler'ssetIn(['i18n', 'pt-br', ...])only touched the top-leveli18nbranch and never the one that actually gets read afterwards (now one level deeper because of the extra nesting), the pt-br write never lands anywhere real.I bisected it to #7667 (merged 2026-04-22). Before that,
invokeEventreturned_data.entry.get('data'), which combined with the.set('data', result)line above made it a round trip back to exactlyresult-- i.e. the handler's own return value, unwrapped again -- so the #7227 pattern worked as documented. #7667 changed the final line toreturn _data.entry, which broke that round trip for anyone using the i18nsetInpattern, even though it was fixing a real and completely separate bug for file collections. I checkedmainjust now (2026-09-01) and it's still there.I confirmed
decap-cms@3.12.2(published 2026-04-17, just before #7667 landed) still has the old, workingreturn _data.entry.get('data');line, so I'm pinning to that as a stopgap for now.Not sure what the right fix looks like without reintroducing the bug #7667 was fixing -- maybe
persistEntryneeds to detect and unwrap this specific shape, orinvokeEventneeds to treat i18n entries differently. Happy to put together a minimal repro repo if that's useful, and I can take a swing at a PR if someone more familiar with this code can point me at which behavior is actually meant to be correct here.Versions:
decap-cms@3.14.1(core3.16.0), GitHub backend,editorial_workflow: true, i18n structuremultiple_folders.-- Marc