Commit 6e1a1487 authored by Viacheslav Pavlov's avatar Viacheslav Pavlov Committed by Matija Obreza

i18n

- src/translations.json for application strings
- translations for common components
- translations split into modules, a script compiles one translation file
- i18n for user module
- i18n for requests module
- i18n for list module
- i18n for accessions module
- i18n for repository module
- i18n for subsets
- i18n for institutes module
- i18n for crop module
parent 59af41c0
const fg = require('fast-glob');
const _ = require('lodash');
const fs = require('fs');
fg([`./src/**/translations.json`, `./src/translations.json`])
.then((entries) => entries.map((path) => getTranslations(path)))
.then((content) => fs.writeFileSync(`locales/en/translations.json`, `{\n${content}}`));
const getTranslations = (path) => {
const prefix = path.substring(path.indexOf('./src/') + './src/'.length, path.indexOf('/translations.json'));
console.log('Loading translations of module', prefix);
const fileContent = fs.readFileSync(path, 'utf8');
return prefix !== '/' ? _(`"${prefix}": ${fileContent}\n`).value() : fileContent.substr(1, fileContent.length - 2);
};
{
"action": {
"add": "Add {{what, lowercase}}",
"applyFilters": "Apply filters",
"approve": "Approve and publish",
"back": "Back",
"backTo": "Back to {{where, lowercase}}",
"cancel": "Cancel",
"delete": "Delete",
"deleteData": "Delete data",
"edit": "Edit",
"login": "Login",
"logout": "Logout",
"nextStep": "Next step",
"reject": "Reject",
"remove": "Remove",
"reset": "Reset",
"return": "Return",
"save": "Save",
"saveChanges": "Save changes",
"search": "Search",
"viewDetails": "View details"
"sendToReview": "Send to review",
"unpublish": "Unpublish"
},
"label": {
"item": "Item",
"item_plural": "Items",
"list": "List",
"list_plural": "Lists",
"prettyNumber": "{{value, number}}"
"csv": {
"autoDetect": "Auto-detect ",
"autoDetectLabel": "Auto-detect CSV settings",
"comma": "Comma",
"configuration": "CSV configuration",
"escapeCharacter": "Escape character",
"other": "Other",
"quoteCharacter": "Quote character",
"semicolon": "Semicolon",
"separator": "Separator character",
"space": "Space",
"tab": "Tab",
"typeYourEscapeCharacter": "Type your escape character",
"typeYourQuoteCharacter": "Type your quote character",
"typeYourSeparator": "Type your separator"
},
"f": {
"dateSearch": "Date search",
"fromIncluding": "From including",
"lastModifiedDate": "Last modified date",
"textSearch": "Text search"
},
"message": {
"confirmDelete": "Deleting the {{what, lowercase}} record is only possible when there is no associated data."
"fileUploader": {
"chooseFiles": "Choose files to upload",
"dragFiles": "Drag files here \n or",
"release": "Release to upload"
},
"label": {
"dateNotProvided": "Date not provided",
"description": "Description",
"either": "Either",
"itemEditorWarn": "Don't use the ItemsEditor for more than 100 items!",
"lastUpdated": "Last updated",
"no": "No",
"prettyNumber": "{{value, number}}",
"sortBy": "Sort By",
"stepsForDataPublication": "Steps for data publication completion",
"title": "Title",
"yes": "Yes"
},
"paginate": {
"numberOfItems": "{{count, number}} {{what, lowercase}}",
......
This diff is collapsed.
This diff is collapsed.
......@@ -19,16 +19,17 @@
"main": "index.js",
"scripts": {
"clean": "rimraf target",
"build": "webpack --config config/webpack-development.config.js",
"build": "npm run generateI18n && webpack --config config/webpack-development.config.js",
"build:production": "cross-env NODE_ENV=production SSR=true webpack --config config/webpack-production.config.js",
"build:server": "cross-env NODE_ENV=production SSR=true webpack --config config/server.config.js",
"serve": "webpack-dev-server --config config/webpack-development.config.js",
"serve": "npm run generateI18n && webpack-dev-server --config config/webpack-development.config.js",
"serve:production": "cross-env NODE_ENV=production webpack-dev-server --config config/webpack-production.config.js",
"build:ssr": "rimraf target && npm run build:production && npm run build:server",
"build:ssr": "npm run generateI18n && rimraf target && npm run build:production && npm run build:server",
"serve:ssr": "npm run build:ssr && cd target/app/server && cross-env SSR=true node server.js",
"debug:ssr": "rimraf target && npm run build && cross-env SSR=true webpack --config config/server.config.js && cd target/app/server && cross-env SSR=true node server.js",
"debug:ssr2": "cross-env SSR=true webpack --config config/server.config.js && cd target/app/server && cross-env SSR=true node server.js",
"run:ssr": "cd target/app/server && cross-env SSR=true node server.js",
"generateI18n": "node i18n/generateI18n.ts",
"i18nscan": "i18next-scanner --config i18next-scanner.config.js 'src/**/*.tsx'"
},
"dependencies": {
......@@ -45,6 +46,7 @@
"es-cookie": "^1.2.0",
"express": "^4.16.3",
"express-http-proxy": "^1.2.0",
"fast-glob": "^2.2.3",
"flattenjs": "^1.0.4",
"form-data": "^2.3.2",
"history": "^4.7.2",
......
{
"public": {
"c": {
"accessionCard": {
"addToMyList": "Add to my list"
},
"pdciTable": {
"pdciScore": "PDCI score of this accession is {{score, number}} of 10.0.",
"readPDCI": "Read about Passport Data Completeness Index",
"pdciInstitute": "Average PDCI score for this institute is {{score, number}}."
}
},
"f": {
"filtersTitle": "Filter accessions",
"seqNumber": "Sequential number",
"subtaxon": "Subtaxon",
"originOfMaterial": "Origin of material",
"elevation": "Elevation",
"status": "Status",
"historic": "Historic records",
"available": "Available for distribution",
"mlsStatus": "Included in MLS",
"sgsv": "Backed up in SGSV"
},
"p": {
"display": {
"title": "Accession details",
"subTitle": "Passport data and everything else",
"removeFromMyList": "Remove {{accessionNumber}} from my list",
"addToMyList": "Add {{accessionNumber}} to my list",
"holdingInstitute": "Holding institute",
"DOI": "DOI",
"acquisitionDate": "Acquisition Date",
"availability": "Availability for distribution",
"ITPGRFAMLS": "ITPGRFA MLS",
"donorInstitute": "Donor institute",
"donorAccessionNumber": "Donor accession number",
"safetyDuplicationInstitute": "Safety duplication institute",
"accessionURL": "Accession URL",
"scientificName": "Scientific name",
"cropName": "Crop name",
"providedCropName": "Provided crop name",
"accessionNames": "Accession names",
"collectingInformation": "Collecting information",
"remarks": "Remarks",
"pdci": "Passport Data Completeness Index",
"metadata": "Metadata",
"permanentURL": "Permanent URL",
"associatedDatasets": "Associated Datasets",
"associatedSubsets": "Associated Subsets"
},
"browse": {
"title": "Accession browser",
"subTitle": "Explore curated sets of accessions"
}
}
},
"tab": {
"data": "Accessions",
"overview": "Overview",
"map": "Map"
},
"common": {
"modelName": "Accession",
"modelName_plural": "Accessions",
"menu": "Accessions",
"stats": "Accession record",
"stats_plural": "Accession records",
"acceNumb": "Accession number",
"countryOfOrigin": "Country of origin",
"instituteCode": "Institute code",
"taxonomy": "Taxonomy",
"genus": "Genus",
"species": "Species",
"doi": "DOI",
"sampStat": "Biological status of accession",
"storageType": "Type of germplasm storage",
"alias": {
"OTHERNUMB": "Other identifier",
"ACCENAME": "Accession name",
"DONORNUMB": "Donor accession number",
"COLLNUMB": "Collecting number"
},
"available": {
"true": "Available for distribution",
"false": "Not available for distribution",
"null": "Availability not provided"
},
"mlsStatus": {
"true": "Accession is part of the Multi-lateral system of ITPGRFA",
"false": "Not declared in the Multi-lateral system of ITPGRFA ",
"null": "Status not provided"
},
"coll": {
"collCode": "Collecting institute code",
"collNumb": "Collecting number",
"collDate": "Collecting date of sample",
"collMissId": "Collecting mission identifier",
"collName": "Collecting institute name",
"collSite": "Location of collecting site",
"collSrc": "Collecting source"
},
"geo": {
"latitude": "Latitude of collecting site",
"longitude": "Longitude of collecting site",
"uncertainty": "Coordinate uncertainty",
"datum": "Geodetic datum",
"method": "Georeferencing method",
"elevation": "Elevation of collecting site"
},
"storage": {
"10": "Seed collection",
"11": "Short term seed collection",
"12": "Medium term seed collection",
"13": "Long term seed collection",
"20": "Field collection",
"30": "In vitro collection",
"40": "Cryopreserved collection",
"50": "DNA collection",
"99": "Other"
},
"sampleStatus": {
"100": "Wild",
"110": "Natural",
"120": "Semi-natural/wild",
"130": "Semi-natural/sown",
"200": "Weedy",
"300": "Traditional cultivar/Landrace",
"400": "Breeding/Research Material",
"410": "Breeders Line",
"411": "Synthetic population",
"412": "Hybrid",
"413": "Founder stock/base population",
"414": "Inbred line",
"415": "Segregating population",
"416": "Clonal selection",
"420": "Genetic stock",
"421": "Mutant",
"422": "Cytogenetic stocks",
"423": "Other genetic stocks",
"500": "Advanced/improved cultivar",
"600": "GMO",
"999": "Other"
},
"overview": {
"institute code": "Holding Institute",
"institute country code3": "Country of holding institute",
"crop shortName": "Crop name",
"cropName": "Crop",
"sampStat": "Biological status of accession",
"taxonomy genus": "Genus",
"taxonomy genusSpecies": "Species",
"countryOfOrigin code3": "Country of Origin",
"donorCode": "FAO WIEWS code of donor institute",
"mlsStatus": "ITGPRFA Multi-lateral system",
"available": "Available for distribution",
"duplSite": "Site of safety duplication",
"breederCode": "Breeder code",
"sgsv": "Safety duplicated in Svalbard",
"storage": "Type of Germplasm storage"
}
}
}
\ No newline at end of file
import * as React from 'react';
import {translate} from 'react-i18next';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import { parse } from 'query-string';
......@@ -40,7 +41,7 @@ class BrowsePage extends BrowsePageTemplate<Accession> {
}
public render() {
const { paged, filterCode, currentTab } = this.props;
const { paged, filterCode, currentTab, t} = this.props;
const renderAccession = (s: Accession, index: number) => {
return <AccessionCard key={ s.uuid } index={ index } accession={ s } />;
......@@ -50,11 +51,11 @@ class BrowsePage extends BrowsePageTemplate<Accession> {
<PageLayout sidebar={
<AccessionFilters initialValues={ paged && paged.filter || {} } onSubmit={ this.myApplyFilters } />
}>
<ContentHeader title="Accession browser" subTitle="Explore curated sets of accessions" />
<ContentHeader title="accessions.public.p.browse.title" subTitle="accessions.public.p.browse.subTitle" />
<PaginationComponent
pageObj={ paged }
onChange={ this.onPaginationChange }
displayName="label.accession"
displayName="accessions.common.modelName"
sortOptions={ Accession.SORT_OPTIONS }
infinite
/>
......@@ -62,15 +63,15 @@ class BrowsePage extends BrowsePageTemplate<Accession> {
tab={ currentTab }
actions={
<span>
<Button variant="raised"> Select all </Button>
<Button variant="raised"> Delete </Button>
<Button variant="raised"> Share </Button>
<Button variant="raised">{ t('TODO-Demo Select all') }</Button>
<Button variant="raised">{ t('TODO-Demo Delete') }</Button>
<Button variant="raised">{ t('TODO-Demo Share') }</Button>
</span>
}
>
<Tab name="data" to={ `/a/${filterCode || ''}` }>Accessions</Tab>
<Tab name="overview" to={ `/a/overview/${filterCode || ''}` }>Overview</Tab>
<Tab name="map" to={ `/a/map/${filterCode || '' }` }>Map</Tab>
<Tab name="data" to={ `/a/${filterCode || ''}` }>{ t('accessions.tab.data') }</Tab>
<Tab name="overview" to={ `/a/overview/${filterCode || ''}` }>{ t('accessions.tab.overview') }</Tab>
<Tab name="map" to={ `/a/map/${filterCode || '' }` }>{ t('accessions.tab.map') }</Tab>
</Tabs>
......@@ -107,4 +108,4 @@ const mapDispatchToProps = (dispatch) => bindActionCreators({
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(BrowsePage);
export default translate()(connect(mapStateToProps, mapDispatchToProps)(BrowsePage));
This diff is collapsed.
import * as React from 'react';
import {connect} from 'react-redux';
import {translate} from 'react-i18next';
import {withStyles} from '@material-ui/core/styles';
import {bindActionCreators} from 'redux';
import { loadAccessionsMapInfo } from 'accessions/actions/public';
......@@ -23,6 +24,7 @@ interface IMapPageProps extends React.ClassAttributes<any> {
classes: any;
filterCode: string;
loadAccessionsMapInfo: any;
t: any;
}
const styles = (theme) => ({
......@@ -70,7 +72,7 @@ class BrowsePage extends React.Component<IMapPageProps, any> {
public render() {
const position = [30, 0];
const { mapInfo, currentTab, classes, filterCode } = this.props;
const { mapInfo, currentTab, classes, filterCode, t } = this.props;
if (! mapInfo) {
return <Loading />;
......@@ -81,7 +83,7 @@ class BrowsePage extends React.Component<IMapPageProps, any> {
return (
<PageLayout withFooter>
<ContentHeader title="Accession map" subTitle="Explore accession localities"/>
<ContentHeader title="accessions.public.p.browse.title" subTitle="accessions.public.p.browse.subTitle" />
<Tabs
tab={ currentTab }
actions={
......@@ -93,9 +95,9 @@ class BrowsePage extends React.Component<IMapPageProps, any> {
</span>
}
>
<Tab name="data" to={ `/a/${filterCode || ''}` }>Accessions</Tab>
<Tab name="overview" to={ `/a/overview/${filterCode || ''}` }>Overview</Tab>
<Tab name="map" to={ `/a/map/${filterCode || '' }` }>Map</Tab>
<Tab name="data" to={ `/a/${filterCode || ''}` }>{ t('accessions.tab.data') }</Tab>
<Tab name="overview" to={ `/a/overview/${filterCode || ''}` }>{ t('accessions.tab.overview') }</Tab>
<Tab name="map" to={ `/a/map/${filterCode || '' }` }>{ t('accessions.tab.map') }</Tab>
</Tabs>
<PrettyFilters
prefix="accessions"
......@@ -138,4 +140,4 @@ const mapDispatchToProps = (dispatch) => bindActionCreators({
loadAccessionsMapInfo,
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(BrowsePage));
export default translate()(connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(BrowsePage)));
This diff is collapsed.
import * as React from 'react';
import {connect} from 'react-redux';
import {translate} from 'react-i18next';
import {bindActionCreators} from 'redux';
import { withStyles } from '@material-ui/core/styles';
......@@ -23,8 +24,8 @@ const styles = (theme) => ({
},
});
const AccessionCard = ({ accession, classes, index, addAccessionToMyList, removeAccessionFromMyList, accessions, ...other }:
{ accession: Accession, classes: any, index?: number, addAccessionToMyList: (uuid: string) => void , removeAccessionFromMyList: (uuid: string) => void, accessions: any} & React.ClassAttributes<any>) => {
const AccessionCard = ({ accession, classes, index, addAccessionToMyList, removeAccessionFromMyList, accessions, t, ...other }:
{ accession: Accession, classes: any, index?: number, addAccessionToMyList: (uuid: string) => void , removeAccessionFromMyList: (uuid: string) => void, accessions: any, t: any} & React.ClassAttributes<any>) => {
const isChecked = accession && accessions && accessions.includes(accession.uuid);
let touchTimer;
......@@ -57,7 +58,7 @@ const AccessionCard = ({ accession, classes, index, addAccessionToMyList, remove
{ /* { accession.crop && <CropChips crops={ accession.crop.shortName } /> } */ }
{ accession.countryOfOrigin && ` • ${accession.countryOfOrigin.name}` }
<a className="float-right" onClick={ onCheckedLinkClick }>
{ isChecked ? `Remove` : `+ Add to my list` }
{ isChecked ? t('common:action.remove') : `+ ${t('accessions.public.c.accessionCard.addToMyList')}` }
</a>
</div>
<div>
......@@ -89,4 +90,4 @@ const mapDispatchToProps = (dispatch) => bindActionCreators({
removeAccessionFromMyList,
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(AccessionCard));
export default translate()(connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(AccessionCard)));
import * as React from 'react';
import { reduxForm } from 'redux-form';
import {translate} from 'react-i18next';
import { ACCESSION_FILTERFORM } from 'accessions/constants';
......@@ -12,49 +13,50 @@ import StringArrFilter from 'ui/common/filter/StringArrFilter';
import Accession from 'model/accession/Accession';
import DateFilter from 'ui/common/filter/DateFilter';
const AccessionFilters = ({handleSubmit, initialValues, initialize, ...other}) => {
const AccessionFilters = ({handleSubmit, initialValues, initialize, t, ...other}) => {
// console.log('AccessionFilters', initialValues);
return (
<FiltersBlock title="Filter accessions" handleSubmit={ handleSubmit } initialize={ initialize } { ...other }>
<CollapsibleComponentSearch title="Historic records">
<FiltersBlock title={ t('accessions.public.f.filtersTitle') } handleSubmit={ handleSubmit } initialize={ initialize } { ...other }>
<CollapsibleComponentSearch title={ t('accessions.public.f.historic') }>
<BooleanFilter name="historic"/>
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title="Text search">
<StringArrFilter name="holder.code" label="Institute code" placeholder="NGA039"/>
<StringFilter name="acceNumb" searchType="contains" label="Accession number" placeholder="IRGC"/>
<NumberFilter name="seqNo" label="Sequential number" />
<CollapsibleComponentSearch title={ t('common:f.textSearch') }>
<StringArrFilter name="holder.code" label={ t('accessions.common.instituteCode') } placeholder="NGA039"/>
<StringFilter name="acceNumb" searchType="contains" label={ t('accessions.common.acceNumb') } placeholder="IRGC"/>
<NumberFilter name="seqNo" label={ t('accessions.public.f.seqNumber') } />
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title="Date search">
<DateFilter name="lastModifiedDate" label="Last modified date"/>
<CollapsibleComponentSearch title={ t('common:f.dateSearch') }>
<DateFilter name="lastModifiedDate" label={ t('common:f.lastModifiedDate') }/>
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title="Taxonomy">
<StringArrFilter name="taxa.genus" label="Genus" placeholder="Hordeum"/>
<StringArrFilter name="taxa.species" label="Species" placeholder="vulgare"/>
<StringFilter name="taxa.subtaxa" searchType="contains" label="Subtaxon" placeholder=""/>
<CollapsibleComponentSearch title={ t('accessions.common.taxonomy') }>
<StringArrFilter name="taxa.genus" label={ t('accessions.common.genus') } placeholder="Hordeum"/>
<StringArrFilter name="taxa.species" label={ t('accessions.common.species') } placeholder="vulgare"/>
<StringFilter name="taxa.subtaxa" searchType="contains" label={ t('accessions.public.f.subtaxon') } placeholder=""/>
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title="Origin of material">
<StringArrFilter name="origin.iso3" label="Country of origin" placeholder="SVN"/>
<NumberFilter name="geo.latitude" label="Latitude" />
<NumberFilter name="geo.longitude" label="Longitude" />
<NumberFilter name="geo.elevation" label="Elevation" />
<CollapsibleComponentSearch title={ t('accessions.public.f.originOfMaterial') }>
<StringArrFilter name="origin.iso3" label={ t('accessions.common.countryOfOrigin') } placeholder="SVN"/>
<NumberFilter name="geo.latitude" label={ t('geo.common.latitude') } />
<NumberFilter name="geo.longitude" label={ t('geo.common.longitude') } />
<NumberFilter name="geo.elevation" label={ t('accessions.public.f.elevation') } />
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title="Biological status of accession">
<CollapsibleComponentSearch title={ t('accessions.common.sampStat') }>
<StringArrFilter name="sampStat" options={ Accession.SAMPSTAT } />
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title="Type of germplasm storage">
<CollapsibleComponentSearch title={ t('accessions.common.storageType') }>
<StringArrFilter name="storage" options={ Accession.STORAGE } />
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title="Status">
<BooleanFilter name="available" label="Available for distribution" />
<BooleanFilter name="mlsStatus" label="Included in MLS" />
<BooleanFilter name="sgsv" label="Backed up in SGSV" />
<CollapsibleComponentSearch title={ t('accessions.public.f.status') }>
<BooleanFilter name="historic" label={ t('accessions.public.f.historic') } />
<BooleanFilter name="available" label={ t('accessions.public.f.available') } />
<BooleanFilter name="mlsStatus" label={ t('accessions.public.f.mlsStatus') } />
<BooleanFilter name="sgsv" label={ t('accessions.public.f.sgsv') } />
</CollapsibleComponentSearch>
</FiltersBlock>
);
};
export default reduxForm({
export default translate()(reduxForm({
enableReinitialize: true,
destroyOnUnmount: false,
form: ACCESSION_FILTERFORM,
})(AccessionFilters);
})(AccessionFilters));
......@@ -58,10 +58,10 @@ class PdciTable extends React.Component<IPdciTableProps, any> {
<Grid>
<div style={ {marginBottom: '2em', padding: '2em', backgroundColor: '#e7e7e7', borderRadius: '6px'} }>
<span style={ {fontSize: '1.5em'} }>
{ t('accession.pdciScore', {score: pdci.score}) }
{ t('accessions.public.c.pdciTable.pdciScore', {score: pdci.score}) }
</span>
{ ' ' }
<a href="#">Read about Passport Data Completeness Index</a>
<a href="#">{ t('accessions.public.c.pdciTable.readPDCI') }</a>
</div>
</Grid>
<Grid container justify={ 'center' } spacing={ 16 }>
......
......@@ -27,7 +27,7 @@ export const checkSoftwareVersion = () => (dispatch, getState) => {
const currentSoftVersion: string = window.softwareCommit;
if (currentSoftVersion !== softwareVersion) {
dispatch(showSnackbar('New version is available', PageReloadButton));
dispatch(showSnackbar('common:label.newVersionAvailable', PageReloadButton));
}
setTimeout(() => dispatch(checkSoftwareVersion()), delay);
......
{
"menu": {
"about": "About Genesys",
"contact": "Contact us",
"what-is-genesys": "What is Genesys?",
"history-of-genesys": "History of Genesys",
"newsletter": "Genesys Newsletter",
"faq": "Frequently asked questions",
"how-to-use-genesys": "How to use Genesys?",
"disclaimer": "Disclaimer",
"terms": "Terms and Conditions of Use",
"copying": "Copyright policy",
"privacy": "Privacy policy"
}
}
......@@ -40,7 +40,7 @@ class MenuStepper extends React.Component<IMenuStepperProps> {
{ menu.items.map((menuItem: MenuItem) => (
<Link to={ menuItem.url }>
<div className={ classes.menuItem }>
{ t(`content.${menuItem.text}`) }
{ t(`cms.${menuItem.text}`) }
</div>
</Link>
))
......
{
"public": {
"c": {
"cropCard": {
"originalName": "Original name",
"registered": "Registered"
},
"cropSelector": {
"helper": "Contact helpdesk@genesys-pgr.org to register additional crops."
}
},
"p": {
"display": {
"title": "Crop details",
"generalInformation": "General information",
"otherNames": "Other names"
},
"browse": {
"title": "Crop list",
"subTitle": "Genesys crops directory",
"createCrop": "Create crop"
}
}
},
"admin": {
"c": {
"cropForm": {
"cropCode": "Crop code",
"cropTitle": "Crop title",
"otherNames": "Other names",
"version": "Version: {{version: numeric}}",
"alreadyInUse": "Already in use"
}
},
"p": {
"edit": {
"onDelete": {
"message": "Delete crop '{{cropName, lowercase}}'?",
"description": "Note, deleting any crop causes mayhem."
}
}
}
},
"common": {
"modelName": "Crop",
"modelName_plural": "Crops",
"menu": "Crops",
"accessionsInGenesys": "Accessions in genesys"
}
}
\ No newline at end of file
import * as React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import { translate } from 'react-i18next';
// Actions
import {createCrop} from 'crop/actions/admin';
......@@ -19,6 +20,7 @@ import Authorize from 'ui/common/authorized/Authorize';