Commit 571e1eaa authored by Matija Obreza's avatar Matija Obreza

Merge branch '402-i18n' into 'master'

Resolve "i18n"

Closes #402

See merge request !318
parents dab5265b eab4fc9f
Pipeline #7459 passed with stages
in 7 minutes and 59 seconds
const fg = require('fast-glob');
const _ = require('lodash');
const fs = require('fs');
const getPrefix = (path) => path.substring(path.indexOf('./src/') + './src/'.length, path.indexOf('/translations.json'));
fg([`./src/**/translations.json`, `./src/translations.json`])
.then((entries) => entries.sort((a, b) => getPrefix(a).localeCompare(getPrefix(b))))
.then((entries) => entries.map((path) => getTranslations(path)))
.then((content) => fs.writeFileSync(`locales/en/translations.json`, `{\n${content}}`));
const getTranslations = (path) => {
const prefix = getPrefix(path);
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}}",
"approve": "Approve",
"backTo": "Back to {{where, lowercase}}",
"backToDashboard": "Back to dashboard",
"cancel": "Cancel",
"close": "Close",
"collapse": "Collapse",
"delete": "Delete",
"download": "Download",
"edit": "Edit",
"login": "Login",
"logout": "Logout",
"publish": "Publish",
"saveChanges": "Save changes",
"search": "Search",
"viewDetails": "View details"
"unpublish": "Unpublish",
"un-publish": "Un-publish",
"viewDetails": "View details",
"applyFilters": "Apply filters",
"back": "Back",
"backToList": "Back to list",
"create": "Create",
"deleteData": "Delete data",
"manage": "Manage",
"nextStep": "Next step",
"openSidebar": "Open sidebar",
"read": "Read",
"reject": "Reject",
"remove": "Remove",
"reset": "Reset",
"return": "Return",
"save": "Save",
"show": "Show",
"sendToReview": "Send to review",
"view": "View",
"write": "Write",
"acceptAndPublish": "ACCEPT AND PUBLISH"
},
"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",
"keyword": "Keyword search",
"fromIncluding": "From including",
"lastModifiedDate": "Last modified date",
"textSearch": "Text search"
},
"label": {
"item": "Item",
"item_plural": "Items",
"list": "List",
"list_plural": "Lists"
"list_plural": "Lists",
"created": "Created",
"dateNotProvided": "Date not provided",
"description": "Description",
"either": "Either",
"filters": "Filters",
"from": "From",
"itemEditorWarn": "Don't use the ItemsEditor for more than 100 items!",
"lastModified": "Last modified",
"lastUpdated": "Last updated",
"modified": "Modified",
"no": "No",
"prettyNumber": "{{value, number}}",
"registered": "Registered",
"sortBy": "Sort By",
"status": "Status",
"stepsForDataPublication": "Steps for data publication completion",
"title": "Title",
"to": "To",
"untitled": "Untitled",
"UUID": "UUID",
"version": "Version {{version, numeric}}",
"yes": "Yes",
"basicMarkdown": "Basic markdown supported",
"fullMarkdown": "Full markdown supported",
"previewMarkdown": "Preview markdown"
},
"message": {
"confirmDelete": "Deleting the {{what, lowercase}} record is only possible when there is no associated data."
},
"paginate": {
"numberOfItems": "{{count}} {{what, lowercase}}"
"numberOfItems": "{{count}} {{what, lowercase}}",
"estimatedNumberOfItems": "About {{count, number}} {{what, lowercase}}"
},
"permissions": {
"label": "Permissions",
"managePermissions": "Manage permissions",
"class": "Class",
"objectID": "Object ID",
"owner": "Owner",
"inheritsPermissions": "inheritsPermissions",
"effectivePermissions": "Effective permissions",
"sid": "Sid"
},
"snackbar": {
"resettingFilters": "Resetting filters...",
"applyingFilters": "Applying filters..."
},
"sort": {
"title": "Title",
"version": "Version",
"owner": "Owner",
"createdDate": "Created date",
"latestEdit": "Latest edit",
"lastModifiedDateAsc": "Last modified date (oldest first)",
"lastModifiedDateDesc": "Last update (newest first)"
},
"status": {
"published": "Published",
"inReview": "In review",
"inProgress": "In progress"
}
}
This diff is collapsed.
This diff is collapsed.
......@@ -19,17 +19,18 @@
"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",
"build:serverdebug": "cross-env 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",
"build:ssrdebug": "rimraf target && npm run build && npm run build:serverdebug",
"debug:ssr": "npm run build:ssrdebug && cd target/app/server && cross-env SSR=true node server.js",
"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": {
......@@ -47,6 +48,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",
......
......@@ -25,11 +25,11 @@ export const loadUserProfile = () => (dispatch, getState) => {
export const changePassword = (newPassword: string, oldPassword: string) => (dispatch, getState) => {
return UserProfileService.changePassword(newPassword, oldPassword)
.then(() => {
dispatch(showSnackbar('Password was changed successfully'));
dispatch(showSnackbar('user.common.passChangeSuccess'));
}).catch((error) => {
const data = _.get(error, 'response.data');
log('Save error', data.error);
dispatch(showSnackbar(data.error || 'Password change error'));
dispatch(showSnackbar(data.error || 'user.common.passChangeError'));
throw new SubmissionError({ title: 'Password change error', _error: data.error });
});
};
......
{
"admin": {
"c": {
"cropForm": {
"cropCode": "Crop code",
"cropTitle": "Crop title",
"back": "Back to crop list"
}
},
"p": {
"edit": {
"onDelete": {
"message": "Delete crop '{{cropName, lowercase}}'?",
"description": "Note, deleting any crop causes mayhem."
}
}
}
},
"common": {
"modelName": "Crop",
"modelName_plural": "Crops",
"subtitle": "List of crops registered with Genesys",
"title": "Crops"
},
"public": {
"c": {
"cropSelector": {
"helper": "Contact helpdesk@genesys-pgr.org to register additional crops."
}
},
"p": {
"browse": {
"title": "What do you want to do?"
}
}
}
}
\ No newline at end of file
......@@ -36,7 +36,7 @@ class BrowsePage extends React.Component<IBrowsePageProps & any, any> {
return (
<PageLayout>
<Authorize role="ROLE_ADMINISTRATOR">
<ContentHeaderWithButton title="What do you want to do?" buttons={ <Button variant="raised" onClick={ createCrop }>{ t('common:action.add', { what: t('label.crop') }) }</Button> } />
<ContentHeaderWithButton title={ t('crop.public.p.browse.title') } buttons={ <Button variant="raised" onClick={ createCrop }>{ t('common:action.add', { what: t('crop.common.modelName') }) }</Button> } />
</Authorize>
<Grid container spacing={ 0 } className="back-gray">
{ crops && crops.sort((a, b) => a.name.localeCompare(b.name)).map((crop: Crop) => (
......
......@@ -3,6 +3,7 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Grid from '@material-ui/core/Grid';
import Paper from '@material-ui/core/Paper';
import { translate } from 'react-i18next';
import {log} from 'utilities/debug';
......@@ -20,6 +21,7 @@ interface IDescriptorEditPageProps extends React.ClassAttributes<any> {
saveCrop: (crop: Crop) => void;
deleteCrop: (crop: Crop) => void;
crop: Crop;
t: any;
}
class CropEditPage extends React.Component<IDescriptorEditPageProps, any> {
......@@ -42,12 +44,12 @@ class CropEditPage extends React.Component<IDescriptorEditPageProps, any> {
}
private onDelete = () => {
const { crop, deleteCrop } = this.props;
const { crop, deleteCrop, t } = this.props;
confirm(`Delete crop '${crop.name}'?`, {
description: `Note, deleting any crop causes mayhem.`,
confirmLabel: 'Delete',
abortLabel: 'Cancel',
confirm(t('crops.admin.p.edit.onDelete.message', {cropName: crop.name}), {
description: t('crops.admin.p.edit.onDelete.description'),
confirmLabel: t('common:action.delete'),
abortLabel: t('common:action.cancel'),
}).then(() => {
deleteCrop(crop);
}).catch(() => {
......@@ -56,7 +58,7 @@ class CropEditPage extends React.Component<IDescriptorEditPageProps, any> {
}
public render() {
const {shortName} = this.props;
const {shortName, t} = this.props;
let {crop} = this.props;
if (!crop && !shortName) {
......@@ -72,7 +74,7 @@ class CropEditPage extends React.Component<IDescriptorEditPageProps, any> {
<PageLayout>
<Grid item xs={ 12 }>
<Paper className="p-20 mb-10">
<CropForm initialValues={ crop } onSubmit={ this.onSave } onDelete={ this.onDelete } />
<CropForm initialValues={ crop } onSubmit={ this.onSave } onDelete={ this.onDelete } t={ t }/>
</Paper>
</Grid>
</PageLayout>
......@@ -92,4 +94,4 @@ const mapDispatchToProps = (dispatch) => bindActionCreators({
deleteCrop,
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(CropEditPage);
export default translate()(connect(mapStateToProps, mapDispatchToProps)(CropEditPage));
......@@ -7,19 +7,19 @@ import {CROP_FORM} from 'constants/crop';
import {TextField} from 'ui/common/text-field';
import Validators from 'utilities/Validators';
const CropForm = ({error, handleSubmit, initialValues, onDelete}) => {
const CropForm = ({error, handleSubmit, initialValues, onDelete, t}) => {
return (
<form onSubmit={ handleSubmit }>
{ initialValues && initialValues.version && <div>Version: { initialValues.version }</div> }
{ initialValues && initialValues.version && <div> { t('common:label.version', {version: initialValues.version}) }</div> }
<Field required name="shortName" label="Crop code" component={ TextField } validate={ [ Validators.required, Validators.maxLength20 ] } />
<Field required name="name" label="Crop title" component={ TextField } validate={ [ Validators.required ] } />
<Field required name="shortName" label={ t('crop.admin.c.cropForm.cropCode') } component={ TextField } validate={ [ Validators.required, Validators.maxLength20 ] } />
<Field required name="name" label={ t('crop.admin.c.cropForm.cropTitle') } component={ TextField } validate={ [ Validators.required ] } />
<div>{ error && <strong>{ error }</strong> }</div>
<Button variant="raised" type="submit">Save changes</Button>
{ (initialValues._permissions && initialValues._permissions.delete) && <Button onClick={ onDelete } type="button">Delete</Button> }
<Link to="/crops"><Button type="button">Back to crop list</Button></Link>
<Button variant="raised" type="submit">{ t('common:action.saveChanges') }</Button>
{ (initialValues._permissions && initialValues._permissions.delete) && <Button onClick={ onDelete } type="button">{ t('common:action.delete') }</Button> }
<Link to="/crops"><Button type="button">{ t('crop.admin.c.cropForm.back') }</Button></Link>
</form>
);
};
......
......@@ -54,7 +54,7 @@ const renderRadioButton = (input, code) => (
const CropSelector = ({t, crops, label, single, fields, input}: ICropSelectorProps) => crops && (
<FormControl fullWidth>
<FormLabel>{ label }</FormLabel>
<FormHelperText>{ t('m.crop.helper') }</FormHelperText>
<FormHelperText>{ t('crop.public.c.cropSelector.helper') }</FormHelperText>
<Grid container>
{
crops.sort((a, b) => a.name.localeCompare(b.name)).map((crop: Crop) => (
......
......@@ -15,8 +15,8 @@ const publicRoutes = [
path: '/datasets',
component: Wrapper,
extraProps: {
title: 'p.datasets.title',
subtitle: 'p.datasets.subtitle',
title: 'datasets.common.modelName_plural',
subtitle: 'datasets.common.subtitle',
},
routes: [
{
......@@ -44,7 +44,7 @@ const dashboardRoutes = [
component: DatasetStepper,
auth: [ROLE_USER, ROLE_ADMINISTRATOR],
extraProps: {
title: 'Phenotypic dataset publisher',
title: 'datasets.dashboard.p.stepper.publisher',
},
routes: [
{
......@@ -59,7 +59,7 @@ const dashboardRoutes = [
component: DatasetStepper,
auth: [ROLE_USER, ROLE_ADMINISTRATOR],
extraProps: {
title: 'Phenotypic dataset publisher',
title: 'datasets.dashboard.p.stepper.publisher',
},
routes: [
...datasetSteps,
......
{
"common": {
"delete": "Deleting the dataset will remove all related data.",
"modelName": "Dataset",
"modelName_plural": "Datasets",
"menu": "Datasets",
"creator": {
"role": {
"MANAGER": "Data manager",
"COLLECTOR": "Data collector",
"DIGITIZER": "Data digitizer",
"CURATOR": "Data curator",
"null": "Not specified"
}
}
},
"dashboard": {
"p": {
"stepper": {
"basicInfo": {
"title": "Basic information",
"titleField": {
"label": "Dataset title",
"placeholder": "Name given to the dataset (e.g., Characterization of maize accessions in Kenya)"
},
"partner": {
"label": "Select Data provider",
"placeholder": "Data provider"
},
"version": {
"label": "Dataset version"
},
"description": {
"label": "Dataset description",
"placeholder": "An abstract, short or long description of the dataset. Descriptive details improves discoverability of the resource."
},
"created": {
"label": "Date of creation of the dataset",
"placeholder": "YYYYMMDD. If the month or day are missing, this should be indicated with ‘00’ [double zero] (e.g. 1975----, 19750000; 197506--, 19750600)"
},
"language": {
"label": "Language",
"placeholder": "Select language of the dataset"
},
"source": {
"label": "Source",
"placeholder": "A related resource from which the described dataset is derived (e.g., journal article, data article, conference proceedings). Use DOI, URL, Journal title, or Journal/conference title; vol., no. (year)"
},
"crops": {
"label": "Crops"
}
},
"attachments": {
"title": "Dataset attachments",
"name": "File name",
"label": "File label",
"description": "Description of file content",
"upload": "Upload file",
"datasetFile": "Dataset file"
},
"creators": {
"role": "Role",
"title": "Dataset creators",
"name": "Full name",
"namePlaceholder": "Jane A. Doe",
"instAffiliation": "Institutional affiliation",
"email": "Email address",
"phone": "Phone number",
"fax": "Fax",
"phonePlaceholder": "+1 555 1231 Ext. 42",
"instAddress": "Institutional address",
"instAddressPlaceholder": "Address of institution of affiliation when the dataset was created.",
"add": "Add dataset creator"
},
"location": {
"title": "Location and timing",
"search": "Search...",
"copyright": "{{openingTag}}OpenStreetMap{{closingTag}} contributors",
"add": "Add another location",
"iso": "ISO3 Country code",
"isoPlaceholder": "GBR",
"description": "Description of environment and conditions at the site of evaluation",
"endingDate": "Ending date of characterization/evaluation",
"datePlaceholder": "YYYYMMDD. If the month or day are missing, this should be indicated with ‘00’ [double zero] (e.g. 1975----, 19750000; 197506--, 19750600)",
"startingDate": "Starting date of characterization/evaluation",
"longitude": "Decimal longitude",
"longitudePlaceholder": "The longitude of the site where the characterization/evaluation was conducted (decimal degrees).",
"latitude": "Decimal latitude",
"latitudePlaceholder": "The latitude of the site where the characterization/evaluation was conducted (decimal degrees).",
"locality": "Locality",
"localityPlaceholder": "Description of the locality where the characterization/evaluation was performed.",
"state": "State",
"country": "Country",
"countryPlaceholder": "Select country where the characterization/evaluation was performed."
},
"listOfAccessions": {
"title": "List of accessions",
"instructions": "INSTRUCTIONS FOR USE",
"useSheet": "Use the \"Acccessions\" sheet from the template",
"template": "Genesys Catalog template",
"rowsDescr": "The first row in the template contains the header names, whilst the second contains a description of the content expected on each column.",
"dontChangeNames": "Do not change the header names in the template",
"templateInstruction": "Fill the template with the informations of the accessions, remove the row that contains the description of each column (second row) and save the file.",
"excel": "Copy and paste the table from Excel into the text field \"List of accessions described in the dataset\".",
"listDescribed": "List of accessions described in the dataset",
"placeholder": "Paste accessions data here (comma separated)",
"rowCount": "Accessions {{rows, numeric}} rows"
},
"pastingTraits": {
"title": "Select descriptors in batch",
"matched": "Matched",
"noDescriptors": "No matching descriptors"
},
"traits": "Browse and select descriptors",
"reorder": "Reorder descriptors",
"review": "Review and publish",
"subtitle": "Publish your datasets",
"publisher": "Phenotypic dataset publisher"
}
}
},
"public": {
"common": {
"accessionsNumber": "Number of accessions",
"crop": "Crop",
"traitsNumber": "Number of traits"
},
"c": {
"card": {
"evaluationPeriod": "Evaluation period"
},
"datasetDisplay": {
"accessionsEvaluated": "Accessions evaluated",
"contact": "Data provider contact information",
"creators": "Dataset creators",
"dataAndResources": "Data and resources",
"datasetFile": "Dataset file",
"datasetMetadata": "Dataset metadata",
"otherMetadata": "Other Metadata",
"locations": "Locations",
"traitsObserved": "Traits observed",
"useAndLicensing": "Dataset use and licensing",
"confirmDescription": {
"approve": "After approving the dataset no changes are permitted, a new version must be created.",
"onPublish": "The dataset will be reviewing by administrator before publishing."
},
"properties": {
"datasetDate": "Date of dataset",
"email": "Email",
"evaluationEnd": "End of evaluation",
"evaluationStart": "Start of evaluation",
"licensed": "Licensed under",
"metadataCreateDate": "Metadata create date",
"metadataUpdatedDate": "Metadata updated date",
"moreInformation": "More information",
"relatedResources": "Related resources",
"phone": "Phone number",
"address": "Address"
}
},
"locationMap": {
"dateOfEvaluation": "Date of evaluation",
"description": "Description",
"iso": "ISO3 country code",
"location": "Location",
"link": "{{aOpen}}OpenStreetMap{{aClose}} contributors"
},
"suggestionsForm": {
"matches": "Suggested matches",
"apply": "Apply filters"
},
"generic": "Type {{boldHit}} does not have a SearchHit implementation."
},
"f": {
"traitKeywords": "Trait keywords",
"rice": "mardi rice",
"partner": "Data provider",
"faoWiews": "FAO WIEWS Institute code",
"evaluationSite": "Evaluation site",
"countryEv": "Country evaluation",
"countryPlaceholder": "Germany",
"latitude": "Latitude",
"longitude": "Longitude",
"crop": "Crop",
"accession": "Accessions",
"genus": "Genus",
"genusPlaceholder": "Manihot",
"accNumber": "Accession number",
"accDoi": "Accession DOI",
"licence": "Licence",
"rights": "rights",
"keywordSearch": "Keyword search"
},
"p": {
"browse": {
"subtitle": "Datasets published by Genesys data providers"
},
"display": {
"title": "Dataset details"
}
}
},
"sort": {
"accessionCountAsc": "Accession count (low to high)",
"accessionCountDesc": "Accession count (high to low)",
"descriptorCountAsc": "Descriptor count (low to high)",
"descriptorCountDesc": "Descriptor count (high to low)",
"startDate": "Experiment start date",
"endDate": "Experiment end date"
}
}
\ No newline at end of file
......@@ -120,12 +120,12 @@ class BrowsePage extends React.Component<IDatasetsProps, any> {
<DatasetFilters initialValues={ pagination.filter } onSubmit={ this.applyFilters } />
}>
<ScrollToTopOnMount />
<ContentHeader title="Datasets" subtitle="Datasets published by Genesys partners" />
<ContentHeader title="datasets.common.modelName_plural" subtitle="datasets.public.p.browse.subtitle" />
<Grid item xs={ 12 }>
<PaginationComponent
pageObj={ paged }
onChange={ this.onPaginationChange }
displayName="label.dataset"
displayName="datasets.common.modelName"
sortOptions={ Dataset.SORT_OPTIONS }
infinite
/>
......
......@@ -59,7 +59,7 @@ class DatasetDetail extends React.Component<IDatasetDetailProps, any> {
<PageLayout>
<Grid container spacing={ 0 }>
<Grid item xs={ 12 } className={ `back-gray-yellow pl-20 pr-20 pt-10 pb-10 ${classes.backSection}` }>
<h4 className="font-bold lh-35 m-0">{ t('p.dataset.title') }</h4>
<h4 className="font-bold lh-35 m-0">{ t('datasets.public.p.display.title') }</h4>
<div className={ classes.flexGrow }/>
<BackButton defaultTarget="/datasets" defaultBackText={ t('common:action.backTo', { where: t('common:label.list') }) }/>
</Grid>
......
import * as React from 'react';
import {translate} from 'react-i18next';
import Dataset from 'model/catalog/Dataset';
......@@ -14,12 +15,13 @@ import McpdDate from 'ui/common/time/McpdDate';
interface IDatasetCardProps extends React.ClassAttributes<any> {
className?: string;
dataset: Dataset;
t: any;
}
class DatasetCard extends React.Component<IDatasetCardProps, any> {
public render() {
const { dataset, className } = this.props;
const { dataset, className, t } = this.props;
if (! dataset) {
return null;
......@@ -39,13 +41,23 @@ class DatasetCard extends React.Component<IDatasetCardProps, any> {
<CardContent>
<Markdown className="pb-20" firstParagraph source={ dataset.description } />
<Properties>
<PropertiesItem title="Data provider:"><PartnerLink to={ dataset.owner } /></PropertiesItem>
<PropertiesItem title={ t('partners.common.modelName') }>
<PartnerLink to={ dataset.owner } />
</PropertiesItem>
{ dataset.crops && dataset.crops.length > 0 &&
<PropertiesItem title="Crop:"><CropChips crops={ dataset.crops } /></PropertiesItem>
<PropertiesItem title={ t('crops.common.modelName') }>
<