관리 메뉴

CASSIE'S BLOG

i18n 영어버전 한글버전 포토폴리오 사이트 나누기 본문

PROGRAMMING/프로젝트

i18n 영어버전 한글버전 포토폴리오 사이트 나누기

ITSCASSIE1107 2023. 11. 17. 10:53

우여곡절기 정리할 곳 

 

 

페이지를 두개 만들어서 버튼 클릭에 따라 돌려도 되고,
전역에서 값 내려줘서 글만 바꿔치기 해도 됩니당
둘다 가능한 방법이긴 해요

 

페이지 두개 만들어서 버튼 클릭에 따라 돌리고있는데..유기적으로 안 돌아감.........

일단 자료 조사 한 후에 코드 건들기. 

 

https://www.youtube.com/watch?v=OfJ6Q-JFLL0

 

 

 

key값을 일일이 줘서 번역본을 만들면 된다하는데 할 수 있을 것 같은데...?

 

 

 

 

✅ npm install i18next react-i18next

 

✅ public 안에 locales en폴더 만들고 kor 폴더 만들고 각각 같은 translation.json 파일 만들어주기 

 

- src
  - locales
    - en
      - translation.json
    - ko
      - translation.json

 

useTranslation hook 쓰기

 

import React from "react";
import styled, { keyframes } from "styled-components";
import { Link } from "react-router-dom";
import Heading from "../atoms/Heading";
import { useTranslation } from "react-i18next";

function AboutTxtWrap() {
    const { t } = useTranslation();

    return (
        <StyledHome>
            <div className="left">
                <div className="color-block"></div>
                <div className="img-box"></div>
            </div>
            <div className="right">
                <div className="tit-wrap">
                    <Heading level="2">HI! HOW ARE YOU?</Heading>
                    <Heading>
                        I'M{" "}
                        <span className="message">
                            <strong>YEOOUL KIM</strong>
                            <strong>WEB DEVELOPER</strong>
                            <strong>FULL-STACK DEVELOPER</strong>
                        </span>
                    </Heading>
                    {/* <p>I’m a Tunisian based web designer & front‑end developer focused on crafting clean & user‑friendly experiences, I am passionate about building excellent software that improves the lives of those around me.</p> */}
                    <p>
                    {t("about.greeting")}
                    </p>
                </div>
                <section>
                    <Heading level="2">PERSONAL INFOS</Heading>
                    <ul>
                        <li>
                            <span>Name : </span>김00
                        </li>
                        <li>
                            <span>Age : </span>30, 1992.11
                        </li>
                        <li>
                            <span>Phone : </span>010.0000.0000
                        </li>
                        <li>
                            <span>Address : </span>{t("about.address")}
                        </li>
                        <li>
                            <span>Email : </span>000@naver.com
                        </li>
                        {/* <li>
                            <span>Name : </span>Steve
                        </li>
                        <li>
                            <span>Name : </span>Steve
                        </li> */}
                    </ul>
                </section>
                <Link to="/about" className="more-btn">
                    MORE ABOUT ME
                </Link>
            </div>
        </StyledHome>
    );
}

