From 31e77ede0c783434b50d2c6d67d8b4c0ebb14a76 Mon Sep 17 00:00:00 2001 From: Florian Gareis Date: Mon, 4 Apr 2022 17:22:52 +0000 Subject: [PATCH 1/9] Add initial i18n documentation --- content/docs/i18n.mdx | 167 +++++++++++++++++++++++ content/pages/assets/support.module.scss | 2 +- src/components/docs/i18n.js | 142 +++++++++++++++++++ src/sidebars/docs.js | 1 + 4 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 content/docs/i18n.mdx create mode 100644 src/components/docs/i18n.js diff --git a/content/docs/i18n.mdx b/content/docs/i18n.mdx new file mode 100644 index 00000000..57597dc2 --- /dev/null +++ b/content/docs/i18n.mdx @@ -0,0 +1,167 @@ +--- +id: i18n +title: i18n +description: Translate jsonforms +--- + +import I18nExample from '../../src/components/docs/i18n'; + +:::note + +Please note that the renderer sets themselves are not yet translated. +You can find the current status here: +https://github.com/eclipsesource/jsonforms/issues/1826 + +::: + +The translate functionality of JSONForms is integrated into the core component. +In order to translate JSONForms, you need to set a translation function and provide it to the JSONForms object: +```ts +const translator = (key, defaultMessage) => { + console.log(`Key: ${key}, Default Message: ${defaultMessage}`); + return defaultMessage; +}; + +const [locale, setLocale] = useState<'de'|'en'>('de'); + + +``` + +The i18n prop consist of three components: `locale`, `translate`, `translateError`. + +## `locale` + +Allows to specify the current locale of the application. + +## `translate` + +Allows to provide a translation function, which handles the actual translation. + +:::caution + +The translate function should be side effect free and should be stable (memoized) to avoid unnecessary re-renderings, i.e. the translate function should only change if there are new translations. + +::: + + +The translate function has the following parameters: + +### `key` +The key is used to identify the string, that needs to be translated. +It can be set using the `UI Schema`, the `JSON Schema` or by default is generated based on the property path. + +#### UI Schema option + +The key can be set via the `i18n` UI Schema option: +```json +{ + "type": "Control", + "label": "name", + "scope": "#/properties/name", + "options": { + "i18n": "customName" + } +} +``` + +#### JSON Schema + +The key can also be set with the JSON Schema `i18n` option: +```json +{ + "name": { + "type": "string", + "i18n": "myCUstomName" + } +} +``` + +#### Default: Property path + +Is none of the above is set, the property path will be used as the key. +e.g. `name`. + +### `defaultMessage` +The default message is provided by JSONForms and can act as a fallback. + +:::note + +If the defaultMessage is `undefined` it should not be modified and still return `undefined` (and not an empty string or similar). + +::: + +## `translateError` + +`` in the sections below refers to the key of the field (see `key` section above). + +#### Evaluation order + +#### `.error.custom` + +If `error.custom` is set, just a single error will be output, no matter how many errors are present. + +#### `.error.` + +e.g. `name.error.required` + +If no `error.custom` is set, the translator will look for a concrete message for a specific field and a specific error. +In the example above, the function will look for a `required` error on the `name` field. + +#### `error.` + +e.g. `error.required` + +If no specific field error message is set, the translator will then look for an error message, that is independent of the field. +In the example case above, the function will look for a `required` error translation. + +#### Default error translator + +Next the translator will look for a default message, which is provided by the core framework. + +#### Default AJV error message + +If none of the above are set and no default error is available, the error message provided by AJV will be output. + +## Enum translation + +Enum translations are a little different then the other control translations. +In order to translate enum values, you need to add one additional attribute to the translation for every value. +For example: +Let's assume we have a field `Gender`, which has three possible values: +- `Male` +- `Female` +- `Other` + +If we now want to translate these, we need to reference them in the translation like this: +```js +gender: { + label: 'Geschlecht', + description: 'Das Geschlecht auswählen', + Male: 'Männlich', + Female: 'Weiblich', + Other: 'Divers' +}, +``` + +So the `label` attribute translates the `label` of the field, just like with any other control field. The same with the `description` attribute. +All the other options translate the possible values of the enum. + +## Access translation in custom renderer sets + +If you want to access the translation function within a custom renderer set, you can use the JSONForms context for that: +``` +const ctx = useJsonForms(); + +const locale = ctx.i18n.locale; +const translate = ctx.i18n.translate; +const translateError = ctx.i18n.translateError; +``` + +With this you can for example change phone number patterns based on the current locale for validation. + +## Example + + diff --git a/content/pages/assets/support.module.scss b/content/pages/assets/support.module.scss index 9b56c4f2..03158831 100644 --- a/content/pages/assets/support.module.scss +++ b/content/pages/assets/support.module.scss @@ -1,4 +1,4 @@ -h3 { +.comparison_container h3 { font-size: 2rem; padding: 0; } diff --git a/src/components/docs/i18n.js b/src/components/docs/i18n.js new file mode 100644 index 00000000..4b3fd4cd --- /dev/null +++ b/src/components/docs/i18n.js @@ -0,0 +1,142 @@ +import React, { useMemo, useState } from 'react'; +import get from 'lodash/get'; +import { + materialCells, + materialRenderers, +} from '@jsonforms/material-renderers'; +import Button from '@mui/material/Button'; +import { JsonForms } from '@jsonforms/react'; + +const i18n = { + schema: { + properties: { + firstName: { + type: "string" + }, + lastName: { + type: "string" + }, + email: { + type: "string" + }, + gender: { + type: "string", + enum: [ + "Male", + "Female", + "Other" + ] + } + }, + required: [ + "email" + ] + }, + uischema: { + type: "VerticalLayout", + elements: [ + { + type: "VerticalLayout", + elements: [ + { + type: "Control", + scope: "#/properties/firstName" + }, + { + type: "Control", + scope: "#/properties/lastName" + } + ] + }, + { + type: "Control", + scope: "#/properties/email" + }, + { + type: "Control", + scope: "#/properties/gender" + } + ] + } +} + +export const en = { + firstName: { + label: 'First Name', + description: 'The first name of the person', + }, + lastName: { + label: 'Last Name', + }, + email: { + label: 'Email' + }, + gender: { + label: 'Gender', + Male: 'Male', + Female: 'Female', + Other: 'Diverse' + }, + error: { + required: 'This field is required', + }, +}; + +export const de = { + firstName: { + label: 'Vorname', + description: 'Der Vorname der Person', + }, + lastName: { + label: 'Nachname', + }, + email: { + label: 'Email' + }, + gender: { + label: 'Geschlecht', + Male: 'Männlich', + Female: 'Weiblich', + Other: 'Divers' + }, + error: { + required: 'Dieses Feld muss ausgefüllt werden.', + }, +}; + +const I18nExample = () => { + const [locale, setLocale] = useState('de'); + + const createTranslator = (locale) => (key, defaultMessage) => { + return get(locale === 'en' ? en : de, key) ?? defaultMessage; + }; + + const translation = useMemo(() => createTranslator(locale), [locale]); + + const switchLocale = () => { + if(locale === 'en') { + setLocale('de'); + } else { + setLocale('en'); + } + }; + + return ( +
+ + +
+ Current language: {locale} +
+ ); +}; + +export default I18nExample; diff --git a/src/sidebars/docs.js b/src/sidebars/docs.js index 3a53ad5f..813acdcb 100644 --- a/src/sidebars/docs.js +++ b/src/sidebars/docs.js @@ -10,6 +10,7 @@ module.exports = { items: ['uischema/uischema', 'uischema/controls', 'uischema/layouts', 'uischema/rules'], }, 'labels', + 'i18n', 'renderer-sets', 'ref-resolving', 'readonly', From 113793bf9e857ade71593fcfa4df2d8465bc3529 Mon Sep 17 00:00:00 2001 From: Florian Gareis Date: Tue, 5 Apr 2022 08:23:23 +0000 Subject: [PATCH 2/9] Enhance i18n documentation --- content/docs/i18n.mdx | 74 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/content/docs/i18n.mdx b/content/docs/i18n.mdx index 57597dc2..a6089f3e 100644 --- a/content/docs/i18n.mdx +++ b/content/docs/i18n.mdx @@ -8,7 +8,7 @@ import I18nExample from '../../src/components/docs/i18n'; :::note -Please note that the renderer sets themselves are not yet translated. +Please note that the renderer sets themselves are not yet translatable. You can find the current status here: https://github.com/eclipsesource/jsonforms/issues/1826 @@ -35,6 +35,8 @@ The i18n prop consist of three components: `locale`, `translate`, `translateErro ## `locale` Allows to specify the current locale of the application. +This can be used by renderers to render locale specific UI elements (for example formatting of dates). + ## `translate` @@ -50,8 +52,9 @@ The translate function should be side effect free and should be stable (memoized The translate function has the following parameters: ### `key` + The key is used to identify the string, that needs to be translated. -It can be set using the `UI Schema`, the `JSON Schema` or by default is generated based on the property path. +It can be set using the `UI Schema`, the `JSON Schema` or is generated by default based on the property path. #### UI Schema option @@ -67,42 +70,95 @@ The key can be set via the `i18n` UI Schema option: } ``` +Therefore the translation will be invoked with `customName.label` & `customName.description`. + #### JSON Schema -The key can also be set with the JSON Schema `i18n` option: +The key can also be set with the custom JSON Schema `i18n` option: ```json { "name": { "type": "string", - "i18n": "myCUstomName" + "i18n": "myCustomName" } } ``` +Therefore the translation will be invoked with `myCustomName.label` & `myCustomName.description`. + #### Default: Property path Is none of the above is set, the property path will be used as the key. e.g. `name`. +``` +{ + properties: { + firstName: { + type: "string" + }, + address: { + type: "object", + properties: { + street: { + type: "string" + } + } + } + comments: { + type: "array", + items: { + type: "object", + properties: { + message: { + type: "string" + }, + } + } + } + } +} +``` + +The paths in the above example would be: +- `firstName.label` & `firstName.description` +- `address.street.label` & `address.street.description` +- `comments.message.label` & `comments.message.description` (the path for arrays will not contain indices) + ### `defaultMessage` -The default message is provided by JSONForms and can act as a fallback. + +The default message is provided by JSONForms and can act as a fallback or could be translated. :::note -If the defaultMessage is `undefined` it should not be modified and still return `undefined` (and not an empty string or similar). +If the defaultMessage is `undefined` it should not be modified and still return as `undefined` (and not an empty string or similar). +JSON Forms will use `undefined when the message could be skipped or another generic key could be tried. ::: -## `translateError` +### `values` -`` in the sections below refers to the key of the field (see `key` section above). +The values can contain additional context, if the key of the string is not enough to determine the correct translation. +For example all error translations will have the error as part of the values object. -#### Evaluation order +## Error translation + +TODO: Description #### `.error.custom` If `error.custom` is set, just a single error will be output, no matter how many errors are present. +## `translateError` + +The `translateError` is called for every AJV error. +`translateError` is an optional parameter to the i18n object. Usually it is not expected to customize this option. +By default error it works like this: + +`` in the sections below refers to the key of the field (see `key` section above). + +### Evaluation order + #### `.error.` e.g. `name.error.required` From 331dffe356ef5ab2e43781c318fff407bdf9f309 Mon Sep 17 00:00:00 2001 From: Florian Gareis Date: Tue, 5 Apr 2022 17:26:35 +0000 Subject: [PATCH 3/9] Add error customization --- content/docs/i18n.mdx | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/content/docs/i18n.mdx b/content/docs/i18n.mdx index a6089f3e..af77bd46 100644 --- a/content/docs/i18n.mdx +++ b/content/docs/i18n.mdx @@ -141,13 +141,16 @@ JSON Forms will use `undefined when the message could be skipped or another gen The values can contain additional context, if the key of the string is not enough to determine the correct translation. For example all error translations will have the error as part of the values object. -## Error translation +## Error Customizations TODO: Description #### `.error.custom` If `error.custom` is set, just a single error will be output, no matter how many errors are present. +The `translate` function is called for `error.custom` +The `translateError` function is called for each error. +By default it behaves like this: ## `translateError` @@ -181,6 +184,23 @@ Next the translator will look for a default message, which is provided by the co If none of the above are set and no default error is available, the error message provided by AJV will be output. +### Example + +If we have the following schema: +```js +{ + phone: { + type: "string", + minLength: 10, + pattern: "^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$" + } +} +``` +And the order in which the error keys are evaluated is the following: +- `phone.error.custom`: if this is set, the `pattern` and `minLength` error will be ignore and just this message is output +- `phone.error.pattern` & `phone.error.minLength` +- `error.pattern` & `error.minLength` + ## Enum translation Enum translations are a little different then the other control translations. From c3ef2bb3fd3d729ce1fd2997f0e6594506c19aad Mon Sep 17 00:00:00 2001 From: Florian Gareis Date: Tue, 12 Apr 2022 11:58:46 +0200 Subject: [PATCH 4/9] Apply suggestions from code review Co-authored-by: Stefan Dirix --- content/docs/i18n.mdx | 65 ++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 38 deletions(-) diff --git a/content/docs/i18n.mdx b/content/docs/i18n.mdx index af77bd46..080007b8 100644 --- a/content/docs/i18n.mdx +++ b/content/docs/i18n.mdx @@ -15,7 +15,7 @@ https://github.com/eclipsesource/jsonforms/issues/1826 ::: The translate functionality of JSONForms is integrated into the core component. -In order to translate JSONForms, you need to set a translation function and provide it to the JSONForms object: +In order to translate JSONForms, you need to set a translation function and provide it to the JSONForms component: ```ts const translator = (key, defaultMessage) => { console.log(`Key: ${key}, Default Message: ${defaultMessage}`); @@ -30,12 +30,12 @@ const [locale, setLocale] = useState<'de'|'en'>('de'); /> ``` -The i18n prop consist of three components: `locale`, `translate`, `translateError`. +The i18n prop consist of three components: `locale`, `translate` and `translateError`. ## `locale` Allows to specify the current locale of the application. -This can be used by renderers to render locale specific UI elements (for example formatting of dates). +This can be used by renderers to render locale specific UI elements, for example for locale-aware formatting of numbers. ## `translate` @@ -131,15 +131,16 @@ The default message is provided by JSONForms and can act as a fallback or could :::note -If the defaultMessage is `undefined` it should not be modified and still return as `undefined` (and not an empty string or similar). -JSON Forms will use `undefined when the message could be skipped or another generic key could be tried. +If the `defaultMessage` is `undefined` you should also return `undefined` if you don't have a translation for the given key. +Returning an empty string (or something similar) instead may result in undesired behavior. +JSON Forms will use `undefined` when the message could be skipped or another more generic key could be tried. ::: ### `values` -The values can contain additional context, if the key of the string is not enough to determine the correct translation. -For example all error translations will have the error as part of the values object. +The values can contain additional context for the current translation. +For example all error translations will have the AJV error object as part of the values object. ## Error Customizations @@ -147,10 +148,9 @@ TODO: Description #### `.error.custom` -If `error.custom` is set, just a single error will be output, no matter how many errors are present. -The `translate` function is called for `error.custom` -The `translateError` function is called for each error. -By default it behaves like this: +If `.error.custom` is set, just this single message will be used, no matter how many errors are present. + +This is useful if there are many JSON Schema validaton keywords defined for a single property, but a single cohesive message shall be displayed. ## `translateError` @@ -164,29 +164,28 @@ By default error it works like this: #### `.error.` -e.g. `name.error.required` +Example keys: `name.error.required`, `address.street.error.pattern` -If no `error.custom` is set, the translator will look for a concrete message for a specific field and a specific error. -In the example above, the function will look for a `required` error on the `name` field. +The default `translateError` will first look for a concrete message for a specific field and a specific error type. #### `error.` -e.g. `error.required` +Example keys: `error.required`, `error.pattern` -If no specific field error message is set, the translator will then look for an error message, that is independent of the field. -In the example case above, the function will look for a `required` error translation. +After checking field specific translations, `translateError` will then look for form-wide translations of errors, independent of each respective field. +This is useful to customize for example `required` or `pattern` messages for all properties. -#### Default error translator +#### error message Next the translator will look for a default message, which is provided by the core framework. #### Default AJV error message -If none of the above are set and no default error is available, the error message provided by AJV will be output. +If none of the above apply, the `message` provided by the AJV error object will be used. ### Example -If we have the following schema: +Consider the following schema for an object attribute: ```js { phone: { @@ -196,38 +195,28 @@ If we have the following schema: } } ``` -And the order in which the error keys are evaluated is the following: -- `phone.error.custom`: if this is set, the `pattern` and `minLength` error will be ignore and just this message is output +The order in which the error keys are evaluated is the following: +- `phone.error.custom`: if this is set, the `pattern` and `minLength` errors will be ignores and just this message is used - `phone.error.pattern` & `phone.error.minLength` - `error.pattern` & `error.minLength` ## Enum translation -Enum translations are a little different then the other control translations. -In order to translate enum values, you need to add one additional attribute to the translation for every value. +Enum translations are based on the respective entries: + - For JSON Schema `enum`, the stringified value is used. + - For JSON Schema `oneOf` enums which consist of (`title`, `const`) pairs a specialized `i18n` key or `title` is used. + +Therefore, in order to translate enum values, an additional key is checked for each enum entry. For example: Let's assume we have a field `Gender`, which has three possible values: - `Male` - `Female` - `Other` -If we now want to translate these, we need to reference them in the translation like this: -```js -gender: { - label: 'Geschlecht', - description: 'Das Geschlecht auswählen', - Male: 'Männlich', - Female: 'Weiblich', - Other: 'Divers' -}, -``` - -So the `label` attribute translates the `label` of the field, just like with any other control field. The same with the `description` attribute. -All the other options translate the possible values of the enum. ## Access translation in custom renderer sets -If you want to access the translation function within a custom renderer set, you can use the JSONForms context for that: +If you want to directly access the i18n properties within a custom renderer, you can use the JSONForms context for that: ``` const ctx = useJsonForms(); From cbcf7b538a24778bcff971753f732d9845d66adf Mon Sep 17 00:00:00 2001 From: Florian Gareis Date: Tue, 12 Apr 2022 12:10:45 +0200 Subject: [PATCH 5/9] Apply more suggestions from code review --- content/docs/i18n.mdx | 105 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 93 insertions(+), 12 deletions(-) diff --git a/content/docs/i18n.mdx b/content/docs/i18n.mdx index 080007b8..bfad39a6 100644 --- a/content/docs/i18n.mdx +++ b/content/docs/i18n.mdx @@ -48,8 +48,13 @@ The translate function should be side effect free and should be stable (memoized ::: +The type of the translate is -The translate function has the following parameters: +``` +(key: string, defaultMessage: string | undefined) => string | undefined) +``` + +and there for has the following parameters: ### `key` @@ -89,7 +94,6 @@ Therefore the translation will be invoked with `myCustomName.label` & `myCustomN #### Default: Property path Is none of the above is set, the property path will be used as the key. -e.g. `name`. ``` { @@ -122,6 +126,7 @@ e.g. `name`. The paths in the above example would be: - `firstName.label` & `firstName.description` +- `address.label` & `address.description` - `address.street.label` & `address.street.description` - `comments.message.label` & `comments.message.description` (the path for arrays will not contain indices) @@ -142,20 +147,59 @@ JSON Forms will use `undefined` when the message could be skipped or another mor The values can contain additional context for the current translation. For example all error translations will have the AJV error object as part of the values object. +## `translateError` + +The `translateError` function is called whenever a single message is to be extracted from an AJV error object. + +The type of the `translateError` function is + +``` +(error: ErrorObject, translate: Translator, uischema?: UISchemaElement) => string +``` + +* The `error` is the AJV error object +* `translate` is the i18n `translate` function handed over to JSON Forms +* In cases where a UI Schema Element can be correlated to an `error` it will also be handed over +* The `translateError` function always returns a `string` + +Usually this method does not need to be customized as JSON Forms already provides a sensible default implementation. A reason to customize this method could be to integrate third party frameworks like `ajv-i18n`. + +For more information about how errors can be customized, see the following section. + ## Error Customizations -TODO: Description +For each control a list of errors are determined. +This section describes the default behavior of JSON forms to offer translation support for them. -#### `.error.custom` +### `.error.custom` -If `.error.custom` is set, just this single message will be used, no matter how many errors are present. +Before invoking the `translateError` function, JSON Forms will check whether a `.error.custom` translation exists. +If a `.error.custom` exists, just this single message will be used, no matter how many errors are present. This is useful if there are many JSON Schema validaton keywords defined for a single property, but a single cohesive message shall be displayed. ## `translateError` -The `translateError` is called for every AJV error. -`translateError` is an optional parameter to the i18n object. Usually it is not expected to customize this option. +The `translateError` function is called whenever a single message is to be extracted from an AJV error object. + +The type of the `translateError` function is + +``` +(error: ErrorObject, translate: Translator, uischema?: UISchemaElement) => string +``` + +* The `error` is the AJV error object +* `translate` is the i18n `translate` function handed over to JSON Forms +* In cases where a UI Schema Element can be correlated to an `error` it will also be handed over +* The `translateError` function always returns a `string` + +Usually this method does not need to be customized as JSON Forms already provides a sensible default implementation. A reason to customize this method could be to integrate third party frameworks like `ajv-i18n`. + +For more information about how errors can be customized, see the following section + +If no `.error.custom` message is returned by the `translate` function, `translateError` will be called for each AJV error and the results combined. + +The default implementation of `translateError` will invoke `translate` multiple times to determine the best message for the given error. Therefore it's usually not necessary to customize `translateError` itself. By default error it works like this: `` in the sections below refers to the key of the field (see `key` section above). @@ -177,7 +221,7 @@ This is useful to customize for example `required` or `pattern` messages for all #### error message -Next the translator will look for a default message, which is provided by the core framework. +At last the default `translateError` implementation will check whether the `message` of the error object has a specific translation. #### Default AJV error message @@ -208,10 +252,47 @@ Enum translations are based on the respective entries: Therefore, in order to translate enum values, an additional key is checked for each enum entry. For example: -Let's assume we have a field `Gender`, which has three possible values: -- `Male` -- `Female` -- `Other` +Let's assume we have an enum attribute `gender`, which looks like this: + +```js +{ + gender: { + type: "string", + enum: ["male", "female", "other"] + } +} +``` + +In this case the `translate` function will be invoked with the keys `gender.male`, `gender.female` and `gender.other` to translate these enum values. In case `gender` had an `i18n` property, it would be used instead, i.e. `.male` etc. + +Let's assume we have a `oneOf` enum attribute gender which looks like this: + +```js +{ + gender: { + oneOf: [ + { + title: "Male", + const: 0 + }, + { + title: "Female", + const: "f", + i18n: "fem" + }, + { + const: null + } + ] + } +} +``` + +Here the requested keys are: + +* `gender.Male` - property path + `title` of the `oneOf` entry +* `fem` - direct usage of the `i18n` property for the `oneOf` entry +* `null` - the `title` attribute is missing, therefore the `null` value is stringified to `'null'`. ## Access translation in custom renderer sets From a57605d273fc1a7af113db26c7f79d828147e568 Mon Sep 17 00:00:00 2001 From: Florian Gareis Date: Tue, 12 Apr 2022 12:40:12 +0200 Subject: [PATCH 6/9] Enhance i18n doc --- content/docs/i18n.mdx | 81 +++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 50 deletions(-) diff --git a/content/docs/i18n.mdx b/content/docs/i18n.mdx index bfad39a6..3f5f0d71 100644 --- a/content/docs/i18n.mdx +++ b/content/docs/i18n.mdx @@ -14,8 +14,8 @@ https://github.com/eclipsesource/jsonforms/issues/1826 ::: -The translate functionality of JSONForms is integrated into the core component. -In order to translate JSONForms, you need to set a translation function and provide it to the JSONForms component: +The translate functionality of JSON Forms is integrated into the core component. +In order to translate JSON Forms, you need to set a translation function and provide it to the JSON Forms component: ```ts const translator = (key, defaultMessage) => { console.log(`Key: ${key}, Default Message: ${defaultMessage}`); @@ -50,7 +50,7 @@ The translate function should be side effect free and should be stable (memoized The type of the translate is -``` +```ts (key: string, defaultMessage: string | undefined) => string | undefined) ``` @@ -93,29 +93,29 @@ Therefore the translation will be invoked with `myCustomName.label` & `myCustomN #### Default: Property path -Is none of the above is set, the property path will be used as the key. +If none of the above is set, the property path will be used as the key. -``` +```json { - properties: { - firstName: { - type: "string" + "properties": { + "firstName": { + "type": "string" }, - address: { - type: "object", - properties: { - street: { - type: "string" + "address": { + "type": "object", + "properties": { + "street": { + "type": "string" } } - } - comments: { - type: "array", - items: { - type: "object", - properties: { - message: { - type: "string" + }, + "comments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "message": { + "type": "string" }, } } @@ -132,7 +132,7 @@ The paths in the above example would be: ### `defaultMessage` -The default message is provided by JSONForms and can act as a fallback or could be translated. +The default message is provided by JSON Forms and can act as a fallback or could be translated. :::note @@ -144,7 +144,7 @@ JSON Forms will use `undefined` when the message could be skipped or another mor ### `values` -The values can contain additional context for the current translation. +`values` can contain additional context for the current translation. For example all error translations will have the AJV error object as part of the values object. ## `translateError` @@ -153,7 +153,7 @@ The `translateError` function is called whenever a single message is to be extra The type of the `translateError` function is -``` +```ts (error: ErrorObject, translate: Translator, uischema?: UISchemaElement) => string ``` @@ -162,41 +162,21 @@ The type of the `translateError` function is * In cases where a UI Schema Element can be correlated to an `error` it will also be handed over * The `translateError` function always returns a `string` -Usually this method does not need to be customized as JSON Forms already provides a sensible default implementation. A reason to customize this method could be to integrate third party frameworks like `ajv-i18n`. +Usually this method does not need to be customized as JSON Forms already provides a sensible default implementation. +A reason to customize this method could be to integrate third party frameworks like `ajv-i18n`. -For more information about how errors can be customized, see the following section. +For more information about how errors can be customized, see the following section: ## Error Customizations -For each control a list of errors are determined. +For each control a list of errors is determined. This section describes the default behavior of JSON forms to offer translation support for them. ### `.error.custom` Before invoking the `translateError` function, JSON Forms will check whether a `.error.custom` translation exists. -If a `.error.custom` exists, just this single message will be used, no matter how many errors are present. - This is useful if there are many JSON Schema validaton keywords defined for a single property, but a single cohesive message shall be displayed. -## `translateError` - -The `translateError` function is called whenever a single message is to be extracted from an AJV error object. - -The type of the `translateError` function is - -``` -(error: ErrorObject, translate: Translator, uischema?: UISchemaElement) => string -``` - -* The `error` is the AJV error object -* `translate` is the i18n `translate` function handed over to JSON Forms -* In cases where a UI Schema Element can be correlated to an `error` it will also be handed over -* The `translateError` function always returns a `string` - -Usually this method does not need to be customized as JSON Forms already provides a sensible default implementation. A reason to customize this method could be to integrate third party frameworks like `ajv-i18n`. - -For more information about how errors can be customized, see the following section - If no `.error.custom` message is returned by the `translate` function, `translateError` will be called for each AJV error and the results combined. The default implementation of `translateError` will invoke `translate` multiple times to determine the best message for the given error. Therefore it's usually not necessary to customize `translateError` itself. @@ -297,8 +277,9 @@ Here the requested keys are: ## Access translation in custom renderer sets -If you want to directly access the i18n properties within a custom renderer, you can use the JSONForms context for that: -``` +If you want to directly access the i18n properties within a custom renderer, you can use the JSON Forms context for that: + +```js const ctx = useJsonForms(); const locale = ctx.i18n.locale; From 2f886df8e49d4f3a5c935c3f62466ab9781e29cf Mon Sep 17 00:00:00 2001 From: Florian Gareis Date: Tue, 3 May 2022 08:10:07 +0000 Subject: [PATCH 7/9] Add missing useMemo() --- content/docs/i18n.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/content/docs/i18n.mdx b/content/docs/i18n.mdx index 3f5f0d71..e9abf5f6 100644 --- a/content/docs/i18n.mdx +++ b/content/docs/i18n.mdx @@ -23,9 +23,10 @@ const translator = (key, defaultMessage) => { }; const [locale, setLocale] = useState<'de'|'en'>('de'); +const translation = useMemo(() => translator(locale), [locale]); ``` From f3fea2f434eddd748d9f60f7a84e7b57899ad6d2 Mon Sep 17 00:00:00 2001 From: LukasBoll Date: Tue, 6 Sep 2022 10:44:33 +0200 Subject: [PATCH 8/9] add documentation for UI schema translation Signed-off-by: Lukas Boll lukas-bool@web.de --- content/docs/i18n.mdx | 74 ++++++++++++++++++++++++++++++++++--- src/components/docs/i18n.js | 38 ++++++++++++++++++- 2 files changed, 105 insertions(+), 7 deletions(-) diff --git a/content/docs/i18n.mdx b/content/docs/i18n.mdx index e9abf5f6..8b8463ca 100644 --- a/content/docs/i18n.mdx +++ b/content/docs/i18n.mdx @@ -4,7 +4,7 @@ title: i18n description: Translate jsonforms --- -import I18nExample from '../../src/components/docs/i18n'; +import { I18nExample, ValuesTable } from '../../src/components/docs/i18n'; :::note @@ -52,7 +52,7 @@ The translate function should be side effect free and should be stable (memoized The type of the translate is ```ts -(key: string, defaultMessage: string | undefined) => string | undefined) +(key: string, defaultMessage: string | undefined, context: any) => string | undefined) ``` and there for has the following parameters: @@ -143,10 +143,13 @@ JSON Forms will use `undefined` when the message could be skipped or another mor ::: -### `values` +### `context` -`values` can contain additional context for the current translation. -For example all error translations will have the AJV error object as part of the values object. +`context` can contain additional information for the current translation. The following parameters can be provided: + + + +Schema translation provide all properties, while UI schema translations only provide the `uischema`-property. ## `translateError` @@ -275,6 +278,67 @@ Here the requested keys are: * `fem` - direct usage of the `i18n` property for the `oneOf` entry * `null` - the `title` attribute is missing, therefore the `null` value is stringified to `'null'`. +## UI Schema Translation + +The UI schema has the elements `group`, `category` and `label`, which can also be translated. + +If a i18n-key is provided in a `group` or `category` element, `.label` will be used as key. +If no i18n key is provided, the value of the `label`-property is used as key. +In case nether a i18n-key nor a label is provided, `.label` will be used as default key. + +The `label` UI schema element will use `.text` as key, if provided. +If no i18n key is provided, the value of the `text`-property is used as key. + + +Let's assume we have the following UI schema: + +```js +const uischema = { + type: 'VerticalLayout', + elements: [ + { + type: 'Control', + scope: '#/properties/user', + options: { + detail: { + type: 'Group', + i18n: 'i18nUser', + } + } + }, + { + type: 'Control', + scope: '#/properties/address', + options: { + detail: { + type: 'Group', + label: 'labelAddress', + } + } + }, + { + type: 'Control', + scope: '#/properties/address' + }, + { + type: 'Label', + i18n: 'i18nLabel' + }, + { + type: 'Label', + text: 'textLabel' + }, + ] +}; +``` + +Here the requested keys are: + +* `i18nUser.label` - i18n + `label` +* `labelAddress` - direct usage of the `label` property +* `address.label` - property path + `label` +* `i18nLabel.text` - i18n + `text` +* `textLabel` - direct usage of the `text` property ## Access translation in custom renderer sets diff --git a/src/components/docs/i18n.js b/src/components/docs/i18n.js index 4b3fd4cd..07ca5d0a 100644 --- a/src/components/docs/i18n.js +++ b/src/components/docs/i18n.js @@ -6,6 +6,11 @@ import { } from '@jsonforms/material-renderers'; import Button from '@mui/material/Button'; import { JsonForms } from '@jsonforms/react'; +import TableCell from '@mui/material/TableCell/TableCell'; +import TableHead from '@mui/material/TableHead/TableHead'; +import Table from '@mui/material/Table/Table'; +import TableBody from '@mui/material/TableBody/TableBody'; +import TableRow from '@mui/material/TableRow/TableRow'; const i18n = { schema: { @@ -104,7 +109,7 @@ export const de = { }, }; -const I18nExample = () => { +export const I18nExample = () => { const [locale, setLocale] = useState('de'); const createTranslator = (locale) => (key, defaultMessage) => { @@ -139,4 +144,33 @@ const I18nExample = () => { ); }; -export default I18nExample; +export const ValuesTable = () => ( +
+ + + + Parameter + Description + + + + + errors + Array of AJV errors, that occurred during validation + + + path + The path of the translated element + + + schema + The schema of the translated element + + + uischema + The uischema of the translated element + + +
+
+); \ No newline at end of file From f5819eb9a3cd47d6debe18db30fe2b7e1c35fb05 Mon Sep 17 00:00:00 2001 From: LukasBoll Date: Mon, 3 Oct 2022 15:37:38 +0200 Subject: [PATCH 9/9] review changes --- content/docs/i18n.mdx | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/content/docs/i18n.mdx b/content/docs/i18n.mdx index 8b8463ca..2308e644 100644 --- a/content/docs/i18n.mdx +++ b/content/docs/i18n.mdx @@ -31,17 +31,17 @@ const translation = useMemo(() => translator(locale), [locale]); /> ``` -The i18n prop consist of three components: `locale`, `translate` and `translateError`. +The `i18n` prop consists of three components: `locale`, `translate` and `translateError`. ## `locale` -Allows to specify the current locale of the application. +Specifies the current locale of the application. This can be used by renderers to render locale specific UI elements, for example for locale-aware formatting of numbers. ## `translate` -Allows to provide a translation function, which handles the actual translation. +Provides a translation function handling the actual translation. :::caution @@ -55,11 +55,11 @@ The type of the translate is (key: string, defaultMessage: string | undefined, context: any) => string | undefined) ``` -and there for has the following parameters: +with the following parameters: ### `key` -The key is used to identify the string, that needs to be translated. +The key is used to identify the string that needs to be translated. It can be set using the `UI Schema`, the `JSON Schema` or is generated by default based on the property path. #### UI Schema option @@ -70,13 +70,11 @@ The key can be set via the `i18n` UI Schema option: "type": "Control", "label": "name", "scope": "#/properties/name", - "options": { - "i18n": "customName" - } + "i18n": "customName" } ``` -Therefore the translation will be invoked with `customName.label` & `customName.description`. +Therefore, the translation will be invoked with `customName.label` & `customName.description`. #### JSON Schema @@ -90,7 +88,7 @@ The key can also be set with the custom JSON Schema `i18n` option: } ``` -Therefore the translation will be invoked with `myCustomName.label` & `myCustomName.description`. +Therefore, the translation will be invoked with `myCustomName.label` & `myCustomName.description`. #### Default: Property path @@ -125,7 +123,7 @@ If none of the above is set, the property path will be used as the key. } ``` -The paths in the above example would be: +The paths in the above example are: - `firstName.label` & `firstName.description` - `address.label` & `address.description` - `address.street.label` & `address.street.description` @@ -137,7 +135,7 @@ The default message is provided by JSON Forms and can act as a fallback or could :::note -If the `defaultMessage` is `undefined` you should also return `undefined` if you don't have a translation for the given key. +If the `defaultMessage` is `undefined`, you should also return `undefined` if there is no translation for the given key. Returning an empty string (or something similar) instead may result in undesired behavior. JSON Forms will use `undefined` when the message could be skipped or another more generic key could be tried. @@ -149,7 +147,7 @@ JSON Forms will use `undefined` when the message could be skipped or another mor -Schema translation provide all properties, while UI schema translations only provide the `uischema`-property. +Schema translations provide all properties, while UI schema translations only provide the `uischema` property. ## `translateError` @@ -284,7 +282,7 @@ The UI schema has the elements `group`, `category` and `label`, which can also b If a i18n-key is provided in a `group` or `category` element, `.label` will be used as key. If no i18n key is provided, the value of the `label`-property is used as key. -In case nether a i18n-key nor a label is provided, `.label` will be used as default key. +In case neither a i18n-key nor a label is provided, `.label` will be used as default key. The `label` UI schema element will use `.text` as key, if provided. If no i18n key is provided, the value of the `text`-property is used as key.