Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Copy link
Copy Markdown
Contributor

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.

const page = this.#store.pageAsset()?.page;
if (page) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ import {
DotCMSContentType,
DotCMSContentlet,
DotCMSTempFile,
DotLanguage,
DotTreeNode,
FeaturedFlags,
SeoMetaTags,
Expand Down Expand Up @@ -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 });
}
});

Expand Down Expand Up @@ -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) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 String() to convert all field values when building translation data, which will improperly serialize non-string types (numbers, booleans, dates) into string representations. This violates content type field definitions and will corrupt data integrity for non-string fields in translated pages.


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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 [High] Array field values corrupted during translation

The code uses typeof value === 'object' which includes arrays, then spreads them as objects via Object.keys().forEach(), converting arrays to {0:...,1:...} objects. This corrupts multi-select/tag fields that require array values.

}

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;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6280,6 +6280,7 @@ locale.error.message=Locale ID already exists.

editpage.language-change-missing-lang-populate.confirm.header=Create New Language Version?
editpage.language-change-missing-lang-populate.confirm.message=Page does not exist in the selected language. Create a new version in {0}?
editpage.language-change-missing-lang-populate.success=A new page version was created in {0}.
editpage.viewing.variant=Viewing <b>{0}</b> Variant
editpage.editing.variant=Editing <b>{0}</b> Variant
editpage.file.upload.not.image=The file dropped was not an image.
Expand Down
Loading