const messageslide = keyframes`
    0% {
        top: 0;
        width: 0;
    }
    1% {
        width: 0;
    }
    11% {
        width: 100%;
    }
    22% {
        width: 100%;
    }
    32% {
        top: 0;
        width: 0;
    }
    33% {
        top: -4.5rem;
    }
    34% {
        width: 0;
    }
    44% {
        width: 100%;
    }
    55% {
        width: 100%;
    }
    65% {
        top: -4.5rem;
        width: 0;
    }
    66%{
        top: -9rem;
    }
    67% {
        width: 0;
    }
    77% {
        width: 100%;
    }
    89% {
        width: 100%;
    }
    99% {
        top: -9rem;
        width: 0;
    }
    100% {
        top: 0;
        width: 0;
    }
`;
const messageslideMo = keyframes`
    0% {
        top: 0;
        width: 0;
    }
    1% {
        width: 0;
    }
    11% {
        width: 100%;
    }
    22% {
        width: 100%;
    }
    32% {
        top: 0;
        width: 0;
    }
    33% {
        top: -3.5rem;
    }
    34% {
        width: 0;
    }
    44% {
        width: 100%;

    }
    55% {
        width: 100%;
    }
    65% {
        top: -3.5rem;
        width: 0;
    }
    66%{
        top: -7rem;
    }
    67% {
        width: 0;
    }
    77% {
        width: 100%;
    }
    89% {
        width: 100%;
    }
    99% {
        top: -7rem;
        width: 0;
    }
    100% {
        top: 0;
        width: 0;
    }
`;
const StyledHome = styled.main`
    display: flex;
    height: 100vh;
    max-width: 1600px;
    margin: auto;
    .left {
        flex: 0 0 35%;
        position: relative;
        max-width: 600px;
        background-color: #111;
        &::after {
            content: "";
            display: inline-block;
            position: fixed;
            top: 0;
            left: calc(50% - 1440px);
            z-index: -1;
            width: 50%;
            max-width: 1000px;
            height: 100%;
            background-color: ${(props) => props.theme.mainColor};
        }
        .color-block {
            position: absolute;
            top: -60%;
            left: -40vw;
            z-index: 0;
            width: 50vw;
            height: 200%;
            transform: rotate(-12deg);
            background-color: ${(props) => props.theme.mainColor};
        }
        .img-box {
            height: 90vh;
            margin: 5vh 0 0 30px;
            background-image: url(${(props) => props.theme.mainSrc});
            background-size: cover;
            background-repeat: no-repeat;
            background-position: top;
            border-radius: 15px;
            box-shadow: 0 0 7px rgba(0, 0, 0, 0.9);
            position: relative;
            z-index: 1;
        }
    }
    .right {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: flex-start;
        padding: 0 10% 0 7%;
        .tit-wrap {
            margin-bottom: 60px;
            h2 {
                font-size: 22px;
            }
            h1 {
                margin-bottom: 20px;
                font-size: 45px;
                font-weight: bold;
                white-space: nowrap;
                overflow: hidden;
                position: relative;
                .message {
                    display: block;
                    overflow: hidden;
                    position: absolute;
                    top: 0;
                    left: 80px;  
                    animation: ${messageslide} 8s ease-in-out infinite;
                    strong {
                        display: block;
                        color: ${(props) => props.theme.mainColor};
                    }
                }
            }
            p {
                font-size: 16px;
                line-height: 1.6;
                padding: 0 15% 0 0;
                max-width: 670px;
                word-break: keep-all;
            }
        }
        section {
            margin-bottom: 40px;
            h2 {
                margin-bottom: 20px;
                font-size: 26px;
                font-weight: 600;
            }
            ul {
                display: flex;
                flex-wrap: wrap;
                li {
                    width: 50%;
                    padding-bottom: 10px;
                    font-size: 16px;
                    font-weight: 500;
                    color: ${(props) => props.theme.mainColor};
                    span {
                        font-weight: 400;
                        opacity: 0.8;
                        color: #fff;
                    }
                }
            }
        }
        .more-btn {
            padding: 0px 40px;
            border-radius: 26px;
            background-color: ${(props) => props.theme.mainColor};
            font-size: 15px;
            font-weight: 500;
            color: #fff;
            line-height: 46px;
            letter-spacing: 0.5px;
        }
    }
    @media ${(props) => props.theme.laptop} {
        .left {
            .color-block {
                top: -60%;
                left: -25vw;
                width: 40vw;
                height: 200%;
                transform: rotate(-9deg);
            }
        }
        .right {
            padding-right: 5%;
            .tit-wrap {
                margin-bottom: 40px;
                h1 {
                    margin-bottom: 10px;
                    letter-spacing: -0.5px;
                }
            }
            section {
                h2 {
                    margin-bottom: 10px;
                }
                ul {
                    li {
                        width: 100%;
                    }
                }
            }
        }
    }
    @media ${(props) => props.theme.mobile} {
        flex-wrap: wrap;
        .left {
            flex: none;
            width: 100%;
            overflow: hidden;
            .color-block {
                left: -40vw;
                width: 80vw;
                min-width: 250px;
                transform: rotate(-12deg);
            }
            .img-box {
                width: 90vw;
                height: 55vh;
                margin: 3vh 5vw;
                background-position: center;
            }
        }
        .right {
            width: 100%;
            flex: none;
            margin: 5vh 0;
            padding-bottom: 5vh;
            .tit-wrap {
                h1 {
                    font-size: 35px;
                    .message {
                        left: 60px;
                        animation: ${messageslideMo} 8s ease-in-out infinite;
                    }
                }
                h2 {
                    font-size: 20px;
                }
                p {
                    padding-right: 5%;
                }
            }
        }
    }
`;

export default AboutTxtWrap;

 

 

Yes, your code looks good! You've effectively used the useTranslation hook to translate text content, and your styled components are structured in a clear and organized manner. The use of keyframes for animation and media queries for responsiveness also seems appropriate.

If your translation keys ("about.greeting" and "about.address") are defined correctly in your translation files, and the translations are set up appropriately, this should work as intended.

If you have any specific issues or if there's anything else you'd like to add or modify, feel free to let me know!

 

 

{
    "about": {
      "greeting": "안녕하세요, 저는 김여울입니다. 😀 \n새로운 기술에 관심이 많으며 동료들과의 커뮤니케이션과 상호 피드백 및 리뷰를 좋아합니다. 현재 프론트엔드 개발자로 구직중입니다. 맡은 일에 자부심을 갖고 도전을 경험할 수 있는 회사에서 일하고 싶습니다.",
      "address": "Seoul, Mapo-gu"
    }
  }
 

 

✅src폴더에 i18n.js만들기

 

✅i18n.js 기본 설정해주기. 

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';

// Import translations
import translationEN from './locales/en/translation.json';
import translationKO from './locales/ko/translation.json';

