refactor: remove localization

Why:
- I was doing half-ass job at it in the first place (code-wise)
- maintaining sites for more than one locale proved to be more difficult
  than initially anticipated
- having more than one local does not add much value
- overall not worth it
This commit is contained in:
Kim, Jimin 2023-06-17 19:18:46 +09:00
parent e26738ee07
commit 4404518359
30 changed files with 99 additions and 691 deletions

View file

@ -9,59 +9,36 @@ import { DataToPass } from "."
export default function parsePortfolio(data: DataToPass): void { export default function parsePortfolio(data: DataToPass): void {
const { urlPath, markdownRaw, markdownData } = data const { urlPath, markdownRaw, markdownData } = data
if (urlPath.endsWith(".kr")) { if (markdownData.badges) {
const contentID = urlPath.slice(0, urlPath.length - 3) ;(markdownData.badges as string[]).forEach((slug) => {
// todo: handle cases when icon is not on simple-icons
if (portfolioData.projects[contentID]) { portfolioData.skills.add(slug)
portfolioData.projects[contentID] = {
...portfolioData.projects[contentID],
overview_kr: markdownData.overview as string,
}
} else {
portfolioData.projects[contentID] = {
name: "",
image: "",
overview_en: "",
overview_kr: markdownData.overview as string,
badges: [],
repo: "",
}
}
} else {
if (markdownData.badges) {
;(markdownData.badges as string[]).forEach((slug) => {
// todo: handle cases when icon is not on simple-icons
portfolioData.skills.add(slug) // eslint-disable-next-line @typescript-eslint/no-var-requires
const icon = require("simple-icons")[slug]
// eslint-disable-next-line @typescript-eslint/no-var-requires const color = tinycolor(icon.hex).lighten(5).desaturate(5)
const icon = require("simple-icons")[slug]
const color = tinycolor(icon.hex).lighten(5).desaturate(5) // save svg icon
writeToFile(
`${iconsDirectoryPath}/${icon.slug}.json`,
JSON.stringify({
svg: icon.svg,
hex: color.toHexString(),
isDark: color.isDark(),
title: icon.title,
})
)
})
}
// save svg icon portfolioData.projects[urlPath] = {
writeToFile( name: markdownData.name as string,
`${iconsDirectoryPath}/${icon.slug}.json`, image: markdownData.image as string,
JSON.stringify({ overview: markdownData.overview as string,
svg: icon.svg, badges: (markdownData.badges as string[]) || [],
hex: color.toHexString(), repo: (markdownData.repo as string) || "",
isDark: color.isDark(),
title: icon.title,
})
)
})
}
portfolioData.projects[urlPath] = {
name: markdownData.name as string,
image: markdownData.image as string,
overview_en: markdownData.overview as string,
overview_kr: portfolioData.projects[urlPath]
? portfolioData.projects[urlPath].overview_kr
: "",
badges: (markdownData.badges as string[]) || [],
repo: (markdownData.repo as string) || "",
}
} }
writeToFile( writeToFile(

View file

@ -10,17 +10,15 @@ export default function parseUnsearchable(data: DataToPass): void {
// convert path like /XXX/YYY/ZZZ to /YYY/ZZZ // convert path like /XXX/YYY/ZZZ to /YYY/ZZZ
const urlPath = _urlPath.slice(_urlPath.slice(1).indexOf("/") + 1) const urlPath = _urlPath.slice(_urlPath.slice(1).indexOf("/") + 1)
if (!urlPath.endsWith(".kr.md")) { addDocument({
addDocument({ title: markdownData.title,
title: markdownData.title, body: markdownData.content,
body: markdownData.content, url: urlPath,
url: urlPath, })
})
// Parse data that will be written to map.js // Parse data that will be written to map.js
map.unsearchable[urlPath] = { map.unsearchable[urlPath] = {
title: markdownData.title as string, title: markdownData.title as string,
}
} }
/** /**

View file

@ -1,29 +0,0 @@
---
overview: 유니티 게임을 위한 모드. 인-게임 UI와 OBS 오버레이를 제공.
---
## 개요
War Brokers Mods, 줄여서 WBM은 게임 [War Brokers](https://warbrokers.io)를 위한 모드입니다.
## 모드
C#으로 만들어졌으며, [BepInEx](https://github.com/BepInEx/BepInEx) 프레임워크를 사용하여 게임의 여러 측면을 패치합니다.
## OBS 오버레이
<p align="center">
<img alt="OBS 오버레이" src="/img/portfolio/wbm-overlays.png" />
</p>
[OBS 스튜디오](https://github.com/obsproject/obs-studio)를 위한 오버레이.
웹 기술을 이용하여 제작되었습니다 (자바스크립트, CSS, HTML 등).
## 설치기
<p align="center">
<img alt="설치기" src="/img/portfolio/wbm-installer.png" />
</p>
간단한 크로스 플랫폼 설칙 및 업데이트 관리기.
[tauri](https://github.com/tauri-apps/tauri), [rust](https://github.com/rust-lang/rust), [svelte](https://github.com/sveltejs/svelte), 그리고 [tailwind css](https://github.com/tailwindlabs/tailwindcss)를 사용해 만들어졌습니다.

View file

@ -1,29 +0,0 @@
---
title: 소개
---
## 누구세요?
이름: 김지민<br />
거주지: 서울, 대한만국<br />
출생: 2002년<br />
MBTI: [논리적인 사색가 INTP-A](https://www.16personalities.com/ko/성격유형-intp)
<p align="center">
<img alt="MBTI result" src="/img/mbti.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 100%; width: 600px" />
2023년 1월 2일에 <a href="https://16personalities.com">16personalities.com</a> 검사 결과
</p>
## 링크
- [깃허브](https://github.com/developomp)
- [목표](/goals)
## 연락
아래 기재된 방법 외에 다른 방법으로 연락을 할 시 답변을 드리지 못 할 수 있습니다.
| 플랫폼 | 아이디 | 답변 시간 |
| --------------------------------: | :----------------------------------: | :-------- |
| [디스코드](https://discord.com) | developomp#0001 (501277805540147220) | 즉각 |
| [지메일](https://mail.google.com) | developomp@gmail.com | 2~4일 |

View file

@ -1,61 +0,0 @@
---
title: 목표
---
## 프로그래밍
- 깃허브에서 별 X개 얻기 (본인 미포함)
- [x] 10
- [ ] 50
- [ ] 100
- [ ] 500
- [ ] 1,000
- 깃허브에서 1년에 X개의 기여하기 (1월 1일 ~ 12월 31일)
- [x] 1000 ([2021](https://github.com/developomp?tab=overview&from=2021-12-01&to=2021-12-31))
- [x] 2000 ([2022](https://github.com/developomp?tab=overview&from=2022-12-01&to=2022-12-31))
- [ ] 3000
- 알고리즘 문제풀이 ([solved.ac](https://solved.ac))
- 모든 X 문제 풀기
- [x] [브론즈 V](https://solved.ac/problems/level/1)
- [ ] [브론즈 IV](https://solved.ac/problems/level/2)
- X티어 달성하기
- [x] 브론즈
- [x] 실버
- [x] 골드 (현재 골드 IV)
- [ ] 플래티넘
- [ ] 다이아
- ~~루비~~
- X자리수 랭킹 달성하기
- [ ] 4
- [ ] 3
## 기술
- 분당 X타 치기 (규칙): 일분동안 타자를 친 후 맞은 타의 개수를 센다. 테스트는 [10fastfingers.com](https://10fastfingers.com/typing-test)를 통해 진행한다. 타자 속도는 일관성이 있어야 하며 요구시 같은 결과를 낼 수 있어야 한다.)
- 한국어
- [x] 100
- [x] 150
- [x] 200
- [ ] 250
- [ ] 300
- 영어
- [x] 100
- [x] 150
- [x] 200
- [x] 250
- [ ] 300
- [x] 키보드 안보고 타자치기
## Etc
- [ ] 유튜브에 만족할만한 퀄리티의 100만 조회수 이상의 영상 올리기
- [ ] 취직
- [ ] FAANG (또는 이에 준하는) 기업에 취직
- [ ] 집 구매
- [ ] 기술적 특이점 목격하기
- [ ] 달에 가기
- X번째 생일 축하하기
- [x] 15
- [x] 20
- [ ] 25
- [ ] 30

View file

@ -1,34 +0,0 @@
---
title: 이력서
---
## 김지민
[![깃허브](https://img.shields.io/badge/깃허브-black?style=for-the-badge&logo=github)](https://github.com/developomp)
[![포트폴리오](https://img.shields.io/badge/포트폴리오-grey?style=for-the-badge)](/portfolio)
프론트엔드 엔지니어 지망생
무엇이든지 직접 만들어보아야 직성이 풀리고 궁금한 것이 있으면 나사 하나,
전선 하나까지 뜯어서 원리를 이해하기 전까지 만족할 줄 모르는 천성 개발자 김지민입니다.
특징:
- [아치 리눅스](https://archlinux.org)를 사용중 ([깃허브](https://github.com/developomp/setup))
- 한국어와 영어를 원어민 수준으로 구사 할 수 있음
이메일: developomp@gmail.com
## 교육
### [홍익대학교](https://wwwce.hongik.ac.kr) 컴퓨터공학과
- 2022년 3월 - 현재
## 깃허브
<img alt="github metrics" src="https://raw.githubusercontent.com/developomp/developomp/master/github-metrics.svg" style="display: block; margin-left: auto; margin-right: auto; max-width: 100%;">
## 기술
<img alt="programming skills" src="/img/skills.svg" style="display: block; margin-left: auto; margin-right: auto; max-width: 100%;" />

View file

@ -2,7 +2,7 @@ import darkTheme from "@developomp-site/theme/dist/dark.json"
import lightTheme from "@developomp-site/theme/dist/light.json" import lightTheme from "@developomp-site/theme/dist/light.json"
import { useContext, useEffect, useState } from "react" import { useContext, useEffect, useState } from "react"
import { Routes, Route, useNavigate, useLocation } from "react-router-dom" import { Routes, Route } from "react-router-dom"
import styled, { ThemeProvider } from "styled-components" import styled, { ThemeProvider } from "styled-components"
import { Helmet } from "react-helmet-async" import { Helmet } from "react-helmet-async"
import { isIE } from "react-device-detect" import { isIE } from "react-device-detect"
@ -37,18 +37,9 @@ const StyledContentContainer = styled.div`
export default function App() { export default function App() {
const { globalState } = useContext(globalContext) const { globalState } = useContext(globalContext)
const { locale } = globalState
const navigate = useNavigate()
const { pathname } = useLocation()
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
// update url on locale change
useEffect(() => {
navigate(locale + pathname.slice(3))
}, [locale])
useEffect(() => { useEffect(() => {
// set loading to false if all fonts are loaded // set loading to false if all fonts are loaded
// checks if document.fonts.onloadingdone is supported on the browser // checks if document.fonts.onloadingdone is supported on the browser
@ -59,10 +50,6 @@ export default function App() {
} else { } else {
setIsLoading(false) setIsLoading(false)
} }
// automatically add locale prefix if it's not specified
if (!pathname.startsWith("/en") && !pathname.startsWith("/kr"))
navigate(`/${globalState.locale}${pathname}`)
}, []) }, [])
if (isIE) if (isIE)
@ -93,29 +80,12 @@ export default function App() {
<Loading /> <Loading />
) : ( ) : (
<Routes> <Routes>
{/* <Route index element={<Home />} />
Using this ugly code because the developers of react-router-dom decided that <Route path="search" element={<Search />} />
removing regex support was a good idea. <Route path="portfolio" element={<Portfolio />} />
https://github.com/remix-run/react-router/issues/7285 <Route path="404" element={<NotFound />} />
*/} <Route path="loading" element={<Loading />} />
<Route path="*" element={<Page />} />
<Route path="en">
<Route index element={<Home />} />
<Route path="search" element={<Search />} />
<Route path="portfolio" element={<Portfolio />} />
<Route path="404" element={<NotFound />} />
<Route path="loading" element={<Loading />} />
<Route path="*" element={<Page />} />
</Route>
<Route path="kr">
<Route index element={<Home />} />
<Route path="search" element={<Search />} />
<Route path="portfolio" element={<Portfolio />} />
<Route path="404" element={<NotFound />} />
<Route path="loading" element={<Loading />} />
<Route path="*" element={<Page />} />
</Route>
</Routes> </Routes>
)} )}
</StyledContentContainer> </StyledContentContainer>

