Skip to content

Commit 6384ec3

Browse files
committed
feat (btn): multi-languages features implemented
1 parent 11244a5 commit 6384ec3

29 files changed

+9394
-145
lines changed

package-lock.json

Lines changed: 7905 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@
1919
"@fortawesome/react-fontawesome": "^0.2.2",
2020
"axios": "^1.7.2",
2121
"chart.js": "^4.4.3",
22+
"i18next": "^24.2.3",
23+
"i18next-browser-languagedetector": "^8.0.4",
2224
"papaparse": "^5.4.1",
2325
"react": "^18.3.1",
2426
"react-chartjs-2": "^5.2.0",
2527
"react-dom": "^18.2.0",
28+
"react-i18next": "^15.4.1",
2629
"react-router-dom": "^6.23.1",
2730
"react-toastify": "^10.0.5"
2831
},
@@ -32,17 +35,17 @@
3235
"@types/react": "^18.2.66",
3336
"@types/react-dom": "^18.2.22",
3437
"@vitejs/plugin-react": "^4.2.1",
38+
"autoprefixer": "^10.4.19",
3539
"eslint": "^8.57.0",
3640
"eslint-plugin-react": "^7.34.1",
3741
"eslint-plugin-react-hooks": "^4.6.0",
3842
"eslint-plugin-react-refresh": "^0.4.6",
3943
"husky": "^9.0.11",
4044
"lint-staged": "^15.2.5",
45+
"postcss": "^8.4.38",
4146
"prettier": "3.2.5",
4247
"tailwindcss": "^3.4.4",
43-
"vite": "^5.2.0",
44-
"autoprefixer": "^10.4.19",
45-
"postcss": "^8.4.38"
48+
"vite": "^5.2.0"
4649
},
4750
"lint-staged": {
4851
"src/**/*.{js,jsx,ts,tsx}": [

src/components/ArticleForm/ArticleForm.jsx

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
22
import CountryPickList from './CountryPicker';
33
import fetchLocation from '../../api/fetchLocation';
44
import { toast } from 'react-toastify';
5+
import { useTranslation } from 'react-i18next';
56

67
const ArticleForm = ({ onSubmit, loading, countryUrl, continentUrl }) => {
78
const [formErrors, setFormErrors] = useState({});
@@ -12,6 +13,7 @@ const ArticleForm = ({ onSubmit, loading, countryUrl, continentUrl }) => {
1213
date: today.toISOString().split('T')[0],
1314
access: 'all-access',
1415
});
16+
const { t } = useTranslation();
1517

1618
useEffect(() => {
1719
const getLocation = async (lat, lon) => {
@@ -23,7 +25,7 @@ const ArticleForm = ({ onSubmit, loading, countryUrl, continentUrl }) => {
2325
country: countryCode,
2426
}));
2527
} catch (error) {
26-
toast.error(`Error fetching location data.`, {
28+
toast.error(t('form.errorLocation'), {
2729
autoClose: false,
2830
position: 'top-right',
2931
hideProgressBar: true,
@@ -40,7 +42,7 @@ const ArticleForm = ({ onSubmit, loading, countryUrl, continentUrl }) => {
4042
getLocation(latitude, longitude);
4143
},
4244
() => {
43-
toast.error(`Error getting geolocation.`, {
45+
toast.error(t('form.errorGeolocation'), {
4446
autoClose: false,
4547
position: 'top-right',
4648
hideProgressBar: true,
@@ -50,15 +52,15 @@ const ArticleForm = ({ onSubmit, loading, countryUrl, continentUrl }) => {
5052
},
5153
);
5254
} else {
53-
toast.error(`Geolocation is not supported by this browser.`, {
55+
toast.error(t('form.geolocationNotSupported'), {
5456
autoClose: false,
5557
position: 'top-right',
5658
hideProgressBar: true,
5759
draggable: true,
5860
progress: undefined,
5961
});
6062
}
61-
}, []);
63+
}, [t]);
6264

6365
const [country, setCountry] = useState('CD');
6466
const [continent, setContinent] = useState('Africa');
@@ -81,8 +83,8 @@ const ArticleForm = ({ onSubmit, loading, countryUrl, continentUrl }) => {
8183

8284
const validateForm = () => {
8385
const errors = {};
84-
if (!form.country) errors.country = 'Country is required';
85-
if (!form.date) errors.date = 'La date est requise';
86+
if (!form.country) errors.country = t('form.countryRequired');
87+
if (!form.date) errors.date = t('form.dateRequired');
8688
return errors;
8789
};
8890

@@ -107,7 +109,7 @@ const ArticleForm = ({ onSubmit, loading, countryUrl, continentUrl }) => {
107109
<form onSubmit={handleSubmit} className='w-full formBorder py-5 rounded-xl max-md:w-[95vw]'>
108110
<div className='flex flex-col gap-[0.5rem] justify-between items-center w-full'>
109111
<div className='text-start mb-2 py-5'>
110-
<p className='date text-[20px] max-md:text-xs text-center'>Veuillez remplir le formulaire pour obtenir les articles souhaités</p>
112+
<p className='date text-[20px] max-md:text-xs text-center'>{t('form.fillForm')}</p>
111113
</div>
112114

113115
<div className='inputs flex gap-[1rem] max-md:flex-col max-md:text-xs'>
@@ -124,29 +126,28 @@ const ArticleForm = ({ onSubmit, loading, countryUrl, continentUrl }) => {
124126
/>
125127
<div className='select_container country_select'>
126128
<div>
127-
<label className='select_label'>Date</label>
128-
129+
<label className='select_label'>{t('form.date')}</label>
129130
<input id='fullDate' type='date' name='date' className='select_options' value={form.date} onChange={handleChange} />
130131
</div>
131132
</div>
132133

133134
<div className='select_container country_select'>
134135
<div>
135136
<label htmlFor='' className='select_label'>
136-
Platform
137+
{t('form.platform')}
137138
</label>
138139
<select className='select_options' name='access' value={form.access} onChange={handleChange}>
139-
<option value='all-access'>all-access</option>
140-
<option value='desktop'>desktop</option>
141-
<option value='mobile-app'>mobile-app</option>
142-
<option value='mobile-web'>mobile-web</option>
140+
<option value='all-access'>{t('form.allAccess')}</option>
141+
<option value='desktop'>{t('form.desktop')}</option>
142+
<option value='mobile-app'>{t('form.mobileApp')}</option>
143+
<option value='mobile-web'>{t('form.mobileWeb')}</option>
143144
</select>
144145
{formErrors.access && <div className='error'>{formErrors.access}</div>}
145146
</div>
146147
</div>
147148
</div>
148149
<button type='submit' className=' py-[0.7rem] my-5 bg-green-500 text-white px-6 text-[18px] font-600 w-56 max-md:text-xs'>
149-
{loading ? 'Envoie en cours...' : 'Envoyer'}
150+
{loading ? t('form.sending') : t('form.submit')}
150151
</button>
151152
</div>
152153
</form>

src/components/ArticleForm/CountryPicker.jsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
import { useEffect, useRef } from 'react';
33
import countries from '../../helpers/countriesIsoCodes';
44
import { Menu } from '../common/Menu';
5+
import { useTranslation } from 'react-i18next';
56

67
export default function CountryPickList({ country, onChangeCountry, defaultCountry, continent, onChangeContinent, defaultContinent }) {
78
const countryRef = useRef(null);
9+
const { t } = useTranslation();
810

911
// Set the default country when the Component is mounted
1012
useEffect(() => {
@@ -29,7 +31,7 @@ export default function CountryPickList({ country, onChangeCountry, defaultCount
2931
return (
3032
<div className='country_select flex max-md:flex-col'>
3133
<Menu
32-
label='Continent'
34+
label={t('form.continent')}
3335
className='continent'
3436
data={Object.keys(countries).map((el) => ({ value: el, label: el }))}
3537
value={continent}
@@ -38,7 +40,7 @@ export default function CountryPickList({ country, onChangeCountry, defaultCount
3840
/>
3941

4042
<Menu
41-
label='Pays'
43+
label={t('form.country')}
4244
className='country'
4345
data={countries[continent ?? defaultContinent].map((el) => ({ value: el.code, label: el.name }))}
4446
value={country}

src/components/ArticleView/ArticleCard.jsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import pageNameDecoder from '../../helpers/pageNameDecoder';
77
import { useEffect, useState } from 'react';
88
import Button from '../common/Button';
99
import fetchArticleDescription from '../../api/fetchArticleDescription';
10+
import { useTranslation } from 'react-i18next';
1011

1112
const ArticleCard = ({ article, project, views_ceil, rank, country }) => {
1213
const [url, setUrl] = useState(null);
1314
const [editors, setEditors] = useState(null);
1415
const [description, setDescription] = useState(null);
16+
const { t } = useTranslation();
1517

1618
useEffect(() => {
1719
const fetchImages = async () => {
@@ -57,28 +59,28 @@ const ArticleCard = ({ article, project, views_ceil, rank, country }) => {
5759
</h3>
5860
<div className='article-description flex flex-col gap-2'>
5961
<p>
60-
<span>Description: </span>
61-
{description ? description : 'pas de description'}
62+
<span>{t('article.description')}: </span>
63+
{description ? description : t('article.noDescription')}
6264
<span></span>
6365
</p>
6466
<p>
65-
<span>Country:</span> <span>{country ? country : 'N/A'}</span>
67+
<span>{t('article.country')}:</span> <span>{country ? country : t('article.notAvailable')}</span>
6668
</p>
6769
<p>
68-
<span>Project: </span> <span>{project}</span>
70+
<span>{t('article.project')}: </span> <span>{project}</span>
6971
</p>
7072
<p>
71-
<span>Rank: </span> <span>{rank}</span>
73+
<span>{t('article.rank')}: </span> <span>{rank}</span>
7274
</p>
7375
<p>
74-
<span>Views : </span> <span>{views_ceil}</span>
76+
<span>{t('article.views')}: </span> <span>{views_ceil}</span>
7577
</p>
7678
<p>
77-
<span>Editors : </span> <span>{editors ? editors : 'Not found'}</span>
79+
<span>{t('article.editors')}: </span> <span>{editors ? editors : t('article.notFound')}</span>
7880
</p>
7981
</div>
8082

81-
<Button event={() => (window.location.href = getPageURL(article, project))} text="Lire l'article" className='article-link' />
83+
<Button event={() => (window.location.href = getPageURL(article, project))} text={t('article.readArticle')} className='article-link' />
8284
</div>
8385
</div>
8486
);

src/components/ArticleView/ArticleList.jsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
import getPageURL from '../../helpers/getPageUrl';
2+
import { useTranslation } from 'react-i18next';
23

34
export default function ListArticlesResult({ articlesData }) {
5+
const { t } = useTranslation();
6+
47
return (
58
<div className='p-4 text-white'>
69
<div className='overflow-x-auto'>
710
<table className='min-w-full'>
811
<thead>
912
<tr>
10-
<th className='border-b border-solid border-black px-4 py-2 text-center'>Rank</th>
11-
<th className='border-b border-solid border-black px-4 py-2 text-center'>Country</th>
12-
<th className='border-b border-solid border-black px-4 py-2 text-left'>Articles</th>
13-
<th className='border-b border-solid border-black px-4 py-2 text-left'>Project</th>
14-
<th className='border-b border-solid border-black px-4 py-2 text-left'>Views</th>
13+
<th className='border-b border-solid border-black px-4 py-2 text-center'>{t('table.rank')}</th>
14+
<th className='border-b border-solid border-black px-4 py-2 text-center'>{t('table.country')}</th>
15+
<th className='border-b border-solid border-black px-4 py-2 text-left'>{t('table.articles')}</th>
16+
<th className='border-b border-solid border-black px-4 py-2 text-left'>{t('table.project')}</th>
17+
<th className='border-b border-solid border-black px-4 py-2 text-left'>{t('table.views')}</th>
1518
</tr>
1619
</thead>
1720
<tbody>

src/components/Layouts/NavBar.jsx

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ import { useEffect, useState } from 'react';
22
import '../../styles/NavBar.css'; // Nous allons ajouter des styles pour la transition
33
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
44
import { faArrowsUpToLine, faBook, faBug, faCode, faComment, faCopyright, faPager, faUsers } from '@fortawesome/free-solid-svg-icons';
5+
import { useTranslation } from 'react-i18next';
6+
import LanguageSelector from '../common/LanguageSelector';
57

68
const NavBar = () => {
79
const [helpMenuOpen, setHelpMenuOpen] = useState(false);
10+
const { t } = useTranslation();
811

912
const toggleHelpMenu = () => {
1013
setHelpMenuOpen(!helpMenuOpen);
@@ -29,56 +32,57 @@ const NavBar = () => {
2932
<div className='flex items-center justify-between bg-blue-800 h-16 px-6 py-2'>
3033
<a href='/'>
3134
<div className='text-white text-xl font-bold'>
32-
Emi <span className=' font-light max-md:hidden'>- articles les plus visités par pays</span>
35+
Emi <span className=' font-light max-md:hidden'>{t('nav.subtitle')}</span>
3336
</div>
3437
</a>
3538

36-
<div className='flex items-center justify-between'>
39+
<div className='flex items-center justify-between gap-4'>
3740
<ul className='flex items-center justify-center gap-4 text-white'>
3841
<li>
3942
<a href='/page-views' className=' text-white underline'>
40-
<span className='max-md:hidden'>Page views</span>
43+
<span className='max-md:hidden'>{t('nav.pageViews')}</span>
4144
<span className='hidden max-md:block'>
4245
<FontAwesomeIcon icon={faPager} style={{ color: '#ffffff' }} />
4346
</span>
4447
</a>
4548
</li>
4649
<li>
4750
<a href='/top-africa' className=' text-white underline'>
48-
<span className='max-md:hidden'>Top Africa</span>
51+
<span className='max-md:hidden'>{t('nav.topAfrica')}</span>
4952
<span className='hidden max-md:block'>
5053
<FontAwesomeIcon icon={faArrowsUpToLine} style={{ color: '#ffffff' }} />
5154
</span>
5255
</a>
5356
</li>
5457
</ul>
58+
<LanguageSelector />
5559
<div>
5660
<button onClick={toggleHelpMenu} className='help-icon'>
57-
Aide
61+
{t('common.help')}
5862
</button>
5963
{helpMenuOpen && (
6064
<div className='help-menu absolute shadow-2xl right-1 rounded-xl bg-white'>
6165
<a href='https://meta.wikimedia.org/wiki/Emi_Solution' className='footer-link' target='_blank'>
62-
<FontAwesomeIcon icon={faBook} /> Documentation
66+
<FontAwesomeIcon icon={faBook} /> {t('common.documentation')}
6367
</a>
6468
<a href='https://github.com/kaliacad/mostvisitedarticle' className='footer-link' target='_blank'>
65-
<FontAwesomeIcon icon={faCode} /> View source
69+
<FontAwesomeIcon icon={faCode} /> {t('common.viewSource')}
6670
</a>
6771
<a href='https://github.com/kaliacad/mostvisitedarticle/issues' className='footer-link' target='_blank'>
68-
<FontAwesomeIcon icon={faBug} /> Report an issue
72+
<FontAwesomeIcon icon={faBug} /> {t('common.reportIssue')}
6973
</a>
7074
<a
7175
href='https://meta.wikimedia.org/w/index.php?title=Talk:Emi_Solution&action=edit&redlink=1'
7276
className='footer-link'
7377
target='_blank'
7478
>
75-
<FontAwesomeIcon icon={faComment} /> Feedback
79+
<FontAwesomeIcon icon={faComment} /> {t('common.feedback')}
7680
</a>
7781
<a href='https://github.com/kaliacad.org/' className='footer-link' target='_blank'>
78-
<FontAwesomeIcon icon={faUsers} /> Developed by Kali Academy
82+
<FontAwesomeIcon icon={faUsers} /> {t('common.developedBy')}
7983
</a>
8084
<a href='https://kaliacademy.org/' className='footer-link' target='_blank'>
81-
<FontAwesomeIcon icon={faCopyright} /> Kali Academy
85+
<FontAwesomeIcon icon={faCopyright} /> {t('common.kaliAcademy')}
8286
</a>
8387
</div>
8488
)}

0 commit comments

Comments
 (0)