← Dev Log

2023년 09월 12일

·6 min read

오늘 한 일

  • gloddy 개발
    • 모임 개설 페이지 다국어 추가
  • gdsc 회의
  • 블로그 다크모드 추가

다국어 추가

드디어 미루고 미루던 다국어를 추가했다.

계속 QA 반영하느라 다국어 추가를 지금에서야 했다.

세팅하는건 저번에 해두어서 그대로 가져와서 썼다. 폴더 구조가 많이 바뀌면서 점점 깊어지고 있다..ㅎㅎ

json 파일 생성

동일한 key값을 가진 ko, en 폴더에 각각 json 파일을 만든다.

// ko/grouping.json
{
  "headerTitle": "매칭",
  "create": {
    "headerTitle": "모임 개설하기",
    "title": {
      "label": "모임 제목",
      "placeholder": "제목을 입력해주세요."
    },
    "content": {
      "label": "활동 소개글",
      "placeholder": "활동 소개글을 작성해주세요."
    },
    "meetDate": {
      "label": "모임 일시",
      "placeholder": "모임 일시를 설정해주세요.",
      "year": "년",
      "month": "월"
    },
    "time": {
      "label": "시작 시간",
      "am": "오전",
      "pm": "오후",
      "hour": "시간",
      "minute": "분"
    },
    "place": {
      "label": "모임 위치",
      "placeholder": "모임 위치를 설정해주세요.",
      "noResult": "검색 결과가 없습니다."
    },
    "maxUser": {
      "label": "모임 인원"
    },
    "error": {
      "time": "현재 시간 이후로 설정해주세요."
    },
    "continue": "완료",
    "submit": {
      "label": "완료",
      "message": "모임 개설 후 수정 및 삭제가 불가합니다.\n계속하시겠어요?",
      "ok": "네",
      "cancel": "아니오"
    }
  }
}

// en/grouping.json
{
  "headerTitle": "Matching",
  "create": {
    "headerTitle": "Create Group",
    "title": {
      "label": "Group Title",
      "placeholder": "Please input the title."
    },
    "content": {
      "label": "Group Info",
      "placeholder": "Please write an introduction for the activity."
    },
    "meetDate": {
      "label": "Date and Time",
      "placeholder": "Set the date and time for the meeting.",
      "year": "Year",
      "month": "Month"
    },
    "time": {
      "label": "Start time",
      "am": "AM",
      "pm": "PM",
      "hour": "Hour",
      "minute": "Minute"
    },
    "place": {
      "label": "Location",
      "placeholder": "Please set the location for the meeting.",
      "noResult": "No results found."
    },
    "maxUser": {
      "label": "Number of Participants"
    },
    "error": {
      "time": "Please set a time later than the current time."
    },
    "continue": "Continue",
    "submit": {
      "label": "Submit",
      "message": "No changes can be made after creating the group.\nDo you wish to continue?",
      "ok": "Yes",
      "cancel": "No"
    }
  }
}

다국어 적용

해당하는 곳에 useTranslation을 import하고 t를 사용하면 된다.

const { t } = useTranslation('grouping')

// ...
;<div className="px-20 pb-8 pt-20">
  <p className="px-4 text-subtitle-3 text-sign-secondary">{t('create.title.label')}</p>
  <Spacing size={4} />
  <TextFieldController
    placeholder={t('create.title.placeholder')}
    hookForm={hookForm}
    register={register('title', {
      required: true,
      maxLength: 30,
    })}
    maxCount={30}
  />
</div>

개선할 점

t에 들어가는 key값을 타입 추론을 통해 자동으로 생성할 수 있을 것 같다. 추후에 개선해보자.

블로그 다크모드 추가

첫번 째 시도

next-themes 라이브러리를 사용했다.

import { useTheme } from 'next-themes'

const { theme, setTheme } = useTheme()

// ...

;<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
  {theme === 'dark' ? 'Light' : 'Dark'}
</button>

문제점

하지만 이 친구는 client side에서만 동작하기 때문에 렌더링 이후 아이콘이 보이게 되고, vanilla extract와 같이 사용했을 때 hydration 불일치 문제도 발생했다.

이때 경고 콘솔이 뜨게 되는데, next-themes는 그냥 그걸 끄라고 한다.. suppressHydrationWarning을 사용하면 된다고 한다. (이건 뭐지.. 별게 다있군)

// app/layout.jsx

import { Providers } from './providers'

export default function Layout({ children }) {
  return (
    <html suppressHydrationWarning>
      <head />
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}

난 찝찝해서 다른 방법을 찾아보았다.

두번 째 시도

찾아보다가 라이브러리를 쓰지 않고 구현하는 방법을 찾았다.

쿠키에 scheme 값을 저장하고, 이를 통해 테마를 설정하는 방법이다.

next-themes는 시스템 테마를 따라가지만, 서버에서는 시스템 테마를 알 수 없기 때문에 그냥 디폴트로 dark모드를 설정했다.

// utils/colorScheme.ts
import { getCookie, setCookie } from 'cookies-next'

export const getCurrentScheme = async () => {
  if (typeof window === 'undefined') {
    const { cookies } = await import('next/headers')

    return cookies().has('scheme') ? cookies().get('scheme')?.value : 'dark'
  }

  return getCookie('scheme', { path: '/' })
}

export const toggleScheme = async () => {
  const scheme = await getCurrentScheme()

  const newScheme = scheme === 'dark' ? 'light' : 'dark'

  setCookie('scheme', newScheme, {
    path: '/',
  })

  return newScheme
}

그 다음 darkMode/lightMode 스타일을 만들어준다.

const colors = createThemeContract({
  background: null,
  text: null,
})

export const lightTheme = createTheme(colors, {
  background: 'white',
  text: 'black',
})

export const darkTheme = createTheme(colors, {
  background: 'black',
  text: 'white',
})

마지막으로 Layout에 적용해준다.

// app/layout.tsx

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const scheme = await getCurrentScheme()

  return (
    <html
      lang="ko"
      className={cn(pretendard.className, {
        [darkTheme]: scheme === 'dark',
        [lightTheme]: scheme === 'light',
      })}
    >
      <body>
        <Header />
        <main className={styles.content}>{children}</main>
      </body>
    </html>
  )
}

이제 피그마에서 디자인 끄적이는 중이다. 피그마도 제대로 배워서 잘쓰고 싶은 마음이 있지만 시간이 없어서..ㅎㅎ 그냥 깔끔하게만 디자인 해야겠다.


내일 할 일

  • gloddy 개발
    • 모임 상세 페이지 다국어 추가