diff --git a/.babelrc b/.babelrc
new file mode 100644
index 00000000..0e6d835a
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,11 @@
+{
+ "plugins": [
+ [
+ "formatjs",
+ {
+ "idInterpolationPattern": "[sha512:contenthash:base64:6]",
+ "ast": true
+ }
+ ]
+ ]
+}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d3b007bf..f5ee0581 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@
- Add filter schema to registry to create dynamic filters in items tab
- Render text value instead of key value in vocabulary select
- Create scale images in IImageAttachment behavior
+ - Add i18n ( english, catalan, spanish)
0.23.1
diff --git a/package.json b/package.json
index d555b919..bd3559ea 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"jwt-decode": "3.1.2",
"prop-types": "15.7.2",
"react-beautiful-dnd": "13.1.1",
+ "react-intl": "6.5.5",
"react-useportal": "1.0.19",
"uuid": "9.0.1"
},
@@ -29,9 +30,11 @@
"devDependencies": {
"@babel/cli": "7.12.10",
"@babel/core": "7.12.10",
+ "@formatjs/cli": "^6.2.4",
"@testing-library/jest-dom": "5.11.6",
"@testing-library/react": "11.2.2",
"@testing-library/user-event": "12.6.0",
+ "babel-plugin-formatjs": "^10.5.10",
"husky": "4.3.6",
"microbundle": "0.13.0",
"prettier": "2.2.1",
@@ -47,7 +50,13 @@
"build:js": "rm -rf ./dist && microbundle --jsx React.createElement --no-compress --sourcemap",
"build:css": "rm -rf ./dist/css && mkdir ./dist/css && sass ./src/guillo-gmi/scss/styles.sass ./dist/css/style.css",
"prepublish": "yarn build",
- "test": "vitest run"
+ "test": "vitest run",
+ "intl-extract": "formatjs extract 'src/**/*.js' --out-file src/guillo-gmi/locales/en.json --id-interpolation-pattern '[sha512:contenthash:base64:6]'",
+ "intl-compile-en": "formatjs compile src/guillo-gmi/locales/en.json --ast --out-file src/guillo-gmi/locales/compiled/en.json",
+ "intl-compile-ca": "formatjs compile src/guillo-gmi/locales/ca.json --ast --out-file src/guillo-gmi/locales/compiled/ca.json",
+ "intl-compile-es": "formatjs compile src/guillo-gmi/locales/es.json --ast --out-file src/guillo-gmi/locales/compiled/es.json",
+ "intl-compile": "npm run intl-compile-en && npm run intl-compile-es && npm run intl-compile-ca"
+
},
"eslintConfig": {
"extends": "react-app"
diff --git a/src/guillo-gmi/components/behavior_view.js b/src/guillo-gmi/components/behavior_view.js
index 0dbe18b5..8a673d49 100644
--- a/src/guillo-gmi/components/behavior_view.js
+++ b/src/guillo-gmi/components/behavior_view.js
@@ -1,6 +1,8 @@
import React from 'react'
import { useTraversal } from '../contexts'
import { get } from '../lib/utils'
+import { useIntl } from 'react-intl'
+import { genericMessages } from '../locales/generic_messages'
export function BehaviorsView({ context, schema }) {
const Ctx = useTraversal()
@@ -34,9 +36,10 @@ export function BehaviorsView({ context, schema }) {
}
export function BehaviorNotImplemented() {
+ const intl = useIntl()
return (
- Image |
+ {intl.formatMessage(genericMessages.image)} |
-
+
diff --git a/src/guillo-gmi/components/behaviors/imultiattachment.js b/src/guillo-gmi/components/behaviors/imultiattachment.js
index b50cbcdd..2fa9863b 100644
--- a/src/guillo-gmi/components/behaviors/imultiattachment.js
+++ b/src/guillo-gmi/components/behaviors/imultiattachment.js
@@ -9,8 +9,14 @@ import { EditableField } from '../fields/editableField'
import { Delete } from '../ui'
import { Confirm } from '../../components/modal'
import { Table } from '../ui'
+import { useIntl } from 'react-intl'
+import {
+ genericFileMessages,
+ genericMessages,
+} from '../../locales/generic_messages'
export function IMultiAttachment({ properties, values }) {
+ const intl = useIntl()
const [fileKey, setFileKey] = useState('')
const [file, setFile] = useState()
const [fileKeyToDelete, setFileKeyToDelete] = useState(undefined)
@@ -23,7 +29,7 @@ export function IMultiAttachment({ properties, values }) {
const uploadFile = async (ev) => {
ev.preventDefault()
if (!fileKey && !file) {
- setError('Provide a file and a key name')
+ setError(intl.formatMessage(genericFileMessages.error_file_key_name))
return
}
setLoading(true)
@@ -31,14 +37,14 @@ export function IMultiAttachment({ properties, values }) {
const endpoint = `${Ctx.path}@upload/files/${fileKey}`
const req = await Ctx.client.upload(endpoint, file)
if (req.status !== 200) {
- setError('Failed to upload file')
+ setError(intl.formatMessage(genericFileMessages.error_upload_file))
setLoading(false)
return
}
setFileKey('')
setFile(undefined)
setLoading(false)
- Ctx.flash(`${fileKey} uploaded!`, 'success')
+ Ctx.flash(intl.formatMessage(genericFileMessages.file_uploaded), 'success')
Ctx.refresh()
}
@@ -48,12 +54,12 @@ export function IMultiAttachment({ properties, values }) {
const endpoint = `${Ctx.path}@delete/files/${fileKeyToDelete}`
const req = await Ctx.client.delete(endpoint, file)
if (req.status !== 200) {
- setError('Failed to delete file')
+ setError(intl.formatMessage(genericFileMessages.failed_delete_file))
setLoading(false)
return
}
setLoading(false)
- Ctx.flash(`${fileKeyToDelete} delete!`, 'success')
+ setError(intl.formatMessage(genericFileMessages.failed_delete_file))
Ctx.refresh()
}
@@ -67,7 +73,12 @@ export function IMultiAttachment({ properties, values }) {
loading={loading}
onCancel={() => setFileKeyToDelete(undefined)}
onConfirm={() => deleteFile(fileKeyToDelete)}
- message={`Are you sure to remove: ${fileKeyToDelete}?`}
+ message={
+ (intl.formatMessage(
+ genericFileMessages.confirm_message_delete_file
+ ),
+ { fileKeyToDelete })
+ }
/>
)}
@@ -97,13 +108,17 @@ export function IMultiAttachment({ properties, values }) {
))}
{Object.keys(values['files']).length === 0 && (
|
- No files uploaded |
+
+ {intl.formatMessage(genericFileMessages.no_files_uploaded)}
+ |
)}
{modifyContent && (
-
+
diff --git a/src/guillo-gmi/components/behaviors/imultiimageattachment.js b/src/guillo-gmi/components/behaviors/imultiimageattachment.js
index 06145283..2c466b8f 100644
--- a/src/guillo-gmi/components/behaviors/imultiimageattachment.js
+++ b/src/guillo-gmi/components/behaviors/imultiimageattachment.js
@@ -9,10 +9,16 @@ import { EditableField } from '../fields/editableField'
import { useConfig } from '../../hooks/useConfig'
import { Table } from '../ui'
import { Input } from '../input/input'
+import { useIntl } from 'react-intl'
+import {
+ genericFileMessages,
+ genericMessages,
+} from '../../locales/generic_messages'
const _sizesImages = ['large', 'preview', 'mini', 'thumb']
export function IMultiImageAttachment({ properties, values }) {
+ const intl = useIntl()
const cfg = useConfig()
const [fileKey, setFileKey] = useState('')
const [file, setFile] = useState(null)
@@ -27,7 +33,7 @@ export function IMultiImageAttachment({ properties, values }) {
const uploadFile = async (ev) => {
ev.preventDefault()
if (!fileKey && !file) {
- setError('Provide a file and a key name')
+ setError(intl.formatMessage(genericFileMessages.error_file_key_name))
return
}
setLoading(true)
@@ -35,7 +41,7 @@ export function IMultiImageAttachment({ properties, values }) {
const endpoint = `${Ctx.path}@upload/images/${fileKey}`
const req = await Ctx.client.upload(endpoint, file)
if (req.status !== 200) {
- setError('Failed to upload file')
+ setError(intl.formatMessage(genericFileMessages.error_upload_file))
setLoading(false)
return
}
@@ -51,7 +57,11 @@ export function IMultiImageAttachment({ properties, values }) {
}
if (hasError) {
- setError(`Failed to upload file ${endpointSize}`)
+ setError(
+ intl.formatMessage(genericFileMessages.error_upload_file_size, {
+ size: sizesImages[i],
+ })
+ )
setLoading(false)
return
}
@@ -60,7 +70,7 @@ export function IMultiImageAttachment({ properties, values }) {
setFileKey('')
setFile(undefined)
setLoading(false)
- Ctx.flash(`${fileKey} uploaded!`, 'success')
+ Ctx.flash(intl.formatMessage(genericFileMessages.image_uploaded), 'success')
Ctx.refresh()
}
@@ -70,12 +80,12 @@ export function IMultiImageAttachment({ properties, values }) {
const endpoint = `${Ctx.path}@delete/images/${fileKeyToDelete}`
const req = await Ctx.client.delete(endpoint, file)
if (req.status !== 200) {
- setError('Failed to delete file')
+ setError(intl.formatMessage(genericFileMessages.failed_delete_file))
setLoading(false)
return
}
setLoading(false)
- Ctx.flash(`${fileKeyToDelete} delete!`, 'success')
+ Ctx.flash(intl.formatMessage(genericFileMessages.image_deleted), 'success')
Ctx.refresh()
}
@@ -89,7 +99,12 @@ export function IMultiImageAttachment({ properties, values }) {
loading={loading}
onCancel={() => setFileKeyToDelete(undefined)}
onConfirm={() => deleteFile()}
- message={`Are you sure to remove: ${fileKeyToDelete}?`}
+ message={
+ (intl.formatMessage(
+ genericFileMessages.confirm_message_delete_file
+ ),
+ { fileKeyToDelete })
+ }
/>
)}
@@ -120,13 +135,18 @@ export function IMultiImageAttachment({ properties, values }) {
))}
{Object.keys(values['images']).length === 0 && (
|
- No images uploaded |
+
+ {intl.formatMessage(genericFileMessages.no_images_uploaded)}
+ |
)}
{modifyContent && (
-
+
diff --git a/src/guillo-gmi/components/behaviors/imultiimageorderedattachment.js b/src/guillo-gmi/components/behaviors/imultiimageorderedattachment.js
index 33620e01..d4100c5f 100644
--- a/src/guillo-gmi/components/behaviors/imultiimageorderedattachment.js
+++ b/src/guillo-gmi/components/behaviors/imultiimageorderedattachment.js
@@ -8,6 +8,11 @@ import { useConfig } from '../../hooks/useConfig'
import React, { useEffect, useState } from 'react'
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd'
import { v4 as uuidv4 } from 'uuid'
+import { defineMessages, useIntl } from 'react-intl'
+import {
+ genericFileMessages,
+ genericMessages,
+} from '../../locales/generic_messages'
const StrictModeDroppable = ({ children, ...props }) => {
const [enabled, setEnabled] = useState(false)
@@ -33,8 +38,18 @@ const reorder = (list, startIndex, endIndex) => {
}
const _sizesImages = ['large', 'preview', 'mini', 'thumb']
-
+const messages = defineMessages({
+ failed_to_sort_images: {
+ id: 'failed_to_sort_images',
+ defaultMessage: 'Failed to sort images',
+ },
+ images_sorted: {
+ id: 'images_sorted',
+ defaultMessage: 'Images sorted',
+ },
+})
export function IMultiImageOrderedAttachment({ properties, values }) {
+ const intl = useIntl()
const cfg = useConfig()
const [sortedList, setSortedList] = useState(Object.keys(values['images']))
const [file, setFile] = useState(null)
@@ -67,20 +82,20 @@ export function IMultiImageOrderedAttachment({ properties, values }) {
const endpoint = `${Ctx.path}@sort/images/`
const req = await Ctx.client.patch(endpoint, resultSorted)
if (req.status !== 200) {
- setError('Failed to sorted images')
+ setError(intl.formatMessage(messages.failed_to_sort_images))
setLoading(false)
return
}
setLoading(false)
- Ctx.flash(`Images sorted`, 'success')
+ Ctx.flash(intl.formatMessage(messages.images_sorted), 'success')
Ctx.refresh()
}
const uploadFile = async (ev) => {
ev.preventDefault()
if (!file) {
- setError('Provide a file and a key name')
+ setError(intl.formatMessage(genericFileMessages.error_file_key_name))
return
}
setLoading(true)
@@ -89,7 +104,7 @@ export function IMultiImageOrderedAttachment({ properties, values }) {
const endpoint = `${Ctx.path}@upload/images/${fileKey}`
const req = await Ctx.client.upload(endpoint, file)
if (req.status !== 200) {
- setError('Failed to upload file')
+ setError(intl.formatMessage(genericFileMessages.error_upload_file))
setLoading(false)
return
}
@@ -105,7 +120,11 @@ export function IMultiImageOrderedAttachment({ properties, values }) {
}
if (hasError) {
- setError(`Failed to upload file ${endpointSize}`)
+ setError(
+ intl.formatMessage(genericFileMessages.error_upload_file_size, {
+ size: sizesImages[i],
+ })
+ )
setLoading(false)
return
}
@@ -113,7 +132,7 @@ export function IMultiImageOrderedAttachment({ properties, values }) {
setFile(undefined)
setLoading(false)
- Ctx.flash(`Image uploaded!`, 'success')
+ Ctx.flash(intl.formatMessage(genericFileMessages.image_uploaded), 'success')
Ctx.refresh()
}
@@ -123,12 +142,12 @@ export function IMultiImageOrderedAttachment({ properties, values }) {
const endpoint = `${Ctx.path}@delete/images/${fileKeyToDelete}`
const req = await Ctx.client.delete(endpoint, file)
if (req.status !== 200) {
- setError('Failed to delete file')
+ setError(intl.formatMessage(genericFileMessages.failed_delete_file))
setLoading(false)
return
}
setLoading(false)
- Ctx.flash(`Image deleted!`, 'success')
+ Ctx.flash(intl.formatMessage(genericFileMessages.image_deleted), 'success')
Ctx.refresh()
}
@@ -139,7 +158,12 @@ export function IMultiImageOrderedAttachment({ properties, values }) {
loading={loading}
onCancel={() => setFileKeyToDelete(undefined)}
onConfirm={() => deleteFile()}
- message={`Are you sure to remove the image?`}
+ message={
+ (intl.formatMessage(
+ genericFileMessages.confirm_message_delete_file
+ ),
+ { fileKeyToDelete })
+ }
/>
)}
@@ -189,11 +213,15 @@ export function IMultiImageOrderedAttachment({ properties, values }) {
{Object.keys(values['images']).length === 0 && (
-
+
+ {intl.formatMessage(genericFileMessages.no_images_uploaded)}
+
)}
{modifyContent && (
-
+
diff --git a/src/guillo-gmi/components/behaviors/iworkflow.js b/src/guillo-gmi/components/behaviors/iworkflow.js
index c89de017..aab7c61a 100644
--- a/src/guillo-gmi/components/behaviors/iworkflow.js
+++ b/src/guillo-gmi/components/behaviors/iworkflow.js
@@ -3,8 +3,33 @@ import { useTraversal } from '../../contexts'
import { Confirm } from '../modal'
import { useCrudContext } from '../../hooks/useCrudContext'
import { ItemModel } from '../../models'
+import { defineMessages, useIntl } from 'react-intl'
+
+const messages = defineMessages({
+ status_changed_ok: {
+ id: 'status_changed_ok',
+ defaultMessage: 'Great status changed!',
+ },
+ status_changed_error: {
+ id: 'status_changed_error',
+ defaultMessage: 'Failed to status changed!: {error}',
+ },
+ confirm_message: {
+ id: 'confirm_message',
+ defaultMessage: 'Are you sure to change state: {title}?',
+ },
+ current_state: {
+ id: 'current_state',
+ defaultMessage: 'Current state: {state}',
+ },
+ actions: {
+ id: 'actions',
+ defaultMessage: 'Actions:',
+ },
+})
export function IWorkflow() {
+ const intl = useIntl()
const Ctx = useTraversal()
const { post, loading } = useCrudContext()
const modifyContent = Ctx.hasPerm('guillotina.ModifyContent')
@@ -34,9 +59,14 @@ export function IWorkflow() {
)
await loadDefinition()
if (!isError) {
- Ctx.flash(`Great status changed!`, 'success')
+ Ctx.flash(intl.formatMessage(messages.status_changed_ok), 'success')
} else {
- Ctx.flash(`Failed to status changed!: ${errorMessage}`, 'danger')
+ Ctx.flash(
+ intl.formatMessage(messages.status_changed_error, {
+ error: errorMessage,
+ }),
+ 'danger'
+ )
}
Ctx.refresh()
@@ -51,9 +81,9 @@ export function IWorkflow() {
loading={loading}
onCancel={() => setWorkflowAction(null)}
onConfirm={doWorkflowAction}
- message={`Are you sure to change state: ${
- Ctx.context.title || Ctx.context['@name']
- }?`}
+ message={intl.formatMessage(messages.confirm_message, {
+ title: Ctx.context.title || Ctx.context['@name'],
+ })}
/>
)}
@@ -62,7 +92,7 @@ export function IWorkflow() {
className="has-text-weight-bold"
data-test={`textInfoStatus-${currentState}`}
>
- Current state: {currentState}
+ {intl.formatMessage(messages.current_state, { state: currentState })}
{modifyContent && (
@@ -70,7 +100,7 @@ export function IWorkflow() {
className=" is-flex is-align-items-center has-text-weight-bold"
data-test={`textInfoStatus-${currentState}`}
>
-
+
{definition.transitions.map((transition) => {
return (
)
}
@@ -63,6 +66,7 @@ export function CreateButton() {
}
export function ContextToolbar({ AddButton, ...props }) {
+ const intl = useIntl()
const [state, setState] = useSetState(initialState)
const [location, setLocation, del] = useLocation()
const traversal = useTraversal()
@@ -110,7 +114,7 @@ export function ContextToolbar({ AddButton, ...props }) {
onChange={(ev) => setSearchValue(ev.target.value)}
type="text"
className="input is-size-7"
- placeholder="Search..."
+ placeholder={intl.formatMessage(genericMessages.search)}
data-test="inputFilterTest"
/>
diff --git a/src/guillo-gmi/components/error_boundary.js b/src/guillo-gmi/components/error_boundary.js
index 543a82c3..dcfffe1f 100644
--- a/src/guillo-gmi/components/error_boundary.js
+++ b/src/guillo-gmi/components/error_boundary.js
@@ -1,8 +1,9 @@
import React from 'react'
+import { injectIntl } from 'react-intl'
const style = { color: '#F44336', fontSize: 20, paddingBottom: 20 }
-export default class ErrorBoundary extends React.Component {
+class ErrorBoundaryComponent extends React.Component {
state = {}
componentDidCatch(error, errorInfo) {
@@ -19,7 +20,12 @@ export default class ErrorBoundary extends React.Component {
if (hasError) {
return (
- Something went wrong.
+
+ {this.props.intl.formatMessage({
+ id: 'something_went_wrong',
+ defaultMessage: 'Something went wrong.',
+ })}
+
{errorMsg && (
{errorMsg}
@@ -33,3 +39,5 @@ export default class ErrorBoundary extends React.Component {
return this.props.children
}
}
+
+export default injectIntl(ErrorBoundaryComponent)
diff --git a/src/guillo-gmi/components/fields/downloadField.js b/src/guillo-gmi/components/fields/downloadField.js
index 25306374..faf44f89 100644
--- a/src/guillo-gmi/components/fields/downloadField.js
+++ b/src/guillo-gmi/components/fields/downloadField.js
@@ -1,7 +1,10 @@
import * as React from 'react'
import { useTraversal } from '../../contexts'
+import { useIntl } from 'react-intl'
+import { genericMessages } from '../../locales/generic_messages'
export const DownloadField = ({ value }) => {
+ const intl = useIntl()
const Ctx = useTraversal()
const { data, field } = value
@@ -45,7 +48,7 @@ export const DownloadField = ({ value }) => {
getField(false)
}}
>
- Open
+ {intl.formatMessage(genericMessages.open)}
@@ -57,7 +60,7 @@ export const DownloadField = ({ value }) => {
getField(true)
}}
>
- Download
+ {intl.formatMessage(genericMessages.download)}
diff --git a/src/guillo-gmi/components/fields/editableField.js b/src/guillo-gmi/components/fields/editableField.js
index ac4e2375..5e63b7bb 100644
--- a/src/guillo-gmi/components/fields/editableField.js
+++ b/src/guillo-gmi/components/fields/editableField.js
@@ -7,6 +7,11 @@ import { useRef } from 'react'
import { useEffect } from 'react'
import { Icon } from '../ui'
import { get } from '../../lib/utils'
+import { useIntl } from 'react-intl'
+import {
+ genericFileMessages,
+ genericMessages,
+} from '../../locales/generic_messages'
export function EditableField({
field,
@@ -16,6 +21,7 @@ export function EditableField({
modifyContent,
required,
}) {
+ const intl = useIntl()
const ref = useRef()
const [isEdit, setEdit] = useState(false)
const [val, setValue] = useState(value)
@@ -40,12 +46,18 @@ export function EditableField({
if (ev) ev.preventDefault()
if (!field) {
- Ctx.flash(`Provide a key name!`, 'danger')
+ Ctx.flash(
+ intl.formatMessage(genericMessages.error_provide_key_name),
+ 'danger'
+ )
return
}
if (!val && required) {
- Ctx.flash(`${field} is mandatory!`, 'danger')
+ Ctx.flash(
+ intl.formatMessage(genericMessages.mandatory_field, { field }),
+ 'danger'
+ )
return
}
@@ -57,17 +69,29 @@ export function EditableField({
const endpoint = `${Ctx.path}@upload/${field}`
const req = await Ctx.client.upload(endpoint, value)
if (req.status !== 200) {
- Ctx.flash(`Failed to upload file ${field}!`, 'danger')
+ Ctx.flash(
+ intl.formatMessage(genericFileMessages.error_upload_file),
+ 'danger'
+ )
} else {
- Ctx.flash(`${field} uploaded!`, 'success')
+ Ctx.flash(
+ intl.formatMessage(genericFileMessages.file_uploaded),
+ 'success'
+ )
}
} else {
const data = ns ? { [ns]: { [field]: val } } : { [field]: val }
const dataPatch = await patch(data)
if (dataPatch.isError) {
- Ctx.flash(`Error in field ${field}!`, 'danger')
+ Ctx.flash(
+ intl.formatMessage(genericMessages.error_in_field, { field }),
+ 'danger'
+ )
} else {
- Ctx.flash(`Field ${field}, updated!`, 'success')
+ Ctx.flash(
+ intl.formatMessage(genericMessages.field_updated, { field }),
+ 'success'
+ )
}
}
@@ -79,16 +103,25 @@ export function EditableField({
if (ev) ev.preventDefault()
if (schema?.widget === 'file') {
if (!field || (!val && required)) {
- Ctx.flash(`You can't delete ${field}!`, 'danger')
+ Ctx.flash(
+ intl.formatMessage(genericMessages.can_not_delete_field, { field }),
+ 'danger'
+ )
return
}
const data = ns ? { [ns]: { [field]: null } } : { [field]: null }
const dataPatch = await patch(data)
if (dataPatch.isError) {
- Ctx.flash(`Error in field ${field}!`, 'danger')
+ Ctx.flash(
+ intl.formatMessage(genericMessages.error_in_field, { field }),
+ 'danger'
+ )
} else {
- Ctx.flash(`Field ${field}, deleted!`, 'success')
+ Ctx.flash(
+ intl.formatMessage(genericMessages.field_deleted, { field }),
+ 'success'
+ )
}
setEdit(false)
@@ -139,7 +172,7 @@ export function EditableField({
onClick={saveField}
dataTest="editableFieldBtnSaveTest"
>
- Save
+ {intl.formatMessage(genericMessages.save)}
@@ -148,7 +181,7 @@ export function EditableField({
onClick={() => setEdit(false)}
dataTest="editableFieldBtnCancelTest"
>
- Cancel
+ {intl.formatMessage(genericMessages.cancel)}
{!required && fieldHaveDeleteButton(schema) && (
@@ -158,7 +191,7 @@ export function EditableField({
onClick={deleteField}
dataTest="editableFieldBtnDeleteTest"
>
- Delete
+ {intl.formatMessage(genericMessages.delete)}
)}
diff --git a/src/guillo-gmi/components/fields/renderField.js b/src/guillo-gmi/components/fields/renderField.js
index 8afab0b5..75173889 100644
--- a/src/guillo-gmi/components/fields/renderField.js
+++ b/src/guillo-gmi/components/fields/renderField.js
@@ -2,6 +2,7 @@ import React from 'react'
import { DownloadField } from './downloadField'
import { useVocabulary } from '../../hooks/useVocabulary'
import { get } from '../../lib/utils'
+import { useIntl } from 'react-intl'
const plain = ['string', 'number', 'boolean']
export function RenderField({ value, Widget }) {
@@ -39,10 +40,14 @@ const FieldValue = ({ field, value }) => (
)
-export const DEFAULT_VALUE_EDITABLE_FIELD = 'Click to edit'
-export const DEFAULT_VALUE_NO_EDITABLE_FIELD = ' -- '
-
export function RenderFieldComponent({ schema, field, val, modifyContent }) {
+ const intl = useIntl()
+ const DEFAULT_VALUE_EDITABLE_FIELD = intl.formatMessage({
+ id: 'default_value_editable_field',
+ defaultMessage: 'Click to edit',
+ })
+ const DEFAULT_VALUE_NO_EDITABLE_FIELD = ' -- '
+
const getRenderProps = () => {
const renderProps = {
value:
diff --git a/src/guillo-gmi/components/guillotina.js b/src/guillo-gmi/components/guillotina.js
index 224ad7bc..9b0d59c2 100644
--- a/src/guillo-gmi/components/guillotina.js
+++ b/src/guillo-gmi/components/guillotina.js
@@ -9,8 +9,24 @@ import { useLocation } from '../hooks/useLocation'
import { guillotinaReducer } from '../reducers/guillotina'
import { initialState } from '../reducers/guillotina'
import { Loading } from './ui/loading'
+import { IntlProvider } from 'react-intl'
+import langCA from '../locales/compiled/ca.json'
+import langES from '../locales/compiled/es.json'
+import langEN from '../locales/compiled/en.json'
-export function Guillotina({ auth, ...props }) {
+function loadLocaleData(locale) {
+ switch (locale) {
+ case 'ca':
+ return langCA
+ case 'es':
+ return langES
+ default:
+ return langEN
+ }
+}
+
+export function Guillotina({ auth, locale, ...props }) {
+ const messages = loadLocaleData(locale)
const url = props.url || 'http://localhost:8080' // without trailing slash
const config = props.config || {}
const client = useGuillotinaClient()
@@ -71,35 +87,37 @@ export function Guillotina({ auth, ...props }) {
const Action = action.action ? registry.getAction(action.action) : null
return (
-
- {!errorStatus && (
-
- {permissions && (
-
- {action.action && }
-
-
-
-
+
+
+ {!errorStatus && (
+
+ {permissions && (
+
+ {action.action && }
+
-
-
- {Main && (
-
-
- {state.loading && }
- {!state.loading && }
-
-
- )}
- {/* Guillotina {JSON.stringify(state.context)} */}
-
- )}
-
- )}
- {errorStatus === 'notallowed' && }
- {errorStatus === 'notfound' && }
-
+
+ {Main && (
+
+
+ {state.loading && }
+ {!state.loading && }
+
+
+ )}
+ {/* Guillotina {JSON.stringify(state.context)} */}
+
+ )}
+
+ )}
+ {errorStatus === 'notallowed' && }
+ {errorStatus === 'notfound' && }
+
+
)
}
diff --git a/src/guillo-gmi/components/input/email.js b/src/guillo-gmi/components/input/email.js
index 62e0e021..13803ca2 100644
--- a/src/guillo-gmi/components/input/email.js
+++ b/src/guillo-gmi/components/input/email.js
@@ -2,13 +2,18 @@ import React from 'react'
import { Input } from './input'
import { isEmail } from '../../lib/validators'
import { Icon } from '../ui/icon'
+import { useIntl } from 'react-intl'
export const EmailInput = ({ value = '', dataTest, ...rest }) => {
+ const intl = useIntl()
return (
}
diff --git a/src/guillo-gmi/components/input/input_list.js b/src/guillo-gmi/components/input/input_list.js
index 6cfc4b51..9a2831a8 100644
--- a/src/guillo-gmi/components/input/input_list.js
+++ b/src/guillo-gmi/components/input/input_list.js
@@ -1,8 +1,10 @@
import * as React from 'react'
import { Input } from './input'
+import { useIntl } from 'react-intl'
export const InputList = React.forwardRef(
({ value, onChange, dataTest }, ref) => {
+ const intl = useIntl()
const [inputValue, setInputValue] = React.useState('')
const addTags = (event) => {
if (event.key === 'Enter' && event.target.value !== '') {
@@ -33,7 +35,10 @@ export const InputList = React.forwardRef(
addTags(event)}
value={inputValue}
ref={ref}
diff --git a/src/guillo-gmi/components/input/search_input.js b/src/guillo-gmi/components/input/search_input.js
index f9a4fc90..9beadacd 100644
--- a/src/guillo-gmi/components/input/search_input.js
+++ b/src/guillo-gmi/components/input/search_input.js
@@ -8,6 +8,8 @@ import ErrorZone from '../error_zone'
import { Loading } from '../ui'
import { generateUID } from '../../lib/helpers'
import { useConfig } from '../../hooks/useConfig'
+import { useIntl } from 'react-intl'
+import { genericMessages } from '../../locales/generic_messages'
function debounce(func, wait) {
let timeout
return function () {
@@ -44,6 +46,7 @@ export const SearchInput = ({
dataTestItem = 'searchInputItemTest',
renderTextItemOption = null,
}) => {
+ const intl = useIntl()
const [options, setOptions] = useSetState(initialState)
const [isOpen, setIsOpen] = React.useState(false)
const [searchTerm, setSearchTerm] = React.useState('')
@@ -169,7 +172,7 @@ export const SearchInput = ({
data-test={dataTestSearchInput}
className="input"
type="text"
- placeholder="Search..."
+ placeholder={intl.formatMessage(genericMessages.search)}
value={searchTerm}
onChange={(ev) => {
delayedQuery(ev.target.value)
@@ -200,7 +203,9 @@ export const SearchInput = ({
})}
{options.items && options.items.length === 0 && (
- No results
+
+ {intl.formatMessage(genericMessages.no_results)}
+
)}
{options.items && options.items_total > options.items.length && (
@@ -214,7 +219,7 @@ export const SearchInput = ({
handleSearch(options.page + 1, true)
}}
>
- Load more...
+ {intl.formatMessage(genericMessages.load_more)}
)}
diff --git a/src/guillo-gmi/components/input/select.js b/src/guillo-gmi/components/input/select.js
index 25661913..07a28909 100644
--- a/src/guillo-gmi/components/input/select.js
+++ b/src/guillo-gmi/components/input/select.js
@@ -3,6 +3,8 @@ import PropTypes from 'prop-types'
import ErrorZone from '../error_zone'
import { classnames, generateUID } from '../../lib/helpers'
import { get } from '../../lib/utils'
+import { useIntl } from 'react-intl'
+import { genericMessages } from '../../locales/generic_messages'
// @ TODO implement hasErrors
/** @type any */
@@ -29,6 +31,7 @@ export const Select = React.forwardRef(
},
ref
) => {
+ const intl = useIntl()
const [uid] = useState(generateUID('select'))
const onUpdate = (ev) => {
@@ -45,7 +48,9 @@ export const Select = React.forwardRef(
}
if (appendDefault) {
- options = [{ text: 'Choose...', value: '' }].concat(options)
+ options = [
+ { text: intl.formatMessage(genericMessages.choose), value: '' },
+ ].concat(options)
}
const statusClasses = error ? 'is-danger' : ''
diff --git a/src/guillo-gmi/components/input/upload.js b/src/guillo-gmi/components/input/upload.js
index 92de3e8b..8269656d 100644
--- a/src/guillo-gmi/components/input/upload.js
+++ b/src/guillo-gmi/components/input/upload.js
@@ -1,7 +1,9 @@
import React from 'react'
import { lightFileReader } from '../../lib/client'
+import { useIntl } from 'react-intl'
export function FileUpload({ label, onChange, ...props }) {
+ const intl = useIntl()
const changed = async (event) => {
const file = await lightFileReader(event.target.files[0])
onChange(file)
@@ -21,7 +23,13 @@ export function FileUpload({ label, onChange, ...props }) {
- {label || 'Choose a file…'}
+
+ {label ||
+ intl.formatMessage({
+ id: 'choose_file',
+ defaultMessage: 'Choose a file',
+ })}
+
diff --git a/src/guillo-gmi/components/modal.js b/src/guillo-gmi/components/modal.js
index d7f348eb..3fb543e5 100644
--- a/src/guillo-gmi/components/modal.js
+++ b/src/guillo-gmi/components/modal.js
@@ -1,6 +1,8 @@
import React from 'react'
import usePortal from 'react-useportal'
import { Button } from './input/button'
+import { useIntl } from 'react-intl'
+import { genericMessages } from '../locales/generic_messages'
export function Modal(props) {
const { isActive, setActive, children } = props
@@ -25,6 +27,7 @@ export function Modal(props) {
}
export function Confirm({ message, onCancel, onConfirm, loading }) {
+ const intl = useIntl()
const setActive = () => onCancel()
return (
@@ -38,7 +41,7 @@ export function Confirm({ message, onCancel, onConfirm, loading }) {
onClick={() => onCancel()}
data-test="btnCancelModalTest"
>
- Cancel
+ {intl.formatMessage(genericMessages.cancel)}
@@ -64,6 +67,7 @@ export function PathTree({
onConfirm,
onCancel,
}) {
+ const intl = useIntl()
return (
{title}
@@ -90,7 +94,7 @@ export function PathTree({
onClick={onCancel}
data-test="btnCancelModalTest"
>
- Cancel
+ {intl.formatMessage(genericMessages.cancel)}
diff --git a/src/guillo-gmi/components/notallowed.js b/src/guillo-gmi/components/notallowed.js
index 6e4e2867..b32d4ba1 100644
--- a/src/guillo-gmi/components/notallowed.js
+++ b/src/guillo-gmi/components/notallowed.js
@@ -1,13 +1,20 @@
import React from 'react'
import { Icon } from './ui/icon'
+import { useIntl } from 'react-intl'
export function NotAllowed() {
+ const intl = useIntl()
return (
- Not Allowed
+
+ {intl.formatMessage({
+ id: 'not_allowed',
+ defaultMessage: 'Not Allowed',
+ })}
+
)
}
diff --git a/src/guillo-gmi/components/notfound.js b/src/guillo-gmi/components/notfound.js
index a70d524b..0526dd9c 100644
--- a/src/guillo-gmi/components/notfound.js
+++ b/src/guillo-gmi/components/notfound.js
@@ -1,13 +1,20 @@
import React from 'react'
import { Icon } from './ui/icon'
+import { useIntl } from 'react-intl'
export function NotFound() {
+ const intl = useIntl()
return (
- 404. Not Found
+
+ {intl.formatMessage({
+ id: 'not_found',
+ defaultMessage: '404. Not Found',
+ })}
+
)
}
diff --git a/src/guillo-gmi/components/pagination.js b/src/guillo-gmi/components/pagination.js
index d14d0717..8deeaba2 100644
--- a/src/guillo-gmi/components/pagination.js
+++ b/src/guillo-gmi/components/pagination.js
@@ -1,7 +1,9 @@
import React from 'react'
+import { useIntl } from 'react-intl'
/* eslint jsx-a11y/anchor-is-valid: "off" */
export function Pagination({ current, total, doPaginate, pager }) {
+ const intl = useIntl()
const maxPages = Math.ceil(total / pager)
if (maxPages <= 1) {
return null
@@ -10,9 +12,20 @@ export function Pagination({ current, total, doPaginate, pager }) {
return (
- {current + 1}/
- {maxPages} of
- {`${total} items`}
+
+ {intl.formatMessage(
+ {
+ id: 'pagination',
+ defaultMessage:
+ '{currentPage} / {totalPages} of {totalItems} items',
+ },
+ {
+ currentPage: current + 1,
+ totalPages: maxPages,
+ totalItems: total,
+ }
+ )}
+
| |