-
Notifications
You must be signed in to change notification settings - Fork 24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add initial i18n documentation #222
Changes from 8 commits
31e77ed
113793b
331dffe
c3ef2bb
cbcf7b5
a57605d
2f886df
f3fea2f
f5819eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,359 @@ | ||||||
--- | ||||||
id: i18n | ||||||
title: i18n | ||||||
description: Translate jsonforms | ||||||
--- | ||||||
|
||||||
import { I18nExample, ValuesTable } from '../../src/components/docs/i18n'; | ||||||
|
||||||
:::note | ||||||
|
||||||
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 | ||||||
|
||||||
::: | ||||||
|
||||||
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}`); | ||||||
return defaultMessage; | ||||||
}; | ||||||
|
||||||
const [locale, setLocale] = useState<'de'|'en'>('de'); | ||||||
const translation = useMemo(() => translator(locale), [locale]); | ||||||
|
||||||
<JsonForms | ||||||
i18n={{locale: locale, translate: translation}} | ||||||
... | ||||||
/> | ||||||
``` | ||||||
|
||||||
The i18n prop consist of three components: `locale`, `translate` and `translateError`. | ||||||
|
||||||
## `locale` | ||||||
|
||||||
Allows to specify the current locale of the application. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
:::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. | ||||||
|
||||||
::: | ||||||
|
||||||
TheZoker marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
The type of the translate is | ||||||
|
||||||
```ts | ||||||
(key: string, defaultMessage: string | undefined, context: any) => string | undefined) | ||||||
``` | ||||||
|
||||||
and there for has the following parameters: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
### `key` | ||||||
|
||||||
The key is used to identify the string, that needs to be translated. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
It can be set using the `UI Schema`, the `JSON Schema` or is generated by default 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" | ||||||
} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was changed. |
||||||
} | ||||||
``` | ||||||
|
||||||
Therefore the translation will be invoked with `customName.label` & `customName.description`. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
#### JSON Schema | ||||||
|
||||||
The key can also be set with the custom JSON Schema `i18n` option: | ||||||
```json | ||||||
{ | ||||||
"name": { | ||||||
"type": "string", | ||||||
"i18n": "myCustomName" | ||||||
} | ||||||
} | ||||||
``` | ||||||
|
||||||
Therefore the translation will be invoked with `myCustomName.label` & `myCustomName.description`. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
#### Default: Property path | ||||||
|
||||||
If none of the above is set, the property path will be used as the key. | ||||||
|
||||||
```json | ||||||
{ | ||||||
"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: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
- `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) | ||||||
|
||||||
### `defaultMessage` | ||||||
|
||||||
The default message is provided by JSON Forms and can act as a fallback or could be translated. | ||||||
|
||||||
:::note | ||||||
|
||||||
If the `defaultMessage` is `undefined` you should also return `undefined` if you don't have a translation for the given key. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
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. | ||||||
|
||||||
::: | ||||||
|
||||||
### `context` | ||||||
|
||||||
`context` can contain additional information for the current translation. The following parameters can be provided: | ||||||
|
||||||
<ValuesTable/> | ||||||
|
||||||
Schema translation provide all properties, while UI schema translations only provide the `uischema`-property. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
## `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 | ||||||
|
||||||
```ts | ||||||
(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: | ||||||
|
||||||
TheZoker marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
## Error Customizations | ||||||
|
||||||
For each control a list of errors is determined. | ||||||
This section describes the default behavior of JSON forms to offer translation support for them. | ||||||
|
||||||
### `<i18nkey>.error.custom` | ||||||
|
||||||
TheZoker marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
Before invoking the `translateError` function, JSON Forms will check whether a `<i18nkey>.error.custom` translation exists. | ||||||
This is useful if there are many JSON Schema validaton keywords defined for a single property, but a single cohesive message shall be displayed. | ||||||
|
||||||
If no `<i18nkey>.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: | ||||||
|
||||||
`<i18nkey>` in the sections below refers to the key of the field (see `key` section above). | ||||||
|
||||||
### Evaluation order | ||||||
|
||||||
#### `<i18nkey>.error.<keyword>` | ||||||
|
||||||
Example keys: `name.error.required`, `address.street.error.pattern` | ||||||
|
||||||
The default `translateError` will first look for a concrete message for a specific field and a specific error type. | ||||||
|
||||||
#### `error.<keyword>` | ||||||
|
||||||
Example keys: `error.required`, `error.pattern` | ||||||
|
||||||
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. | ||||||
|
||||||
#### error message | ||||||
|
||||||
At last the default `translateError` implementation will check whether the `message` of the error object has a specific translation. | ||||||
|
||||||
#### Default AJV error message | ||||||
|
||||||
If none of the above apply, the `message` provided by the AJV error object will be used. | ||||||
|
||||||
### Example | ||||||
|
||||||
Consider the following schema for an object attribute: | ||||||
```js | ||||||
{ | ||||||
phone: { | ||||||
type: "string", | ||||||
minLength: 10, | ||||||
pattern: "^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$" | ||||||
} | ||||||
} | ||||||
``` | ||||||
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 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 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. `<i18nkey>.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'`. | ||||||
|
||||||
## 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, `<i18n>.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, `<property-path>.label` will be used as default key. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
The `label` UI schema element will use `<i18n>.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 | ||||||
|
||||||
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; | ||||||
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 | ||||||
|
||||||
<I18nExample /> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
h3 { | ||
.comparison_container h3 { | ||
font-size: 2rem; | ||
padding: 0; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.