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": { "action": {
"add": "Add {{what, lowercase}}", "add": "Add {{what, lowercase}}",
"approve": "Approve",
"backTo": "Back to {{where, lowercase}}", "backTo": "Back to {{where, lowercase}}",
"backToDashboard": "Back to dashboard",
"cancel": "Cancel", "cancel": "Cancel",
"close": "Close",
"collapse": "Collapse",
"delete": "Delete", "delete": "Delete",
"download": "Download",
"edit": "Edit", "edit": "Edit",
"login": "Login", "login": "Login",
"logout": "Logout", "logout": "Logout",
"publish": "Publish",
"saveChanges": "Save changes",
"search": "Search", "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": { "label": {
"item": "Item", "item": "Item",
"item_plural": "Items", "item_plural": "Items",
"list": "List", "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": { "message": {
"confirmDelete": "Deleting the {{what, lowercase}} record is only possible when there is no associated data." "confirmDelete": "Deleting the {{what, lowercase}} record is only possible when there is no associated data."
}, },
"paginate": { "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 @@ ...@@ -19,17 +19,18 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"clean": "rimraf target", "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: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: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", "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", "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", "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", "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", "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", "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'" "i18nscan": "i18next-scanner --config i18next-scanner.config.js 'src/**/*.tsx'"
}, },
"dependencies": { "dependencies": {
...@@ -47,6 +48,7 @@ ...@@ -47,6 +48,7 @@
"es-cookie": "^1.2.0", "es-cookie": "^1.2.0",
"express": "^4.16.3", "express": "^4.16.3",
"express-http-proxy": "^1.2.0", "express-http-proxy": "^1.2.0",
"fast-glob": "^2.2.3",
"flattenjs": "^1.0.4", "flattenjs": "^1.0.4",
"form-data": "^2.3.2", "form-data": "^2.3.2",
"history": "^4.7.2", "history": "^4.7.2",
......
...@@ -25,11 +25,11 @@ export const loadUserProfile = () => (dispatch, getState) => { ...@@ -25,11 +25,11 @@ export const loadUserProfile = () => (dispatch, getState) => {
export const changePassword = (newPassword: string, oldPassword: string) => (dispatch, getState) => { export const changePassword = (newPassword: string, oldPassword: string) => (dispatch, getState) => {
return UserProfileService.changePassword(newPassword, oldPassword) return UserProfileService.changePassword(newPassword, oldPassword)
.then(() => { .then(() => {
dispatch(showSnackbar('Password was changed successfully')); dispatch(showSnackbar('user.common.passChangeSuccess'));
}).catch((error) => { }).catch((error) => {
const data = _.get(error, 'response.data'); const data = _.get(error, 'response.data');
log('Save error', data.error); 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 }); 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> { ...@@ -36,7 +36,7 @@ class BrowsePage extends React.Component<IBrowsePageProps & any, any> {
return ( return (
<PageLayout> <PageLayout>
<Authorize role="ROLE_ADMINISTRATOR"> <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> </Authorize>
<Grid container spacing={ 0 } className="back-gray"> <Grid container spacing={ 0 } className="back-gray">
{ crops && crops.sort((a, b) => a.name.localeCompare(b.name)).map((crop: Crop) => ( { crops && crops.sort((a, b) => a.name.localeCompare(b.name)).map((crop: Crop) => (
......
...@@ -3,6 +3,7 @@ import { connect } from 'react-redux'; ...@@ -3,6 +3,7 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import Grid from '@material-ui/core/Grid'; import Grid from '@material-ui/core/Grid';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
import { translate } from 'react-i18next';
import {log} from 'utilities/debug'; import {log} from 'utilities/debug';
...@@ -20,6 +21,7 @@ interface IDescriptorEditPageProps extends React.ClassAttributes<any> { ...@@ -20,6 +21,7 @@ interface IDescriptorEditPageProps extends React.ClassAttributes<any> {
saveCrop: (crop: Crop) => void; saveCrop: (crop: Crop) => void;
deleteCrop: (crop: Crop) => void; deleteCrop: (crop: Crop) => void;
crop: Crop; crop: Crop;
t: any;
} }
class CropEditPage extends React.Component<IDescriptorEditPageProps, any> { class CropEditPage extends React.Component<IDescriptorEditPageProps, any> {
...@@ -42,12 +44,12 @@ class CropEditPage extends React.Component<IDescriptorEditPageProps, any> { ...@@ -42,12 +44,12 @@ class CropEditPage extends React.Component<IDescriptorEditPageProps, any> {
} }
private onDelete = () => { private onDelete = () => {
const { crop, deleteCrop } = this.props; const { crop, deleteCrop, t } = this.props;
confirm(`Delete crop '${crop.name}'?`, { confirm(t('crops.admin.p.edit.onDelete.message', {cropName: crop.name}), {
description: `Note, deleting any crop causes mayhem.`, description: t('crops.admin.p.edit.onDelete.description'),
confirmLabel: 'Delete', confirmLabel: t('common:action.delete'),
abortLabel: 'Cancel', abortLabel: t('common:action.cancel'),
}).then(() => { }).then(() => {
deleteCrop(crop); deleteCrop(crop);
}).catch(() => { }).catch(() => {
...@@ -56,7 +58,7 @@ class CropEditPage extends React.Component<IDescriptorEditPageProps, any> { ...@@ -56,7 +58,7 @@ class CropEditPage extends React.Component<IDescriptorEditPageProps, any> {
} }
public render() { public render() {
const {shortName} = this.props; const {shortName, t} = this.props;
let {crop} = this.props; let {crop} = this.props;
if (!crop && !shortName) { if (!crop && !shortName) {
...@@ -72,7 +74,7 @@ class CropEditPage extends React.Component<IDescriptorEditPageProps, any> { ...@@ -72,7 +74,7 @@ class CropEditPage extends React.Component<IDescriptorEditPageProps, any> {
<PageLayout> <PageLayout>
<Grid item xs={ 12 }> <Grid item xs={ 12 }>
<Paper className="p-20 mb-10"> <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> </Paper>
</Grid> </Grid>
</PageLayout> </PageLayout>
...@@ -92,4 +94,4 @@ const mapDispatchToProps = (dispatch) => bindActionCreators({ ...@@ -92,4 +94,4 @@ const mapDispatchToProps = (dispatch) => bindActionCreators({
deleteCrop, deleteCrop,
}, dispatch); }, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(CropEditPage); export default translate()(connect(mapStateToProps, mapDispatchToProps)(CropEditPage));
...@@ -7,19 +7,19 @@ import {CROP_FORM} from 'constants/crop'; ...@@ -7,19 +7,19 @@ import {CROP_FORM} from 'constants/crop';
import {TextField} from 'ui/common/text-field'; import {TextField} from 'ui/common/text-field';
import Validators from 'utilities/Validators'; import Validators from 'utilities/Validators';
const CropForm = ({error, handleSubmit, initialValues, onDelete}) => { const CropForm = ({error, handleSubmit, initialValues, onDelete, t}) => {
return ( return (
<form onSubmit={ handleSubmit }> <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="shortName" label={ t('crop.admin.c.cropForm.cropCode') } component={ TextField } validate={ [ Validators.required, Validators.maxLength20 ] } />
<Field required name="name" label="Crop title" component={ TextField } validate={ [ Validators.required ] } /> <Field required name="name" label={ t('crop.admin.c.cropForm.cropTitle') } component={ TextField } validate={ [ Validators.required ] } />
<div>{ error && <strong>{ error }</strong> }</div> <div>{ error && <strong>{ error }</strong> }</div>
<Button variant="raised" type="submit">Save changes</Button> <Button variant="raised" type="submit">{ t('common:action.saveChanges') }</Button>
{ (initialValues._permissions && initialValues._permissions.delete) && <Button onClick={ onDelete } type="button">Delete</Button> } { (initialValues._permissions && initialValues._permissions.delete) && <Button onClick={ onDelete } type="button">{ t('common:action.delete') }</Button> }
<Link to="/crops"><Button type="button">Back to crop list</Button></Link> <Link to="/crops"><Button type="button">{ t('crop.admin.c.cropForm.back') }</Button></Link>
</form> </form>
); );
}; };
......
...@@ -54,7 +54,7 @@ const renderRadioButton = (input, code) => ( ...@@ -54,7 +54,7 @@ const renderRadioButton = (input, code) => (
const CropSelector = ({t, crops, label, single, fields, input}: ICropSelectorProps) => crops && ( const CropSelector = ({t, crops, label, single, fields, input}: ICropSelectorProps) => crops && (
<FormControl fullWidth> <FormControl fullWidth>
<FormLabel>{ label }</FormLabel> <FormLabel>{ label }</FormLabel>
<FormHelperText>{ t('m.crop.helper') }</FormHelperText> <FormHelperText>{ t('crop.public.c.cropSelector.helper') }</FormHelperText>
<Grid container> <Grid container>
{ {
crops.sort((a, b) => a.name.localeCompare(b.name)).map((crop: Crop) => ( crops.sort((a, b) => a.name.localeCompare(b.name)).map((crop: Crop) => (
......
...@@ -15,8 +15,8 @@ const publicRoutes = [ ...@@ -15,8 +15,8 @@ const publicRoutes = [
path: '/datasets', path: '/datasets',
component: Wrapper, component: Wrapper,
extraProps: { extraProps: {
title: 'p.datasets.title', title: 'datasets.common.modelName_plural',
subtitle: 'p.datasets.subtitle', subtitle: 'datasets.common.subtitle',
}, },
routes: [ routes: [
{ {
...@@ -44,7 +44,7 @@ const dashboardRoutes = [ ...@@ -44,7 +44,7 @@ const dashboardRoutes = [
component: DatasetStepper, component: DatasetStepper,
auth: [ROLE_USER, ROLE_ADMINISTRATOR], auth: [ROLE_USER, ROLE_ADMINISTRATOR],
extraProps: { extraProps: {
title: 'Phenotypic dataset publisher', title: 'datasets.dashboard.p.stepper.publisher',
}, },
routes: [ routes: [
{ {
...@@ -59,7 +59,7 @@ const dashboardRoutes = [ ...@@ -59,7 +59,7 @@ const dashboardRoutes = [
component: DatasetStepper, component: DatasetStepper,
auth: [ROLE_USER, ROLE_ADMINISTRATOR], auth: [ROLE_USER, ROLE_ADMINISTRATOR],
extraProps: { extraProps: {
title: 'Phenotypic dataset publisher', title: 'datasets.dashboard.p.stepper.publisher',
}, },
routes: [ routes: [
...datasetSteps, ...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",