Skip to content

Commit 87dfe8a

Browse files
authored
Merge branch "feature/internationalization-i18n" into "develop"
Internationalization i18n
2 parents 1ffd990 + 220872e commit 87dfe8a

18 files changed

Lines changed: 284 additions & 77 deletions

.github/workflows/workflow.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ jobs:
4949

5050
# 5. Build Test (Ensures the app actually compiles)
5151
- name: Build Production Bundle
52+
env:
53+
VITE_AGENT_API_BASE_URL: ${{ secrets.VITE_AGENT_API_BASE_URL }}
5254
run: yarn build
5355

5456
# 6. Quick Bundle Size Report

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@
1616
"axios": "^1.13.2",
1717
"bootstrap": "^5.3.8",
1818
"dompurify": "^3.3.1",
19+
"i18next": "^25.7.2",
20+
"i18next-browser-languagedetector": "^8.2.0",
21+
"i18next-http-backend": "^3.0.2",
1922
"react": "^19.1.1",
2023
"react-bootstrap": "^2.10.10",
2124
"react-datepicker": "^8.9.0",
2225
"react-dom": "^19.1.1",
26+
"react-i18next": "^16.5.0",
2327
"react-icons": "^5.5.0",
2428
"react-medium-image-zoom": "^5.4.0",
2529
"react-router": "^7.9.5",

public/locales/en/translation.json

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
{
2+
"galacticView": {
3+
"title": "GalacticView",
4+
"description": "GalacticView is built for space enthusiasts by space enthusiasts. Explore the wonders of the universe with us! You can create blog posts, share your favorite images, and stay updated with the latest space news.",
5+
"copyright": "© 2025 GalacticView. All rights reserved."
6+
},
7+
"navigation": {
8+
"epicData": "Epic Data",
9+
"imageOfTheDay": "Image of the Day",
10+
"blogPost": "Blog Post"
11+
},
12+
"epicDataPost": {
13+
"title": "NASA's EPIC camera onboard the NOAA DSCOVR spacecraft",
14+
"description": "The stunning \"Blue Marble\" images of the full, sunlit side of Earth are captured daily by the Earth Polychromatic Imaging Camera (EPIC) aboard NOAA's Deep Space Climate Observatory (DSCOVR) satellite. Positioned a million miles away, DSCOVR provides a unique, continuous view of our dynamic planet as it rotates, offering a fresh perspective on weather patterns, clouds, and natural landscapes.",
15+
"cameraAndSatelliteTitle": "Camera and Satellite",
16+
"cameraAndSatelliteDescription": "The Earth Polychromatic Imaging Camera (EPIC) is the instrument, and it is mounted on the Deep Space Climate Observatory (DSCOVR) satellite.",
17+
"locationTitle": "Location",
18+
"locationDescription": "The satellite is positioned about a million miles away, between the Earth and the sun, which allows it to capture the entire sunlit side of the planet.",
19+
"imageCaptureTitle": "Image Capture",
20+
"imageCaptureDescription": "EPIC takes multiple images of Earth's sunlit face each day, with each full-color image being a composite of three separate images.",
21+
"card": {
22+
"seeMore": "See More",
23+
"itemsCount": "items for date",
24+
"imageDate": "Images taken: "
25+
}
26+
},
27+
"months": {
28+
"january": "January",
29+
"february": "February",
30+
"march": "March",
31+
"april": "April",
32+
"may": "May",
33+
"june": "June",
34+
"july": "July",
35+
"august": "August",
36+
"september": "September",
37+
"october": "October",
38+
"november": "November",
39+
"december": "December"
40+
},
41+
"footer": {
42+
"home": "Home",
43+
"explore": "Explore",
44+
"blog": "Blog",
45+
"aboutUs": "About Us",
46+
"contact": "Contact",
47+
"privacyPolicy": "Privacy Policy",
48+
"termsOfService": "Terms of Service",
49+
"helpCenter": "Help Center",
50+
"followUs": "Follow Us",
51+
"facebook": "Facebook",
52+
"twitter": "Twitter",
53+
"instagram": "Instagram"
54+
},
55+
"imageOfTheDay": {
56+
"title": "Image of the Day",
57+
"today": "TODAY",
58+
"errorImage": "Media type not supported.",
59+
"container": {
60+
"loading": "Loading...",
61+
"failed": "Failed to load image of the day."
62+
},
63+
"history": {
64+
"title": "Search for images from the past",
65+
"search": "Search for image"
66+
},
67+
"searchResults": {
68+
"error": "Error FIX IT"
69+
}
70+
},
71+
"agent": {
72+
"welcome": "\uD83D\uDC4B How can I help you explore the cosmos today?",
73+
"placeholder": "Ask me anything about space..."
74+
},
75+
"loading": "Loading..."
76+
}