const resources = {
  en: {
    translation: translationEN,
  },
  ko: {
    translation: translationKO,
  },
};

i18n
  .use(initReactI18next) // passes i18n down to react-i18next
  .init({
    resources,
    lng: 'ko', // default language
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false,
    },
  });

export default i18n;


fallbackLng: 'en'는 사용자가 선택한 언어에 대한 번역이 없을 경우, 어플리케이션이 영어(en)를 대체 언어로 사용하도록 설정한 것입니다. 사용자가 원하는 언어에 대한 번역이 없을 때, 어플리케이션은 기본적으로 영어로 텍스트를 표시합니다. 이렇게 함으로써 사용자는 번역이 없더라도 언어 이해가 가능한 내용을 볼 수 있게 됩니다.

 

before

 

import React, { useState, useEffect } from "react";
import styled from "styled-components";
import { Link } from "react-router-dom";

function Header(props) {
    const [Load, setLoad] = useState(false);
    useEffect(() => {
        setTimeout(() => {
            setLoad(true);
        }, 700);
        return () => setLoad(false);
    }, []);

    const handleLanguageChange = () => {
        // 여기서 language 변경을 처리하는 함수를 호출
        if (props.setLanguage) {
            props.setLanguage();
        }
    };

    return (
        <StyledHeader className={`${Load ? "on" : ""}`}>
            <ul>
                <li className={props.page === "home" ? "active" : ""}>
                    {/* Eng or Kor 부분 클릭 시 handleLanguageChange 함수 호출 */}
                    <div onClick={handleLanguageChange}>
                        <Link>
                        <span>Language</span>
                        {props.language === "kor" ? "Eng" : "Kor"}
                        <i className="fas fa-globe"></i>
                        </Link>
                    </div>
                </li>
                <li className={props.page === "home" ? "active" : ""}>
                    <Link to="/">
                        <span>Home</span>
                        <i className="fas fa-home"></i>
                    </Link>
                </li>
                <li className={props.page === "about" ? "active" : ""}>
                    <Link to="/about">
                        <span>About</span>
                        <i className="fas fa-user"></i>
                    </Link>
                </li>
                <li className={props.page === "projects" ? "active" : ""}>
                    <Link to="/projects">
                        <span>Projects</span>
                        <i className="fas fa-tasks"></i>
                    </Link>
                </li>
                <li className={props.page === "blog" ? "active" : ""}>
                    <Link to="/blog">
                        <span>Blog</span>
                        <i className="fas fa-book"></i>
                    </Link>
                </li>
                <li>
                    {/* eslint-disable-next-line react/jsx-no-target-blank */}
                    <a href="https://github.com/mangwoncassie" target="_blank">
                        <span>Github</span>
                        <i className="fab fa-github"></i>
                    </a>
                </li>
            </ul>
        </StyledHeader>
    );
}

const StyledHeader = styled.header`
    position: fixed;
    right: 20px;
    top: 60px;
    bottom: 0;
    z-index: 100;
    opacity: 0;
    &.on {
        top: 0;
        opacity: 1;
        transition: all 0.85s;
    }
    ul {
        display: flex;
        justify-content: center;
        align-items: flex-end;
        flex-direction: column;
        height: 100%;
        li {
            position: relative;
            &:not(:last-child) {
                margin: 0 0 20px 0;
            }
            &.active {
                a {
                    background-color: ${(props) => props.theme.mainColor};
                }
            }
            &:hover {
                a {
                    padding: 13px 14px 13px 25px;
                    background-color: ${(props) => props.theme.mainColor};
                    span {
                        position: relative;
                        opacity: 1;
                        padding-right: 10px;
                        color: #fff;
                    }
                }
            }
            a {
                display: inline-block;
                padding: 13px 14px;
                border-radius: 30px;
                background: #2b2a2a;
                transition: all 0.2s;
                span {
                    position: absolute;
                    right: 0px;
                    display: inline-block;
                    opacity: 0;
                    font-size: 16px;
                    font-weight: 600;
                    letter-spacing: 0.5px;
                    line-height: 1.2;
                    text-transform: uppercase;
                    vertical-align: text-top;
                    transition: opacity 0.3s ease, padding 0.3s ease;
                    color: transparent;
                }
                i {
                    width: 25px;
                    height: 25px;
                    font-size: 20px;
                    text-align: center;
                    line-height: 25px;
                }
            }
        }
    }
`;

export default Header;

 

 

 

혼자해보다가 실패한 것

 

import React, { Component, useState } from "react";
import Header from "../organisms/Header";
import Preloader from "../molecules/Preloader";
import HomeTxtWrap from "../organisms/HomeTxtWrap";
import HomeTxtWrapEng from "../organisms/HomeTxtWrapEng";
import styled from "styled-components";

export class Home extends Component {
    constructor(props) {
        super(props);
        this.state = {
            language: "kor",
        };
    }

    setLanguage = (language) => {
        this.setState({ language });
      };


