Skip to content

Commit

Permalink
Merge b3d5af5 into 81e4dd6
Browse files Browse the repository at this point in the history
  • Loading branch information
sdirix committed Oct 26, 2021
2 parents 81e4dd6 + b3d5af5 commit 50088f3
Show file tree
Hide file tree
Showing 22 changed files with 947 additions and 261 deletions.
8 changes: 5 additions & 3 deletions packages/angular-material/example/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const itemTester: UISchemaTester = (_schema, schemaPath, _path) => {
[schema]="selectedExample.schema"
[uischema]="selectedExample.uischema"
[renderers]="renderers"
[locale]="currentLocale"
[i18n]="i18n"
[uischemas]="uischemas"
[readonly]="readonly"
[config]="config"
Expand All @@ -86,7 +86,9 @@ export class AppComponent {
readonly renderers = angularMaterialRenderers;
readonly examples = getExamples();
selectedExample: ExampleDescription;
currentLocale = 'en-US';
i18n = {
locale: 'en-US'
}
private readonly = false;
data: any;
uischemas: { tester: UISchemaTester; uischema: UISchemaElement; }[] = [
Expand All @@ -102,7 +104,7 @@ export class AppComponent {
}

changeLocale(locale: string) {
this.currentLocale = locale;
this.i18n.locale = locale;
}

toggleReadonly() {
Expand Down
6 changes: 0 additions & 6 deletions packages/angular-material/test/number-control.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,6 @@ describe(
getJsonFormsService(component).init({
core: state, i18n: {
locale: 'en',
localizedSchemas: undefined,
localizedUISchemas: undefined
}
});
getJsonFormsService(component).updateCore(
Expand All @@ -168,8 +166,6 @@ describe(
getJsonFormsService(component).init({
core: state, i18n: {
locale: 'en',
localizedSchemas: undefined,
localizedUISchemas: undefined
},config: {
useGrouping: false
},
Expand All @@ -196,8 +192,6 @@ describe(
getJsonFormsService(component).init({
core: state, i18n: {
locale: 'en',
localizedSchemas: undefined,
localizedUISchemas: undefined
},config: {
useGrouping: true
},
Expand Down
25 changes: 17 additions & 8 deletions packages/angular/src/jsonforms-root.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@
THE SOFTWARE.
*/
import {
Component,
EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges
Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges
} from '@angular/core';
import { Actions, JsonFormsRendererRegistryEntry, JsonSchema, UISchemaElement, UISchemaTester, ValidationMode } from '@jsonforms/core';
import { Actions, JsonFormsI18nState, JsonFormsRendererRegistryEntry, JsonSchema, UISchemaElement, UISchemaTester, ValidationMode } from '@jsonforms/core';
import Ajv, { ErrorObject } from 'ajv';
import { JsonFormsAngularService, USE_STATE_VALUE } from './jsonforms.service';
@Component({
Expand All @@ -42,11 +41,11 @@ export class JsonForms implements OnChanges, OnInit {
@Input() renderers: JsonFormsRendererRegistryEntry[];
@Input() uischemas: { tester: UISchemaTester; uischema: UISchemaElement; }[];
@Output() dataChange = new EventEmitter<any>();
@Input() locale: string;
@Input() readonly: boolean;
@Input() validationMode: ValidationMode;
@Input() ajv: Ajv;
@Input() config: any;
@Input() i18n: JsonFormsI18nState;
@Output() errors = new EventEmitter<ErrorObject[]>();

private previousData:any;
Expand All @@ -67,7 +66,7 @@ export class JsonForms implements OnChanges, OnInit {
validationMode: this.validationMode
},
uischemas: this.uischemas,
i18n: { locale: this.locale, localizedSchemas: undefined, localizedUISchemas: undefined },
i18n: this.i18n,
renderers: this.renderers,
config: this.config,
readonly: this.readonly
Expand All @@ -87,6 +86,14 @@ export class JsonForms implements OnChanges, OnInit {
this.initialized = true;
}

ngDoCheck(): void {
// we can't use ngOnChanges as then nested i18n changes will not be detected
// the update will result in a no-op when the parameters did not change
this.jsonformsService.updateI18n(
Actions.updateI18n(this.i18n?.locale, this.i18n?.translate, this.i18n?.translateError)
);
}

// tslint:disable-next-line: cyclomatic-complexity
ngOnChanges(changes: SimpleChanges): void {
if (!this.initialized) {
Expand All @@ -97,7 +104,7 @@ export class JsonForms implements OnChanges, OnInit {
const newUiSchema = changes.uischema;
const newRenderers = changes.renderers;
const newUischemas = changes.uischemas;
const newLocale = changes.locale;
const newI18n = changes.i18n;
const newReadonly = changes.readonly;
const newValidationMode = changes.validationMode;
const newAjv = changes.ajv;
Expand All @@ -121,8 +128,10 @@ export class JsonForms implements OnChanges, OnInit {
this.jsonformsService.setUiSchemas(newUischemas.currentValue);
}

if (newLocale && !newLocale.isFirstChange()) {
this.jsonformsService.setLocale(newLocale.currentValue);
if (newI18n && !newI18n.isFirstChange()) {
this.jsonformsService.updateI18n(
Actions.updateI18n(newI18n.currentValue?.locale, newI18n.currentValue?.translate, newI18n.currentValue?.translateError)
);
}

if (newReadonly && !newReadonly.isFirstChange()) {
Expand Down
22 changes: 13 additions & 9 deletions packages/angular/src/jsonforms.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,16 @@ import {
JsonFormsState,
JsonFormsSubStates,
JsonSchema,
LocaleActions,
I18nActions,
RankedTester,
setConfig,
SetConfigAction,
UISchemaActions,
UISchemaElement,
uischemaRegistryReducer,
UISchemaTester,
ValidationMode
ValidationMode,
updateI18n
} from '@jsonforms/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { JsonFormsBaseRenderer } from './base.renderer';
Expand All @@ -56,9 +57,10 @@ export class JsonFormsAngularService {
private _state: JsonFormsSubStates;
private state: BehaviorSubject<JsonFormsState>;

init(initialState: JsonFormsSubStates = { core: { data: undefined, schema: undefined, uischema: undefined } }) {
init(initialState: JsonFormsSubStates = { core: { data: undefined, schema: undefined, uischema: undefined, validationMode: 'ValidateAndShow' } }) {
this._state = initialState;
this._state.config = configReducer(undefined, setConfig(this._state.config));
this._state.i18n = i18nReducer(this._state.i18n, updateI18n(this._state.i18n?.locale, this._state.i18n?.translate, this._state.i18n?.translateError));
this.state = new BehaviorSubject({ jsonforms: this._state });
const data = initialState.core.data;
const schema = initialState.core.schema ?? generateJsonSchema(data);
Expand Down Expand Up @@ -117,16 +119,18 @@ export class JsonFormsAngularService {
this.updateSubject();
}

updateLocale<T extends LocaleActions>(localeAction: T): T {
const localeState = i18nReducer(this._state.i18n, localeAction);
this._state.i18n = localeState;
this.updateSubject();
return localeAction;
updateI18n<T extends I18nActions>(i18nAction: T): T {
const i18nState = i18nReducer(this._state.i18n, i18nAction);
if (i18nState !== this._state.i18n) {
this._state.i18n = i18nState;
this.updateSubject();
}
return i18nAction;
}

updateCore<T extends CoreActions>(coreAction: T): T {
const coreState = coreReducer(this._state.core, coreAction);
if(coreState !== this._state.core) {
if (coreState !== this._state.core) {
this._state.core = coreState;
this.updateSubject();
}
Expand Down
68 changes: 39 additions & 29 deletions packages/core/src/actions/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { generateDefaultUISchema, generateJsonSchema } from '../generators';

import { RankedTester } from '../testers';
import { UISchemaTester, ValidationMode } from '../reducers';
import { ErrorTranslator, Translator } from '../i18n';

export const INIT: 'jsonforms/INIT' = 'jsonforms/INIT';
export const UPDATE_CORE: 'jsonforms/UPDATE_CORE' = `jsonforms/UPDATE_CORE`;
Expand All @@ -51,10 +52,10 @@ export const SET_VALIDATION_MODE: 'jsonforms/SET_VALIDATION_MODE' =
'jsonforms/SET_VALIDATION_MODE';

export const SET_LOCALE: 'jsonforms/SET_LOCALE' = `jsonforms/SET_LOCALE`;
export const SET_LOCALIZED_SCHEMAS: 'jsonforms/SET_LOCALIZED_SCHEMAS' =
'jsonforms/SET_LOCALIZED_SCHEMAS';
export const SET_LOCALIZED_UISCHEMAS: 'jsonforms/SET_LOCALIZED_UISCHEMAS' =
'jsonforms/SET_LOCALIZED_UISCHEMAS';
export const SET_TRANSLATOR: 'jsonforms/SET_TRANSLATOR' =
'jsonforms/SET_TRANSLATOR';
export const UPDATE_I18N: 'jsonforms/UPDATE_I18N' =
'jsonforms/UPDATE_I18N';

export const ADD_DEFAULT_DATA: 'jsonforms/ADD_DEFAULT_DATA' = `jsonforms/ADD_DEFAULT_DATA`;
export const REMOVE_DEFAULT_DATA: 'jsonforms/REMOVE_DEFAULT_DATA' = `jsonforms/REMOVE_DEFAULT_DATA`;
Expand Down Expand Up @@ -275,33 +276,21 @@ export const unregisterUISchema = (
};
};

export type LocaleActions =
export type I18nActions =
| SetLocaleAction
| SetLocalizedSchemasAction
| SetLocalizedUISchemasAction;
| SetTranslatorAction
| UpdateI18nAction

export interface SetLocaleAction {
type: 'jsonforms/SET_LOCALE';
locale: string;
locale: string | undefined;
}

export const setLocale = (locale: string): SetLocaleAction => ({
export const setLocale = (locale: string | undefined): SetLocaleAction => ({
type: SET_LOCALE,
locale
});

export interface SetLocalizedSchemasAction {
type: 'jsonforms/SET_LOCALIZED_SCHEMAS';
localizedSchemas: Map<string, JsonSchema>;
}

export const setLocalizedSchemas = (
localizedSchemas: Map<string, JsonSchema>
): SetLocalizedSchemasAction => ({
type: SET_LOCALIZED_SCHEMAS,
localizedSchemas
});

export interface SetSchemaAction {
type: 'jsonforms/SET_SCHEMA';
schema: JsonSchema;
Expand All @@ -312,16 +301,37 @@ export const setSchema = (schema: JsonSchema): SetSchemaAction => ({
schema
});

export interface SetLocalizedUISchemasAction {
type: 'jsonforms/SET_LOCALIZED_UISCHEMAS';
localizedUISchemas: Map<string, UISchemaElement>;
export interface SetTranslatorAction {
type: 'jsonforms/SET_TRANSLATOR';
translator?: Translator;
errorTranslator?: ErrorTranslator;
}

export const setTranslator = (
translator?: Translator,
errorTranslator?: ErrorTranslator
): SetTranslatorAction => ({
type: SET_TRANSLATOR,
translator,
errorTranslator
});

export interface UpdateI18nAction {
type: 'jsonforms/UPDATE_I18N';
locale: string | undefined;
translator: Translator | undefined;
errorTranslator: ErrorTranslator | undefined;
}

export const setLocalizedUISchemas = (
localizedUISchemas: Map<string, UISchemaElement>
): SetLocalizedUISchemasAction => ({
type: SET_LOCALIZED_UISCHEMAS,
localizedUISchemas
export const updateI18n = (
locale: string | undefined,
translator: Translator | undefined,
errorTranslator: ErrorTranslator | undefined
): UpdateI18nAction => ({
type: UPDATE_I18N,
locale,
translator,
errorTranslator
});

export interface SetUISchemaAction {
Expand Down
17 changes: 17 additions & 0 deletions packages/core/src/i18n/i18nTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ErrorObject } from 'ajv';
import { JsonSchema, UISchemaElement } from '../models';

export type Translator = {
(id: string, defaultMessage: string, values?: any): string;
(id: string, defaultMessage: undefined, values?: any): string | undefined;
}

export type ErrorTranslator = (error: ErrorObject, translate: Translator, uischema?: UISchemaElement) => string;

export interface JsonFormsI18nState {
locale?: string;
translate?: Translator;
translateError?: ErrorTranslator;
}

export type i18nJsonSchema = JsonSchema & {i18n?: string};
76 changes: 76 additions & 0 deletions packages/core/src/i18n/i18nUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { ErrorObject } from 'ajv';
import { UISchemaElement } from '../models';
import { formatErrorMessage } from '../util';
import { i18nJsonSchema, ErrorTranslator, Translator } from './i18nTypes';

export const getI18nKey = (
schema: i18nJsonSchema | undefined,
uischema: UISchemaElement | undefined,
key: string
): string | undefined => {
if (uischema?.options?.i18n) {
return `${uischema.options.i18n}.${key}`;
}
if (schema?.i18n) {
return `${schema.i18n}.${key}`;
}
return undefined;
};

export const defaultTranslator: Translator = (_id: string, defaultMessage: string | undefined) => defaultMessage;

export const defaultErrorTranslator: ErrorTranslator = (error, t, uischema) => {
// check whether there is a special keyword message
const keyInSchemas = getI18nKey(
error.parentSchema,
uischema,
`error.${error.keyword}`
);
const specializedKeywordMessage = keyInSchemas && t(keyInSchemas, undefined);
if (specializedKeywordMessage !== undefined) {
return specializedKeywordMessage;
}

// check whether there is a generic keyword message
const genericKeywordMessage = t(`error.${error.keyword}`, undefined);
if (genericKeywordMessage !== undefined) {
return genericKeywordMessage;
}

// check whether there is a customization for the default message
const messageCustomization = t(error.message, undefined);
if (messageCustomization !== undefined) {
return messageCustomization;
}

// rewrite required property messages (if they were not customized) as we place them next to the respective input
if (error.keyword === 'required') {
return t('is a required property', 'is a required property');
}

return error.message;
};

/**
* Returns the determined error message for the given errors.
* All errors must correspond to the given schema and uischema.
*/
export const getCombinedErrorMessage = (
errors: ErrorObject[],
et: ErrorTranslator,
t: Translator,
schema?: i18nJsonSchema,
uischema?: UISchemaElement
) => {
if (errors.length > 0 && t) {
// check whether there is a special message which overwrites all others
const keyInSchemas = getI18nKey(schema, uischema, 'error.custom');
const specializedErrorMessage = keyInSchemas && t(keyInSchemas, undefined);
if (specializedErrorMessage !== undefined) {
return specializedErrorMessage;
}
}
return formatErrorMessage(
errors.map(error => et(error, t, uischema))
);
};
2 changes: 2 additions & 0 deletions packages/core/src/i18n/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './i18nTypes';
export * from './i18nUtil';
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ export * from './util';

export * from './Helpers';
export * from './store';
export * from './i18n';

0 comments on commit 50088f3

Please sign in to comment.