[Batch Edit] Edit multiple languages of a localizable asset metadata field#3899
Open
xIrusux wants to merge 11 commits into
Open
[Batch Edit] Edit multiple languages of a localizable asset metadata field#3899xIrusux wants to merge 11 commits into
xIrusux wants to merge 11 commits into
Conversation
…field (#2492) Assets could only batch-edit a single locale of a localizable metadata field. Mirror the data-object multi-row model: entries are identified by (key + locale), each field click adds a row for the next unused locale, each row has its own content-language picker that excludes locales used by the field's other rows, and the field leaves the "fields to add" panel once every locale has a row. Asset metadata differs from object localizedfields: values are submitted as flat PatchCustomMetadata { name, language, data, type } and language: null (language-neutral) is a valid value. Each localizable row's fields are namespaced under a Form.Group keyed by locale so rows don't collide on the flat metadata name, and the null "no language" row is kept. Locale candidates come from user.contentLanguages via PermissionBasedLanguageSelectionControl, matching the grid language switcher and column config. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove verbose and issue-reference comments, keeping only minimal notes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Extends asset batch editing to support multiple locale rows per localizable metadata field.
Changes:
- Identifies batch edits by field and locale.
- Adds locale-aware form grouping and filtering.
- Builds one metadata patch per locale row.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
dropdown-filter.ts |
Keeps fields available until all locales are added. |
use-batch-edit.ts |
Manages locale-specific batch-edit rows. |
batch-edit-provider.tsx |
Adds the language-neutral form key. |
batch-edit-modal.tsx |
Extracts locale-grouped values into patches. |
batch-edit-list-container.tsx |
Renders locale pickers and grouped editors. |
Comment on lines
+41
to
+42
| const localeFormKey = batchEdit.locale ?? NO_LOCALE_FORM_KEY | ||
| const rowKey = batchEdit.localizable ? `${batchEdit.key}-${localeFormKey}` : batchEdit.key |
Comment on lines
+44
to
+48
| const body = batchEdit.localizable | ||
| ? ( | ||
| <Form.Group name={ [localeFormKey] }> | ||
| <DefaultBatchEdit batchEdit={ batchEdit } /> | ||
| </Form.Group> |
| // Namespace each localizable row's fields under a Form.Group keyed by locale so rows sharing | ||
| // a field key don't collide on the flat metadata name. | ||
| const localeFormKey = batchEdit.locale ?? NO_LOCALE_FORM_KEY | ||
| const rowKey = batchEdit.localizable ? `${batchEdit.key}-${localeFormKey}` : batchEdit.key |
Comment on lines
+63
to
+64
| onChange={ (language) => { | ||
| updateLocale(batchEdit, language) |
Comment on lines
+49
to
+50
| const candidateLocales: Array<string | null> = [null, ...contentLanguages] | ||
| const nextLocale = candidateLocales.find(locale => !usedLocales.includes(locale)) |
Use !== instead of negated ===; use a Set for used-locale lookup. Drop a redundant comment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment on lines
+39
to
+44
| const localeFormKey = batchEdit.locale ?? NO_LOCALE_FORM_KEY | ||
| const rowKey = batchEdit.localizable ? `${batchEdit.key}-${localeFormKey}` : batchEdit.key | ||
|
|
||
| const body = batchEdit.localizable | ||
| ? ( | ||
| <Form.Group name={ [localeFormKey] }> |
Comment on lines
+57
to
65
| <PermissionBasedLanguageSelectionControl | ||
| excludeLocales={ usedByOtherRows } | ||
| isNullable={ !isNullUsedByOtherRows } | ||
| key="language-selection" | ||
| languages={ languages } | ||
| onSelectLanguage={ (language) => { | ||
| updateLocale(batchEdit.key, transformLanguage(language)) | ||
| onChange={ (language) => { | ||
| updateLocale(batchEdit, language) | ||
| } } | ||
| selectedLanguage={ selectedLanguage } | ||
| value={ batchEdit.locale ?? null } | ||
| /> |
| const addOrUpdateBatchEdit = (column: AvailableColumn): void => { | ||
| const newEdit: BatchEdit = column | ||
| // Each click on a localizable field adds a row for the next unused locale (null first). | ||
| if (column.localizable) { |
Comment on lines
+12
to
+36
| import { useElementContext } from '@Pimcore/modules/element/hooks/use-element-context' | ||
| import { useElementDraft } from '@Pimcore/modules/element/hooks/use-element-draft' | ||
|
|
||
| /** | ||
| * The content languages the current user may view/edit for the current element, i.e. the user's | ||
| * content languages narrowed by the element's `localizedView` permission. Requires an element | ||
| * context. This is the same set PermissionBasedLanguageSelectionControl offers, so callers that | ||
| * need to stay in sync with that picker (e.g. deriving available locales) should use it too. | ||
| */ | ||
| export const usePermittedContentLanguages = (): string[] => { | ||
| const user = useUser() | ||
| const elementContext = useElementContext() | ||
| const elementDraft = useElementDraft(elementContext.id, elementContext.elementType) | ||
| const contentLanguages = Array.isArray(user.contentLanguages) ? user.contentLanguages as string[] : [] | ||
|
|
||
| if (!('permissions' in elementDraft)) { | ||
| return contentLanguages | ||
| } | ||
|
|
||
| const permissions = elementDraft.permissions as Record<string, any> | ||
| const viewableLanguages: string[] = permissions?.localizedView?.split(',') ?? [] | ||
|
|
||
| if (viewableLanguages.length === 0 || (viewableLanguages.length === 1 && viewableLanguages[0] === 'default')) { | ||
| return contentLanguages | ||
| } |
Comment on lines
+42
to
+44
| if (column.localizable) { | ||
| const usedLocales = new Set( | ||
| batchEdits.filter(edit => edit.key === column.key).map(edit => edit.locale ?? null) |
Comment on lines
+28
to
+31
| const siblingLocales = batchEdit.localizable | ||
| ? batchEdits | ||
| .filter(edit => edit.key === batchEdit.key && (edit.locale ?? null) !== (batchEdit.locale ?? null)) | ||
| .map(edit => edit.locale ?? null) |
Contributor
Author
There was a problem hiding this comment.
but we use uuids they are always uniqe
Comment on lines
+57
to
+61
| const updatedEdits: BatchEdit[] = [...batchEdits] | ||
| const existingIndex = batchEdits.findIndex(edit => edit.key === column.key) | ||
|
|
||
| if (existingIndex !== -1) { | ||
| updatedEdits[existingIndex] = newEdit | ||
| updatedEdits[existingIndex] = { ...column, rowId: batchEdits[existingIndex].rowId, locale: null } |
…tity Revert the usePermittedContentLanguages hook and shared with-element-context change (the localizedView filter is a no-op — tracked separately); use user.contentLanguages directly. Match batch-edit entry identity by key + group (and rowId for self-exclusion), consistent with the fields-to-add filter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|
||
| export const useBatchEdit = (): UseBatchEditHookReturn => { | ||
| const { batchEdits, setBatchEdits } = useContext(BatchEditContext) | ||
| const contentLanguages = (useUser().contentLanguages ?? []) as string[] |
| const { getArgs } = useDataQueryHelper() | ||
| const { hasType } = useDynamicTypeResolver() | ||
| const { refreshGrid } = useRefreshGrid(elementType) | ||
| const contentLanguages = (useUser().contentLanguages ?? []) as string[] |
user.contentLanguages is typed as object; guard with Array.isArray before spreading so a non-array value can't break adding rows or the completion check. Also correct the error useEffect deps so a folder-patch failure is always reported. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
| .filter(edit => edit.key === column.key && areGroupsEqual(edit.group, column.group)) | ||
| .map(edit => edit.locale ?? null) | ||
| ) | ||
| const candidateLocales: Array<string | null> = [null, ...contentLanguages] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



What
Extends the multi-language batch-edit to assets: a localizable metadata field can now be edited in several locales at once, one input row per locale.
UX
How
hooks/use-batch-edit.ts).user.contentLanguagesviaPermissionBasedLanguageSelectionControl, matching the grid language switcher and column config.Form.Groupkeyed by locale, so rows sharing a field key don't collide on the flat metadata name — no shared field components changed.onFinishrebuilds onePatchCustomMetadata { name, language, data, type }per row;language: null(language-neutral) is kept as a valid value.Scope
Assets only. Documents were evaluated and excluded — they have no editable/localizable listing (no batch-edit substrate), so the feature does not apply there. Sibling of the data-object multi-locale change.
Verification
tsc+ eslint clean. Manual QA recommended: add a localizable metadata field, add multiple locale rows, confirm used-locale exclusion, and that saving writes one metadata patch per (name, language).🤖 Generated with Claude Code