[ACS-7570] [ADF] Create generic dialog component (#9678)

This commit is contained in:
Darya Blavanovich
2024-05-23 13:35:23 +02:00
committed by GitHub
parent b916fd5ef9
commit 0d2d08aab3
33 changed files with 965 additions and 1 deletions

View File

@@ -0,0 +1,35 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { TemplateRef } from '@angular/core';
import { Subject } from 'rxjs';
import { DialogSizes } from './dialog.model';
export interface DialogData {
title: string;
description?: string;
confirmButtonTitle?: string;
cancelButtonTitle?: string;
isConfirmButtonDisabled$?: Subject<boolean>;
isCloseButtonHidden?: boolean;
isCancelButtonHidden?: boolean;
dialogSize?: DialogSizes;
contentTemplate?: TemplateRef<any>;
actionsTemplate?: TemplateRef<any>;
descriptionTemplate?: TemplateRef<any>;
headerIcon?: string;
}

View File

@@ -0,0 +1,68 @@
<div
data-automation-id="adf-dialog-container"
class="adf-dialog-container {{dialogSize}}"
>
<div
mat-dialog-title
data-automation-id="adf-dialog-header"
class="adf-dialog-header"
[ngClass]="{'adf-centered-header': data.headerIcon}"
>
<div class="adf-dialog-title-container" [ngClass]="{ 'adf-centered-title': data.headerIcon }">
<mat-icon
*ngIf="data.headerIcon"
color="primary"
class="adf-dialog-header-icon"
>
{{data.headerIcon}}
</mat-icon>
<h2 class="adf-dialog-title">{{data.title}}</h2>
</div>
<button
*ngIf="!isCloseButtonHidden"
mat-icon-button
mat-dialog-close
data-automation-id="adf-dialog-close-button"
>
<mat-icon>close</mat-icon>
</button>
</div>
<div
[ngTemplateOutlet]="data.descriptionTemplate"
[ngClass]="{ 'adf-description': data.description || data.descriptionTemplate }"
>
<ng-container>{{data.description}}</ng-container>
</div>
<mat-dialog-content class="adf-dialog-content">
<ng-container [ngTemplateOutlet]="data.contentTemplate"></ng-container>
</mat-dialog-content>
<mat-dialog-actions class="adf-dialog-actions" [ngClass]="{ 'adf-additional-actions': data.actionsTemplate }">
<div>
<ng-container [ngTemplateOutlet]="data.actionsTemplate"></ng-container>
</div>
<div>
<button
*ngIf="!isCancelButtonHidden"
mat-button
mat-dialog-close
adf-auto-focus
data-automation-id="adf-dialog-actions-cancel"
>
{{cancelButtonTitle | translate}}
</button>
<button
mat-flat-button
color="primary"
data-automation-id="adf-dialog-actions-confirm"
[disabled]="isConfirmButtonDisabled$ | async"
(click)="onConfirm()"
>
{{confirmButtonTitle | translate}}
</button>
</div>
</mat-dialog-actions>
</div>

View File

@@ -0,0 +1,226 @@
$dialog-large-l-width: 1075px;
$dialog-large-l-height: 907px;
$dialog-medium-l-width: 691px;
$dialog-medium-l-height: 778px;
$dialog-small-l-width: 461px;
$dialog-small-s-width: 346px;
$dialog-small-l-height: 432px;
$dialog-small-s-height: 360px;
$l-screen: 1920px;
$m-screen: 1680px;
$s-screen: 1440px;
$xs-screen: 375px;
$breakpoint-alert-with-additional-buttons-centered: 1469px;
$breakpoint-medium-with-additional-buttons-centered: 1469px;
$breakpoint-large-with-additional-buttons-centered: 642px;
$dialog-padding: 24px;
.adf-dialog-container {
min-width: calc(269px - $dialog-padding * 2);
min-height: calc(300px - $dialog-padding * 2);
position: relative;
display: flex;
flex-direction: column;
max-height: calc(84vh - $dialog-padding * 2);
.adf-dialog-header,
.adf-dialog-content,
.adf-dialog-actions {
margin: 0;
padding: 0;
}
.adf-dialog-header {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: $dialog-padding;
&.adf-centered-header {
align-items: flex-start;
}
.adf-dialog-title {
font-size: large;
font-weight: 200;
margin: 0;
}
.adf-dialog-header-icon {
width: 48px;
height: 48px;
font-size: 48px;
}
}
.adf-description {
padding-top: $dialog-padding;
}
.adf-dialog-content {
margin: $dialog-padding 0;
overflow: auto;
flex: 1;
max-height: none;
line-height: 20px;
}
.adf-dialog-header,
.adf-dialog-actions {
&::after {
content: ' ';
position: absolute;
left: -$dialog-padding;
display: block;
height: 0;
border-bottom: 1px solid var(--adf-theme-foreground-divider-color);
width: calc(100% + $dialog-padding * 2);
}
}
.adf-dialog-actions::after {
top: 0;
}
.adf-dialog-header::after {
bottom: 0;
}
.adf-dialog-actions {
position: relative;
display: flex;
justify-content: space-between;
padding-top: $dialog-padding;
text-transform: capitalize;
min-height: auto;
// TODO: Update after migration to Angular 15
/* stylelint-disable-next-line selector-class-pattern */
.mat-button.mat-button-base + .mat-button-base {
margin-left: 16px;
}
// TODO: Update after migration to Angular 15
/* stylelint-disable-next-line selector-class-pattern */
.mat-button {
text-transform: capitalize;
}
}
// TODO: Update after migration to Angular 15
/* stylelint-disable-next-line selector-class-pattern */
.mat-dialog-container {
border-radius: 8px;
}
&.adf-large {
max-width: calc(56vw - $dialog-padding * 2);
max-height: calc(84vh - $dialog-padding * 2);
@media screen and (min-width: $l-screen) {
max-width: calc($dialog-large-l-width - $dialog-padding * 2);
}
@media screen and (min-height: $dialog-large-l-width) {
max-height: calc($dialog-large-l-height - $dialog-padding * 2);
}
@media screen and (max-width: $xs-screen) {
max-width: calc(100vw - $dialog-padding * 2);
max-height: calc(100vh - $dialog-padding * 2);
}
@media screen and (max-width: $breakpoint-large-with-additional-buttons-centered) {
.adf-additional-actions {
justify-content: center;
}
}
}
&.adf-medium {
max-width: calc(36vw - $dialog-padding * 2);
max-height: calc(72vh - $dialog-padding * 2);
@media screen and (min-width: $l-screen) {
max-width: calc($dialog-medium-l-width - $dialog-padding * 2);
}
@media screen and (max-width: $xs-screen) {
max-width: calc(100vw - $dialog-padding * 2);
max-height: calc(100vh - $dialog-padding * 2);
}
@media screen and (min-height: $dialog-large-l-width) {
max-height: calc($dialog-medium-l-height - $dialog-padding * 2);
}
@media screen and (max-width: $breakpoint-medium-with-additional-buttons-centered) {
.adf-additional-actions {
justify-content: center;
}
}
}
&.adf-alert {
.adf-centered-title {
padding-left: 40px;
margin: auto;
text-align: center;
}
.adf-dialog-content {
margin: 0;
}
.adf-dialog-header {
padding: 0;
}
.adf-dialog-header,
.adf-dialog-actions {
&::after {
display: none;
}
}
max-width: calc(24vw - $dialog-padding * 2);
max-height: calc(40vh - $dialog-padding * 2);
@media screen and (min-width: $m-screen) {
max-width: calc($dialog-small-l-width - $dialog-padding * 2);
max-height: calc($dialog-small-l-height - $dialog-padding * 2);
}
@media screen and (max-width: $s-screen) {
max-width: calc($dialog-small-s-width - $dialog-padding * 2);
max-height: calc($dialog-small-s-height - $dialog-padding * 2);
}
@media screen and (max-width: $xs-screen) {
max-width: calc(84vw - $dialog-padding * 2);
max-height: calc(41vh - $dialog-padding * 2);
}
@media screen and (max-height: $dialog-small-l-height) {
max-height: calc(84vh - $dialog-padding * 2);
}
@media screen and (max-width: $breakpoint-alert-with-additional-buttons-centered) {
.adf-additional-actions {
justify-content: center;
}
}
}
@media screen and (max-width: $xs-screen) {
.adf-additional-actions {
justify-content: center;
}
}
}

View File

@@ -0,0 +1,259 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ContentTestingModule } from '../../testing/content.testing.module';
import { DialogComponent } from './dialog.component';
import { DialogData } from './dialog-data.interface';
import { DialogSize } from './dialog.model';
describe('DialogComponent', () => {
let component: DialogComponent;
let fixture: ComponentFixture<DialogComponent>;
let closeButton: HTMLButtonElement;
let cancelButton: HTMLButtonElement;
let confirmButton: HTMLButtonElement;
let dialogContainer: HTMLElement;
const data: DialogData = {
title: 'Title',
description: 'Description that can be longer or shorter'
};
const dialogRef = {
close: jasmine.createSpy('close')
};
const setupBeforeEach = (dialogOptions: DialogData = data) => {
TestBed.configureTestingModule({
imports: [ContentTestingModule],
providers: [
{ provide: MAT_DIALOG_DATA, useValue: dialogOptions },
{ provide: MatDialogRef, useValue: dialogRef }
]
}).compileComponents();
dialogRef.close.calls.reset();
fixture = TestBed.createComponent(DialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
confirmButton = fixture.nativeElement.querySelector('[data-automation-id="adf-dialog-actions-confirm"]');
closeButton = fixture.nativeElement.querySelector('[data-automation-id="adf-dialog-close-button"]');
cancelButton = fixture.nativeElement.querySelector(
'[data-automation-id="adf-dialog-actions-cancel"]');
dialogContainer = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-dialog-container"]');
};
describe('when init with default data', () => {
beforeEach(() => {
setupBeforeEach();
});
it('should have default template elements', () => {
expect(dialogContainer).toBeDefined();
expect(fixture.nativeElement.querySelector('.adf-dialog-header')).toBeDefined();
expect(fixture.nativeElement.querySelector('.adf-dialog-content')).toBeDefined();
expect(fixture.nativeElement.querySelector('.adf-dialog-actions')).toBeDefined();
});
it('should have default values for the dialog', () => {
expect(closeButton).toBeDefined();
expect(dialogContainer.classList).toContain('adf-medium');
expect(confirmButton.innerText).toBe('COMMON.APPLY');
expect(cancelButton.innerText).toBe('COMMON.CANCEL');
});
});
describe('confirm action', () => {
const mockButtonTitle = 'mockTitle';
beforeEach(() => {
setupBeforeEach({ ...data, confirmButtonTitle: mockButtonTitle});
fixture.detectChanges();
});
it('should disable confirm button after confirm button was clicked', (done) => {
expect(confirmButton.getAttribute('disabled')).toBeFalsy();
confirmButton.click();
fixture.detectChanges();
expect(confirmButton.getAttribute('disabled')).toBeTruthy();
component.isConfirmButtonDisabled$.subscribe((value) => {
expect(value).toBeTrue();
done();
});
});
it('should disable confirm button with updated confirmButtonDisabled$', () => {
component.isConfirmButtonDisabled$.next(false);
expect(confirmButton.getAttribute('disabled')).toBeFalsy();
component.isConfirmButtonDisabled$.next(true);
fixture.detectChanges();
expect(confirmButton.getAttribute('disabled')).toBeTruthy();
});
it('should close dialog', () => {
component.onConfirm();
expect(dialogRef.close).toHaveBeenCalled();
});
it('should set correct button title', () => {
expect(component.confirmButtonTitle).toEqual(mockButtonTitle);
});
});
describe('cancel action', () => {
beforeEach(() => {
setupBeforeEach();
});
it('should close dialog when cancel button was clicked', () => {
cancelButton.click();
fixture.detectChanges();
expect(dialogRef.close).toHaveBeenCalled();
});
it('should close dialog when close button was clicked', () => {
closeButton.click();
fixture.detectChanges();
expect(dialogRef.close).toHaveBeenCalled();
});
});
describe('when isCloseButtonHidden and isCancelButtonHidden set to true', () => {
beforeEach(() => {
setupBeforeEach({
...data,
isCloseButtonHidden: true,
isCancelButtonHidden: true
});
});
it ('should hide close button', () => {
expect(closeButton).toBeNull();
});
it ('should hide close button', () => {
expect(cancelButton).toBeNull();
});
});
describe('when dialog has large size', () => {
beforeEach(() => {
setupBeforeEach({ ...data, dialogSize: DialogSize.Large});
});
it('should have correct dialogSize value', () => {
expect(component.dialogSize).toEqual(DialogSize.Large);
});
it(`should contain ${DialogSize.Large} class`, () => {
expect(dialogContainer.classList).toContain(DialogSize.Large);
});
it('should not have header and actions border', () => {
const headerBorder = fixture.nativeElement.querySelector('.adf-alert .adf-dialog-header::after');
const actionsBorder = fixture.nativeElement.querySelector('.adf-alert .adf-dialog-actions::after');
expect(headerBorder).toBeDefined();
expect(actionsBorder).toBeDefined();
});
});
describe('when dialog has medium size', () => {
beforeEach(() => {
setupBeforeEach({ ...data, dialogSize: DialogSize.Medium});
});
it('should have correct dialogSize value', () => {
expect(component.dialogSize).toEqual(DialogSize.Medium);
});
it(`should contain ${DialogSize.Medium} class`, () => {
expect(dialogContainer.classList).toContain(DialogSize.Medium);
});
it('should not have header and actions border', () => {
const headerBorder = fixture.nativeElement.querySelector('.adf-alert .adf-dialog-header::after');
const actionsBorder = fixture.nativeElement.querySelector('.adf-alert .adf-dialog-actions::after');
expect(headerBorder).toBeDefined();
expect(actionsBorder).toBeDefined();
});
});
describe('when dialog has alert size', () => {
describe('when dialog has not an ican', () => {
beforeEach(() => {
setupBeforeEach({ ...data, dialogSize: DialogSize.Alert});
});
it('should have correct dialogSize value', () => {
expect(component.dialogSize).toEqual(DialogSize.Alert);
});
it(`should contain ${DialogSize.Alert} class`, () => {
expect(dialogContainer.classList).toContain(DialogSize.Alert);
});
it('should not have header and actions border', () => {
const headerBorder = fixture.nativeElement.querySelector('.adf-alert .adf-dialog-header::after');
const actionsBorder = fixture.nativeElement.querySelector('.adf-alert .adf-dialog-actions::after');
expect(headerBorder).toBeNull();
expect(actionsBorder).toBeNull();
});
it('should not center header content', () => {
const header = fixture.nativeElement.querySelector('.adf-centered-header');
expect(header).toBeNull();
});
});
describe('when header contains icon', () => {
beforeEach(() => {
setupBeforeEach({
...data,
dialogSize: DialogSize.Alert,
headerIcon: 'access_time'
});
});
it('should have icon element', () => {
const headerIcon = fixture.nativeElement.querySelector('.adf-dialog-header-icon');
expect(headerIcon).toBeDefined();
});
it('should center header content', () => {
const header = fixture.nativeElement.querySelector('.adf-centered-header');
expect(header).toBeDefined();
});
});
});
});

View File

@@ -0,0 +1,74 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, Inject, OnDestroy, ViewEncapsulation } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { DialogData } from './dialog-data.interface';
import { BehaviorSubject, Subject } from 'rxjs';
import { DialogSize, DialogSizes } from './dialog.model';
import { MaterialModule } from '../../material.module';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { takeUntil } from 'rxjs/operators';
@Component({
standalone: true,
selector: 'adf-dialog',
templateUrl: './dialog.component.html',
styleUrls: ['./dialog.component.scss'],
imports: [CommonModule, MaterialModule, TranslateModule],
encapsulation: ViewEncapsulation.None
})
export class DialogComponent implements OnDestroy {
isConfirmButtonDisabled$ = new BehaviorSubject<boolean>(false);
isCloseButtonHidden: boolean;
isCancelButtonHidden: boolean;
dialogSize: DialogSizes;
confirmButtonTitle: string;
cancelButtonTitle: string;
disableSubmitButton = false;
private onDestroy$ = new Subject<void>();
constructor(
@Inject(MAT_DIALOG_DATA)
public data: DialogData,
public dialogRef: MatDialogRef<DialogComponent>
) {
if (data) {
this.isCancelButtonHidden = data.isCancelButtonHidden || false;
this.isCloseButtonHidden = data.isCloseButtonHidden || false;
this.dialogSize = data.dialogSize || DialogSize.Medium;
this.confirmButtonTitle = data.confirmButtonTitle || 'COMMON.APPLY';
this.cancelButtonTitle = data.cancelButtonTitle || 'COMMON.CANCEL';
if (data.isConfirmButtonDisabled$) {
data.isConfirmButtonDisabled$.pipe(takeUntil(this.onDestroy$)).subscribe((value) => this.isConfirmButtonDisabled$.next(value));
}
}
}
onConfirm() {
this.isConfirmButtonDisabled$.next(true);
this.dialogRef.close();
}
ngOnDestroy() {
this.onDestroy$.next();
this.onDestroy$.complete();
}
}

