Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Missing Localized Content When Duplicating Entries with i18n Enabled #7371

Open
starkovio opened this issue Jan 15, 2025 · 1 comment
Open
Labels
type: bug code to address defects in shipped code

Comments

@starkovio
Copy link

Describe the bug

When i18n is enabled in the CMS, duplicating an entry in a collection results in only the content of the defaultLocale being duplicated. Content from all other locales is missing in the duplicated entry.

To Reproduce

  1. Enable i18n in the CMS configuration, with at least 2 locales (e.g. en and de).
  2. Create a collection with i18n.
  3. Create an entry in a collection with content in multiple locales (e.g., defaultLocale (en) and other locale de`).
  4. Duplicate the created entry.
  5. Observe the duplicated entry:
    • Only the content for the defaultLocale is copied.
    • Content for other locale is missing.

Expected behavior

When duplicating an entry with i18n enabled, the localized content for all configured locales should also be duplicated.

Screenshots

On the video you could see, that I have an entry in a new collection called Posts Test. This entry has translations in EN (English) and DE (German). When I want to duplicate the entry, it creates a new entry, but only the content from the defaultLocale are duplicated, only EN content, the DE content is missing.

Screen.Recording.2025-01-14.at.11.26.38.mov

Applicable Versions:

  • Decap CMS version: 3.4.0
  • Git provider: GitHub
  • OS: MacOS Monterey 12.2.1
  • Browser version Chrome 131.0.6778.265
  • Node.JS version: 21

CMS configuration

This is adjusted config from /dev-test/config.yml.

Changes:

  • i18n is set up for the CMS.
  • For testing purposes, a new collection posts_test is added with i18n turned on.
backend:
  name: test-repo

site_url: https://example.com

publish_mode: editorial_workflow
media_folder: assets/uploads

i18n:
  structure: multiple_folders
  locales: [en, de, fr]
  default_locale: en
collections: # A list of collections the CMS should be able to edit
  - name: 'posts' # Used in routes, ie.: /admin/collections/:slug/edit
    label: 'Posts' # Used in the UI
    label_singular: 'Post' # Used in the UI, ie: "New Post"
    description: >
      The description is a great place for tone setting, high level information, and editing
      guidelines that are specific to a collection.
    folder: '_posts'
    slug: '{{year}}-{{month}}-{{day}}-{{slug}}'
    summary: '{{title}} -- {{year}}/{{month}}/{{day}}'
    create: true # Allow users to create new documents in this collection
    view_filters:
      - label: Posts With Index
        field: title
        pattern: 'This is post #'
      - label: Posts Without Index
        field: title
        pattern: front matter post
      - label: Drafts
        field: draft
        pattern: true
    view_groups:
      - label: Year
        field: date
        pattern: \d{4}
      - label: Drafts
        field: draft
    fields: # The fields each document in this collection have
      - { label: 'Title', name: 'title', widget: 'string', tagname: 'h1' }
      - { label: 'Draft', name: 'draft', widget: 'boolean', default: false }
      - {
          label: 'Publish Date',
          name: 'date',
          widget: 'datetime',
          format: 'YYYY-MM-DD HH:mm',
          default: '{{now}}',
        }
      - label: 'Cover Image'
        name: 'image'
        widget: 'image'
        required: false
        tagname: ''

      - { label: 'Body', name: 'body', widget: 'markdown', hint: 'Main content goes here.' }

  - name: 'posts_test' # Used in routes, ie.: /admin/collections/:slug/edit
    label: 'Posts Test' # Used in the UI
    label_singular: 'Post Test' # Used in the UI, ie: "New Post"
    description: >
      The description is a great place for tone setting, high level information, and editing
      guidelines that are specific to a collection.
    folder: '_posts_test'
    slug: '{{year}}-{{month}}-{{day}}-{{slug}}'
    summary: '{{title}} -- {{year}}/{{month}}/{{day}}'
    create: true # Allow users to create new documents in this collection
    i18n: true
    view_filters:
      - label: Posts With Index
        field: title
        pattern: 'This is post #'
      - label: Posts Without Index
        field: title
        pattern: front matter post
      - label: Drafts
        field: draft
        pattern: true
    view_groups:
      - label: Year
        field: date
        pattern: \d{4}
      - label: Drafts
        field: draft
    fields: # The fields each document in this collection have
      - { label: 'Title', name: 'title', widget: 'string', tagname: 'h1', i18n: 'translate' }
      - { label: 'Draft', name: 'draft', widget: 'boolean', default: false, i18n: 'translate' }
      - {
          label: 'Publish Date',
          name: 'date',
          widget: 'datetime',
          format: 'YYYY-MM-DD HH:mm',
          default: '{{now}}',
          i18n: 'translate'
        }
      - label: 'Cover Image'
        name: 'image'
        widget: 'image'
        required: false
        tagname: ''
        i18n: 'duplicate'

      - { label: 'Body', name: 'body', widget: 'markdown', hint: 'Main content goes here.', i18n: 'translate' }

  - name: 'restaurants' # Used in routes, ie.: /admin/collections/:slug/edit
    label: 'Restaurants' # Used in the UI
    label_singular: 'Restaurant' # Used in the UI, ie: "New Post"
    description: >
      Restaurants is an entry type used for testing galleries, relations and other widgets.
      The tests must be written in such way that adding new fields does not affect previous flows.
    folder: '_restaurants'
    slug: '{{year}}-{{month}}-{{day}}-{{slug}}'
    summary: '{{title}} -- {{year}}/{{month}}/{{day}}'
    create: true # Allow users to create new documents in this collection
    fields: # The fields each document in this collection have
      - { label: 'Title', name: 'title', widget: 'string', tagname: 'h1' }
      - { label: 'Body', name: 'body', widget: 'markdown', hint: 'Main content goes here.' }
      - { name: 'gallery', widget: 'image', choose_url: true, media_library: {config: {multiple: true, max_files: 999}}}
      - { name: 'post', widget: relation, collection: posts, multiple: true, search_fields: [ "title" ], display_fields: [ "title" ], value_field: "{{slug}}", filters: [ {field: "draft", values: [false]} ] }
      - name: authors
        label: Authors
        label_singular: 'Author'
        widget: list
        fields:
          - { label: 'Name', name: 'name', widget: 'string', hint: 'First and Last' }
          - { label: 'Description', name: 'description', widget: 'markdown' }

  - name: 'faq' # Used in routes, ie.: /admin/collections/:slug/edit
    label: 'FAQ' # Used in the UI
    folder: '_faqs'
    create: true # Allow users to create new documents in this collection
    fields: # The fields each document in this collection have
      - { label: 'Question', name: 'title', widget: 'string', tagname: 'h1' }
      - { label: 'Answer', name: 'body', widget: 'markdown' }

  - name: 'settings'
    label: 'Settings'
    delete: false # Prevent users from deleting documents in this collection
    editor:
      preview: false
    files:
      - name: 'general'
        label: 'Site Settings'
        file: '_data/settings.json'
        description: 'General Site Settings'
        fields:
          - { label: 'Global title', name: 'site_title', widget: 'string' }
          - label: 'Post Settings'
            name: posts
            widget: 'object'
            fields:
              - {
                  label: 'Number of posts on frontpage',
                  name: front_limit,
                  widget: number,
                  min: 1,
                  max: 10,
                }
              - { label: 'Default Author', name: author, widget: string }
              - {
                  label: 'Default Thumbnail',
                  name: thumb,
                  widget: image,
                  class: 'thumb',
                  required: false,
                }

      - name: 'authors'
        label: 'Authors'
        file: '_data/authors.yml'
        description: 'Author descriptions'
        fields:
          - name: authors
            label: Authors
            label_singular: 'Author'
            widget: list
            fields:
              - { label: 'Name', name: 'name', widget: 'string', hint: 'First and Last' }
              - { label: 'Description', name: 'description', widget: 'markdown' }

  - name: 'kitchenSink' # all the things in one entry, for documentation and quick testing
    label: 'Kitchen Sink'
    folder: '_sink'
    create: true
    fields:
      - label: 'Related Post'
        name: 'post'
        widget: 'relationKitchenSinkPost'
        collection: 'posts'
        display_fields: ['title', 'datetime']
        search_fields: ['title', 'body']
        value_field: 'title'
      - { label: 'Title', name: 'title', widget: 'string' }
      - { label: 'Boolean', name: 'boolean', widget: 'boolean', default: true }
      - { label: 'Map', name: 'map', widget: 'map' }
      - { label: 'Text', name: 'text', widget: 'text', hint: 'Plain text, not markdown' }
      - { label: 'Number', name: 'number', widget: 'number', hint: 'To infinity and beyond!' }
      - { label: 'Markdown', name: 'markdown', widget: 'markdown' }
      - { label: 'Datetime', name: 'datetime', widget: 'datetime' }
      - { label: 'Image', name: 'image', widget: 'image' }
      - { label: 'File', name: 'file', widget: 'file' }
      - { label: 'Select', name: 'select', widget: 'select', options: ['a', 'b', 'c'] }
      - {
          label: 'Select multiple',
          name: 'select_multiple',
          widget: 'select',
          options: ['a', 'b', 'c'],
          multiple: true,
        }
      - { label: 'Hidden', name: 'hidden', widget: 'hidden', default: 'hidden' }
      - { label: 'Color', name: 'color', widget: 'color' }
      - label: 'Object'
        name: 'object'
        widget: 'object'
        collapsed: true
        fields:
          - label: 'Related Post'
            name: 'post'
            widget: 'relationKitchenSinkPost'
            collection: 'posts'
            search_fields: ['title', 'body']
            value_field: 'title'
          - { label: 'String', name: 'string', widget: 'string' }
          - { label: 'Boolean', name: 'boolean', widget: 'boolean', default: false }
          - { label: 'Text', name: 'text', widget: 'text' }
          - { label: 'Number', name: 'number', widget: 'number' }
          - { label: 'Markdown', name: 'markdown', widget: 'markdown' }
          - { label: 'Datetime', name: 'datetime', widget: 'datetime' }
          - { label: 'Image', name: 'image', widget: 'image' }
          - { label: 'File', name: 'file', widget: 'file' }
          - { label: 'Select', name: 'select', widget: 'select', options: ['a', 'b', 'c'] }
      - label: 'List'
        name: 'list'
        widget: 'list'
        fields:
          - { label: 'String', name: 'string', widget: 'string' }
          - { label: 'Boolean', name: 'boolean', widget: 'boolean' }
          - { label: 'Text', name: 'text', widget: 'text' }
          - { label: 'Number', name: 'number', widget: 'number' }
          - { label: 'Markdown', name: 'markdown', widget: 'markdown' }
          - { label: 'Datetime', name: 'datetime', widget: 'datetime' }
          - { label: 'Image', name: 'image', widget: 'image' }
          - { label: 'File', name: 'file', widget: 'file' }
          - { label: 'Select', name: 'select', widget: 'select', options: ['a', 'b', 'c'] }
          - label: 'Object'
            name: 'object'
            widget: 'object'
            fields:
              - { label: 'String', name: 'string', widget: 'string' }
              - { label: 'Boolean', name: 'boolean', widget: 'boolean' }
              - { label: 'Text', name: 'text', widget: 'text' }
              - { label: 'Number', name: 'number', widget: 'number' }
              - { label: 'Markdown', name: 'markdown', widget: 'markdown' }
              - { label: 'Datetime', name: 'datetime', widget: 'datetime' }
              - { label: 'Image', name: 'image', widget: 'image' }
              - { label: 'File', name: 'file', widget: 'file' }
              - { label: 'Select', name: 'select', widget: 'select', options: ['a', 'b', 'c'] }
              - label: 'List'
                name: 'list'
                widget: 'list'
                fields:
                  - label: 'Related Post'
                    name: 'post'
                    widget: 'relationKitchenSinkPost'
                    collection: 'posts'
                    search_fields: ['title', 'body']
                    value_field: 'title'
                  - { label: 'String', name: 'string', widget: 'string' }
                  - { label: 'Boolean', name: 'boolean', widget: 'boolean' }
                  - { label: 'Text', name: 'text', widget: 'text' }
                  - { label: 'Number', name: 'number', widget: 'number' }
                  - { label: 'Markdown', name: 'markdown', widget: 'markdown' }
                  - { label: 'Datetime', name: 'datetime', widget: 'datetime' }
                  - { label: 'Image', name: 'image', widget: 'image' }
                  - { label: 'File', name: 'file', widget: 'file' }
                  - { label: 'Select', name: 'select', widget: 'select', options: ['a', 'b', 'c'] }
                  - { label: 'Hidden', name: 'hidden', widget: 'hidden', default: 'hidden' }
                  - label: 'Object'
                    name: 'object'
                    widget: 'object'
                    fields:
                      - { label: 'String', name: 'string', widget: 'string' }
                      - { label: 'Boolean', name: 'boolean', widget: 'boolean' }
                      - { label: 'Text', name: 'text', widget: 'text' }
                      - { label: 'Number', name: 'number', widget: 'number' }
                      - { label: 'Markdown', name: 'markdown', widget: 'markdown' }
                      - { label: 'Datetime', name: 'datetime', widget: 'datetime' }
                      - { label: 'Image', name: 'image', widget: 'image' }
                      - { label: 'File', name: 'file', widget: 'file' }
                      - {
                          label: 'Select',
                          name: 'select',
                          widget: 'select',
                          options: ['a', 'b', 'c'],
                        }
      - label: 'Typed List'
        name: 'typed_list'
        widget: 'list'
        types:
          - label: 'Type 1 Object'
            name: 'type_1_object'
            widget: 'object'
            fields:
              - { label: 'String', name: 'string', widget: 'string' }
              - { label: 'Boolean', name: 'boolean', widget: 'boolean' }
              - { label: 'Text', name: 'text', widget: 'text' }
          - label: 'Type 2 Object'
            name: 'type_2_object'
            widget: 'object'
            fields:
              - { label: 'Number', name: 'number', widget: 'number' }
              - { label: 'Select', name: 'select', widget: 'select', options: ['a', 'b', 'c'] }
              - { label: 'Datetime', name: 'datetime', widget: 'datetime' }
              - { label: 'Markdown', name: 'markdown', widget: 'markdown' }
          - label: 'Type 3 Object'
            name: 'type_3_object'
            widget: 'object'
            fields:
              - { label: 'Image', name: 'image', widget: 'image' }
              - { label: 'File', name: 'file', widget: 'file' }
  - name: pages # a nested collection
    label: Pages
    label_singular: 'Page'
    folder: _pages
    create: true
    nested: { depth: 100 }
    fields:
      - label: Title
        name: title
        widget: string
    meta: { path: { widget: string, label: 'Path', index_file: 'index' } }
@starkovio
Copy link
Author

starkovio commented Jan 15, 2025

Hi everyone,

I recently encountered a bug in Decap CMS where localized content goes missing when duplicating entries with i18n enabled. I provided a fix in this PR: PR #7372.

This issue is critical for teams managing multilingual content with Decap CMS, and I believe the fix will be highly beneficial for many users.

Could someone from the team please review the PR and consider merging it? I’d be happy to address any questions or feedback.

Thank you so much for your time and effort! 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug code to address defects in shipped code
Projects
None yet
Development

No branches or pull requests

1 participant