View file

@ -1,6 +1,5 @@
import styled from "styled-components" import styled from "styled-components"
import LocaleToggleButton from "./LocaleToggleButton"
import ThemeToggleButton from "./ThemeToggleButton" import ThemeToggleButton from "./ThemeToggleButton"
import SearchButton from "./SearchButton" import SearchButton from "./SearchButton"
@ -13,7 +12,6 @@ const RightButtons = styled.div`
export default () => { export default () => {
return ( return (
<RightButtons> <RightButtons>
<LocaleToggleButton />
<ThemeToggleButton /> <ThemeToggleButton />
<SearchButton /> <SearchButton />
</RightButtons> </RightButtons>

View file

@ -1,49 +0,0 @@
import type { SiteLocale } from "../../../globalContext"
import { useContext } from "react"
import styled from "styled-components"
import ReactTooltip from "react-tooltip"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { faLanguage } from "@fortawesome/free-solid-svg-icons"
import { ActionsEnum, globalContext } from "../../../globalContext"
import { HeaderButtonCSS } from "../HeaderButton"
interface Props {
locale: SiteLocale
}
const LocaleToggleButton = styled.button<Props>`
${HeaderButtonCSS}
border: none;
width: 72px;
${(props) => (props.locale == "en" ? "" : "transform: scaleX(-1);")};
`
export default () => {
const { globalState, dispatch } = useContext(globalContext)
return (
<>
<LocaleToggleButton
data-tip
data-for="locale"
onClick={() => {
dispatch({
type: ActionsEnum.UPDATE_LOCALE,
payload: globalState.locale == "en" ? "kr" : "en",
})
}}
locale={globalState.locale}
>
<FontAwesomeIcon icon={faLanguage} />
</LocaleToggleButton>
<ReactTooltip id="locale" type="dark" effect="solid">
<span>{globalState.locale == "en" ? "English" : "한국어"} </span>
</ReactTooltip>
</>
)
}

View file

@ -1,4 +1,3 @@
import { useContext } from "react"
import { Link } from "react-router-dom" import { Link } from "react-router-dom"
import ReactTooltip from "react-tooltip" import ReactTooltip from "react-tooltip"
@ -6,23 +5,18 @@ import HeaderButton from "../HeaderButton"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { faSearch } from "@fortawesome/free-solid-svg-icons" import { faSearch } from "@fortawesome/free-solid-svg-icons"
import { globalContext } from "../../../globalContext"
const SearchButton = () => { const SearchButton = () => {
const { globalState } = useContext(globalContext)
const { locale } = globalState
return ( return (
<> <>
<div> <div>
<Link data-tip data-for="search" to={`/${locale}/search`}> <Link data-tip data-for="search" to="/search">
<HeaderButton> <HeaderButton>
<FontAwesomeIcon icon={faSearch} /> <FontAwesomeIcon icon={faSearch} />
</HeaderButton> </HeaderButton>
</Link> </Link>
</div> </div>
<ReactTooltip id="search" type="dark" effect="solid"> <ReactTooltip id="search" type="dark" effect="solid">
<span>{locale == "en" ? "Search" : "검색"}</span> <span>Search</span>
</ReactTooltip> </ReactTooltip>
</> </>
) )

View file

@ -40,11 +40,7 @@ const ThemeToggleButton = () => {
{!isMobile && ( {!isMobile && (
<ReactTooltip id="theme" type="dark" effect="solid"> <ReactTooltip id="theme" type="dark" effect="solid">
{globalState.locale == "en" ? ( <span>Using {theme} theme</span>
<span>Using {theme} theme</span>
) : (
<span>{theme == "dark" ? "어두운" : "밝은"} </span>
)}
</ReactTooltip> </ReactTooltip>
)} )}
</> </>

View file

@ -1,4 +1,3 @@
import { useContext } from "react"
import { Link } from "react-router-dom" import { Link } from "react-router-dom"
import styled from "styled-components" import styled from "styled-components"
import ReadProgress from "./ReadProgress" import ReadProgress from "./ReadProgress"
@ -7,7 +6,6 @@ import Nav from "./Nav"
import Sidebar from "../Sidebar" import Sidebar from "../Sidebar"
import { globalContext } from "../../globalContext"
import Buttons from "./Buttons" import Buttons from "./Buttons"
const Header = styled.header` const Header = styled.header`
@ -44,13 +42,10 @@ const Icon = styled.img`
` `
export default () => { export default () => {
const { globalState } = useContext(globalContext)
const { locale } = globalState
return ( return (
<Header> <Header>
<Container> <Container>
<Link to={`/${locale}`}> <Link to="/">
<Icon src="/icon/icon_circle.svg" /> <Icon src="/icon/icon_circle.svg" />
</Link> </Link>
<Nav /> <Nav />

View file

@ -1,11 +1,9 @@
import { useContext } from "react"
import styled from "styled-components" import styled from "styled-components"
import { Link } from "react-router-dom" import { Link } from "react-router-dom"
import HeaderButton from "./HeaderButton" import HeaderButton from "./HeaderButton"
import NavbarData from "../../data/NavbarData" import NavbarData from "../../data/NavbarData"
import { globalContext } from "../../globalContext"
const Nav = styled.div` const Nav = styled.div`
display: flex; display: flex;
@ -18,15 +16,11 @@ const Nav = styled.div`
` `
export default () => { export default () => {
const { globalState } = useContext(globalContext)
return ( return (
<Nav> <Nav>
{NavbarData.map((item, index) => ( {NavbarData.map((item, index) => (
<Link key={index} to={globalState.locale + item.path}> <Link key={index} to={item.path}>
<HeaderButton> <HeaderButton>{item.title}</HeaderButton>
{globalState.locale == "en" ? item.title_en : item.title_kr}
</HeaderButton>
</Link> </Link>
))} ))}
</Nav> </Nav>

View file

@ -1,4 +1,3 @@
import { useContext } from "react"
import styled from "styled-components" import styled from "styled-components"
import { Link } from "react-router-dom" import { Link } from "react-router-dom"
@ -15,8 +14,6 @@ import Tag from "./Tag"
import TagList from "./TagList" import TagList from "./TagList"
import MainContent from "./MainContent" import MainContent from "./MainContent"
import { globalContext } from "../globalContext"
const PostCard = styled(MainContent)` const PostCard = styled(MainContent)`
box-shadow: 0 4px 10px rgb(0 0 0 / 10%); box-shadow: 0 4px 10px rgb(0 0 0 / 10%);
text-align: left; text-align: left;
@ -63,13 +60,9 @@ export default (props: Props) => {
const { postData } = props const { postData } = props
const { content_id, wordCount, date, readTime, title, tags } = postData const { content_id, wordCount, date, readTime, title, tags } = postData
const { globalState } = useContext(globalContext)
return ( return (
<PostCard> <PostCard>
<PostCardContainer <PostCardContainer to={content_id}>
to={`/${globalState.locale}${content_id.replace(/(.kr)$/g, "")}`}
>
<Title> <Title>
{title || "No title"} {title || "No title"}
{/* show "(series)" for urls that matches regex "/series/<series-title>" */} {/* show "(series)" for urls that matches regex "/series/<series-title>" */}

View file

@ -4,11 +4,10 @@
import type { Item } from "../../data/NavbarData" import type { Item } from "../../data/NavbarData"
import { useCallback, useContext, useState } from "react" import { useCallback, useState } from "react"
import { Link } from "react-router-dom" import { Link } from "react-router-dom"
import styled from "styled-components" import styled from "styled-components"
import { globalContext } from "../../globalContext"
import button from "../../styles/button" import button from "../../styles/button"
const SidebarLink = styled(Link)` const SidebarLink = styled(Link)`
@ -41,7 +40,6 @@ interface Props {
} }
const SubMenu = (props: Props) => { const SubMenu = (props: Props) => {
const { globalState } = useContext(globalContext)
const [isSubNavOpen, setSubNavOpen] = useState(false) const [isSubNavOpen, setSubNavOpen] = useState(false)
const handleSidebarLinkClick = useCallback(() => { const handleSidebarLinkClick = useCallback(() => {
setSubNavOpen((prev) => !prev) setSubNavOpen((prev) => !prev)
@ -49,17 +47,10 @@ const SubMenu = (props: Props) => {
return ( return (
<> <>
<SidebarLink <SidebarLink to={props.item.path} onClick={handleSidebarLinkClick}>
to={globalState.locale + props.item.path}
onClick={handleSidebarLinkClick}
>
<div> <div>
{props.item.icon} {props.item.icon}
<SidebarLabel> <SidebarLabel>{props.item.title}</SidebarLabel>
{globalState.locale == "en"
? props.item.title_en
: props.item.title_kr}
</SidebarLabel>
</div> </div>
</SidebarLink> </SidebarLink>
</> </>

View file

@ -10,32 +10,27 @@ import {
export type Item = { export type Item = {
path: string path: string
icon: JSX.Element icon: JSX.Element
title_en: string title: string
title_kr: string
} }
const NavbarData: Item[] = [ const NavbarData: Item[] = [
{ {
title_en: "Home", title: "Home",
title_kr: "홈",
path: "/", path: "/",
icon: <FontAwesomeIcon icon={faHome} />, icon: <FontAwesomeIcon icon={faHome} />,
}, },
{ {
title_en: "About", title: "About",
title_kr: "소개",
path: "/about", path: "/about",
icon: <FontAwesomeIcon icon={faUser} />, icon: <FontAwesomeIcon icon={faUser} />,
}, },
{ {
title_en: "Portfolio", title: "Portfolio",
title_kr: "포트폴리오",
path: "/portfolio", path: "/portfolio",
icon: <FontAwesomeIcon icon={faFileLines} />, icon: <FontAwesomeIcon icon={faFileLines} />,
}, },
{ {
title_en: "Resume", title: "Resume",
title_kr: "이력서",
path: "/resume", path: "/resume",
icon: <FontAwesomeIcon icon={faUserTie} />, icon: <FontAwesomeIcon icon={faUserTie} />,
}, },

View file

@ -6,7 +6,6 @@ import lightTheme from "@developomp-site/theme/dist/light.json"
import { createContext, useEffect, useReducer } from "react" import { createContext, useEffect, useReducer } from "react"
import storage from "local-storage-fallback" import storage from "local-storage-fallback"
export type SiteLocale = "en" | "kr"
export type SiteTheme = "dark" | "light" export type SiteTheme = "dark" | "light"
export enum ActionsEnum { export enum ActionsEnum {
@ -14,18 +13,13 @@ export enum ActionsEnum {
UPDATE_LOCALE, UPDATE_LOCALE,
} }
export type GlobalAction = // union of all actions
| { export type GlobalAction = {
type: ActionsEnum.UPDATE_THEME type: ActionsEnum.UPDATE_THEME
payload: SiteTheme payload: SiteTheme
} }
| {
type: ActionsEnum.UPDATE_LOCALE
payload: SiteLocale
}
export interface IGlobalState { export interface IGlobalState {
locale: SiteLocale
currentTheme: SiteTheme currentTheme: SiteTheme
theme: Theme theme: Theme
} }
@ -35,15 +29,7 @@ export interface IGlobalContext {
dispatch: Dispatch<GlobalAction> dispatch: Dispatch<GlobalAction>
} }
function getDefaultLocale(): SiteLocale {
if (window.location.pathname.startsWith("/en")) return "en"
if (window.location.pathname.startsWith("/kr")) return "kr"
return (storage.getItem("locale") as SiteLocale) || "en"
}
const defaultState: IGlobalState = { const defaultState: IGlobalState = {
locale: getDefaultLocale(),
currentTheme: (storage.getItem("theme") || "dark") as SiteTheme, currentTheme: (storage.getItem("theme") || "dark") as SiteTheme,
theme: theme:
((storage.getItem("theme") || "dark") as SiteTheme) === "dark" ((storage.getItem("theme") || "dark") as SiteTheme) === "dark"
@ -60,10 +46,6 @@ function reducer(state = defaultState, action: GlobalAction): IGlobalState {
state.theme = state.currentTheme === "dark" ? darkTheme : lightTheme state.theme = state.currentTheme === "dark" ? darkTheme : lightTheme
break break
case ActionsEnum.UPDATE_LOCALE:
state.locale = action.payload
break
default: default:
break break
} }
@ -79,11 +61,6 @@ export function GlobalStore(props: { children: ReactNode }): ReactElement {
storage.setItem("theme", globalState.currentTheme) storage.setItem("theme", globalState.currentTheme)
}, [globalState.currentTheme]) }, [globalState.currentTheme])
// save locale when it is changed
useEffect(() => {
storage.setItem("locale", globalState.locale)
}, [globalState.locale])
return ( return (
<globalContext.Provider value={{ globalState, dispatch }}> <globalContext.Provider value={{ globalState, dispatch }}>
{props.children} {props.children}

View file

@ -4,7 +4,7 @@
*/ */
import type { Map } from "../../../types/types" import type { Map } from "../../../types/types"
import { useCallback, useContext, useEffect, useState } from "react" import { useCallback, useEffect, useState } from "react"
import { Helmet } from "react-helmet-async" import { Helmet } from "react-helmet-async"
import styled from "styled-components" import styled from "styled-components"
@ -13,8 +13,6 @@ import ShowMoreButton from "./ShowMoreButton"
import _map from "../../data/map.json" import _map from "../../data/map.json"
import { globalContext } from "../../globalContext"
const map: Map = _map const map: Map = _map
const PostList = styled.div` const PostList = styled.div`
@ -26,9 +24,6 @@ const PostList = styled.div`
` `
export default () => { export default () => {
const { globalState } = useContext(globalContext)
const { locale } = globalState
const [howMany, setHowMany] = useState(5) const [howMany, setHowMany] = useState(5)
const [postsLength, setPostsLength] = useState(0) const [postsLength, setPostsLength] = useState(0)
const [postCards, setPostCards] = useState<JSX.Element[]>([]) const [postCards, setPostCards] = useState<JSX.Element[]>([])
@ -71,14 +66,14 @@ export default () => {
return ( return (
<> <>
<Helmet> <Helmet>
<title>pomp | {locale == "en" ? "Home" : "홈"}</title> <title>pomp | Home</title>
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:image" content="/icon/icon.svg" /> <meta property="og:image" content="/icon/icon.svg" />
</Helmet> </Helmet>
<PostList> <PostList>
<h1>{locale == "en" ? "Recent Posts" : "최근 포스트"}</h1> <h1>Recent Posts</h1>
{postCards} {postCards}

View file

@ -1,7 +1,5 @@
import { useContext } from "react"
import styled from "styled-components" import styled from "styled-components"
import { globalContext } from "../../globalContext"
import buttonStyle from "../../styles/button" import buttonStyle from "../../styles/button"
const Button = styled.button` const Button = styled.button`
@ -16,11 +14,5 @@ interface Props {
} }
export default (props: Props) => { export default (props: Props) => {
const { globalState } = useContext(globalContext) return <Button onClick={props.action}>Show more posts</Button>
return (
<Button onClick={props.action}>
{globalState.locale == "en" ? "Show more posts" : "더 많은 포스트 보이기"}
</Button>
)
} }

View file

@ -1,11 +1,8 @@
import { useContext } from "react"
import styled from "styled-components" import styled from "styled-components"
import { Helmet } from "react-helmet-async" import { Helmet } from "react-helmet-async"
import MainContent from "../components/MainContent" import MainContent from "../components/MainContent"
import { globalContext } from "../globalContext"
const StyledNotFound = styled(MainContent)` const StyledNotFound = styled(MainContent)`
text-align: center; text-align: center;
` `
@ -15,8 +12,6 @@ const Styled404 = styled.h1`
` `
const NotFound = () => { const NotFound = () => {
const { globalState } = useContext(globalContext)
return ( return (
<> <>
<Helmet> <Helmet>
@ -35,9 +30,7 @@ const NotFound = () => {
<StyledNotFound> <StyledNotFound>
<Styled404>404</Styled404> <Styled404>404</Styled404>
<br /> <br />
{globalState.locale == "en" Page was not found :(
? "Page was not found :("
: "페이지를 찾을 수 없습니다 :("}
</StyledNotFound> </StyledNotFound>
</> </>
) )

View file

@ -1,4 +1,4 @@
import { useContext, useState, useEffect } from "react" import { useState, useEffect } from "react"
import { Helmet } from "react-helmet-async" import { Helmet } from "react-helmet-async"
import { useLocation } from "react-router-dom" import { useLocation } from "react-router-dom"
import styled from "styled-components" import styled from "styled-components"
@ -12,20 +12,16 @@ import Badge from "../../components/Badge"
import Tag from "../../components/Tag" import Tag from "../../components/Tag"
import NotFound from "../NotFound" import NotFound from "../NotFound"
import TranslationNotAvailable from "./TranslationNotAvailable"
import SeriesControlButtons from "./SeriesControlButtons" import SeriesControlButtons from "./SeriesControlButtons"
import { import {
categorizePageType, categorizePageType,
checkURLValidity,
fetchContent, fetchContent,
PageType, PageType,
URLValidity,
parsePageData, parsePageData,
} from "./helper" } from "./helper"
import Meta from "./Meta" import Meta from "./Meta"
import Toc from "./Toc" import Toc from "./Toc"
import { globalContext } from "../../globalContext"
import type { PageData, Map } from "../../../types/types" import type { PageData, Map } from "../../../types/types"
import _map from "../../data/map.json" import _map from "../../data/map.json"
@ -54,53 +50,18 @@ const ProjectImage = styled.img`
` `
export default function Page() { export default function Page() {
const { globalState } = useContext(globalContext)
const { locale } = globalState
const { pathname } = useLocation() const { pathname } = useLocation()
const [pageData, setPageData] = useState<PageData | undefined>(undefined) const [pageData, setPageData] = useState<PageData | undefined>(undefined)
const [pageType, setPageType] = useState<PageType>(PageType.POST) const [pageType, setPageType] = useState<PageType>(PageType.POST)
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
const [isTranslationAvailable, setIsTranslationAvailable] = useState(true)
// this code runs if either the url or the locale changes // this code runs if either the url or the locale changes
useEffect(() => { useEffect(() => {
const content_id = const content_id = pathname.replace(/\/$/, "") // remove trailing slash
pathname
.replace(/^\/kr/, "") // remove /kr prefix
.replace(/^\/en/, "") // remove /en prefix
.replace(/\/$/, "") + // remove trailing slash
(locale == "en" ? "" : ".kr")
const pageType = categorizePageType(content_id) const pageType = categorizePageType(content_id)
switch (checkURLValidity(content_id, pageType)) { fetchContent(pageType, content_id).then((fetched_content) => {
case URLValidity.VALID: {
// continue if the URL is valid
break
}
case URLValidity.VALID_BUT_IN_OTHER_LOCALE: {
// stop loading and set isTranslationAvailable to true so translation not available page will display
setIsTranslationAvailable(false)
setIsLoading(false)
return
}
case URLValidity.NOT_VALID: {
// stop loading without fetching pageData so 404 page will display
setIsLoading(false)
return
}
}
/**
* Get page data
*/
fetchContent(pageType, content_id, locale).then((fetched_content) => {
if (!fetched_content) { if (!fetched_content) {
// stop loading without fetching pageData so 404 page will display // stop loading without fetching pageData so 404 page will display
setIsLoading(false) setIsLoading(false)
@ -108,17 +69,14 @@ export default function Page() {
return return
} }
setPageData(parsePageData(fetched_content, pageType, content_id, locale)) setPageData(parsePageData(fetched_content, pageType, content_id))
setIsTranslationAvailable(true)
setPageType(pageType) setPageType(pageType)
setIsLoading(false) setIsLoading(false)
}) })
}, [pathname, locale]) }, [pathname])
if (isLoading) return <Loading /> if (isLoading) return <Loading />
if (!isTranslationAvailable) return <TranslationNotAvailable />
if (!pageData) return <NotFound /> if (!pageData) return <NotFound />
return ( return (

View file

@ -1,4 +1,3 @@
import { useContext } from "react"
import styled from "styled-components" import styled from "styled-components"
import { Link } from "react-router-dom" import { Link } from "react-router-dom"
@ -10,7 +9,6 @@ import {
} from "@fortawesome/free-solid-svg-icons" } from "@fortawesome/free-solid-svg-icons"
import buttonStyle from "../../styles/button" import buttonStyle from "../../styles/button"
import { globalContext } from "../../globalContext"
const Container = styled.div` const Container = styled.div`
display: flex; display: flex;
@ -35,13 +33,10 @@ interface Props {
} }
function SeriesControlButtons({ prevURL, seriesHome, nextURL }: Props) { function SeriesControlButtons({ prevURL, seriesHome, nextURL }: Props) {
const { globalState } = useContext(globalContext)
const { locale } = globalState
return ( return (
<Container> <Container>
{prevURL ? ( {prevURL ? (
<Link to={`/${locale}${prevURL}`}> <Link to={prevURL}>
<Button> <Button>
<FontAwesomeIcon icon={faArrowLeft} /> <FontAwesomeIcon icon={faArrowLeft} />
</Button> </Button>
@ -52,14 +47,14 @@ function SeriesControlButtons({ prevURL, seriesHome, nextURL }: Props) {
</DisabledButton> </DisabledButton>
)} )}
<Link to={`/${locale}${seriesHome}`}> <Link to={seriesHome}>
<Button> <Button>
<FontAwesomeIcon icon={faListUl} /> <FontAwesomeIcon icon={faListUl} />
</Button> </Button>
</Link> </Link>
{nextURL ? ( {nextURL ? (
<Link to={`/${locale}${nextURL}`}> <Link to={nextURL}>
<Button> <Button>
<FontAwesomeIcon icon={faArrowRight} /> <FontAwesomeIcon icon={faArrowRight} />
</Button> </Button>

View file

@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from "react" import { useEffect, useState } from "react"
import { Collapse } from "react-collapse" import { Collapse } from "react-collapse"
import storage from "local-storage-fallback" import storage from "local-storage-fallback"
@ -6,8 +6,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { faCaretDown, faCaretUp } from "@fortawesome/free-solid-svg-icons" import { faCaretDown, faCaretUp } from "@fortawesome/free-solid-svg-icons"
import styled from "styled-components" import styled from "styled-components"
import { globalContext } from "../../globalContext"
const StyledTocToggleButton = styled.button` const StyledTocToggleButton = styled.button`
cursor: pointer; cursor: pointer;
border: none; border: none;
@ -25,7 +23,6 @@ const StyledCollapseContainer = styled.div`
` `
const Toc = (props: { data?: string }) => { const Toc = (props: { data?: string }) => {
const { globalState } = useContext(globalContext)
const [isTocOpened, setIsTocOpened] = useState( const [isTocOpened, setIsTocOpened] = useState(
storage.getItem("isTocOpened") == "true" storage.getItem("isTocOpened") == "true"
) )
@ -44,7 +41,7 @@ const Toc = (props: { data?: string }) => {
}} }}
> >
<strong> <strong>
{globalState.locale == "en" ? "Table of Contents " : "목차 "} Table of Contents
<FontAwesomeIcon icon={isTocOpened ? faCaretUp : faCaretDown} /> <FontAwesomeIcon icon={isTocOpened ? faCaretUp : faCaretDown} />
</strong> </strong>
</StyledTocToggleButton> </StyledTocToggleButton>

View file

@ -1,61 +0,0 @@
import { useContext } from "react"
import styled from "styled-components"
import { Helmet } from "react-helmet-async"
import MainContent from "../../components/MainContent"
import { globalContext } from "../../globalContext"
const Card = styled(MainContent)`
text-align: center;
`
const Title = styled.h1`
font-size: 3rem;
`
const TranslationNotAvailable = () => {
const { globalState } = useContext(globalContext)
const { locale } = globalState
const localized_title =
locale == "en" ? "Translation not found" : "번역이 존재하지 않습니다"
return (
<>
<Helmet>
<title>pomp | {localized_title}</title>
<meta property="og:title" content={localized_title} />
<meta
property="og:image"
content="http://developomp.com/icon/icon.svg"
/>
<meta
property="og:description"
content={
locale == "en"
? "This content is not available in English."
: "본 내용의 한국어 번역이 존재하지 않습니다"
}
/>
</Helmet>
<Card>
<Title>{localized_title}</Title>
<br />
{locale == "en" ? (
<>
This post is only available in <b>Korean ()</b>.
</>
) : (
<>
<b>(English)</b> .
</>
)}
</Card>
</>
)
}
export default TranslationNotAvailable

View file

@ -1,8 +1,7 @@
import portfolio from "../../data/portfolio.json" import portfolio from "../../data/portfolio.json"
import _map from "../../data/map.json" import _map from "../../data/map.json"
import type { SiteLocale } from "../../globalContext" import type { Map, PageData } from "../../../types/types"
import type { Map, PageData, PortfolioProject } from "../../../types/types"
const map: Map = _map const map: Map = _map
@ -14,40 +13,12 @@ export enum PageType {
UNSEARCHABLE, UNSEARCHABLE,
} }
export enum URLValidity { export async function fetchContent(pageType: PageType, url: string) {
VALID, // page does exist in selected language
VALID_BUT_IN_OTHER_LOCALE, // page exists but only in another language
NOT_VALID, // page does not exist
}
export async function fetchContent(
pageType: PageType,
url: string,
locale: SiteLocale
) {
try { try {
if (pageType == PageType.UNSEARCHABLE) { if (pageType == PageType.UNSEARCHABLE) {
if (locale == "en") { return await import(`../../data/content/unsearchable${url}.json`)
return await import(`../../data/content/unsearchable${url}.json`)
} else {
try {
return await import(
`../../data/content/unsearchable${url}.${locale}.json`
)
} catch {
return await import(`../../data/content/unsearchable${url}.json`)
}
}
}
if (locale == "en") {
return await import(`../../data/content${url}.json`)
} else { } else {
try { return await import(`../../data/content${url}.json`)
return await import(`../../data/content${url}.${locale}.json`)
} catch {
return await import(`../../data/content${url}.json`)
}
} }
} catch (err) { } catch (err) {
return return
@ -69,93 +40,11 @@ export function categorizePageType(content_id: string): PageType {
return PageType.UNSEARCHABLE return PageType.UNSEARCHABLE
} }
export function checkURLValidity(
content_id: string,
pageType: PageType
): URLValidity {
// content ID of other language
const alt_content_id = content_id.endsWith(".kr")
? content_id.replace(/\.kr$/, "") // remove .kr suffix
: content_id + ".kr" // add .kr suffix
switch (pageType) {
case PageType.SERIES:
case PageType.POST: {
if (map.posts[content_id]) return URLValidity.VALID
if (map.posts[alt_content_id])
return URLValidity.VALID_BUT_IN_OTHER_LOCALE
break
}
case PageType.SERIES_HOME: {
const series_keys = Object.keys(map.series)
if (
series_keys.some((seriesHomeURL) =>
seriesHomeURL.startsWith(content_id)
)
)
return URLValidity.VALID
if (
series_keys.some((seriesHomeURL) =>
seriesHomeURL.startsWith(alt_content_id)
)
)
return URLValidity.VALID_BUT_IN_OTHER_LOCALE
break
}
case PageType.PORTFOLIO_PROJECT: {
const locale: SiteLocale = content_id.endsWith(".kr") ? "kr" : "en"
const portfolio_content_id = content_id.endsWith(".kr")
? content_id.slice(0, content_id.length - 3)
: content_id
try {
const project = portfolio.projects[
portfolio_content_id as keyof typeof portfolio.projects
] as PortfolioProject
if (locale == "en" && project.overview_en) {
return URLValidity.VALID
} else if (locale == "kr" && project.overview_kr) {
return URLValidity.VALID
} else if (
(locale == "en" && project.overview_kr) ||
(locale == "kr" && project.overview_en)
) {
return URLValidity.VALID_BUT_IN_OTHER_LOCALE
}
} catch {
// prevent linting error
}
break
}
case PageType.UNSEARCHABLE: {
if (map.unsearchable[content_id]) return URLValidity.VALID
if (map.unsearchable[alt_content_id])
return URLValidity.VALID_BUT_IN_OTHER_LOCALE
break
}
}
return URLValidity.NOT_VALID
}
export function parsePageData( export function parsePageData(
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
fetched_content: any, fetched_content: any,
pageType: PageType, pageType: PageType,
content_id: string, content_id: string
locale: SiteLocale
): PageData { ): PageData {
// page date to be saved as a react state // page date to be saved as a react state
const pageData: PageData = { const pageData: PageData = {
@ -247,21 +136,15 @@ export function parsePageData(
} }
case PageType.PORTFOLIO_PROJECT: { case PageType.PORTFOLIO_PROJECT: {
const portfolio_content_id = content_id.endsWith(".kr")
? content_id.slice(0, content_id.length - 3)
: content_id
const data = const data =
portfolio.projects[ portfolio.projects[content_id as keyof typeof portfolio.projects]
portfolio_content_id as keyof typeof portfolio.projects
]
pageData.content = fetched_content.content pageData.content = fetched_content.content
pageData.toc = fetched_content.toc pageData.toc = fetched_content.toc
pageData.title = data.name pageData.title = data.name
pageData.image = data.image pageData.image = data.image
pageData.overview = locale == "en" ? data.overview_en : data.overview_kr pageData.overview = data.overview
pageData.badges = data.badges pageData.badges = data.badges
pageData.repo = data.repo pageData.repo = data.repo
@ -269,10 +152,7 @@ export function parsePageData(
} }
case PageType.UNSEARCHABLE: { case PageType.UNSEARCHABLE: {
pageData.title = ( pageData.title = map.unsearchable[content_id].title
map.unsearchable[`${content_id}.${locale}`] ||
map.unsearchable[content_id]
).title
pageData.content = fetched_content.content pageData.content = fetched_content.content
break break

View file

@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from "react" import { useEffect, useState } from "react"
import { Helmet } from "react-helmet-async" import { Helmet } from "react-helmet-async"
import MainContent from "../../components/MainContent" import MainContent from "../../components/MainContent"
@ -7,14 +7,9 @@ import ProjectCard from "./ProjectCard"
import portfolio from "../../data/portfolio.json" import portfolio from "../../data/portfolio.json"
import { globalContext } from "../../globalContext"
import type { PortfolioProject } from "../../../types/types" import type { PortfolioProject } from "../../../types/types"
const Portfolio = () => { const Portfolio = () => {
const { globalState } = useContext(globalContext)
const locale = globalState.locale
const [projects, setProjects] = useState<JSX.Element[]>([]) const [projects, setProjects] = useState<JSX.Element[]>([])
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const [skills, setSkills] = useState<JSX.Element[]>([]) const [skills, setSkills] = useState<JSX.Element[]>([])
@ -48,7 +43,7 @@ const Portfolio = () => {
return ( return (
<> <>
<Helmet> <Helmet>
<title>pomp | {locale == "en" ? "Portfolio" : "포트폴리오"}</title> <title>pomp | Portfolio</title>
<meta property="og:title" content="Portfolio" /> <meta property="og:title" content="Portfolio" />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
@ -61,7 +56,7 @@ const Portfolio = () => {
</Helmet> </Helmet>
<MainContent> <MainContent>
<h1>{locale == "en" ? "Portfolio" : "포트폴리오"}</h1> <h1>Portfolio</h1>
<hr /> <hr />
@ -70,8 +65,8 @@ const Portfolio = () => {
<h2 id="projects"> <h2 id="projects">
<a className="header-anchor" href="#projects"> <a className="header-anchor" href="#projects">
# #
</a>{" "} </a>
{locale == "en" ? "Projects" : "프로젝트"} {" Projects"}
</h2> </h2>
{/* todo: filter projects by skill */} {/* todo: filter projects by skill */}

View file

@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from "react" import { useEffect, useState } from "react"
import styled from "styled-components" import styled from "styled-components"
import { Link } from "react-router-dom" import { Link } from "react-router-dom"
@ -6,7 +6,6 @@ import Badge from "../../components/Badge"
import { cardCSS } from "../../components/Card" import { cardCSS } from "../../components/Card"
import { PortfolioProject } from "../../../types/types" import { PortfolioProject } from "../../../types/types"
import { globalContext } from "../../globalContext"
const StyledProjectCard = styled.div` const StyledProjectCard = styled.div`
${cardCSS} ${cardCSS}
@ -38,9 +37,6 @@ interface ProjectCardProps {
const ProjectCard = (props: ProjectCardProps) => { const ProjectCard = (props: ProjectCardProps) => {
const { projectID, project } = props const { projectID, project } = props
const { globalState } = useContext(globalContext)
const { locale } = globalState
const [badges, setBadges] = useState<JSX.Element[]>([]) const [badges, setBadges] = useState<JSX.Element[]>([])
useEffect(() => { useEffect(() => {
@ -48,7 +44,7 @@ const ProjectCard = (props: ProjectCardProps) => {
}, []) }, [])
return ( return (
<Link to={`/${locale}${projectID}`}> <Link to={projectID}>
<StyledProjectCard> <StyledProjectCard>
<h1>{project.name}</h1> <h1>{project.name}</h1>
<StyledImg src={project.image} /> <StyledImg src={project.image} />
@ -57,7 +53,7 @@ const ProjectCard = (props: ProjectCardProps) => {
<hr /> <hr />
<div <div
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: locale == "en" ? project.overview_en : project.overview_kr, __html: project.overview,
}} }}
/> />
</StyledProjectCard> </StyledProjectCard>

View file

@ -1,4 +1,4 @@
import { useCallback, useContext, useEffect, useState } from "react" import { useCallback, useEffect, useState } from "react"
import styled from "styled-components" import styled from "styled-components"
import { useSearchParams } from "react-router-dom" import { useSearchParams } from "react-router-dom"
import { Helmet } from "react-helmet-async" import { Helmet } from "react-helmet-async"
@ -16,7 +16,6 @@ import MainContent from "../../components/MainContent"
import SearchBar from "./SearchBar" import SearchBar from "./SearchBar"
import TagSelect, { TagsData } from "./TagSelect" import TagSelect, { TagsData } from "./TagSelect"
import { ClearDateButton, DateRangeControl, StyledDateRange } from "./DateRange" import { ClearDateButton, DateRangeControl, StyledDateRange } from "./DateRange"
import { globalContext } from "../../globalContext"
import "react-date-range/dist/styles.css" import "react-date-range/dist/styles.css"
import "react-date-range/dist/theme/default.css" import "react-date-range/dist/theme/default.css"
@ -98,9 +97,6 @@ function isSelectedTagsInPost(selectedTags?: TagsData[], postTags?: string[]) {
} }
const Search = () => { const Search = () => {
const { globalState } = useContext(globalContext)
const locale = globalState.locale
// URL search parameters // URL search parameters
const [URLSearchParams, setURLSearchParams] = useSearchParams() const [URLSearchParams, setURLSearchParams] = useSearchParams()
@ -222,11 +218,11 @@ const Search = () => {
return ( return (
<> <>
<Helmet> <Helmet>
<title>pomp | {locale == "en" ? "Search" : "검색"}</title> <title>pomp | Search</title>
</Helmet> </Helmet>
<StyledSearch> <StyledSearch>
<h1>{locale == "en" ? "Search" : "검색"}</h1> <h1>Search</h1>
<StyledSearchContainer> <StyledSearchContainer>
<DateRangeControl> <DateRangeControl>
@ -235,7 +231,7 @@ const Search = () => {
setDateRange(defaultDateRange) setDateRange(defaultDateRange)
}} }}
> >
{locale == "en" ? "Reset date range" : "날짜 범위 초기화"} Reset date range
</ClearDateButton> </ClearDateButton>
<StyledDateRange <StyledDateRange
editableDateInputs editableDateInputs
@ -256,15 +252,13 @@ const Search = () => {
type="search" type="search"
value={searchInput} value={searchInput}
autoComplete="off" autoComplete="off"
placeholder={locale == "en" ? "Search" : "검색"} placeholder="Search"
onChange={(event) => setSearchInput(event.target.value)} onChange={(event) => setSearchInput(event.target.value)}
onKeyPress={(event) => { onKeyPress={(event) => {
event.key === "Enter" && searchInput && doSearch() event.key === "Enter" && searchInput && doSearch()
}} }}
/> />
{locale == "kr" && "결과: "} {postCards.length} result{postCards.length > 1 && "s"}
{postCards.length}{" "}
{locale == "en" && (postCards.length > 1 ? "results" : "result")}
<TagSelect <TagSelect
defaultValue={selectedTags} defaultValue={selectedTags}
onChange={(newValue) => { onChange={(newValue) => {

View file

@ -32,13 +32,12 @@ interface TagSelectProps {
const TagSelect = (props: TagSelectProps) => { const TagSelect = (props: TagSelectProps) => {
const { globalState } = useContext(globalContext) const { globalState } = useContext(globalContext)
const { theme } = globalState const { theme } = globalState
const locale = globalState.locale
const { onChange, defaultValue: selectedTags } = props const { onChange, defaultValue: selectedTags } = props
return ( return (
<StyledReactTagsContainer> <StyledReactTagsContainer>
<Select <Select
placeholder={locale == "en" ? "Select tags..." : "태그를 선택하세요"} placeholder="Select tags..."
theme={(reactSelectTheme) => ({ theme={(reactSelectTheme) => ({
...reactSelectTheme, ...reactSelectTheme,
colors: { colors: {

View file

@ -128,8 +128,7 @@ export interface PortfolioOverview {
export interface PortfolioProject { export interface PortfolioProject {
name: string name: string
image: string // url to the image image: string // url to the image
overview_en: string overview: string
overview_kr: string
badges: string[] // array of valid simpleIcons slug badges: string[] // array of valid simpleIcons slug
repo: string // url of the git repository repo: string // url of the git repository
} }