Skip to content

Commit d0f5c29

Browse files
committed
translate array controls in material renderers
#1826
1 parent 3861749 commit d0f5c29

File tree

10 files changed

+128
-28
lines changed

10 files changed

+128
-28
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
export interface DefaultTranslation {
2+
key: ArrayTranslationEnum,
3+
default: (variable?: string) => string
4+
}
5+
6+
export enum ArrayTranslationEnum{
7+
addTooltip = 'addTooltip',
8+
addAriaLabel = 'addAriaLabel',
9+
removeTooltip = 'removeTooltip',
10+
upAriaLabel = 'upAriaLabel',
11+
downAriaLabel = 'downAriaLabel',
12+
noSelection = 'noSelection',
13+
removeAriaLabel = 'removeAriaLabel',
14+
noDataMessage = 'noDataMessage',
15+
deleteDialogHeader = 'deleteDialogHeader',
16+
deleteDialogMsg = 'deleteDialogMsg',
17+
deleteDialogAccept = 'deleteDialogAccept',
18+
deleteDialogDecline = 'deleteDialogDecline'
19+
}
20+
21+
export type ArrayTranslations = {
22+
[key in ArrayTranslationEnum]?: string
23+
}
24+
25+
export const arrayDefaultTranslations: DefaultTranslation[] = [
26+
{key: ArrayTranslationEnum.addTooltip, default: (input) => input?`Add to ${input}`:'Add'},
27+
{key: ArrayTranslationEnum.addAriaLabel, default: (input) => input?`Add to ${input}`:'Add'},
28+
{key: ArrayTranslationEnum.removeTooltip, default: () => 'Delete'},
29+
{key: ArrayTranslationEnum.upAriaLabel, default: (input) => `Move ${input} up`},
30+
{key: ArrayTranslationEnum.downAriaLabel, default: (input) => `Move ${input} down`},
31+
{key: ArrayTranslationEnum.removeAriaLabel, default: () => 'Delete button'},
32+
{key: ArrayTranslationEnum.noDataMessage, default: () => 'No Data'},
33+
{key: ArrayTranslationEnum.noSelection, default: () => 'No Selection'},
34+
{key: ArrayTranslationEnum.deleteDialogHeader, default: () => 'Confirm Deletion'},
35+
{key: ArrayTranslationEnum.deleteDialogMsg, default: () => 'Are you sure you want to delete the selected entry?'},
36+
{key: ArrayTranslationEnum.deleteDialogAccept, default: () => 'YES'},
37+
{key: ArrayTranslationEnum.deleteDialogDecline, default: () => 'NO'}
38+
]
39+
40+
export const arrayTranslations: DefaultTranslation[] = Object.values(arrayDefaultTranslations).map(value=>value);

packages/core/src/i18n/i18nUtil.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { isInternationalized, Labelable, UISchemaElement } from '../models';
33
import { getControlPath } from '../reducers';
44
import { formatErrorMessage } from '../util';
55
import type { i18nJsonSchema, ErrorTranslator, Translator } from './i18nTypes';
6+
import { DefaultTranslation, ArrayTranslations } from './arrayTranslations'
67

