Skip to content

Commit a672127

Browse files
authored
feat: translate enums in React Material expand panels
Expand panels in the React Material UI renderer set show one of the contained values in their expandable bar. These values are now translated in case they are enums or oneOf enums.
1 parent 255ee6f commit a672127

File tree

10 files changed

+947
-39
lines changed

10 files changed

+947
-39
lines changed

packages/core/src/models/uischema.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,3 +309,7 @@ export const isLabelable = (obj: unknown): obj is Labelable =>
309309

310310
export const isLabeled = <T = never>(obj: unknown): obj is Labeled<T> =>
311311
isLabelable(obj) && ['string', 'boolean'].includes(typeof obj.label);
312+
313+
export const isControlElement = (
314+
uiSchema: UISchemaElement
315+
): uiSchema is ControlElement => uiSchema.type === 'Control';

packages/core/src/testers/testers.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import type {
4141
import {
4242
deriveTypes,
4343
hasType,
44+
isEnumSchema,
4445
isOneOfEnumSchema,
4546
resolveSchema,
4647
} from '../util';
@@ -369,14 +370,7 @@ export const isOneOfControl = and(
369370
*/
370371
export const isEnumControl = and(
371372
uiTypeIs('Control'),
372-
or(
373-
schemaMatches((schema) =>
374-
Object.prototype.hasOwnProperty.call(schema, 'enum')
375-
),
376-
schemaMatches((schema) =>
377-
Object.prototype.hasOwnProperty.call(schema, 'const')
378-
)
379-
)
373+
schemaMatches((schema) => isEnumSchema(schema))
380374
);
381375

382376
/**

packages/core/src/util/label.ts

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,28 @@
2525

2626
import startCase from 'lodash/startCase';
2727

28-
import type { ControlElement, JsonSchema, LabelDescription } from '../models';
28+
import {
29+
ControlElement,
30+
JsonSchema,
31+
LabelDescription,
32+
UISchemaElement,
33+
} from '../models';
2934
import { decode } from './path';
35+
import { getI18nKeyPrefix, Translator } from '../i18n';
36+
import { Resolve } from './util';
37+
import {
38+
getFirstPrimitiveProp,
39+
isEnumSchema,
40+
isOneOfEnumSchema,
41+
} from './schema';
42+
import get from 'lodash/get';
43+
import { findUiControl, getPropPath } from './uischema';
44+
import {
45+
EnumOption,
46+
enumToEnumOptionMapper,
47+
oneOfToEnumOptionMapper,
48+
} from './renderer';
49+
import isEqual from 'lodash/isEqual';
3050

3151
const deriveLabel = (
3252
controlElement: ControlElement,
@@ -81,3 +101,75 @@ const labelDescription = (text: string, show: boolean): LabelDescription => ({
81101
text: text,
82102
show: show,
83103
});
104+
105+
/**
106+
* Compute the child label title for array based controls
107+
* @param data the data of the control
108+
* @param childPath the child path
109+
* @param childLabelProp the dotted path to the value used as child label
110+
* @param {JsonSchema} schema the json schema for this control
111+
* @param {JsonSchema} rootSchema the root json schema
112+
* @param {Translator} translateFct the translator fonction
113+
* @param {UISchemaElement} uiSchema the uiSchema of the control
114+
*/
115+
export const computeChildLabel = (
116+
data: any,
117+
childPath: string,
118+
childLabelProp: string,
119+
schema: JsonSchema,
120+
rootSchema: JsonSchema,
121+
translateFct: Translator,
122+
uiSchema: UISchemaElement
123+
): string => {
124+
const childData = Resolve.data(data, childPath);
125+
126+
if (!childLabelProp) {
127+
childLabelProp = getFirstPrimitiveProp(schema);
128+
}
129+
130+
// return early in case there is no prop we can query
131+
if (!childLabelProp) {
132+
return '';
133+
}
134+
135+
const currentValue = get(childData, childLabelProp, '');
136+
137+
// check whether the value is part of a oneOf or enum and needs to be translated
138+
const childSchema = Resolve.schema(
139+
schema,
140+
'#' + getPropPath(childLabelProp),
141+
rootSchema
142+
);
143+
144+
let enumOption: EnumOption = undefined;
145+
if (isEnumSchema(childSchema)) {
146+
enumOption = enumToEnumOptionMapper(
147+
currentValue,
148+
translateFct,
149+
getI18nKeyPrefix(
150+
childSchema,
151+
findUiControl(uiSchema, childLabelProp),
152+
childPath + '.' + childLabelProp
153+
)
154+
);
155+
} else if (isOneOfEnumSchema(childSchema)) {
156+
const oneOfArray = childSchema.oneOf as JsonSchema[];
157+
const oneOfSchema = oneOfArray.find((e: JsonSchema) =>
158+
isEqual(e.const, currentValue)
159+
);
160+
161+
if (oneOfSchema) {
162+
enumOption = oneOfToEnumOptionMapper(
163+
oneOfSchema,
164+
translateFct,
165+
getI18nKeyPrefix(
166+
oneOfSchema,
167+
undefined,
168+
childPath + '.' + childLabelProp
169+
)
170+
);
171+
}
172+
}
173+
174+
return enumOption ? enumOption.label : currentValue;
175+
};

packages/core/src/util/schema.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,12 @@ export const isOneOfEnumSchema = (schema: JsonSchema) =>
4848
Object.prototype.hasOwnProperty.call(schema, 'oneOf') &&
4949
schema.oneOf &&
5050
(schema.oneOf as JsonSchema[]).every((s) => s.const !== undefined);
51+
52+
/**
53+
* Tests whether the schema has an enum.
54+
*/
55+
export const isEnumSchema = (schema: JsonSchema) =>
56+
!!schema &&
57+
typeof schema === 'object' &&
58+
(Object.prototype.hasOwnProperty.call(schema, 'enum') ||
59+
Object.prototype.hasOwnProperty.call(schema, 'const'));

packages/core/src/util/uischema.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,13 @@
2424
*/
2525

2626
import isEmpty from 'lodash/isEmpty';
27-
import { isLayout, UISchemaElement } from '../models';
27+
import {
28+
isControlElement,
29+
isLayout,
30+
isScoped,
31+
UISchemaElement,
32+
} from '../models';
33+
import { encode } from './path';
2834

2935
export type IterateCallback = (uischema: UISchemaElement) => void;
3036

@@ -55,3 +61,43 @@ export const iterateSchema = (
5561
}
5662
toApply(uischema);
5763
};
64+
65+
/**
66+
* Transform a dotted path to a uiSchema properties path
67+
* @param path a dotted prop path to a schema value (i.e. articles.comment.author)
68+
* @return the uiSchema properties path (i.e. /properties/articles/properties/comment/properties/author)
69+
*/
70+
export const getPropPath = (path: string): string => {
71+
return `/properties/${path
72+
.split('.')
73+
.map((p) => encode(p))
74+
.join('/properties/')}`;
75+
};
76+
77+
/**
78+
* Find a control in a uiSchema, based on the dotted path of the schema value
79+
* @param {UISchemaElement} uiSchema the uiSchema to search from
80+
* @param path a dotted prop path to a schema value (i.e. articles.comment.author)
81+
* @return {UISchemaElement} or undefined if not found
82+
*/
83+
export const findUiControl = (
84+
uiSchema: UISchemaElement,
85+
path: string
86+
): UISchemaElement | undefined => {
87+
if (isControlElement(uiSchema)) {
88+
if (isScoped(uiSchema) && uiSchema.scope.endsWith(getPropPath(path))) {
89+
return uiSchema;
90+
} else if (uiSchema.options?.detail) {
91+
return findUiControl(uiSchema.options.detail, path);
92+
}
93+
}
94+
95+
if (isLayout(uiSchema)) {
96+
for (const elem of uiSchema.elements) {
97+
const result = findUiControl(elem, path);
98+
if (result !== undefined) return result;
99+
}
100+
}
101+
102+
return undefined;
103+
};

0 commit comments

Comments
 (0)