diff --git a/I18n.md b/I18n.md index e5757429c3f..62dcb9aa2f5 100644 --- a/I18n.md +++ b/I18n.md @@ -46,6 +46,25 @@ e.g: LIST_DISPLAY HEADERBAR_GO_PORTAL +### Specific translation cases + +In some cases, you want to add some html markup or style to your translations. For this case, you can use Trans component +https://react.i18next.com/components/trans-component.html + +```jsx +import { Trans } from 'react-i18next'; + + + Are you sure you want to remove the {{ resourceLabel: resourceInfo.resourceTypeLabel }} + {{ resourceName: resourceInfo.label }} ? + +``` + +For this case, the translation json value is : +`Are you sure you want to remove the <1>{{ resourceLabel }} <2><1>{{ resourceName }}?` + +More infos in the react-i18next's documentation + ## Use i18n with UI in your App ### Create an instance of i18n diff --git a/package.json b/package.json index 39fe07cf5dc..33c37b3dd36 100644 --- a/package.json +++ b/package.json @@ -25,57 +25,42 @@ "build-storybook": "npm run test:demo && export ACTION='test:demo' && export TRAVIS_BRANCH='master' && export TRAVIS_BUILD_DIR=`pwd` && .travis/after_success_static.sh && .travis/after_success_demo.sh && .travis/after_success_coverage.sh", "publish-storybook": "npm run build-storybook && cd .static && surge", "changelog": "git log --pretty=\"format:%C(bold green)%ad%C(reset) %<(70,trunc)%s\" --date=short --color", - "extract-i18n": "npm run extract-i18n-components && npm run extract-i18n-forms", + "extract-i18n": "npm run extract-i18n-components && npm run extract-i18n-forms && npm run extract-i18n-containers", "extract-i18n-components": "i18next packages/components/src -r --default-values -o i18n/components -n tui-components -l en --write-old false --fileFilter '*.js' --directoryFilter '!__snapshots__'", - "extract-i18n-forms": "i18next packages/forms/src -r --default-values -o i18n/forms -n tui-forms -l en --write-old false --fileFilter '*.js' --directoryFilter '!__snapshots__'" + "extract-i18n-forms": "i18next packages/forms/src -r --default-values -o i18n/forms -n tui-forms -l en --write-old false --fileFilter '*.js' --directoryFilter '!__snapshots__'", + "extract-i18n-containers": "i18next packages/containers/src -r --default-values -o i18n/containers -n tui-containers -l en --write-old false --fileFilter '*.js' --directoryFilter '!__snapshots__'" }, "watch": { "build-cmf": { - "patterns": [ - "packages/cmf/src/**/*" - ], + "patterns": ["packages/cmf/src/**/*"], "extensions": "js" }, "build-components": { - "patterns": [ - "packages/components/src/**/*" - ], + "patterns": ["packages/components/src/**/*"], "extensions": "js,css,scss,svg" }, "build-sagas": { - "patterns": [ - "packages/sagas/src/**/*" - ], + "patterns": ["packages/sagas/src/**/*"], "extensions": "js" }, "build-containers": { - "patterns": [ - "packages/containers/src/**/*" - ], + "patterns": ["packages/containers/src/**/*"], "extensions": "js,css,scss,svg" }, "build-forms": { - "patterns": [ - "packages/forms/src/**/*" - ], + "patterns": ["packages/forms/src/**/*"], "extensions": "js,scss" }, "build-icons": { - "patterns": [ - "packages/icons/src/svg/*" - ], + "patterns": ["packages/icons/src/svg/*"], "extensions": "svg" }, "build-logging": { - "patterns": [ - "packages/logging/src/**/*" - ], + "patterns": ["packages/logging/src/**/*"], "extensions": "js" }, "build-theme": { - "patterns": [ - "packages/theme/src/**/*" - ], + "patterns": ["packages/theme/src/**/*"], "extensions": "css,scss" } }, @@ -83,7 +68,7 @@ "type": "git", "url": "https://github.com/Talend/ui.git" }, - "engines" : { "node" : ">=8.10.0 <9.0.0" }, + "engines": { "node": ">=8.10.0 <9.0.0" }, "version": "0.0.0", "private": true, "workspaces": [ diff --git a/packages/containers/examples/ExampleDeleteResource.js b/packages/containers/examples/ExampleDeleteResource.js index 5bb6d23f76d..759b262971b 100644 --- a/packages/containers/examples/ExampleDeleteResource.js +++ b/packages/containers/examples/ExampleDeleteResource.js @@ -1,4 +1,6 @@ import React from 'react'; +import { I18nextProvider } from 'react-i18next'; +import i18n, { LanguageSwitcher } from './config/i18n'; import { DeleteResource } from '../src'; /* @@ -32,10 +34,12 @@ Also require two fields, uri it contains the uri to call and resourceType, the t const views = { uri: '/myEndpoint', resourceType: 'myResourceType', + resourceTypeLabel: 'resource', header: 'My header title', 'cancel-action': 'dialog:delete:cancel', 'validate-action': 'dialog:delete:validate', routeParams: { id: 'myID' }, + female: true, }; const params = { @@ -47,6 +51,18 @@ const props = { params, }; -export default function ExampleAction() { - return ; -} +export default { + default: () => ( +
+ ; +
+ ), + translated: () => ( + +
+ + +
+
+ ), +}; diff --git a/packages/containers/examples/config/i18n.js b/packages/containers/examples/config/i18n.js index 940b5974308..2b02b99c2a7 100644 --- a/packages/containers/examples/config/i18n.js +++ b/packages/containers/examples/config/i18n.js @@ -1,8 +1,19 @@ +import React from 'react'; import i18n from 'i18next'; import { I18N_DOMAIN_COMPONENTS } from '@talend/react-components'; +import I18N_DOMAIN_CONTAINERS from '../../src/constant'; i18n.init({ + lng: 'en', resources: { + en: { + [I18N_DOMAIN_CONTAINERS]: { + DELETE_RESOURCE_MESSAGE: + 'Are you sure you want to remove the <1>{{ resourceLabel }} <2><1>{{ resourceName }}?', + DELETE_RESOURCE_MESSAGE_female: + 'Are you sure you want to remove the <1>{{ resourceLabel }} <2><1>{{ resourceName }}?', + }, + }, fr: { [I18N_DOMAIN_COMPONENTS]: { LIST_TOOLBAR_DISPLAY: 'Affichage :', @@ -17,6 +28,12 @@ i18n.init({ LIST_FILTER_REMOVE: 'Supprimer le filtre', VIRTUALIZEDLIST_NO_RESULT: 'Pas de résultat', }, + [I18N_DOMAIN_CONTAINERS]: { + DELETE_RESOURCE_MESSAGE: + 'Êtes-vous sûr(e) de vouloir supprimer le <1>{{ resourceLabel }} <2><1>{{ resourceName }} ?', + DELETE_RESOURCE_MESSAGE_female: + 'Êtes-vous sûr(e) de vouloir supprimer la <1>{{ resourceLabel }} <2><1>{{ resourceName }} ?', + }, }, it: { [I18N_DOMAIN_COMPONENTS]: { @@ -32,10 +49,41 @@ i18n.init({ LIST_FILTER_REMOVE: 'Rimuova il filtro', VIRTUALIZEDLIST_NO_RESULT: 'Nessun risultato', }, + [I18N_DOMAIN_CONTAINERS]: { + DELETE_RESOURCE_MESSAGE: 'Sei sicuro di voler eliminare {{resourceLabel}} ', + }, }, }, - debug: false, + saveMissing: true, + debug: true, wait: true, // globally set to wait for loaded translations in translate hoc }); +export const LanguageSwitcher = () => { + const style = { + position: 'fixed', + bottom: 0, + width: '100vw', + textAlign: 'center', + zIndex: 100000, + }; + + function renderBtn(locale, isDefault) { + return ( + + ); + } + + return ( + + ); +}; + export default i18n; diff --git a/packages/containers/package.json b/packages/containers/package.json index 9ef1ec65019..8a965cc2262 100644 --- a/packages/containers/package.json +++ b/packages/containers/package.json @@ -112,12 +112,14 @@ "bson-objectid": "1.1.5", "classnames": "2.2.5", "immutable": "3.8.1", + "i18next": "^9.0.0", "invariant": "2.2.2", "keycode": "2.2.0", "lodash": "4.17.4", "prop-types": "15.5.10", "react": "^15.6.2", "react-bootstrap": "0.31.5", + "react-i18next": "^5.2.0", "react-redux": "5.0.5", "reselect": "^2.5.4" }, diff --git a/packages/containers/src/DeleteResource/DeleteResource.container.js b/packages/containers/src/DeleteResource/DeleteResource.container.js index 4916b612441..da8e2e0ba5b 100644 --- a/packages/containers/src/DeleteResource/DeleteResource.container.js +++ b/packages/containers/src/DeleteResource/DeleteResource.container.js @@ -2,15 +2,18 @@ import React from 'react'; import PropTypes from 'prop-types'; import { componentState } from '@talend/react-cmf'; import { ConfirmDialog } from '@talend/react-components'; +import { translate, Trans } from 'react-i18next'; import { getActionsProps } from '../actionAPI'; import deleteResourceConst from './deleteResource.constants'; +import DEFAULT_I18N from '../translate'; +import I18N_DOMAIN_CONTAINERS from '../constant'; /** * DeleteResource is used to delete a specific resource. * When the component is mounted, it opens a confirm dialog. * It uses the saga matching pattern to launch a race between the cancel and validate action. */ -export default class DeleteResource extends React.Component { +export class DeleteResource extends React.Component { static displayName = 'Container(DeleteResource)'; static propTypes = { ...componentState.propTypes, @@ -19,11 +22,16 @@ export default class DeleteResource extends React.Component { header: PropTypes.string, uri: PropTypes.string.isRequired, resourceType: PropTypes.string.isRequired, + resourceTypeLabel: PropTypes.string, + female: PropTypes.string, }; static contextTypes = { registry: PropTypes.object.isRequired, store: PropTypes.object.isRequired, }; + static defaultProps = { + t: DEFAULT_I18N.t.bind(DEFAULT_I18N), + }; constructor(props, context) { super(props, context); @@ -49,6 +57,9 @@ export default class DeleteResource extends React.Component { getResourceInfo() { return { resourceType: this.props.resourceType, + resourceTypeLabel: this.props.resourceTypeLabel + ? this.props.resourceTypeLabel + : this.props.resourceType, uri: this.props.uri, ...this.getLabel(), id: this.props.params.id, @@ -70,6 +81,9 @@ export default class DeleteResource extends React.Component { const resourceInfo = this.getResourceInfo(); const validateAction = this.getActions(deleteResourceConst.VALIDATE_ACTION, resourceInfo); const cancelAction = this.getActions(deleteResourceConst.CANCEL_ACTION, resourceInfo); + const i18nKey = this.props.female + ? 'DELETE_RESOURCE_MESSAGE_female' + : 'DELETE_RESOURCE_MESSAGE'; return ( -
{resourceInfo.label}
+
+ + Are you sure you want to remove the {{ resourceLabel: resourceInfo.resourceTypeLabel }} + {{ resourceName: resourceInfo.label }} ? + +
); } } + +export default translate(I18N_DOMAIN_CONTAINERS, { i18n: DEFAULT_I18N })(DeleteResource); diff --git a/packages/containers/src/DeleteResource/DeleteResource.test.js b/packages/containers/src/DeleteResource/DeleteResource.test.js index 1b392f4c8c0..b04437a0d66 100644 --- a/packages/containers/src/DeleteResource/DeleteResource.test.js +++ b/packages/containers/src/DeleteResource/DeleteResource.test.js @@ -3,7 +3,7 @@ import { shallow } from 'enzyme'; import { store } from '@talend/react-cmf/lib/mock'; import Immutable from 'immutable'; -import Container from './DeleteResource.container'; +import { DeleteResource } from './DeleteResource.container'; import Connected from './DeleteResource.connect'; const state = store.state(); @@ -40,10 +40,12 @@ describe('Container DeleteResource', () => { resource: new Immutable.Map({ label: 'myLabel' }), header: 'My header title', params: { id: 'myResourceID' }, + resourceTypeLabel: 'resourceLabel', + female: true, 'validate-action': 'dialog:delete:validate', 'cancel-action': 'dialog:delete:cancel', }; - const wrapper = shallow(, { context }); + const wrapper = shallow(, { context }); expect(wrapper.getElement()).toMatchSnapshot(); }); it('should render with wrong resourceInfo params', () => { @@ -55,14 +57,14 @@ describe('Container DeleteResource', () => { 'validate-action': 'dialog:delete:validate', 'cancel-action': 'dialog:delete:cancel', }; - const wrapper = shallow(, { context }); + const wrapper = shallow(, { context }); expect(wrapper.getElement()).toMatchSnapshot(); }); }); describe('Connected DeleteResource', () => { it('should connect TestGenerator', () => { - expect(Connected.displayName).toBe(`Connect(CMF(${Container.displayName}))`); - expect(Connected.WrappedComponent).toBe(Container); + expect(Connected.displayName).toBe('Connect(CMF(Translate(Container(DeleteResource))))'); + expect(Connected.WrappedComponent).toBe(DeleteResource); }); }); diff --git a/packages/containers/src/DeleteResource/README.md b/packages/containers/src/DeleteResource/README.md index e34446b8cc5..c4a2704591f 100644 --- a/packages/containers/src/DeleteResource/README.md +++ b/packages/containers/src/DeleteResource/README.md @@ -69,9 +69,11 @@ Required : * **redirectUrl** : is the url to redirect when delete is complete or cancel action is triggered Optional : +* **resourceLabel** : is the parameter to show the type to remove if the resourceType is not readable by the user * **routerParamAttribute** : is the attribute defined in the route to give the resource id * **resourcePath** : array of string, is appended to resourceType key to deep location of a subset of a collection element the delete service will use it to check if the resource exist in your application state tree +* **female** : Only for i18n, allow to set the i18nkey to tell of the resource type if female or not example with resourceType only ```javascript diff --git a/packages/containers/src/DeleteResource/__snapshots__/DeleteResource.test.js.snap b/packages/containers/src/DeleteResource/__snapshots__/DeleteResource.test.js.snap index 4459f127121..9e2d916d980 100644 --- a/packages/containers/src/DeleteResource/__snapshots__/DeleteResource.test.js.snap +++ b/packages/containers/src/DeleteResource/__snapshots__/DeleteResource.test.js.snap @@ -13,6 +13,7 @@ exports[`Container DeleteResource should render with proper resourceInfo params "id": "myResourceID", "label": "myLabel", "resourceType": "myResourceType", + "resourceTypeLabel": "resourceLabel", "uri": "/myEndpoint", }, }, @@ -33,6 +34,7 @@ exports[`Container DeleteResource should render with proper resourceInfo params "id": "myResourceID", "label": "myLabel", "resourceType": "myResourceType", + "resourceTypeLabel": "resourceLabel", "uri": "/myEndpoint", }, }, @@ -41,7 +43,23 @@ exports[`Container DeleteResource should render with proper resourceInfo params } >
- myLabel + + Are you sure you want to remove the + Object { + "resourceLabel": "resourceLabel", + } + + + Object { + "resourceName": "myLabel", + } + + + ? +
`; @@ -59,6 +77,7 @@ exports[`Container DeleteResource should render with wrong resourceInfo params 1 "id": "myResourceID", "label": "", "resourceType": "unknownResourceType", + "resourceTypeLabel": "unknownResourceType", "uri": "/myEndpoint", }, }, @@ -79,6 +98,7 @@ exports[`Container DeleteResource should render with wrong resourceInfo params 1 "id": "myResourceID", "label": "", "resourceType": "unknownResourceType", + "resourceTypeLabel": "unknownResourceType", "uri": "/myEndpoint", }, }, @@ -86,6 +106,24 @@ exports[`Container DeleteResource should render with wrong resourceInfo params 1 } } > -
+
+ + Are you sure you want to remove the + Object { + "resourceLabel": "unknownResourceType", + } + + + Object { + "resourceName": "", + } + + + ? + +
`; diff --git a/packages/containers/src/constant.js b/packages/containers/src/constant.js new file mode 100644 index 00000000000..2cf1fa99525 --- /dev/null +++ b/packages/containers/src/constant.js @@ -0,0 +1,3 @@ +const I18N_DOMAIN_CONTAINERS = 'tui-containers'; + +export default I18N_DOMAIN_CONTAINERS; diff --git a/packages/containers/src/translate.js b/packages/containers/src/translate.js new file mode 100644 index 00000000000..26e95cfa7ac --- /dev/null +++ b/packages/containers/src/translate.js @@ -0,0 +1,5 @@ +import { createInstance } from 'i18next'; + +// https://github.com/i18next/i18next/issues/936#issuecomment-307550677 +const DEFAULT_I18N = createInstance({}, () => {}); +export default DEFAULT_I18N;