src/components/AgentChatWidget.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect, useRef, useState } from 'react';
22
import * as React from 'react';
33
import { Button, Card, CloseButton, Form, InputGroup } from 'react-bootstrap';
4+
import { useTranslation } from 'react-i18next';
45
import { IoSend } from 'react-icons/io5';
56
import TextareaAutosize from 'react-textarea-autosize';
67

@@ -10,6 +11,8 @@ import style from './AgentChatWidget.module.css';
1011
const MAX_MESSAGE_LENGTH = 512;
1112

1213
function AgentChatWidget() {
14+
const { t } = useTranslation();
15+
1316
const [isOpen, setIsOpen] = useState(false);
1417
const [inputMessage, setInputMessage] = useState('');
1518
const { messages, isLoading, sendMessage } = useAgentChat();
@@ -58,7 +61,7 @@ function AgentChatWidget() {
5861
</Card.Header>
5962
<Card.Body className={style.cardBody}>
6063
<div className={style.cardBodyWelcomeText}>
61-
{messages.length === 0 ? <p>👋 How can I help you explore the cosmos today?</p> : null}
64+
{messages.length === 0 ? <p>{t('agent.welcome')}</p> : null}
6265
</div>
6366
<div className={style.messagesContainer}>
6467
{messages.map((msg) => (
@@ -80,7 +83,7 @@ function AgentChatWidget() {
8083
as={TextareaAutosize}
8184
minRows={1}
8285
maxRows={5}
83-
placeholder={'Ask me anything about space...'}
86+
placeholder={t('agent.placeholder')}
8487
className={style.chatInput}
8588
value={inputMessage}
8689
onChange={(e) => setInputMessage(e.target.value)}

src/components/FooterBar.tsx

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,36 @@
1+
import { useTranslation } from 'react-i18next';
2+
13
import styles from './FooterBar.module.css';
24

35
function FooterBar() {
6+
const { t } = useTranslation();
7+
48
return (
59
<footer className={styles.footerBar}>
610
<div className={styles.firstDiv}>
711
<img src="/logo/logo-light.png" alt="App Logo" className={styles.logo} />
8-
<h1>GalacticView </h1>
9-
<p>
10-
GalacticView is built for space enthusiasts by space enthusiasts. Explore the wonders of the
11-
universe with us! You can create blog posts, share your favorite images, and stay updated with the
12-
latest space news.
13-
</p>
14-
<p>© 2025 GalacticView. All rights reserved.</p>
12+
<h1>{t('galacticView.title')}</h1>
13+
<p>{t('galacticView.description')}</p>
14+
<p>{t('galacticView.copyright')}</p>
1515
</div>
1616
<div className={styles.secondDiv}>
1717
<div>
18-
<p>Home</p>
19-
<p>Explore</p>
20-
<p>Blog</p>
21-
<p>About Us</p>
18+
<p>{t('footer.home')}</p>
19+
<p>{t('footer.explore')}</p>
20+
<p>{t('footer.blog')}</p>
21+
<p>{t('footer.aboutUs')}</p>
2222
</div>
2323
<div>
24-
<p>Contact</p>
25-
<p>Privacy Policy</p>
26-
<p>Terms of Service</p>
27-
<p>Help Center</p>
24+
<p>{t('footer.contact')}</p>
25+
<p>{t('footer.privacyPolicy')}</p>
26+
<p>{t('footer.termsOfService')}</p>
27+
<p>{t('footer.helpCenter')}</p>
2828
</div>
2929
<div>
30-
<p>Follow Us</p>
31-
<p>Facebook</p>
32-
<p>Twitter</p>
33-
<p>Instagram</p>
30+
<p>{t('footer.followUs')}</p>
31+
<p>{t('footer.facebook')}</p>
32+
<p>{t('footer.twitter')}</p>
33+
<p>{t('footer.instagram')}</p>
3434
</div>
3535
</div>
3636
</footer>

src/components/NavigationBar.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@ import Container from 'react-bootstrap/Container';
44
import Image from 'react-bootstrap/Image';
55
import Nav from 'react-bootstrap/Nav';
66
import Navbar from 'react-bootstrap/Navbar';
7+
import { useTranslation } from 'react-i18next';
78

89
import { searchNasaLibrary } from '../api/nasaImageAndVideoLibrary.api.ts';
910
import type { NasaImageAndVideoLibraryType } from '../types/NasaImageAndVideoLibraryType.ts';
1011
import style from './NavigationBar.module.css';
1112
import SearchResults from './search/SearchResults.tsx';
1213

1314
function NavigationBar() {
15+
const { t } = useTranslation();
16+
1417
const [query, setQuery] = useState('');
1518
const [results, setResults] = useState<NasaImageAndVideoLibraryType | null>(null);
1619
const [showResults, setShowResults] = useState(false);
@@ -57,13 +60,13 @@ function NavigationBar() {
5760
<Navbar.Collapse id="basic-navbar-nav">
5861
<Nav className={`ms-auto ${style.navStyle}`}>
5962
<Nav.Link href="/epicdata" className={style.navLinkStyle}>
60-
EpicData
63+
{t('navigation.epicData')}
6164
</Nav.Link>
6265
<Nav.Link href="/imageoftheday" className={style.navLinkStyle}>
63-
Image of the Day
66+
{t('navigation.imageOfTheDay')}
6467
</Nav.Link>
6568
<Nav.Link href="/blogpost" className={style.navLinkStyle}>
66-
Blogpost
69+
{t('navigation.blogPost')}
6770
</Nav.Link>
6871
</Nav>
6972
</Navbar.Collapse>

src/components/epicdata/EpicDataCard.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Button, Card, Spinner } from 'react-bootstrap';
2+
import { useTranslation } from 'react-i18next';
23

34
import { useNasaEpicDataByDate } from '../../query/nasaEpicData.query.ts';
45
import type { NasaEpicDataType } from '../../types/NasaEpicDataTypes.ts';
@@ -10,20 +11,24 @@ interface epicDataCardProps {
1011
}
1112

1213
function EpicDataCard(props: epicDataCardProps) {
14+
const { t } = useTranslation();
15+
1316
const { date, isLoading } = props;
1417
const { data: dataOnDate = [] as NasaEpicDataType[] } = useNasaEpicDataByDate(date);
1518

1619
return (
1720
<Card className={style.card}>
1821
<Card.Body className={style.epicDataCardBody}>
1922
{isLoading ? <Spinner animation="border" /> : null}
20-
<Card.Title>Images taken: {date}</Card.Title>
23+
<Card.Title>
24+
{t('epicDataPost.card.imageDate')} {date}
25+
</Card.Title>
2126
<Card.Subtitle className="mb-2 text-muted">{dataOnDate[0]?.caption}</Card.Subtitle>
2227
<Card.Text>
23-
{dataOnDate.length} items for date {date}
28+
{dataOnDate.length} {t('epicDataPost.card.itemsCount')} {date}
2429
</Card.Text>
2530
<Button variant="dark" href={`/epicdata/${date}`}>
26-
See more
31+
{t('epicDataPost.card.seeMore')}
2732
</Button>
2833
</Card.Body>
2934
</Card>

src/components/imageoftheday/ImageOfTheDay.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'react-medium-image-zoom/dist/styles.css';
22

3+
import { useTranslation } from 'react-i18next';
34
import Zoom from 'react-medium-image-zoom';
45

56
import type { NasaApodDataType } from '../../types/NasaApodDataType.ts';
@@ -11,6 +12,8 @@ interface ImageOfTheDayProps {
1112
}
1213

1314
function ImageOfTheDay(props: ImageOfTheDayProps) {
15+
const { t } = useTranslation();
16+
1417
const data = props.data;
1518
const today = new Date();
1619
const todayString = getUTCDateString(today);
@@ -20,8 +23,8 @@ function ImageOfTheDay(props: ImageOfTheDayProps) {
2023
<div className={style.leftDiv}>
2124
{data?.date === todayString ? (
2225
<>
23-
<p className={'section-tagline'}>TODAY</p>
24-
<h1>Image of the Day</h1>
26+
<p className={'section-tagline'}>{t('imageOfTheDay.today')}</p>
27+
<h1>{t('imageOfTheDay.title')}</h1>
2528
</>
2629
) : (
2730
<h2 className={'section-tagline'}>{data?.date}</h2>
@@ -44,7 +47,7 @@ function ImageOfTheDay(props: ImageOfTheDayProps) {
4447
allowFullScreen
4548
/>
4649
) : (
47-
<p>Media type not supported.</p>
50+
<p>{t('imageOfTheDay.errorImage')}</p>
4851
)}
4952
</div>
5053
</div>

src/components/imageoftheday/ImageOfTheDayContainer.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { useTranslation } from 'react-i18next';
2+
13
import { useNasaApodData } from '../../query/nasaApodData.query';
24
import ImageOfTheDay from './ImageOfTheDay.tsx';
35

@@ -6,11 +8,13 @@ interface ImageOfTheDayContainerProps {
68
}
79

810
function ImageOfTheDayContainer(props: ImageOfTheDayContainerProps) {
11+
const { t } = useTranslation();
12+
913
const { date } = props;
1014
const { data, isLoading, isError } = useNasaApodData(date);
1115

12-
if (isLoading) return <p style={{ marginLeft: '10rem' }}>Loading...</p>;
13-
if (isError) return <p style={{ marginLeft: '10rem' }}>Failed to load image of the day.</p>;
16+
if (isLoading) return <p style={{ marginLeft: '10rem' }}>{t('imageOfTheDay.container.loading')}</p>;
17+
if (isError) return <p style={{ marginLeft: '10rem' }}>{t('imageOfTheDay.container.failed')}</p>;
1418
if (!data) return null;
1519

1620
return <ImageOfTheDay data={data} />;

src/components/imageoftheday/ImageOfTheDayHistory.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useMemo, useState } from 'react';
44
import { Button } from 'react-bootstrap';
55
import Form from 'react-bootstrap/Form';
66
import DatePicker from 'react-datepicker';
7+
import { useTranslation } from 'react-i18next';
78

89
import { toUTCDateOnly } from '../../utils/dateUtils.ts';
910
import style from './ImageOfTheDayHistory.module.css';
@@ -13,6 +14,8 @@ interface Props {
1314
}
1415

1516
function ImageOfTheDayHistory(props: Props) {
17+
const { t } = useTranslation();
18+
1619
const { setSearchedDate } = props;
1720
const todayUtc = useMemo(() => toUTCDateOnly(new Date()), []);
1821
const [date, setDate] = useState<Date>(todayUtc);
@@ -22,7 +25,7 @@ function ImageOfTheDayHistory(props: Props) {
2225
<>
2326
<Form.Group controlId="datePicker" className={style.formGroup}>
2427
<Form.Label>
25-
<h3>Search for images from the past</h3>
28+
<h3>{t('imageOfTheDay.history.title')}</h3>
2629
</Form.Label>
2730
<div className={style.datePickerContainer}>
2831
<DatePicker
@@ -40,7 +43,7 @@ function ImageOfTheDayHistory(props: Props) {
4043
variant="dark"
4144
onClick={() => setSearchedDate(date)}
4245
>
43-
Search for image
46+
{t('imageOfTheDay.history.search')}
4447
</Button>
4548
</div>
4649
</Form.Group>

0 commit comments

Comments
 (0)