78
export const getI18nKeyPrefixBySchema = (
89
schema: i18nJsonSchema | undefined,
@@ -47,6 +48,13 @@ export const getI18nKey = (
4748
return `${getI18nKeyPrefix(schema, uischema, path)}.${key}`;
4849
};
4950

51+
export const addI18nKeyToPrefix = (
52+
I18nKeyPrefix: string,
53+
key: string
54+
): string => {
55+
return `${I18nKeyPrefix}.${key}`;
56+
};
57+
5058
export const defaultTranslator: Translator = (_id: string, defaultMessage: string | undefined) => defaultMessage;
5159

5260
export const defaultErrorTranslator: ErrorTranslator = (error, t, uischema) => {
@@ -123,3 +131,19 @@ export const deriveLabelForUISchemaElement = (uischema: Labelable<boolean>, t: T
123131
const i18nKey = typeof i18nKeyPrefix === 'string' ? `${i18nKeyPrefix}.label` : stringifiedLabel;
124132
return t(i18nKey, stringifiedLabel, { uischema: uischema });
125133
}
134+
135+
export const translateElements = (
136+
t: Translator,
137+
controlElements: DefaultTranslation [],
138+
i18nKeyPrefix: string,
139+
label: string
140+
): ArrayTranslations => {
141+
const translations:ArrayTranslations = {};
142+
console.log("translating...");
143+
controlElements.forEach((controlElement)=>{
144+
const key = addI18nKeyToPrefix(i18nKeyPrefix, controlElement.key);
145+
translations[controlElement.key]=t(key, controlElement.default(label));
146+
console.log("translations: ", translations);
147+
})
148+
return translations;
149+
}

packages/core/src/util/renderer.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ import { composePaths, composeWithUi } from './path';
5656
import { CoreActions, update } from '../actions';
5757
import type { ErrorObject } from 'ajv';
5858
import type { JsonFormsState } from '../store';
59-
import { deriveLabelForUISchemaElement, getCombinedErrorMessage, getI18nKey, getI18nKeyPrefix, getI18nKeyPrefixBySchema, Translator } from '../i18n';
59+
import { deriveLabelForUISchemaElement, getCombinedErrorMessage, getI18nKey, getI18nKeyPrefix, getI18nKeyPrefixBySchema, translateElements, Translator } from '../i18n';
60+
import { arrayDefaultTranslations, ArrayTranslations } from '../i18n/arrayTranslations';
6061

6162
const isRequired = (
6263
schema: JsonSchema,
@@ -370,6 +371,8 @@ export interface StatePropsOfControl extends StatePropsOfScopedRenderer {
370371
*/
371372
required?: boolean;
372373

374+
i18nKeyPrefix?: string;
375+
373376
// TODO: renderers?
374377
}
375378

@@ -469,6 +472,7 @@ export const mapStateToControlProps = (
469472
const schema = resolvedSchema ?? rootSchema;
470473
const t = getTranslator()(state);
471474
const te = getErrorTranslator()(state);
475+
const i18nKeyPrefix = getI18nKeyPrefix(schema, uischema, path);
472476
const i18nLabel = t(getI18nKey(schema, uischema, path, 'label'), label, {schema, uischema, path, errors} );
473477
const i18nDescription = t(getI18nKey(schema, uischema, path, 'description'), description, {schema, uischema, path, errors});
474478
const i18nErrorMessage = getCombinedErrorMessage(errors, te, t, schema, uischema, path);
@@ -487,7 +491,8 @@ export const mapStateToControlProps = (
487491
schema,
488492
config: getConfig(state),
489493
cells: ownProps.cells || state.jsonforms.cells,
490-
rootSchema
494+
rootSchema,
495+
i18nKeyPrefix
491496
};
492497
};
493498

@@ -703,14 +708,15 @@ export const mapStateToArrayControlProps = (
703708
const resolvedSchema = Resolve.schema(schema, 'items', props.rootSchema);
704709
const childErrors = getSubErrorsAt(path, resolvedSchema)(state);
705710

711+
706712
return {
707713
...props,
708714
path,
709715
uischema,
710716
schema: resolvedSchema,
711717
childErrors,
712718
renderers: ownProps.renderers || getRenderers(state),
713-
cells: ownProps.cells || getCells(state)
719+
cells: ownProps.cells || getCells(state),
714720
};
715721
};
716722

@@ -771,7 +777,7 @@ export const mapDispatchToArrayControlProps = (
771777
return array;
772778
})
773779
);
774-
}
780+
},
775781
});
776782