    render() {
        return (
            <StyledDiv>
                <Header page="home" setLanguage={this.setLanguage} />
                {this.state.language === "kor" ? <HomeTxtWrap /> : <HomeTxtWrapEng />}
                <HomeTxtWrap />
                <Preloader />
            </StyledDiv>
        );
    }
}

export default Home;

const StyledDiv = styled.div`
    overflow: hidden;
    @media ${(props) => props.theme.mobile} {
        overflow: auto;
    }
`;

 

 

import React from "react";
import { BrowserRouter, Route } from "react-router-dom";
import AboutPage from "./pages/AboutPage";
import Home from "./pages/Home";
import ProjectsPage from "./pages/ProjectsPage";
import BlogPage from "./pages/BlogPage";
import ProjectDetail from "./pages/ProjectDetail";
import i18n from "../i18n";
import { I18nextProvider } from "react-i18next";

function App() {
    return (
        <BrowserRouter basename={process.env.PUBLIC_URL}>
            <I18nextProvider i18n={i18n}>
                <Route exact path="/" component={Home} />
                <Route exact path="/about" component={AboutPage} />
                <Route exact path="/projects" component={ProjectsPage} />
                <Route exact path="/projects/:name" component={ProjectDetail} />
                <Route exact path="/blog" component={BlogPage} />
            </I18nextProvider>
        </BrowserRouter>
    );
}

export default App;

 

 

import React, { Component, useState } from "react";
import { withTranslation} from "react-i18next"; // i18n 추가
import Header from "../organisms/Header";
import Preloader from "../molecules/Preloader";
import HomeTxtWrap from "../organisms/HomeTxtWrap";
import styled from "styled-components";

export class Home extends Component {
    render() {
        const { t, i18n } = this.props;

        // 언어 변경 함수
        const handleLanguageChange = () => {
            const newLanguage = i18n.language === "ko" ? "en" : "ko";
            i18n.changeLanguage(newLanguage);
        };


        return (
            <StyledDiv>
                <Header page="home" handleLanguageChange={handleLanguageChange} />
                <HomeTxtWrap />
                <Preloader />
            </StyledDiv>
        );
    }
}



export default withTranslation()(Home);

const StyledDiv = styled.div`
    overflow: hidden;
    @media ${(props) => props.theme.mobile} {
        overflow: auto;
    }
`;

 

import React from "react";
import styled, { keyframes } from "styled-components";
import { Link } from "react-router-dom";
import Heading from "../atoms/Heading";
import { useTranslation } from "react-i18next";// 추가

function HomeTxtWrap() {
    const { t } = useTranslation();

    return (
        <StyledHome>
            <div className="left">
                <div className="color-block"></div>
                <div className="img-box"></div>
            </div>
            <div className="right">
                <div className="tit-wrap">
                    <Heading level="2">HI! HOW ARE YOU?</Heading>
                    <Heading>
                        I'M{" "}
                        <span className="message">
                            <strong>YEOOUL KIM</strong>
                            <strong>WEB DEVELOPER</strong>
                            <strong>FULL-STACK DEVELOPER</strong>
                        </span>
                    </Heading>
                    {/* <p>I’m a Tunisian based web designer & front‑end developer focused on crafting clean & user‑friendly experiences, I am passionate about building excellent software that improves the lives of those around me.</p> */}
                    <p>
                    {t("about.greeting")}
                    </p>
                </div>
                <section>
                    <Heading level="2">PERSONAL INFOS</Heading>
                    <ul>
                        <li>
                            <span>Name : </span>김여울
                        </li>
                        <li>
                            <span>Age : </span>30, 1992.11
                        </li>
                        <li>
                            <span>Phone : </span>010.6257.1107
                        </li>
                        <li>
                            <span>Address : </span>{t("about.address")}
                        </li>
                        <li>
                            <span>Email : </span>dudnfsla1@naver.com
                        </li>
                        {/* <li>
                            <span>Name : </span>Steve
                        </li>
                        <li>
                            <span>Name : </span>Steve
                        </li> */}
                    </ul>
                </section>
                <Link to="/about" className="more-btn">
                    MORE ABOUT ME
                </Link>
            </div>
        </StyledHome>
    );
}

