Skip to content

Commit a2c1a6c

Browse files
committed
core: improve Translator type for strict TypeScript compatibility
Fix #2528 - Replace overloaded Translator type with a generic conditional type - Add createTranslator helper to allow implementation without type assertions under strictFunctionTypes and strictNullChecks.
1 parent effb45c commit a2c1a6c

13 files changed

Lines changed: 74 additions & 39 deletions

File tree

MIGRATION.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
11
# Migration guide
22

3+
## Migrating to JSON Forms 3.8
4+
5+
### `Translator` type changed from overloaded signatures to a generic conditional type
6+
7+
The `Translator` type was changed to improve compatibility with TypeScript's `strictFunctionTypes` and `strictNullChecks` compiler options (see [#2528](https://github.com/eclipsesource/jsonforms/issues/2528)).
8+
9+
If you were previously assigning a function directly to the `Translator` type, this may no longer compile:
10+
11+
```ts
12+
// No longer compiles
13+
const t: Translator = (id, defaultMessage) => defaultMessage ?? id;
14+
```
15+
16+
Use the new `createTranslator` helper instead:
17+
18+
```ts
19+
import { createTranslator } from '@jsonforms/core';
20+
21+
const t = createTranslator((id, defaultMessage) => defaultMessage ?? id);
22+
```
23+
24+
This also replaces the `as Translator` workaround that was previously needed under strict TypeScript settings.
25+
326
## Migrating to JSON Forms 3.7
427

528
### Angular support now targets Angular 19 to 21

packages/core/src/i18n/i18nUtil.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,17 @@ export const addI18nKeyToPrefix = (
6565
return `${i18nKeyPrefix}.${key}`;
6666
};
6767

68-
export const defaultTranslator: Translator = (
69-
_id: string,
70-
defaultMessage: string | undefined
71-
) => defaultMessage;
68+
export const createTranslator = (
69+
fn: (
70+
id: string,
71+
defaultMessage: string | undefined,
72+
values?: any
73+
) => string | undefined
74+
): Translator => fn as Translator;
75+
76+
export const defaultTranslator: Translator = createTranslator(
77+
(_id, defaultMessage) => defaultMessage
78+
);
7279