777783
export interface DispatchPropsOfMultiEnumControl {
@@ -1012,6 +1018,7 @@ export const mapStateToOneOfProps = (
10121018
export interface StatePropsOfArrayLayout extends StatePropsOfControlWithDetail {
10131019
data: number;
10141020
minItems?: number;
1021+
translations?: ArrayTranslations;
10151022
}
10161023
/**
10171024
* Map state to table props
@@ -1029,16 +1036,19 @@ export const mapStateToArrayLayoutProps = (
10291036
schema,
10301037
uischema,
10311038
errors,
1039+
i18nKeyPrefix,
1040+
label,
10321041
...props
10331042
} = mapStateToControlWithDetailProps(state, ownProps);
10341043

1044+
10351045
const resolvedSchema = Resolve.schema(schema, 'items', props.rootSchema);
1036-
1046+
const t = getTranslator()(state);
10371047
// TODO Does not consider 'i18n' keys which are specified in the ui schemas of the sub errors
10381048
const childErrors = getCombinedErrorMessage(
10391049
getSubErrorsAt(path, resolvedSchema)(state),
10401050
getErrorTranslator()(state),
1041-
getTranslator()(state),
1051+
t,
10421052
undefined,
10431053
undefined,
10441054
undefined
@@ -1050,12 +1060,14 @@ export const mapStateToArrayLayoutProps = (
10501060
childErrors;
10511061
return {
10521062
...props,
1063+
label,
10531064
path,
10541065
uischema,
10551066
schema: resolvedSchema,
10561067
data: props.data ? props.data.length : 0,
10571068
errors: allErrors,
1058-
minItems: schema.minItems
1069+
minItems: schema.minItems,
1070+
translations: translateElements(t,arrayDefaultTranslations,i18nKeyPrefix, label)
10591071
};
10601072
};
10611073

packages/material-renderers/src/additional/MaterialListWithDetailRenderer.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ export const MaterialListWithDetailRenderer = ({
6161
renderers,
6262
cells,
6363
config,
64-
rootSchema
64+
rootSchema,
65+
translations
6566
}: ArrayLayoutProps) => {
6667
const [selectedIndex, setSelectedIndex] = useState(undefined);
6768
const handleRemoveItem = useCallback(
@@ -92,7 +93,7 @@ export const MaterialListWithDetailRenderer = ({
9293
path,
9394
undefined,
9495
uischema,
95-
rootSchema
96+
rootSchema,
9697
),
9798
[uischemas, schema, uischema.scope, path, uischema, rootSchema]
9899
);
@@ -105,6 +106,7 @@ export const MaterialListWithDetailRenderer = ({
105106
return (
106107
<Hidden xsUp={!visible}>
107108
<ArrayLayoutToolbar
109+
translations={translations}
108110
label={computeLabel(
109111
label,
110112
required,
@@ -146,7 +148,7 @@ export const MaterialListWithDetailRenderer = ({
146148
path={composePaths(path, `${selectedIndex}`)}
147149
/>
148150
) : (
149-
<Typography variant='h6'>No Selection</Typography>
151+
<Typography variant='h6'>{translations.noSelection}</Typography>
150152
)}
151153
</Grid>
152154
</Grid>

packages/material-renderers/src/complex/DeleteDialog.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,17 @@ export interface DeleteDialogProps {
3737
onClose(): void;
3838
onConfirm(): void;
3939
onCancel(): void;
40+
headerText: string,
41+
msg: string,
42+
acceptText: string,
43+
declineText: string
4044
}
4145

4246
export interface WithDeleteDialogSupport {
4347
openDeleteDialog(path: string, data: number): void;
4448
}
4549

46-
export const DeleteDialog = React.memo(({ open, onClose, onConfirm, onCancel }: DeleteDialogProps) => {
50+
export const DeleteDialog = React.memo(({ open, onClose, onConfirm, onCancel, headerText: header, msg, acceptText: accept, declineText: decline }: DeleteDialogProps) => {
4751
return (
4852
<Dialog
4953
open={open}
@@ -53,19 +57,19 @@ export const DeleteDialog = React.memo(({ open, onClose, onConfirm, onCancel }:
5357
aria-describedby='alert-dialog-confirmdelete-description'
5458
>
5559
<DialogTitle id='alert-dialog-confirmdelete-title'>
56-
{'Confirm Deletion'}
60+
{header}
5761
</DialogTitle>
5862
<DialogContent>
5963
<DialogContentText id='alert-dialog-confirmdelete-description'>
60-
Are you sure you want to delete the selected entry?
64+
{msg}
6165
</DialogContentText>
6266
</DialogContent>
6367
<DialogActions>
6468
<Button onClick={onCancel} color='primary'>
65-
No
69+
{decline}
6670
</Button>
6771
<Button onClick={onConfirm} color='primary'>
68-
Yes
72+
{accept}
6973
</Button>
7074
</DialogActions>
7175
</Dialog>

packages/material-renderers/src/complex/MaterialArrayControlRenderer.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ export const MaterialArrayControlRenderer = (props: ArrayLayoutProps) => {
5959
onCancel={deleteCancel}
6060
onConfirm={deleteConfirm}
6161
onClose={deleteClose}
62+
acceptText={props.translations.deleteDialogAccept}
63+
declineText={props.translations.deleteDialogDecline}
64+
headerText={props.translations.deleteDialogHeader}
65+
msg={props.translations.deleteDialogMsg}
6266
/>
6367
</Hidden>
6468
);

packages/material-renderers/src/complex/MaterialTableControl.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import NoBorderTableCell from './NoBorderTableCell';
6565
import TableToolbar from './TableToolbar';
6666
import { ErrorObject } from 'ajv';
6767
import merge from 'lodash/merge';
68+
import { ArrayTranslations } from '@jsonforms/core/lib/i18n/arrayTranslations';
6869

6970
// we want a cell that doesn't automatically span
7071
const styles = {
@@ -129,12 +130,13 @@ const getValidColumnProps = (scopedSchema: JsonSchema) => {
129130

130131
export interface EmptyTableProps {
131132
numColumns: number;
133+
translations: ArrayTranslations;
132134
}
133135

134-
const EmptyTable = ({ numColumns }: EmptyTableProps) => (
136+
const EmptyTable = ({ numColumns, translations }: EmptyTableProps) => (
135137
<TableRow>
136138
<NoBorderTableCell colSpan={numColumns}>
137-
<Typography align='center'>No data</Typography>
139+
<Typography align='center'>{translations.noDataMessage}</Typography>
138140
</NoBorderTableCell>
139141
</TableRow>
140142
);
@@ -333,6 +335,7 @@ interface TableRowsProp {
333335
cells?: JsonFormsCellRendererRegistryEntry[];
334336
moveUp?(path: string, toMove: number): () => void;
335337
moveDown?(path: string, toMove: number): () => void;
338+
translations: ArrayTranslations;
336339
}
337340
const TableRows = ({
338341
data,
@@ -344,12 +347,13 @@ const TableRows = ({
344347
uischema,
345348
config,
346349
enabled,
347-
cells
350+
cells,
351+
translations
348352
}: TableRowsProp & WithDeleteDialogSupport) => {
349353
const isEmptyTable = data === 0;
350354

351355
if (isEmptyTable) {
352-
return <EmptyTable numColumns={getValidColumnProps(schema).length + 1} />;
356+
return <EmptyTable numColumns={getValidColumnProps(schema).length + 1} translations={translations}/>;
353357
}
354358

355359
const appliedUiSchemaOptions = merge({}, config, uischema.options);
@@ -397,7 +401,8 @@ export class MaterialTableControl extends React.Component<
397401
openDeleteDialog,
398402
visible,
399403
enabled,
400-
cells
404+
cells,
405+
translations
401406
} = this.props;
402407

403408
const controlElement = uischema as ControlElement;
@@ -420,6 +425,7 @@ export class MaterialTableControl extends React.Component<
420425
schema={schema}
421426
rootSchema={rootSchema}
422427
enabled={enabled}
428+
translations={translations}
423429
/>
424430
{isObjectSchema && (
425431
<TableRow>
@@ -429,7 +435,7 @@ export class MaterialTableControl extends React.Component<
429435
)}
430436
</TableHead>
431437
<TableBody>
432-
<TableRows openDeleteDialog={openDeleteDialog} {...this.props} />
438+
<TableRows openDeleteDialog={openDeleteDialog} translations={translations} {...this.props} />
433439
</TableBody>
434440
</Table>
435441
</Hidden>

packages/material-renderers/src/complex/TableToolbar.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { Grid, Typography } from '@mui/material';
3737
import AddIcon from '@mui/icons-material/Add';
3838
import ValidationIcon from './ValidationIcon';
3939
import NoBorderTableCell from './NoBorderTableCell';
40+
import { ArrayTranslations } from '@jsonforms/core/lib/i18n/arrayTranslations';
4041

4142
export interface MaterialTableToolbarProps {
4243
numColumns: number;
@@ -47,6 +48,7 @@ export interface MaterialTableToolbarProps {
4748
schema: JsonSchema;
4849
rootSchema: JsonSchema;
4950
enabled: boolean;
51+
translations: ArrayTranslations;
5052
addItem(path: string, value: any): () => void;
5153
}
5254

@@ -63,7 +65,8 @@ const TableToolbar = React.memo(
6365
path,
6466
addItem,
6567
schema,
66-
enabled
68+
enabled,
69+
translations
6770
}: MaterialTableToolbarProps) => (
6871
<TableRow>
6972
<NoBorderTableCell colSpan={numColumns}>
@@ -92,11 +95,11 @@ const TableToolbar = React.memo(
9295
<NoBorderTableCell align='right' style={ fixedCellSmall }>
9396
<Tooltip
9497
id='tooltip-add'
95-
title={`Add to ${label}`}
98+
title={translations.addTooltip}
9699
placement='bottom'
97100
>
98101
<IconButton
99-
aria-label={`Add to ${label}`}
102+
aria-label={translations.addTooltip}
100103
onClick={addItem(path, createDefaultValue(schema))}
101104
size='large'>
102105
<AddIcon />

0 commit comments

Comments
 (0)