const messageslide = keyframes`
    0% {
        top: 0;
        width: 0;
    }
    1% {
        width: 0;
    }
    11% {
        width: 100%;
    }
    22% {
        width: 100%;
    }
    32% {
        top: 0;
        width: 0;
    }
    33% {
        top: -4.5rem;
    }
    34% {
        width: 0;
    }
    44% {
        width: 100%;
    }
    55% {
        width: 100%;
    }
    65% {
        top: -4.5rem;
        width: 0;
    }
    66%{
        top: -9rem;
    }
    67% {
        width: 0;
    }
    77% {
        width: 100%;
    }
    89% {
        width: 100%;
    }
    99% {
        top: -9rem;
        width: 0;
    }
    100% {
        top: 0;
        width: 0;
    }
`;
const messageslideMo = keyframes`
    0% {
        top: 0;
        width: 0;
    }
    1% {
        width: 0;
    }
    11% {
        width: 100%;
    }
    22% {
        width: 100%;
    }
    32% {
        top: 0;
        width: 0;
    }
    33% {
        top: -3.5rem;
    }
    34% {
        width: 0;
    }
    44% {
        width: 100%;

    }
    55% {
        width: 100%;
    }
    65% {
        top: -3.5rem;
        width: 0;
    }
    66%{
        top: -7rem;
    }
    67% {
        width: 0;
    }
    77% {
        width: 100%;
    }
    89% {
        width: 100%;
    }
    99% {
        top: -7rem;
        width: 0;
    }
    100% {
        top: 0;
        width: 0;
    }
`;
const StyledHome = styled.main`
    display: flex;
    height: 100vh;
    max-width: 1600px;
    margin: auto;
    .left {
        flex: 0 0 35%;
        position: relative;
        max-width: 600px;
        background-color: #111;
        &::after {
            content: "";
            display: inline-block;
            position: fixed;
            top: 0;
            left: calc(50% - 1440px);
            z-index: -1;
            width: 50%;
            max-width: 1000px;
            height: 100%;
            background-color: ${(props) => props.theme.mainColor};
        }
        .color-block {
            position: absolute;
            top: -60%;
            left: -40vw;
            z-index: 0;
            width: 50vw;
            height: 200%;
            transform: rotate(-12deg);
            background-color: ${(props) => props.theme.mainColor};
        }
        .img-box {
            height: 90vh;
            margin: 5vh 0 0 30px;
            background-image: url(${(props) => props.theme.mainSrc});
            background-size: cover;
            background-repeat: no-repeat;
            background-position: top;
            border-radius: 15px;
            box-shadow: 0 0 7px rgba(0, 0, 0, 0.9);
            position: relative;
            z-index: 1;
        }
    }
    .right {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: flex-start;
        padding: 0 10% 0 7%;
        .tit-wrap {
            margin-bottom: 60px;
            h2 {
                font-size: 22px;
            }
            h1 {
                margin-bottom: 20px;
                font-size: 45px;
                font-weight: bold;
                white-space: nowrap;
                overflow: hidden;
                position: relative;
                .message {
                    display: block;
                    overflow: hidden;
                    position: absolute;
                    top: 0;
                    left: 80px;  
                    animation: ${messageslide} 8s ease-in-out infinite;
                    strong {
                        display: block;
                        color: ${(props) => props.theme.mainColor};
                    }
                }
            }
            p {
                font-size: 16px;
                line-height: 1.6;
                padding: 0 15% 0 0;
                max-width: 670px;
                word-break: keep-all;
            }
        }
        section {
            margin-bottom: 40px;
            h2 {
                margin-bottom: 20px;
                font-size: 26px;
                font-weight: 600;
            }
            ul {
                display: flex;
                flex-wrap: wrap;
                li {
                    width: 50%;
                    padding-bottom: 10px;
                    font-size: 16px;
                    font-weight: 500;
                    color: ${(props) => props.theme.mainColor};
                    span {
                        font-weight: 400;
                        opacity: 0.8;
                        color: #fff;
                    }
                }
            }
        }
        .more-btn {
            padding: 0px 40px;
            border-radius: 26px;
            background-color: ${(props) => props.theme.mainColor};
            font-size: 15px;
            font-weight: 500;
            color: #fff;
            line-height: 46px;
            letter-spacing: 0.5px;
        }
    }
    @media ${(props) => props.theme.laptop} {
        .left {
            .color-block {
                top: -60%;
                left: -25vw;
                width: 40vw;
                height: 200%;
                transform: rotate(-9deg);
            }
        }
        .right {
            padding-right: 5%;
            .tit-wrap {
                margin-bottom: 40px;
                h1 {
                    margin-bottom: 10px;
                    letter-spacing: -0.5px;
                }
            }
            section {
                h2 {
                    margin-bottom: 10px;
                }
                ul {
                    li {
                        width: 100%;
                    }
                }
            }
        }
    }
    @media ${(props) => props.theme.mobile} {
        flex-wrap: wrap;
        .left {
            flex: none;
            width: 100%;
            overflow: hidden;
            .color-block {
                left: -40vw;
                width: 80vw;
                min-width: 250px;
                transform: rotate(-12deg);
            }
            .img-box {
                width: 90vw;
                height: 55vh;
                margin: 3vh 5vw;
                background-position: center;
            }
        }
        .right {
            width: 100%;
            flex: none;
            margin: 5vh 0;
            padding-bottom: 5vh;
            .tit-wrap {
                h1 {
                    font-size: 35px;
                    .message {
                        left: 60px;
                        animation: ${messageslideMo} 8s ease-in-out infinite;
                    }
                }
                h2 {
                    font-size: 20px;
                }
                p {
                    padding-right: 5%;
                }
            }
        }
    }
`;

