Skip to content

Commit f34e090

Browse files
authored
Enhance internationalization support across multiple pages (#2)
* Enhance internationalization support across Speakers, Sponsor, and Venue pages - Integrated i18n for text elements in Speakers.jsx, including timeline milestones and call for proposals. - Updated Sponsor.jsx to utilize translation for sponsorship details and benefits. - Improved Venue.jsx with translated content for venue details and travel information. - Added icons from lucide-react for better visual representation in Sponsor and Venue sections. Signed-off-by: Steve Yonkeu <yokwejuste@yahoo.com> * Add internationalization support for Track, About, Attend, Home, Sponsor, and Venue pages; update translations in English and French Signed-off-by: Steve Yonkeu <yokwejuste@yahoo.com> * Add i18n support for multiple pages and update content for localization - Integrated `react-i18next` for internationalization in FinancialAid, HealthSafety, Privacy, Terms, and UbuCon pages. - Replaced hardcoded text with translation keys for better localization support. - Updated content structure to accommodate translated text while maintaining existing functionality. Signed-off-by: Steve Yonkeu <yokwejuste@yahoo.com>
1 parent ada8a73 commit f34e090

24 files changed

Lines changed: 2117 additions & 864 deletions

index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
<title>PyCon Cameroon 2026 | Cameroon's Premier Python Conference</title>
1313

1414
<!-- Favicon -->
15-
<link rel="icon" type="image/x-icon" href="/images/branding/preview.jpg">
16-
<link rel="apple-touch-icon" href="/images/branding/preview.jpg">
15+
<link rel="icon" type="image/webp" href="/images/branding/python-cameroon-logo.webp">
16+
<link rel="apple-touch-icon" href="/images/branding/python-cameroon-logo.webp">
1717

1818
<!-- Open Graph / Facebook -->
1919
<meta property="og:type" content="website">

package-lock.json

Lines changed: 99 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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212
"deploy:preview": "npm run build && firebase hosting:channel:deploy preview"
1313
},
1414
"dependencies": {
15+
"i18next": "^26.0.3",
1516
"leaflet": "^1.9.4",
17+
"lucide-react": "^1.7.0",
1618
"react": "^19.2.0",
1719
"react-dom": "^19.2.0",
20+
"react-i18next": "^17.0.2",
1821
"react-leaflet": "^5.0.0",
1922
"react-router-dom": "^7.13.0"
2023
},

src/components/Footer.jsx

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import React from 'react';
22
import { Link } from 'react-router-dom';
3+
import { useTranslation } from 'react-i18next';
34

