diff --git a/.gitignore b/.gitignore index 277d477f9..bf1d8208e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - # dependencies /node_modules /.pnp diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..13a0b097b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "i18n-ally.localesPaths": "src/i18n", + "i18n-ally.keystyle": "nested" +} \ No newline at end of file diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 000000000..c673fdf6a --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,3 @@ +files: + - source: /src/i18n/source/**/*.json + translation: /src/i18n/locales/%two_letters_code%/**/%original_file_name% diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 8b89301ea..29029204d 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -15,6 +15,7 @@ module.exports = { '/templates/', '/technology/', '/contributing/', + '/translation/', '/building-from-source/', '/deployment/', '/changelog/', diff --git a/docs/contributing/README.md b/docs/contributing/README.md index d3baaf187..f7d90761b 100644 --- a/docs/contributing/README.md +++ b/docs/contributing/README.md @@ -30,9 +30,7 @@ Something that's missing on the app that's halting your progress from making the ## Translation -Currently, there is no translation engine in place that allows for people to easily make translations. I'm still figuring out the most elegant way to implement this feature with minimal code and maximum reach. For now, if you would like to see this feature, please vote in the GitHub Issue linked below and if you are willing to contribute, mention in the comments of the issue which language you would like to translate to. - -[Vote for Translation Engine Feature ](https://github.com/AmruthPillai/Reactive-Resume/issues/18) +For information on how to translate the app into your own language, please visit the [Translation](/translation/) section of the documentation. ## Commit Code diff --git a/docs/translation/README.md b/docs/translation/README.md new file mode 100644 index 000000000..ccaffb693 --- /dev/null +++ b/docs/translation/README.md @@ -0,0 +1,5 @@ +--- +title: Translation +--- + +# Translation diff --git a/package-lock.json b/package-lock.json index b4affcf75..2fc75a1a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8617,6 +8617,14 @@ } } }, + "html-parse-stringify2": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz", + "integrity": "sha1-3FZwtyksoVi3vJFsmmc1rIhyg0o=", + "requires": { + "void-elements": "^2.0.1" + } + }, "html-tags": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz", @@ -8790,6 +8798,14 @@ "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz", "integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ==" }, + "i18next": { + "version": "19.3.4", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-19.3.4.tgz", + "integrity": "sha512-ef7AxxutzdhBsBNugE9jgqsbwesG1muJOtZ9ZrPARPs/jXegViTp4+8JCeMp8BAyTIo1Zn0giqc8+2UpqFjU0w==", + "requires": { + "@babel/runtime": "^7.3.1" + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -14234,6 +14250,15 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.7.tgz", "integrity": "sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==" }, + "react-i18next": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.3.4.tgz", + "integrity": "sha512-IRZMD7PAM3C+fJNzRbyLNi1ZD0kc3Z3obBspJjEl+9H+ME41PhVor3BpdIqv/Rm7lUoGhMjmpu42J45ooJ61KA==", + "requires": { + "@babel/runtime": "^7.3.1", + "html-parse-stringify2": "2.0.1" + } + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -17099,6 +17124,11 @@ "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=" + }, "vue": { "version": "2.6.11", "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz", diff --git a/package.json b/package.json index 9a51bdb61..dc9913b3b 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,11 @@ "@testing-library/react": "^10.0.1", "@testing-library/user-event": "^10.0.0", "axios": "^0.19.2", + "i18next": "^19.3.4", "lodash": "^4.17.15", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-i18next": "^11.3.4", "react-markdown": "^4.3.1", "react-scripts": "3.4.1", "react-toastify": "^5.5.0", diff --git a/src/components/App/App.js b/src/components/App/App.js index 3ec26b9e8..589ff0a09 100644 --- a/src/components/App/App.js +++ b/src/components/App/App.js @@ -1,15 +1,10 @@ -/* eslint-disable no-unused-vars */ -import React, { useEffect, useContext } from 'react'; +import React, { useEffect, useContext, Suspense } from 'react'; +import AppContext from '../../context/AppContext'; import LeftSidebar from '../LeftSidebar/LeftSidebar'; import RightSidebar from '../RightSidebar/RightSidebar'; -import AppContext from '../../context/AppContext'; -// Resume Templates -import Onyx from '../../templates/onyx'; -import Pikachu from '../../templates/pikachu'; -import Gengar from '../../templates/gengar'; -import Castform from '../../templates/castform'; +import templates from '../../templates'; const App = () => { const context = useContext(AppContext); @@ -21,37 +16,24 @@ const App = () => { dispatch({ type: 'import_data', payload: storedState }); }, [dispatch]); - const renderTemplate = () => { - switch (theme.layout) { - case 'Onyx': - return ; - case 'Pikachu': - return ; - case 'Gengar': - return ; - case 'Castform': - return ; - default: - return null; - } - }; - return ( -
- + +
+ -
-
- {renderTemplate()} +
+
+ {templates.find(x => theme.layout.toLowerCase() === x.key).component()} +
-
- -
+ +
+
); }; diff --git a/src/components/LeftSidebar/LeftSidebar.js b/src/components/LeftSidebar/LeftSidebar.js index 16e08f311..d06c3bae2 100644 --- a/src/components/LeftSidebar/LeftSidebar.js +++ b/src/components/LeftSidebar/LeftSidebar.js @@ -13,24 +13,23 @@ import ExtrasTab from './tabs/Extras'; import LanguagesTab from './tabs/Languages'; import ReferencesTab from './tabs/References'; -const tabs = [ - 'Profile', - 'Objective', - 'Work Experience', - 'Education', - 'Awards', - 'Certifications', - 'Skills', - 'Languages', - 'References', - 'Extras', -]; - const LeftSidebar = () => { const context = useContext(AppContext); const { state, dispatch } = context; const { data } = state; + const tabs = [ + 'Profile', + 'Objective', + 'Work Experience', + 'Education', + 'Awards', + 'Certifications', + 'Skills', + 'Languages', + 'References', + 'Extras', + ]; const [currentTab, setCurrentTab] = useState('Profile'); const onChange = (key, value) => { dispatch({ diff --git a/src/components/LeftSidebar/tabs/Awards.js b/src/components/LeftSidebar/tabs/Awards.js index d29359330..2b9a61c6e 100644 --- a/src/components/LeftSidebar/tabs/Awards.js +++ b/src/components/LeftSidebar/tabs/Awards.js @@ -1,4 +1,5 @@ import React, { useState, useContext } from 'react'; +import { useTranslation } from 'react-i18next'; import { v4 as uuidv4 } from 'uuid'; import set from 'lodash/set'; @@ -6,10 +7,13 @@ import TextField from '../../../shared/TextField'; import AppContext from '../../../context/AppContext'; import Checkbox from '../../../shared/Checkbox'; import TextArea from '../../../shared/TextArea'; -import { addItem, deleteItem, moveItemUp, moveItemDown } from '../../../utils'; +import { addItem } from '../../../utils'; import ItemActions from '../../../shared/ItemActions'; +import AddItemButton from '../../../shared/AddItemButton'; +import ItemHeading from '../../../shared/ItemHeading'; const AwardsTab = ({ data, onChange }) => { + const { t } = useTranslation(); const context = useContext(AppContext); const { dispatch } = context; @@ -24,7 +28,7 @@ const AwardsTab = ({ data, onChange }) => {
onChange('data.awards.heading', v)} /> @@ -45,12 +49,44 @@ const AwardsTab = ({ data, onChange }) => { /> ))} - + ); }; -const AddItem = ({ dispatch }) => { +const Form = ({ item, onChange, identifier = '' }) => { + const { t } = useTranslation(['leftSidebar', 'app']); + + return ( +
+ onChange(`${identifier}title`, v)} + /> + + onChange(`${identifier}subtitle`, v)} + /> + +