export default HomeTxtWrap;

 

before

 

import React, { useState, useEffect } from "react";
import styled from "styled-components";
import { Link } from "react-router-dom";
import { withTranslation } from "react-i18next"; // withTranslation 추가

function Header(props) {
    const [Load, setLoad] = useState(false);
    useEffect(() => {
        setTimeout(() => {
            setLoad(true);
        }, 700);
        return () => setLoad(false);
    }, []);

    const handleLanguageChange = () => {
        // i18n을 이용하여 언어 변경
        const { i18n } = props;
        const newLanguage = i18n.language === "ko" ? "en" : "ko";
        i18n.changeLanguage(newLanguage);
        if (props.setLanguage) {
          props.setLanguage(newLanguage);
        }
      };

    return (
        <StyledHeader className={`${Load ? "on" : ""}`}>
            <ul>
                <li className={props.page === "home" ? "active" : ""}>
                    {/* Eng or Kor 부분 클릭 시 handleLanguageChange 함수 호출 */}
                    <div onClick={handleLanguageChange}>
                        <Link>
                        <span>Language</span>
                        {props.language === "ko" ? "en" : "ko"}
                        <i className="fas fa-globe"></i>
                        </Link>
                    </div>
                </li>
                <li className={props.page === "home" ? "active" : ""}>
                    <Link to="/">
                        <span>Home</span>
                        <i className="fas fa-home"></i>
                    </Link>
                </li>
                <li className={props.page === "about" ? "active" : ""}>
                    <Link to="/about">
                        <span>About</span>
                        <i className="fas fa-user"></i>
                    </Link>
                </li>
                <li className={props.page === "projects" ? "active" : ""}>
                    <Link to="/projects">
                        <span>Projects</span>
                        <i className="fas fa-tasks"></i>
                    </Link>
                </li>
                <li className={props.page === "blog" ? "active" : ""}>
                    <Link to="/blog">
                        <span>Blog</span>
                        <i className="fas fa-book"></i>
                    </Link>
                </li>
                <li>
                    {/* eslint-disable-next-line react/jsx-no-target-blank */}
                    <a href="https://github.com/mangwoncassie" target="_blank">
                        <span>Github</span>
                        <i className="fab fa-github"></i>
                    </a>
                </li>
            </ul>
        </StyledHeader>
    );
}

const StyledHeader = styled.header`
    position: fixed;
    right: 20px;
    top: 60px;
    bottom: 0;
    z-index: 100;
    opacity: 0;
    &.on {
        top: 0;
        opacity: 1;
        transition: all 0.85s;
    }
    ul {
        display: flex;
        justify-content: center;
        align-items: flex-end;
        flex-direction: column;
        height: 100%;
        li {
            position: relative;
            &:not(:last-child) {
                margin: 0 0 20px 0;
            }
            &.active {
                a {
                    background-color: ${(props) => props.theme.mainColor};
                }
            }
            &:hover {
                a {
                    padding: 13px 14px 13px 25px;
                    background-color: ${(props) => props.theme.mainColor};
                    span {
                        position: relative;
                        opacity: 1;
                        padding-right: 10px;
                        color: #fff;
                    }
                }
            }
            a {
                display: inline-block;
                padding: 13px 14px;
                border-radius: 30px;
                background: #2b2a2a;
                transition: all 0.2s;
                span {
                    position: absolute;
                    right: 0px;
                    display: inline-block;
                    opacity: 0;
                    font-size: 16px;
                    font-weight: 600;
                    letter-spacing: 0.5px;
                    line-height: 1.2;
                    text-transform: uppercase;
                    vertical-align: text-top;
                    transition: opacity 0.3s ease, padding 0.3s ease;
                    color: transparent;
                }
                i {
                    width: 25px;
                    height: 25px;
                    font-size: 20px;
                    text-align: center;
                    line-height: 25px;
                }
            }
        }
    }
`;

export default Header;

 

 

after

header도 export default withTranslation()(Header); 해줘야함.

import React, { useState, useEffect } from "react";
import styled from "styled-components";
import { Link } from "react-router-dom";
import { withTranslation } from "react-i18next"; // withTranslation 추가


