-
Notifications
You must be signed in to change notification settings - Fork 483
feat(uve): auto-create page language version on language switch (#36348) #36358
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -298,10 +298,10 @@ export class DotUveToolbarComponent { | |
| const languageHasTranslation = currentLanguage.translated; | ||
|
|
||
| if (!languageHasTranslation) { | ||
| // Show confirmation dialog to create a new translation | ||
| // Emit to the shell, which creates the language version and reloads. | ||
| const page = this.#store.pageAsset()?.page; | ||
| if (page) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 [High] Missing error handling after translatePage emission leaves language selector in inconsistent state The code emits translatePage but doesn't handle errors or reset the language selector on failure. When autoCreateTranslation fails (caught in editor component's error handler), the toolbar's language selector remains set to the new language while the page stays in original language. The removed dialog's rejection callback previously handled this reset, but current implementation lacks equivalent safety. |
||
| this.createNewTranslation(currentLanguage, page); | ||
| this.translatePage.emit({ page, newLanguage: currentLanguage.id }); | ||
| } | ||
|
|
||
| return; | ||
|
|
@@ -396,39 +396,6 @@ export class DotUveToolbarComponent { | |
| }); | ||
| } | ||
|
|
||
| /* | ||
| * Asks the user for confirmation to create a new translation for a given language. | ||
| * | ||
| * @param {DotLanguage} language - The language to create a new translation for. | ||
| * @private | ||
| * | ||
| * @return {void} | ||
| */ | ||
| private createNewTranslation(language: DotLanguage, page: DotCMSPage): void { | ||
| this.#confirmationService.confirm({ | ||
| header: this.#dotMessageService.get( | ||
| 'editpage.language-change-missing-lang-populate.confirm.header' | ||
| ), | ||
| message: this.#dotMessageService.get( | ||
| 'editpage.language-change-missing-lang-populate.confirm.message', | ||
| language.language | ||
| ), | ||
| rejectIcon: 'hidden', | ||
| acceptIcon: 'hidden', | ||
| key: 'shell-confirm-dialog', | ||
| accept: () => { | ||
| this.translatePage.emit({ | ||
| page: page, | ||
| newLanguage: language.id | ||
| }); | ||
| }, | ||
| reject: () => { | ||
| // If is rejected, bring back the current language on selector | ||
| this.$languageSelector()?.value.set(this.#store.pageLanguage()); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Gets the minimum allowed date for the calendar component. | ||
| * Sets hours/minutes/seconds/milliseconds to 0 to avoid collisions with preview date | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,7 +51,6 @@ import { | |
| DotCMSContentType, | ||
| DotCMSContentlet, | ||
| DotCMSTempFile, | ||
| DotLanguage, | ||
| DotTreeNode, | ||
| FeaturedFlags, | ||
| SeoMetaTags, | ||
|
|
@@ -421,7 +420,7 @@ export class EditEmaEditorComponent implements OnDestroy, AfterViewInit { | |
| const { page, currentLanguage } = this.uveStore.pageTranslateProps(); | ||
|
|
||
| if (currentLanguage && !currentLanguage?.translated) { | ||
| this.createNewTranslation(currentLanguage, page); | ||
| this.translatePage({ page, newLanguage: currentLanguage.id }); | ||
| } | ||
| }); | ||
|
|
||
|
|
@@ -1688,30 +1687,99 @@ export class EditEmaEditorComponent implements OnDestroy, AfterViewInit { | |
| }); | ||
| } | ||
|
|
||
| private createNewTranslation(language: DotLanguage, page: DotCMSPage): void { | ||
| this.confirmationService.confirm({ | ||
| header: this.dotMessageService.get( | ||
| 'editpage.language-change-missing-lang-populate.confirm.header' | ||
| ), | ||
| message: this.dotMessageService.get( | ||
| 'editpage.language-change-missing-lang-populate.confirm.message', | ||
| language.language | ||
| ), | ||
| rejectIcon: 'hidden', | ||
| acceptIcon: 'hidden', | ||
| key: 'shell-confirm-dialog', | ||
| accept: () => { | ||
| this.translatePage({ page, newLanguage: language.id }); | ||
| }, | ||
| reject: () => { | ||
| // If is rejected, bring back the current language on selector | ||
| this.#goBackToCurrentLanguage(); | ||
| /** | ||
| * Create the page's language version on switch. | ||
| * | ||
| * Fires a programmatic workflow NEW action that copies the source page's field | ||
| * values into a new version in the target language, then re-renders via `pageLoad`. | ||
| */ | ||
| translatePage(event: { page: DotCMSPage; newLanguage: number }) { | ||
| this.#autoCreateTranslation(event.page, event.newLanguage); | ||
| } | ||
|
|
||
| #autoCreateTranslation(page: DotCMSPage, newLanguage: number): void { | ||
| // Resolve the real content type so the payload is built from its actual | ||
| // field definitions instead of guessing which page properties to copy. | ||
| this.dotContentTypeService | ||
| .getContentType(page.contentType) | ||
| .pipe( | ||
| switchMap((contentType) => { | ||
| const data = this.#buildTranslationData(contentType, page, newLanguage); | ||
|
|
||
| return this.dotWorkflowActionsFireService.newContentlet( | ||
| page.contentType, | ||
| data | ||
| ); | ||
| }), | ||
| take(1), | ||
| takeUntilDestroyed(this.destroyRef) | ||
| ) | ||
| .subscribe({ | ||
| next: () => { | ||
| const languageName = | ||
| this.uveStore.pageLanguages().find((lang) => lang.id === newLanguage) | ||
| ?.language ?? ''; | ||
|
|
||
| this.messageService.add({ | ||
| severity: 'success', | ||
| summary: this.dotMessageService.get('Workflow-Action'), | ||
| detail: this.dotMessageService.get( | ||
| 'editpage.language-change-missing-lang-populate.success', | ||
| languageName | ||
| ), | ||
| life: 3000 | ||
| }); | ||
|
|
||
| this.uveStore.pageLoad({ language_id: newLanguage.toString() }); | ||
| }, | ||
| error: (error: HttpErrorResponse) => { | ||
| this.dotHttpErrorManagerService.handle(error); | ||
| // On failure, return to the language we came from. | ||
| this.#goBackToCurrentLanguage(); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Build the new-version payload from the content type's actual field definitions. | ||
| * | ||
| * Copies each defined field's value from the source page, preserves `identifier` | ||
| * (so it is a new language version of the same content, not a new one) and | ||
| * overrides `languageId`. Layout/divider fields carry no value on the page object, | ||
| * so they are skipped naturally. | ||
| */ | ||
| #buildTranslationData( | ||
| contentType: DotCMSContentType, | ||
| page: DotCMSPage, | ||
| newLanguage: number | ||
| ): Record<string, string> { | ||
| const source = page as unknown as Record<string, unknown>; | ||
| const data: Record<string, string> = {}; | ||
|
|
||
| (contentType.fields ?? []).forEach((field) => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 [High] Skipping object-type fields creates incomplete translations The code explicitly skips fields with fieldType === 'object' when building translation data (line 1759), resulting in missing data for all object-type fields during language version creation. This includes valid JSON field data that should be preserved across translations. |
||
| const key = field.variable; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 [High] String conversion of all field values corrupts non-string data types The code uses |
||
|
|
||
| if (!key) { | ||
| return; | ||
| } | ||
|
|
||
| const value = source[key]; | ||
|
|
||
| // Only copy primitive field values that exist on the source page. | ||
| if (value === null || value === undefined || typeof value === 'object') { | ||
| return; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 [High] Array field values corrupted during translation The code uses |
||
| } | ||
|
|
||
| data[key] = String(value); | ||
| }); | ||
| } | ||
|
|
||
| translatePage(event: { page: DotCMSPage; newLanguage: number }) { | ||
| this.dialog.translatePage(event); | ||
| data.identifier = page.identifier; | ||
| data.languageId = newLanguage.toString(); | ||
| // Wait for the new version to be indexed before pageLoad re-checks the | ||
| // `translated` flag — otherwise indexing lag could re-trigger the effect. | ||
| data.indexPolicy = 'WAIT_FOR'; | ||
|
|
||
| return data; | ||
| } | ||
|
|
||
| /** | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟠 [High] Automatic language version creation removes user consent mechanism
The removal of the confirmation dialog (createNewTranslation) enables silent content creation on language switch. The code now directly calls translatePage without user confirmation, as shown by the removed dialog service call and conditional workflow. This violates user expectations for destructive actions by removing cancellation capability.