- implement i18n

- translation dynamic for sections
- added articles for SEO
This commit is contained in:
Amruth Pillai
2020-07-16 08:42:19 +05:30
parent b7c565de79
commit a7657b4a5c
74 changed files with 2373 additions and 586 deletions

View File

@ -1,15 +1,16 @@
import React, { Fragment, memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Element } from 'react-scroll';
import sections from '../../../data/rightSections';
import RightNavbar from './RightNavbar';
import styles from './RightSidebar.module.css';
import About from './sections/About';
import Actions from './sections/Actions';
import Colors from './sections/Colors';
import Fonts from './sections/Fonts';
import Layout from './sections/Layout';
import Templates from './sections/Templates';
import Actions from './sections/Actions';
import Settings from './sections/Settings';
import About from './sections/About';
import Templates from './sections/Templates';
const getComponent = (id) => {
switch (id) {
@ -33,16 +34,22 @@ const getComponent = (id) => {
};
const RightSidebar = () => {
const { t } = useTranslation();
return (
<div className="flex">
<div id="RightSidebar" className={styles.container}>
{sections.map(({ id, name, event }) => {
{sections.map(({ id, event }) => {
const Component = getComponent(id);
return (
<Fragment key={id}>
<Element name={id}>
<Component id={id} name={name} event={event} />
<Component
id={id}
name={t(`builder.sections.${id}`)}
event={event}
/>
</Element>
<hr />
</Fragment>

View File

@ -1,31 +1,25 @@
import React, { memo } from 'react';
import { FaCoffee, FaBug } from 'react-icons/fa';
import { FaCoffee, FaBug, FaExternalLinkAlt } from 'react-icons/fa';
import { MdCode } from 'react-icons/md';
import { Trans, useTranslation } from 'react-i18next';
import Button from '../../../shared/Button';
import Heading from '../../../shared/Heading';
import styles from './About.module.css';
const About = () => {
const About = ({ name }) => {
const { t } = useTranslation();
return (
<section>
<Heading>About</Heading>
<Heading>{name}</Heading>
<div className={styles.container}>
<h5>Donate to Reactive Resume</h5>
<h5>{t('builder.about.donate.heading')}</h5>
<p className="leading-loose">
As you know, every nook and cranny of this app is free and
open-source, but servers don&apos;t pay for themselves.
</p>
<p className="leading-loose">
I try to do what I can, but if you found the app helpful, or
you&apos;re in a better position than the others who depend on this
project for their first job, please consider donating{' '}
<span className="font-semibold">
as little as $5 to help keep the project alive
</span>{' '}
:)
<Trans t={t} i18nKey="builder.about.donate.text">
A<span className="font-bold">B</span>C
</Trans>
</p>
<div className="mt-4 flex">
@ -34,19 +28,15 @@ const About = () => {
rel="noreferrer"
target="_blank"
>
<Button icon={FaCoffee}>Buy me a coffee!</Button>
<Button icon={FaCoffee}>{t('builder.about.donate.button')}</Button>
</a>
</div>
</div>
<div className={styles.container}>
<h5>Bug? Feature Request?</h5>
<h5>{t('builder.about.bugFeature.heading')}</h5>
<p className="leading-loose">
Something halting your progress from making a resume? Found a pesky
bug that just won&apos;t quit? Talk about it on the GitHub Issues
section, or send me and email using the actions below.
</p>
<p className="leading-loose">{t('builder.about.bugFeature.text')}</p>
<div className="mt-4 flex">
<a
@ -54,19 +44,27 @@ const About = () => {
rel="noreferrer"
target="_blank"
>
<Button icon={FaBug}>Raise an Issue</Button>
<Button icon={FaBug}>{t('builder.about.bugFeature.button')}</Button>
</a>
</div>
</div>
<div className={styles.container}>
<h5>Source Code</h5>
<h5>{t('builder.about.appreciate.heading')}</h5>
<p className="leading-loose">
Want to run the project from its source? Are you a developer willing
to contribute to the open-source development of this project? Click
the button below.
</p>
<p className="leading-loose">{t('builder.about.appreciate.text')}</p>
<div className="mt-4 flex">
<a href="https://amruthpillai.com" rel="noreferrer" target="_blank">
<Button icon={FaExternalLinkAlt}>amruthpillai.com</Button>
</a>
</div>
</div>
<div className={styles.container}>
<h5>{t('builder.about.sourceCode.heading')}</h5>
<p className="leading-loose">{t('builder.about.sourceCode.text')}</p>
<div className="mt-4 flex">
<a
@ -74,38 +72,20 @@ const About = () => {
rel="noreferrer"
target="_blank"
>
<Button icon={MdCode}>GitHub Repo</Button>
</a>
</div>
</div>
<div className={styles.container}>
<h5>License Information</h5>
<p className="leading-loose">
The project is governed under the MIT License, which you can read more
about below. Basically, you are allowed to use the project anywhere
provided you give credits to the original author.
</p>
<div className="mt-4 flex">
<a
href="https://github.com/AmruthPillai/Reactive-Resume/blob/master/LICENSE"
rel="noreferrer"
target="_blank"
>
<Button icon={MdCode}>MIT License</Button>
<Button icon={MdCode}>
{t('builder.about.sourceCode.button')}
</Button>
</a>
</div>
</div>
<div className="my-4 text-center opacity-50 text-sm">
<p>
Made with Love by{' '}
<Trans t={t} i18nKey="builder.about.footer">
A
<a href="https://amruthpillai.com" rel="noreferrer" target="_blank">
Amruth Pillai
B
</a>
</p>
</Trans>
</div>
</section>
);

View File

@ -1,5 +1,6 @@
import React, { memo, useContext, useState } from 'react';
import { FaFileExport, FaFileImport } from 'react-icons/fa';
import { useTranslation } from 'react-i18next';
import ModalContext from '../../../../contexts/ModalContext';
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
import Button from '../../../shared/Button';
@ -7,9 +8,15 @@ import Heading from '../../../shared/Heading';
import Input from '../../../shared/Input';
import styles from './Actions.module.css';
const Actions = () => {
const [loadDemoText, setLoadDemoText] = useState('Load Demo Data');
const [resetText, setResetText] = useState('Reset Everything');
const Actions = ({ name }) => {
const { t } = useTranslation();
const [loadDemoText, setLoadDemoText] = useState(
t('builder.actions.loadDemoData.button'),
);
const [resetText, setResetText] = useState(
t('builder.actions.resetEverything.button'),
);
const state = useSelector();
const dispatch = useDispatch();
@ -31,66 +38,57 @@ const Actions = () => {
};
const handleLoadDemo = () => {
if (loadDemoText === 'Load Demo Data') {
setLoadDemoText('Are you sure?');
if (loadDemoText === t('builder.actions.loadDemoData.button')) {
setLoadDemoText(t('shared.buttons.confirmation'));
return;
}
dispatch({ type: 'load_demo_data' });
setLoadDemoText('Load Demo Data');
setLoadDemoText(t('builder.actions.loadDemoData.button'));
};
const handleReset = () => {
if (resetText === 'Reset Everything') {
setResetText('Are you sure?');
if (resetText === t('builder.actions.resetEverything.button')) {
setResetText(t('shared.buttons.confirmation'));
return;
}
setResetText('Reset Everything');
setResetText(t('builder.actions.resetEverything.button'));
dispatch({ type: 'reset_data' });
};
return (
<section>
<Heading>Actions</Heading>
<Heading>{name}</Heading>
<div className={styles.container}>
<h5>Import Your Resume</h5>
<h5>{t('builder.actions.import.heading')}</h5>
<p className="leading-loose">
You can import your information from various sources like JSON Resume
or your LinkedIn to autofill most of the data for your resume.
</p>
<p className="leading-loose">{t('builder.actions.import.text')}</p>
<div className="mt-4 flex">
<Button icon={FaFileImport} onClick={handleImport}>
Import
{t('builder.actions.import.button')}
</Button>
</div>
</div>
<div className={styles.container}>
<h5>Export Your Resume</h5>
<h5>{t('builder.actions.export.heading')}</h5>
<p className="leading-loose">
Export your resume as a PDF to share with recruiters or a JSON that
you will be able to import back onto this app on another computer.
</p>
<p className="leading-loose">{t('builder.actions.export.text')}</p>
<div className="mt-4 flex">
<Button icon={FaFileExport} onClick={handleExport}>
Export
{t('builder.actions.export.button')}
</Button>
</div>
</div>
<div className={styles.container}>
<h5>Share Your Resume</h5>
<h5>{t('builder.actions.share.heading')}</h5>
<p className="leading-loose">
The link below will be accessible publicly if you choose to share it,
and viewers would see the latest version of your resume at any time.
</p>
<p className="leading-loose">{t('builder.actions.export.text')}</p>
<div>
<Input
@ -102,11 +100,10 @@ const Actions = () => {
</div>
<div className={styles.container}>
<h5>Load Demo Data</h5>
<h5>{t('builder.actions.loadDemoData.button')}</h5>
<p className="leading-loose">
Unclear on what to do with a fresh blank page? Load some demo data to
see how a resume should look and you can start editing from there.
{t('builder.actions.loadDemoData.text')}
</p>
<div className="mt-4 flex">
@ -115,11 +112,10 @@ const Actions = () => {
</div>
<div className={styles.container}>
<h5>Reset Everything</h5>
<h5>{t('builder.actions.resetEverything.button')}</h5>
<p className="leading-loose">
Feels like you made too many mistakes? No worries, clear everything
with just one click, but be careful if there are no backups.
{t('builder.actions.resetEverything.text')}
</p>
<div className="mt-4 flex">

View File

@ -1,5 +1,6 @@
/* eslint-disable jsx-a11y/control-has-associated-label */
import React, { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from '../../../../contexts/ResumeContext';
import colorOptions from '../../../../data/colorOptions';
import { handleKeyUp } from '../../../../utils';
@ -7,8 +8,9 @@ import Heading from '../../../shared/Heading';
import Input from '../../../shared/Input';
import styles from './Colors.module.css';
const Colors = () => {
const Colors = ({ name }) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const handleClick = (value) => {
dispatch({
@ -22,7 +24,7 @@ const Colors = () => {
return (
<section>
<Heading>Colors</Heading>
<Heading>{name}</Heading>
<div className="mb-6 grid grid-cols-8 col-gap-2 row-gap-6">
{colorOptions.map((color) => (
@ -41,7 +43,7 @@ const Colors = () => {
<Input
type="color"
name="primary"
label="Primary Color"
label={t('builder.colors.primary')}
placeholder="#FF4081"
path="metadata.colors.primary"
/>
@ -49,7 +51,7 @@ const Colors = () => {
<Input
type="color"
name="text"
label="Text Color"
label={t('builder.colors.text')}
placeholder="#444444"
path="metadata.colors.text"
/>
@ -57,7 +59,7 @@ const Colors = () => {
<Input
type="color"
name="background"
label="Background Color"
label={t('builder.colors.background')}
placeholder="#FFFFFF"
path="metadata.colors.background"
/>

View File

@ -6,7 +6,7 @@ import { handleKeyUp } from '../../../../utils';
import Heading from '../../../shared/Heading';
import styles from './Fonts.module.css';
const Fonts = () => {
const Fonts = ({ name }) => {
const dispatch = useDispatch();
const font = useSelector('metadata.font');
@ -22,7 +22,7 @@ const Fonts = () => {
return (
<section>
<Heading>Fonts</Heading>
<Heading>{name}</Heading>
<div className="grid grid-cols-2 gap-8">
{fontOptions.map((x) => (

View File

@ -1,13 +1,17 @@
import React, { memo, useState } from 'react';
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
import { move, reorder } from '../../../../utils';
import Button from '../../../shared/Button';
import Heading from '../../../shared/Heading';
import styles from './Layout.module.css';
const Layout = () => {
const [resetLayoutText, setResetLayoutText] = useState('Reset Layout');
const Layout = ({ name }) => {
const { t } = useTranslation();
const [resetLayoutText, setResetLayoutText] = useState(
t('builder.layout.reset'),
);
const template = useSelector('metadata.template');
const blocks = useSelector(`metadata.layout.${template}`, [[]]);
@ -49,22 +53,21 @@ const Layout = () => {
};
const handleResetLayout = () => {
if (resetLayoutText === 'Reset Layout') {
setResetLayoutText('Are you sure?');
if (resetLayoutText === t('builder.layout.reset')) {
setResetLayoutText(t('shared.buttons.confirmation'));
return;
}
dispatch({ type: 'reset_layout' });
setResetLayoutText('Reset Layout');
setResetLayoutText(t('builder.layout.reset'));
};
return (
<section>
<Heading>Layout</Heading>
<Heading>{name}</Heading>
<p className="leading-loose">
This template supports {blocks.length} blocks. You can re-order or move
sections by dragging/dropping the section names across lists.
{t('builder.layout.text', { count: blocks.length })}
</p>
<div className={`grid gap-8 grid-cols-${blocks.length}`}>
@ -79,7 +82,7 @@ const Layout = () => {
>
<div className="grid gap-3">
<span className="uppercase font-semibold text-xs">
Block {ind + 1}
{t('builder.layout.block')} {ind + 1}
</span>
{el.map((item, index) => (
<Draggable key={item} index={index} draggableId={item}>
@ -90,7 +93,7 @@ const Layout = () => {
{...dragProvided.draggableProps}
{...dragProvided.dragHandleProps}
>
{item}
{t(`builder.sections.${item}`)}
</div>
)}
</Draggable>

View File

@ -3,5 +3,5 @@
}
.draggable {
@apply px-4 py-2 capitalize font-medium rounded bg-primary-200;
@apply px-4 py-2 font-medium rounded bg-primary-200;
}

View File

@ -1,30 +1,42 @@
import React, { memo, useContext, useState } from 'react';
import { FaAngleDown } from 'react-icons/fa';
import { useTranslation, Trans } from 'react-i18next';
import UserContext from '../../../../contexts/UserContext';
import Button from '../../../shared/Button';
import Heading from '../../../shared/Heading';
import styles from './Settings.module.css';
import Input from '../../../shared/Input';
import ThemeContext from '../../../../contexts/ThemeContext';
import SettingsContext from '../../../../contexts/SettingsContext';
import themeConfig from '../../../../data/themeConfig';
import languageConfig from '../../../../data/languageConfig';
import { languages } from '../../../../i18n';
import { useDispatch } from '../../../../contexts/ResumeContext';
const Settings = () => {
const [deleteText, setDeleteText] = useState('Delete Account');
const Settings = ({ name }) => {
const { t } = useTranslation();
const [deleteText, setDeleteText] = useState(
t('builder.settings.dangerZone.button'),
);
const dispatch = useDispatch();
const { deleteAccount } = useContext(UserContext);
const { theme, setTheme } = useContext(ThemeContext);
const { theme, setTheme, language, setLanguage } = useContext(
SettingsContext,
);
const handleChangeTheme = (e) => {
setTheme(e.target.value);
};
const handleChangeLanguage = (e) => {
console.log(e.target.value);
const lang = e.target.value;
setLanguage(lang);
dispatch({ type: 'change_language', payload: lang });
};
const handleDeleteAccount = () => {
if (deleteText === 'Delete Account') {
setDeleteText('Are you sure?');
if (deleteText === t('builder.settings.dangerZone.button')) {
setDeleteText(t('shared.buttons.confirmation'));
return;
}
@ -36,10 +48,10 @@ const Settings = () => {
return (
<section>
<Heading>Settings</Heading>
<Heading>{name}</Heading>
<Input
label="Theme"
label={t('builder.settings.theme')}
type="dropdown"
options={Object.keys(themeConfig)}
value={theme}
@ -47,11 +59,11 @@ const Settings = () => {
/>
<label>
<span>Language</span>
<span>{t('builder.settings.language')}</span>
<div className="relative grid items-center">
<select onChange={handleChangeLanguage}>
{languageConfig.map((x) => (
<option key={x.key} value={x.key}>
<select onChange={handleChangeLanguage} value={language}>
{languages.map((x) => (
<option key={x.code} value={x.code}>
{x.name}
</option>
))}
@ -65,26 +77,23 @@ const Settings = () => {
</label>
<p className="text-sm leading-loose">
If you would like to contribute by providing translations to Reactive
Resume in your language,{' '}
<a
href="https://github.com/AmruthPillai/Reactive-Resume/blob/master/TRANSLATING.md"
rel="noreferrer"
target="_blank"
>
please visit this link
</a>
.
<Trans t={t} i18nKey="builder.settings.translate">
A
<a
href="https://github.com/AmruthPillai/Reactive-Resume/blob/master/TRANSLATING.md"
rel="noreferrer"
target="_blank"
>
B
</a>
C
</Trans>
</p>
<div className={styles.container}>
<h5>Danger Zone</h5>
<h5>{t('builder.settings.dangerZone.heading')}</h5>
<p className="leading-loose">
If you would like to delete your account and erase all your resumes,
its just one button away. Please be weary as this is an irreversible
process.
</p>
<p className="leading-loose">{t('builder.settings.dangerZone.text')}</p>
<div className="mt-4 flex">
<Button isDelete onClick={handleDeleteAccount}>

View File

@ -8,7 +8,7 @@ import { handleKeyUp } from '../../../../utils';
import Heading from '../../../shared/Heading';
import styles from './Templates.module.css';
const Templates = () => {
const Templates = ({ name }) => {
const dispatch = useDispatch();
const template = useSelector('metadata.template');
@ -71,7 +71,7 @@ const Templates = () => {
return (
<section>
<Heading>Templates</Heading>
<Heading>{name}</Heading>
<div className="grid grid-cols-2 gap-8">
{templateOptions.map((x) => (