function Header(props) {
    const [Load, setLoad] = useState(false);
    useEffect(() => {
        setTimeout(() => {
            setLoad(true);
        }, 700);
        return () => setLoad(false);
    }, []);

    const handleLanguageChange = () => {
        // i18n을 이용하여 언어 변경
        const { i18n } = props;
        const newLanguage = i18n.language === "ko" ? "en" : "ko";
        i18n.changeLanguage(newLanguage);
        if (props.setLanguage) {
          props.setLanguage(newLanguage);
        }
      };

    return (
        <StyledHeader className={`${Load ? "on" : ""}`}>
            <ul>
                <li className={props.page === "home" ? "active" : ""}>
                    {/* Eng or Kor 부분 클릭 시 handleLanguageChange 함수 호출 */}
                    <div onClick={handleLanguageChange}>
                        <Link>
                        <span>Language</span>
                        {props.i18n.language === "ko" ? "en" : "ko"}
                        <i className="fas fa-globe"></i>
                        </Link>
                    </div>
                </li>
                <li className={props.page === "home" ? "active" : ""}>
                    <Link to="/">
                        <span>Home</span>
                        <i className="fas fa-home"></i>
                    </Link>
                </li>
                <li className={props.page === "about" ? "active" : ""}>
                    <Link to="/about">
                        <span>About</span>
                        <i className="fas fa-user"></i>
                    </Link>
                </li>
                <li className={props.page === "projects" ? "active" : ""}>
                    <Link to="/projects">
                        <span>Projects</span>
                        <i className="fas fa-tasks"></i>
                    </Link>
                </li>
                <li className={props.page === "blog" ? "active" : ""}>
                    <Link to="/blog">
                        <span>Blog</span>
                        <i className="fas fa-book"></i>
                    </Link>
                </li>
                <li>
                    {/* eslint-disable-next-line react/jsx-no-target-blank */}
                    <a href="https://github.com/mangwoncassie" target="_blank">
                        <span>Github</span>
                        <i className="fab fa-github"></i>
                    </a>
                </li>
            </ul>
        </StyledHeader>
    );
}

const StyledHeader = styled.header`
    position: fixed;
    right: 20px;
    top: 60px;
    bottom: 0;
    z-index: 100;
    opacity: 0;
    &.on {
        top: 0;
        opacity: 1;
        transition: all 0.85s;
    }
    ul {
        display: flex;
        justify-content: center;
        align-items: flex-end;
        flex-direction: column;
        height: 100%;
        li {
            position: relative;
            &:not(:last-child) {
                margin: 0 0 20px 0;
            }
            &.active {
                a {
                    background-color: ${(props) => props.theme.mainColor};
                }
            }
            &:hover {
                a {
                    padding: 13px 14px 13px 25px;
                    background-color: ${(props) => props.theme.mainColor};
                    span {
                        position: relative;
                        opacity: 1;
                        padding-right: 10px;
                        color: #fff;
                    }
                }
            }
            a {
                display: inline-block;
                padding: 13px 14px;
                border-radius: 30px;
                background: #2b2a2a;
                transition: all 0.2s;
                span {
                    position: absolute;
                    right: 0px;
                    display: inline-block;
                    opacity: 0;
                    font-size: 16px;
                    font-weight: 600;
                    letter-spacing: 0.5px;
                    line-height: 1.2;
                    text-transform: uppercase;
                    vertical-align: text-top;
                    transition: opacity 0.3s ease, padding 0.3s ease;
                    color: transparent;
                }
                i {
                    width: 25px;
                    height: 25px;
                    font-size: 20px;
                    text-align: center;
                    line-height: 25px;
                }
            }
        }
    }
`;

export default withTranslation()(Header);

 

 

 

2시간 삽질 해결...

language 에 Link to 연결안해서 그런거임.

console.log 오류 잘 볼 것 '

 

import React, { useState, useEffect } from "react";
import styled from "styled-components";
import { Link } from "react-router-dom";
import { withTranslation } from "react-i18next"; // withTranslation 추가