7380
export const defaultErrorTranslator: ErrorTranslator = (error, t, uischema) => {
7481
// check whether there is a special keyword message

packages/core/src/store/i18nTypes.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import type { ErrorObject } from 'ajv';
22
import type { JsonSchema, UISchemaElement } from '../models';
33

4-
export type Translator = {
5-
(id: string, defaultMessage: string, values?: any): string;
6-
(id: string, defaultMessage: undefined, values?: any): string | undefined;
7-
(id: string, defaultMessage?: string, values?: any): string | undefined;
8-
};
4+
export type Translator = <D extends string | undefined = undefined>(
5+
id: string,
6+
defaultMessage?: D,
7+
values?: any
8+
) => D extends string ? string : string | undefined;
99

1010
export type ErrorTranslator = (
1111
error: ErrorObject,

packages/examples/src/examples/arrays-with-translated-custom-element-label.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
THE SOFTWARE.
2424
*/
2525
import { registerExamples } from '../register';
26-
import { JsonSchema7, Translator } from '@jsonforms/core';
26+
import { createTranslator, JsonSchema7, Translator } from '@jsonforms/core';
2727

2828
export const data = {
2929
article: {
@@ -265,9 +265,9 @@ export const uischema = {
265265
],
266266
};
267267

268-
export const translate: Translator = (key: string) => {
268+
export const translate: Translator = createTranslator((key) => {
269269
return 'translator.' + key;
270-
};
270+
});
271271

272272
registerExamples([
273273
{

packages/examples/src/examples/arraysI18n.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@
2323
THE SOFTWARE.
2424
*/
2525
import { registerExamples } from '../register';
26-
import { ArrayTranslationEnum, Translator } from '@jsonforms/core';
26+
import {
27+
ArrayTranslationEnum,
28+
createTranslator,
29+
Translator,
30+
} from '@jsonforms/core';
2731
import get from 'lodash/get';
2832

2933
export const schema = {
@@ -88,9 +92,9 @@ export const translations = {
8892
'Are you sure you want to delete this comment?',
8993
},
9094
};
91-
export const translate: Translator = (key: string, defaultMessage: string) => {
95+
export const translate: Translator = createTranslator((key, defaultMessage) => {
9296
return get(translations, key) ?? defaultMessage;
93-
};
97+
});
9498

9599
registerExamples([
96100
{

packages/examples/src/examples/categorization.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2323
THE SOFTWARE.
2424
*/
25-
import { Translator } from '@jsonforms/core';
25+
import { createTranslator, Translator } from '@jsonforms/core';
2626
import get from 'lodash/get';
2727
import { registerExamples } from '../register';
2828

@@ -292,9 +292,9 @@ export const translations = {
292292
label: 'Address',
293293
},
294294
};
295-
export const translate: Translator = (key: string, defaultMessage: string) => {
295+
export const translate: Translator = createTranslator((key, defaultMessage) => {
296296
return get(translations, key) ?? defaultMessage;
297-
};
297+
});
298298

299299
registerExamples([
300300
{

packages/examples/src/examples/enumI18n.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
THE SOFTWARE.
2424
*/
2525
import { registerExamples } from '../register';
26-
import { Translator } from '@jsonforms/core';
26+
import { createTranslator, Translator } from '@jsonforms/core';
2727
import get from 'lodash/get';
2828

2929
export const schema = {
@@ -116,12 +116,9 @@ export const translations: Record<string, string> = {
116116
'status.rejected': 'Declined',
117117
};
118118

119-
export const translate: Translator = (
120-
key: string,
121-
defaultMessage: string | undefined
122-
) => {
119+
export const translate: Translator = createTranslator((key, defaultMessage) => {
123120
return get(translations, key) ?? defaultMessage;
124-
};
121+
});
125122

126123
registerExamples([
127124
{

packages/examples/src/examples/i18n.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
AnyAction,
3131
Dispatch,
3232
Translator,
33+
createTranslator,
3334
} from '@jsonforms/core';
3435
import get from 'lodash/get';
3536
import localize from 'ajv-i18n/localize';
@@ -99,9 +100,9 @@ export const translations = {
99100
},
100101
additionalInformationLabel: 'Additional Information',
101102
};
102-
export const translate: Translator = (key: string, defaultMessage: string) => {
103+
export const translate: Translator = createTranslator((key, defaultMessage) => {
103104
return get(translations, key) ?? defaultMessage;
104-
};
105+
});
105106

106107
registerExamples([
107108
{

packages/material-renderers/test/renderers/util.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
JsonFormsCore,
2929
JsonSchema,
3030
TesterContext,
31+
createTranslator,
3132
Translator,
3233
UISchemaElement,
3334
} from '@jsonforms/core';
@@ -58,4 +59,6 @@ export const createTesterContext = (
5859
return { rootSchema, config };
5960
};
6061

61-
export const testTranslator: Translator = (key: string) => 'translator.' + key;
62+
export const testTranslator: Translator = createTranslator(
63+
(key) => 'translator.' + key
64+
);

packages/vue-vuetify/tests/unit/additional/ListWithDetailRenderer.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, it, expect, beforeEach } from 'vitest';
2-
import { clearAllIds, type Translator } from '@jsonforms/core';
2+
import { clearAllIds, createTranslator } from '@jsonforms/core';
33
import ListWithDetailRenderer from '../../../src/additional/ListWithDetailRenderer.vue';
44
import { entry as listWithDetailRendererEntry } from '../../../src/additional/ListWithDetailRenderer.entry';
55
import { mountJsonForms } from '../util';
@@ -30,12 +30,12 @@ describe('ListWithDetailRenderer.vue', () => {
3030
// clear all ids to guarantee that the snapshots will always be generated with the same ids
3131
clearAllIds();
3232
wrapper = mountJsonForms(data, schema, renderers, uischema, undefined, {
33-
translate: ((id, defaultMessage) => {
33+
translate: createTranslator((id, defaultMessage) => {
3434
if (id.endsWith('addAriaLabel')) {
3535
return 'MyAdd';
3636
}
3737
return defaultMessage;
38-
}) as Translator,
38+
}),
3939
});
4040
});
4141

0 commit comments

Comments
 (0)