Commit eab4fc9f authored by Oleksii Savran's avatar Oleksii Savran Committed by Viacheslav Pavlov

I18n

translated ContentHeader

translated ContentHeader (fixed page title)

fixed ContentHeader

translated crops

Translated partners

added i18n for vocabulary module

added i18n for datasets module

added i18n for descriptor lists module

added i18n for descriptors module

added i18n for several common components

added missing translations, added build script

fix after rebase
parent dab5265b
Pipeline #7453 passed with stages
in 5 minutes and 7 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>