function Header(props) {
    const [Load, setLoad] = useState(false);
    useEffect(() => {
        setTimeout(() => {
          setLoad(true);
        }, 700);
        return () => setLoad(false);
      }, []);



    const handleLanguageChange = () => {
        // i18n을 이용하여 언어 변경 withTranslation 얘가 props에 i18n 가져옴
        const { i18n } = props;
        console.log(props+"propspage");

        const currentLanguage = i18n.language;
        const newLanguage = currentLanguage === "ko" ? "en" : "ko";

        i18n.changeLanguage(newLanguage);
        if (props.setLanguage) {
            props.setLanguage(newLanguage);
        }

          // 상태 변경 후 반환되는 컴포넌트에 영향을 주기 위해 여기서 스타일 변경을 구현
          setLoad(true);

    }
       




        return (
            <StyledHeader className={`${Load ? "on" : ""}`}>
                <ul>
                    <li className={props.page === "home" ? "active" : ""}>
                        {/* Eng or Kor 부분 클릭 시 handleLanguageChange 함수 호출 */}
                        <div onClick={handleLanguageChange}>
                            <Link to="/">
                                <span>Language</span>
                                {props.i18n.language === "ko" ? "en" : "ko"}
                                <i className="fas fa-globe"></i>
                            </Link>
                        </div>
                    </li>
                    <li className={props.page === "home" ? "active" : ""}>
                        <Link to="/">
                            <span>Home</span>
                            <i className="fas fa-home"></i>
                        </Link>
                    </li>
                    <li className={props.page === "about" ? "active" : ""}>
                        <Link to="/about">
                            <span>About</span>
                            <i className="fas fa-user"></i>
                        </Link>
                    </li>
                    <li className={props.page === "projects" ? "active" : ""}>
                        <Link to="/projects">
                            <span>Projects</span>
                            <i className="fas fa-tasks"></i>
                        </Link>
                    </li>
                    <li className={props.page === "blog" ? "active" : ""}>
                        <Link to="/blog">
                            <span>Blog</span>
                            <i className="fas fa-book"></i>
                        </Link>
                    </li>
                    <li>
                        {/* eslint-disable-next-line react/jsx-no-target-blank */}
                        <a href="https://github.com/mangwoncassie" target="_blank">
                            <span>Github</span>
                            <i className="fab fa-github"></i>
                        </a>
                    </li>
                </ul>
            </StyledHeader>
        );
   


    return (
        <StyledHeader className={`${Load ? "on" : ""}`}>
            <ul>
                <li className={props.page === "home" ? "active" : ""}>
                    {/* Eng or Kor 부분 클릭 시 handleLanguageChange 함수 호출 */}
                    <div onClick={handleLanguageChange}>
                        <Link>
                            <span>Language</span>
                            {props.i18n.language === "ko" ? "en" : "ko"}
                            <i className="fas fa-globe"></i>
                        </Link>
                    </div>
                </li>
                <li className={props.page === "home" ? "active" : ""}>
                    <Link to="/">
                        <span>Home</span>
                        <i className="fas fa-home"></i>
                    </Link>
                </li>
                <li className={props.page === "about" ? "active" : ""}>
                    <Link to="/about">
                        <span>About</span>
                        <i className="fas fa-user"></i>
                    </Link>
                </li>
                <li className={props.page === "projects" ? "active" : ""}>
                    <Link to="/projects">
                        <span>Projects</span>
                        <i className="fas fa-tasks"></i>
                    </Link>
                </li>
                <li className={props.page === "blog" ? "active" : ""}>
                    <Link to="/blog">
                        <span>Blog</span>
                        <i className="fas fa-book"></i>
                    </Link>
                </li>
                <li>
                    {/* eslint-disable-next-line react/jsx-no-target-blank */}
                    <a href="https://github.com/mangwoncassie" target="_blank">
                        <span>Github</span>
                        <i className="fab fa-github"></i>
                    </a>
                </li>
            </ul>
        </StyledHeader>
    );
};

const StyledHeader = styled.header`
    position: fixed;
    right: 20px;
    top: 60px;
    bottom: 0;
    z-index: 100;
    opacity: 0;
    &.on {
        top: 0;
        opacity: 1;
        transition: all 0.85s;
    }
    ul {
        display: flex;
        justify-content: center;
        align-items: flex-end;
        flex-direction: column;
        height: 100%;
        li {
            position: relative;
            &:not(:last-child) {
                margin: 0 0 20px 0;
            }
            &.active {
                a {
                    background-color: ${(props) => props.theme.mainColor};
                }
            }
            &:hover {
                a {
                    padding: 13px 14px 13px 25px;
                    background-color: ${(props) => props.theme.mainColor};
                    span {
                        position: relative;
                        opacity: 1;
                        padding-right: 10px;
                        color: #fff;
                    }
                }
            }
            a {
                display: inline-block;
                padding: 13px 14px;
                border-radius: 30px;
                background: #2b2a2a;
                transition: all 0.2s;
                span {
                    position: absolute;
                    right: 0px;
                    display: inline-block;
                    opacity: 0;
                    font-size: 16px;
                    font-weight: 600;
                    letter-spacing: 0.5px;
                    line-height: 1.2;
                    text-transform: uppercase;
                    vertical-align: text-top;
                    transition: opacity 0.3s ease, padding 0.3s ease;
                    color: transparent;
                }
                i {
                    width: 25px;
                    height: 25px;
                    font-size: 20px;
                    text-align: center;
                    line-height: 25px;
                }
            }
        }
    }
`;

export default withTranslation()(Header);

 

중괄호 닫고 return 열어야함.

 

{
  "about": {
    "greeting": "Greetings! 👋 I am Cassie Kim from Seoul, South Korea. 😀",
    "interests": [
      "I have a keen interest in embracing new technologies and thrive on effective communication with colleagues, embracing constructive feedback, and engaging in collaborative reviews.",
      "Currently, I am actively seeking opportunities as a frontend developer.",
      "I take pride in my work and seek a dynamic work environment where I can contribute my skills and experience. I am eager to tackle challenges, and I am on the lookout for a company that values innovation and provides a platform for continuous growth.",
      "Feel free to explore my portfolio and get in touch. I look forward to new opportunities and exciting collaborations!"
    ],
    "address": "Seoul, Mapo-gu"
  }
}

 

 

 

이렇게 수정하면 map 함수를 사용하여 각 문단을 순회할 때, 현재 문단이 마지막 문단이 아닌 경우에만 <br /> 태그가 추가됩니다.

 

알겠습니다. 각 문단 사이에 한 줄 띄우려면 <p> 태그 뒤에 <br /> 태그를 추가하면 됩니다. 수정된 코드는 다음과 같습니다:

반응형