View File

@@ -0,0 +1,24 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const DialogSize = {
Large: 'adf-large',
Medium: 'adf-medium',
Alert: 'adf-alert'
} as const;
export type DialogSizes = typeof DialogSize[keyof typeof DialogSize];

View File

@@ -0,0 +1,20 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './dialog-data.interface';
export * from './dialog.model';
export * from './dialog.component';

View File

@@ -22,9 +22,9 @@ export * from './category-selector.dialog';
export * from './dialog.module';
export * from './library/library.dialog';
export * from './dialog';
export * from './download-zip/download-zip.dialog';
export * from './download-zip/download-zip.dialog.module';
export * from './folder-name.validators';

View File

@@ -1,4 +1,8 @@
{
"COMMON": {
"APPLY": "تطبيق",
"CANCEL": "إلغاء"
},
"ADF_VERSION_LIST": {
"ACTIONS": {
"RESTORE": "استعادة",

View File

@@ -1,4 +1,8 @@
{
"COMMON": {
"APPLY": "Použít",
"CANCEL": "Zrušit"
},
"ADF_VERSION_LIST": {
"ACTIONS": {
"RESTORE": "Obnovit",

View File

@@ -1,4 +1,8 @@
{
"COMMON": {
"APPLY": "Anvend",
"CANCEL": "Annuller"
},
"ADF_VERSION_LIST": {
"ACTIONS": {
"RESTORE": "Gendan",

View File

@@ -1,4 +1,8 @@
{
"COMMON": {
"APPLY": "Anwenden",
"CANCEL": "Abbrechen"
},
"ADF_VERSION_LIST": {
"ACTIONS": {
"RESTORE": "Wiederherstellen",

View File

@@ -1,4 +1,8 @@
{
"COMMON": {
"APPLY": "Apply",
"CANCEL": "Cancel"
},
"ADF_VERSION_LIST": {
"ACTIONS": {
"RESTORE": "Restore",

View File

@@ -1,4 +1,8 @@
{
"COMMON": {
"APPLY": "Aplicar",
"CANCEL": "Cancelar"
},
"ADF_VERSION_LIST": {
"ACTIONS": {
"RESTORE": "Restaurar",

View File

@@ -1,4 +1,8 @@
{
"COMMON": {
"APPLY": "Käytä",
"CANCEL": "Peruuta"
},
"ADF_VERSION_LIST": {
"ACTIONS": {
"RESTORE": "Palauta",

View File

@@ -1,4 +1,8 @@
{
"COMMON": {
"APPLY": "Appliquer",
"CANCEL": "Annuler"
},
"ADF_VERSION_LIST": {
"ACTIONS": {
"RESTORE": "Restaurer",

View File

@@ -1,4 +1,8 @@
{
"COMMON": {
"APPLY": "Applica",
"CANCEL": "Annulla"
},
"ADF_VERSION_LIST": {
"ACTIONS": {
"RESTORE": "Ripristina",

View File

@@ -1,4 +1,8 @@
{
"COMMON": {
"APPLY": "適用",
"CANCEL": "キャンセル"
},
"ADF_VERSION_LIST": {
"ACTIONS": {
"RESTORE": "復元",

View File

@@ -1,4 +1,8 @@
{
"COMMON": {
"APPLY": "Bruk",
"CANCEL": "Avbryt"
},
"ADF_VERSION_LIST": {
"ACTIONS": {
"RESTORE": "Gjenopprett",

View File

@@ -1,4 +1,8 @@
{
"COMMON": {
"APPLY": "Toepassen",
"CANCEL": "Annuleren"
},
"ADF_VERSION_LIST": {
"ACTIONS": {
"RESTORE": "Herstellen",

View File

@@ -1,4 +1,8 @@
{
"COMMON": {
"APPLY": "Zastosuj",
"CANCEL": "Anuluj"
},
"ADF_VERSION_LIST": {
"ACTIONS": {
"RESTORE": "Przywróć",

View File

@@ -1,4 +1,8 @@
{
"COMMON": {
"APPLY": "Aplicar",
"CANCEL": "Cancelar"
},
"ADF_VERSION_LIST": {
"ACTIONS": {
"RESTORE": "Restaurar",

View File

@@ -1,4 +1,8 @@
{
"COMMON": {
"APPLY": "Применить",
"CANCEL": "Отмена"
},
"ADF_VERSION_LIST": {
"ACTIONS": {
"RESTORE": "Восстановить",

View File

@@ -1,4 +1,8 @@
{
"COMMON": {
"APPLY": "Tillämpa",
"CANCEL": "Avbryt"
},
"ADF_VERSION_LIST": {
"ACTIONS": {
"RESTORE": "Återställ",

View File

@@ -1,4 +1,8 @@
{
"COMMON": {
"APPLY": "应用",
"CANCEL": "取消"
},
"ADF_VERSION_LIST": {
"ACTIONS": {
"RESTORE": "恢复",