45
const Footer = () => {
6+
const { t } = useTranslation();
7+
58
return (
69
<footer className="footer">
710
<div className="container">
@@ -13,8 +16,7 @@ const Footer = () => {
1316
</div>
1417

1518
<p className="footer-description">
16-
Cameroon's first Python conference, bringing together developers, enthusiasts,
17-
and innovators from across Africa and beyond.
19+
{t('footer.description')}
1820
</p>
1921
<div className="footer-social mt-md">
2022
<a href="https://x.com/PythonCameroon" aria-label="X" data-tooltip="Follow us on X"
@@ -26,8 +28,7 @@ const Footer = () => {
2628
</a>
2729
<a href="https://linkedin.com/company/PythonCameroon" aria-label="LinkedIn" className="social-link">
2830
<svg className="social-icon-svg" viewBox="0 0 24 24">
29-
<path
30-
d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z" />
31+
<path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z" />
3132
</svg>
3233
</a>
3334
<a href="https://discord.gg/mC5zzqGmQ5" aria-label="Discord" className="social-link">
@@ -38,8 +39,7 @@ const Footer = () => {
3839
</a>
3940
<a href="https://github.com/PythonCameroon" aria-label="GitHub" className="social-link">
4041
<svg className="social-icon-svg" viewBox="0 0 24 24">
41-
<path
42-
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
42+
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
4343
</svg>
4444
</a>
4545
<a href="https://chat.whatsapp.com/Ckc80ophGEH0NJFmZAzDMr" aria-label="WhatsApp"
@@ -53,48 +53,47 @@ const Footer = () => {
5353
</div>
5454

5555
<div>
56-
<h4 className="footer-title">About</h4>
56+
<h4 className="footer-title">{t('footer.about')}</h4>
5757
<div className="footer-links">
58-
<Link to="/about">Overview</Link>
59-
<Link to="/about#team">Team</Link>
60-
<Link to="/code-of-conduct">Code of Conduct</Link>
61-
<a href="https://www.python.org/psf-landing/">Python Software Foundation</a>
58+
<Link to="/about">{t('footer.overview')}</Link>
59+
<Link to="/about#team">{t('footer.team')}</Link>
60+
<Link to="/code-of-conduct">{t('footer.codeOfConduct')}</Link>
61+
<a href="https://www.python.org/psf-landing/">{t('footer.psf')}</a>
6262
</div>
6363
</div>
6464

6565
<div>
66-
<h4 className="footer-title">Program</h4>
66+
<h4 className="footer-title">{t('footer.program')}</h4>
6767
<div className="footer-links">
68-
<Link to="/speakers">Speakers</Link>
69-
<Link to="/speakers#guidelines">Proposal Guidelines</Link>
70-
{/* <Link to="/attend">Attend</Link> */}
71-
<Link to="/venue">Venue</Link>
68+
<Link to="/speakers">{t('footer.speakers')}</Link>
69+
<Link to="/speakers#guidelines">{t('footer.proposalGuidelines')}</Link>
70+
<Link to="/venue">{t('footer.venue')}</Link>
7271
</div>
7372
</div>
7473

7574
<div>
76-
<h4 className="footer-title">Contact</h4>
75+
<h4 className="footer-title">{t('footer.contact')}</h4>
7776
<div className="footer-links">
7877
<a href="mailto:organizers@pythoncameroon.org">organizers@pythoncameroon.org</a>
79-
<Link to="/sponsor">Sponsorship</Link>
80-
<Link to="/sponsor">Support</Link>
78+
<Link to="/sponsor">{t('footer.sponsorship')}</Link>
79+
<Link to="/sponsor">{t('footer.support')}</Link>
8180
</div>
8281
</div>
8382
</div>
8483

8584
<div className="footer-bottom">
8685
<div>
87-
<p>© 2026 PyCon Cameroon. All rights reserved.</p>
86+
<p>{t('footer.copyright')}</p>
8887
<p style={{ fontSize: '0.8rem', marginTop: '5px' }}>
89-
Built by <a href="https://github.com/azilmuluh" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--color-orange)' }}>Muluh Azinwi Success</a>
88+
{t('footer.builtBy')} <a href="https://github.com/azilmuluh" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--color-orange)' }}>Muluh Azinwi Success</a>
9089
</p>
9190
</div>
9291
<div>
93-
<Link to="/terms">Terms and Conditions</Link>
92+
<Link to="/terms">{t('footer.terms')}</Link>
9493
<span style={{ margin: '0 var(--spacing-sm)' }}>|</span>
95-
<Link to="/privacy">Privacy Policy</Link>
94+
<Link to="/privacy">{t('footer.privacy')}</Link>
9695
<span style={{ margin: '0 var(--spacing-sm)' }}>|</span>
97-
<Link to="/health-safety">Health & Safety</Link>
96+
<Link to="/health-safety">{t('footer.healthSafety')}</Link>
9897
</div>
9998
</div>
10099
</div>

src/components/Navbar.jsx

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import React, { useState, useEffect } from 'react';
22
import { NavLink, Link, useLocation } from 'react-router-dom';
3+
import { useTranslation } from 'react-i18next';
4+
import { Languages } from 'lucide-react';
35

46
const Navbar = () => {
57
const [isOpen, setIsOpen] = useState(false);
68
const [scrolled, setScrolled] = useState(false);
79
const location = useLocation();
10+
const { t, i18n } = useTranslation();
11+
12+
const toggleLanguage = () => {
13+
i18n.changeLanguage(i18n.language === 'en' ? 'fr' : 'en');
14+
};
815

916
useEffect(() => {
1017
const handleScroll = () => {
@@ -41,16 +48,19 @@ const Navbar = () => {
4148
</Link>
4249

4350
<div className={`nav-links ${isOpen ? 'active' : ''}`} id="navLinks">
44-
<NavLink to="/" className={({ isActive }) => isActive ? "active" : ""}>Home</NavLink>
45-
<NavLink to="/about" className={({ isActive }) => isActive ? "active" : ""}>About</NavLink>
46-
<NavLink to="/speakers" className={({ isActive }) => isActive ? "active" : ""}>Speakers</NavLink>
47-
<NavLink to="/sponsor" className={({ isActive }) => isActive ? "active" : ""}>Sponsor</NavLink>
48-
<NavLink to="/attend" className={({ isActive }) => isActive ? "active" : ""}>Attend</NavLink>
49-
<NavLink to="/venue" className={({ isActive }) => isActive ? "active" : ""}>Venue</NavLink>
51+
<NavLink to="/about" className={({ isActive }) => isActive ? "active" : ""}>{t('nav.about')}</NavLink>
52+
<NavLink to="/speakers" className={({ isActive }) => isActive ? "active" : ""}>{t('nav.speakers')}</NavLink>
53+
<NavLink to="/sponsor" className={({ isActive }) => isActive ? "active" : ""}>{t('nav.sponsor')}</NavLink>
54+
<NavLink to="/attend" className={({ isActive }) => isActive ? "active" : ""}>{t('nav.attend')}</NavLink>
55+
<NavLink to="/venue" className={({ isActive }) => isActive ? "active" : ""}>{t('nav.venue')}</NavLink>
5056
<NavLink to="/ubucon" className={({ isActive }) => isActive ? "active" : ""} style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
5157
<img src="/images/partners/canonical-cm.webp" alt="" style={{ width: '18px', height: '18px', objectFit: 'contain', borderRadius: '50%' }} />
52-
UbuCon
58+
{t('nav.ubucon')}
5359
</NavLink>
60+
<button onClick={toggleLanguage} className="lang-toggle" aria-label="Toggle language">
61+
<Languages size={16} />
62+
{i18n.language === 'en' ? 'FR' : 'EN'}
63+
</button>
5464
</div>
5565

5666
{/* Mobile drawer overlay */}
@@ -66,16 +76,19 @@ const Navbar = () => {
6676
&times;
6777
</button>
6878
<div className="nav-drawer-links">
69-
<NavLink to="/" className={({ isActive }) => isActive ? "active" : ""}>Home</NavLink>
70-
<NavLink to="/about" className={({ isActive }) => isActive ? "active" : ""}>About</NavLink>
71-
<NavLink to="/speakers" className={({ isActive }) => isActive ? "active" : ""}>Speakers</NavLink>
72-
<NavLink to="/sponsor" className={({ isActive }) => isActive ? "active" : ""}>Sponsor</NavLink>
73-
<NavLink to="/attend" className={({ isActive }) => isActive ? "active" : ""}>Attend</NavLink>
74-
<NavLink to="/venue" className={({ isActive }) => isActive ? "active" : ""}>Venue</NavLink>
79+
<NavLink to="/about" className={({ isActive }) => isActive ? "active" : ""}>{t('nav.about')}</NavLink>
80+
<NavLink to="/speakers" className={({ isActive }) => isActive ? "active" : ""}>{t('nav.speakers')}</NavLink>
81+
<NavLink to="/sponsor" className={({ isActive }) => isActive ? "active" : ""}>{t('nav.sponsor')}</NavLink>
82+
<NavLink to="/attend" className={({ isActive }) => isActive ? "active" : ""}>{t('nav.attend')}</NavLink>
83+
<NavLink to="/venue" className={({ isActive }) => isActive ? "active" : ""}>{t('nav.venue')}</NavLink>
7584
<NavLink to="/ubucon" className={({ isActive }) => isActive ? "active" : ""} style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
7685
<img src="/images/partners/canonical-cm.webp" alt="" style={{ width: '22px', height: '22px', objectFit: 'contain', borderRadius: '50%' }} />
77-
UbuCon
86+
{t('nav.ubucon')}
7887
</NavLink>
88+
<button onClick={toggleLanguage} className="lang-toggle" aria-label="Toggle language">
89+
<Languages size={16} />
90+
{i18n.language === 'en' ? 'FR' : 'EN'}
91+
</button>
7992
</div>
8093
</div>
8194

0 commit comments

Comments
 (0)