diff --git a/app/[locale]/about/directions/LocationGuide.tsx b/app/[locale]/about/directions/LocationGuide.tsx index 16c85c10..d5010ba1 100644 --- a/app/[locale]/about/directions/LocationGuide.tsx +++ b/app/[locale]/about/directions/LocationGuide.tsx @@ -7,16 +7,15 @@ import { getPath } from '@/utils/page'; const staffPath = getPath(staff); export default function LocationGuide() { - const t = useTranslations('Footer'); - const tContent = useTranslations('Content'); + const t = useTranslations('Page.about.directions'); return (

- 컴퓨터공학부는 서울대학교 관악 301동(신공학관1)에 있습니다. + {t('description')}
- {tContent('주소')}: {t('address')} + {t('address')}: {t('addressValue')}
- {tContent('전화')}:{' '} + {t('contact')}:{' '} 학부 연락처 diff --git a/app/[locale]/about/future-careers/CareerCompanies.tsx b/app/[locale]/about/future-careers/CareerCompanies.tsx index 0560dc15..18c3b9f9 100644 --- a/app/[locale]/about/future-careers/CareerCompanies.tsx +++ b/app/[locale]/about/future-careers/CareerCompanies.tsx @@ -17,7 +17,7 @@ import Form from '@/components/form/Form'; import { handleServerResponse } from '@/utils/serverActionError'; export default function CareerCompanies({ companies }: { companies: FutureCareers['companies'] }) { - const t = useTranslations('Content'); + const t = useTranslations('Page.about.futureCareers'); const [showCreateForm, toggleCreateForm] = useReducer((x) => !x, false); @@ -32,7 +32,7 @@ export default function CareerCompanies({ companies }: { companies: FutureCareer return (

-

{t('졸업생 창업 기업')}

+

{t('graduateStartups')}

{/* UI가 과하게 깨지는 관계로 모바일 버전에서는 편집 X */}
@@ -56,14 +56,14 @@ export default function CareerCompanies({ companies }: { companies: FutureCareer const TABLE_COLUMN_SIZE = ['sm:w-[3rem]', 'sm:w-[12.5rem]', 'sm:w-80', 'sm:w-20', 'sm:w-32']; function CompanyTableHeader() { - const t = useTranslations('Content'); + const t = useTranslations('Page.about.futureCareers'); return (

{t('연번')}

-

{t('창업 기업명')}

-

{t('홈페이지')}

-

{t('창업연도')}

+

{t('startupName')}

+

{t('startupWebsite')}

+

{t('startupYear')}

{/* 표 본문과 UI 정렬을 맞추기 위함 */}

diff --git a/app/[locale]/about/future-careers/CareerStat.tsx b/app/[locale]/about/future-careers/CareerStat.tsx index fb2827aa..4f9baa70 100644 --- a/app/[locale]/about/future-careers/CareerStat.tsx +++ b/app/[locale]/about/future-careers/CareerStat.tsx @@ -12,13 +12,13 @@ import { Link } from '@/i18n/routing'; import { getPath } from '@/utils/page'; export const CAREER_STAT_ROWS = ['삼성', 'LG', '기타 대기업', '중소기업', '진학', '기타']; -export const CAREER_STAT_COLS = ['학부', '석사', '박사']; +export const CAREER_STAT_COLS = ['bachelor', 'master', 'doctor']; const careerPath = getPath(futureCareers); export default function CareerStat({ stat }: { stat: FutureCareers['stat'] }) { const [idx, setIdx] = useState(0); - const t = useTranslations('Content'); + const t = useTranslations('Page.about.futureCareers'); const year = stat[idx].year; const yearStat = stat.find((x) => x.year === year); @@ -29,7 +29,7 @@ export default function CareerStat({ stat }: { stat: FutureCareers['stat'] }) {

-

{t('졸업생 진로 현황')}

+

{t('graduateCareerStatus')}

x.year.toString())} selectedIndex={idx} @@ -68,12 +68,14 @@ export default function CareerStat({ stat }: { stat: FutureCareers['stat'] }) { } function TableHeader() { + const t = useTranslations('Page.about.futureCareers'); + return (
{CAREER_STAT_COLS.map((colName) => (
-

{colName}

+

{t(colName)}

))}
diff --git a/app/[locale]/about/overview/page.tsx b/app/[locale]/about/overview/page.tsx index b40195a9..bc484df1 100644 --- a/app/[locale]/about/overview/page.tsx +++ b/app/[locale]/about/overview/page.tsx @@ -39,7 +39,7 @@ const overviewPath = getPath(overview); export default async function OverviewPage(props: OverviewPageProps) { const params = await props.params; const { description, attachments, imageURL } = await getOverview(params.locale); - const t = await getTranslations('Content'); + const t = await getTranslations('Page.about.overview'); return ( @@ -66,7 +66,7 @@ export default async function OverviewPage(props: OverviewPageProps) {
-

{t('학부 소개 책자')}

+

{t('brochure')}

소개 책자 소개 책자 diff --git a/app/[locale]/academics/components/courses/CourseCard.tsx b/app/[locale]/academics/components/courses/CourseCard.tsx index c324117e..509f1ca9 100644 --- a/app/[locale]/academics/components/courses/CourseCard.tsx +++ b/app/[locale]/academics/components/courses/CourseCard.tsx @@ -1,7 +1,7 @@ import { useTranslations } from 'next-intl'; import { useEffect, useReducer, useRef } from 'react'; -import { Course, GRADE, SortOption } from '@/apis/types/academics'; +import { Course, SortOption } from '@/apis/types/academics'; import styles from '@/app/[locale]/academics/components/courses/style.module.css'; import { useTypedLocale } from '@/utils/hooks/useTypedLocale'; @@ -12,11 +12,11 @@ interface CourseCardProps { const useSortedProperties = (course: Course, selectedOption: SortOption) => { const lang = useTypedLocale(); - const t = useTranslations('Tag'); + const t = useTranslations('Page.courses'); const classification = course[lang].classification; - const grade = t(GRADE[course.grade]); - const credit = t(`${course.credit}학점`); + const grade = t('grade', { grade: course.grade }); + const credit = t('credit', { credit: course.credit }); switch (selectedOption) { case '교과목 구분': diff --git a/app/[locale]/academics/components/courses/CourseListHeader.tsx b/app/[locale]/academics/components/courses/CourseListHeader.tsx index fef386fa..5f0e08ea 100644 --- a/app/[locale]/academics/components/courses/CourseListHeader.tsx +++ b/app/[locale]/academics/components/courses/CourseListHeader.tsx @@ -4,16 +4,26 @@ import { useTranslations } from 'next-intl'; import { COURSE_ROW_ITEM_WIDTH } from '@/app/[locale]/academics/components/courses/CourseListRow'; +/** + * Grade mapping + * 0: 대학원 + * 1: 1학년 + * 2: 2학년 + * 3: 3학년 + * 4: 4학년 + * 5: 학년 + */ + export default function CourseListHeader() { - const t = useTranslations('Content'); + const t = useTranslations('Page.courses'); return (
- {t('교과목명')} - {t('교과목 구분')} - {t('교과목 번호')} - {t('학점')} - {t('학년')} + {t('name')} + {t('classification')} + {t('code')} + {t('credit', { credit: 0 })} + {t('grade', { grade: 5 })}
); } diff --git a/app/[locale]/academics/components/courses/CourseListRow.tsx b/app/[locale]/academics/components/courses/CourseListRow.tsx index c539f8ec..16138c97 100644 --- a/app/[locale]/academics/components/courses/CourseListRow.tsx +++ b/app/[locale]/academics/components/courses/CourseListRow.tsx @@ -2,7 +2,7 @@ import { useTranslations } from 'next-intl'; -import { Course, GRADE } from '@/apis/types/academics'; +import { Course } from '@/apis/types/academics'; import CourseDetailModal from '@/app/[locale]/academics/components/courses/CourseDetailModal'; import useModal from '@/utils/hooks/useModal'; import { useTypedLocale } from '@/utils/hooks/useTypedLocale'; @@ -67,24 +67,24 @@ function CodeCell({ code }: { code: string }) { } function CreditCell({ credit }: { credit: number }) { - const t = useTranslations('Tag'); + const t = useTranslations('Page.courses'); return ( {credit} - {t('학점')} + {t('credit', { credit })} ); } function GradeCell({ grade }: { grade: number }) { - const t = useTranslations('Tag'); + const t = useTranslations('Page.courses'); return ( - {t(GRADE[grade])} + {t('grade', { grade })} ); } diff --git a/app/[locale]/academics/components/courses/CourseToolbar.tsx b/app/[locale]/academics/components/courses/CourseToolbar.tsx index b27a851e..6c802419 100644 --- a/app/[locale]/academics/components/courses/CourseToolbar.tsx +++ b/app/[locale]/academics/components/courses/CourseToolbar.tsx @@ -48,7 +48,7 @@ interface ViewOptionsProps { } function ViewOptions({ selectedOption, changeOption }: ViewOptionsProps) { - const t = useTranslations('Content'); + const t = useTranslations('Page.courses'); return (
@@ -56,14 +56,14 @@ function ViewOptions({ selectedOption, changeOption }: ViewOptionsProps) { className={selectedOption === '목록형' ? 'text-neutral-800' : 'cursor-pointer'} onClick={() => changeOption('목록형')} > - {t('목록형')} + {t('listView')} | changeOption('카드형')} > - {t('카드형')} + {t('cardView')}
); @@ -74,18 +74,18 @@ interface SortOptionsProps { changeOption: (option: SortOption) => void; } -export const SORT_OPTIONS: SortOption[] = ['학년', '교과목 구분', '학점']; +const SORT_OPTIONS: SortOption[] = ['학년', '교과목 구분', '학점']; function SortOptions({ selectedOption, changeOption }: SortOptionsProps) { return (
{SORT_OPTIONS.map((option) => option === selectedOption ? ( - + ) : ( changeOption(option)} diff --git a/app/[locale]/academics/graduate/courses/GraduateCoursePageContent.tsx b/app/[locale]/academics/graduate/courses/GraduateCoursePageContent.tsx index 50c3142b..bb07c4cf 100644 --- a/app/[locale]/academics/graduate/courses/GraduateCoursePageContent.tsx +++ b/app/[locale]/academics/graduate/courses/GraduateCoursePageContent.tsx @@ -20,7 +20,7 @@ interface CoursePageContentProps { export default function GraduateCoursePageContent({ courses, language }: CoursePageContentProps) { const { selectedOption, changeOptions } = useCourseToolbar(); const { isMobile } = useResponsive(); - const t = useTranslations('Content'); + const t = useTranslations('Page.courses'); if (isMobile && selectedOption.view !== '목록형') { changeOptions({ type: 'view', option: '목록형' }); @@ -31,7 +31,7 @@ export default function GraduateCoursePageContent({ courses, language }: CourseP -

{t('교과목 정보')}

+

{t('courseInformation')}

-

{t('교과목 정보')}

+

{t('courseInformation')}

{nextPost && } - {prevPost && } + {prevPost && }
{id && ( @@ -79,12 +79,9 @@ function RowIcon({ type }: { type: RowType }) { } function RowDescription({ type }: { type: RowType }) { - const t = useTranslations('Content'); + const t = useTranslations('common'); - const description = type == 'next' ? '다음글' : '이전글'; - return ( -

{t(description)}

- ); + return

{t(type)}

; } function RowPostTitle({ title }: { title?: string }) { @@ -101,14 +98,14 @@ function RowPostTitle({ title }: { title?: string }) { } function PostListLink({ href }: { href: string }) { - const t = useTranslations('Content'); + const t = useTranslations('common'); return ( - {t('목록')} + {t('list')} ); } diff --git a/app/[locale]/community/council/report/[id]/page.tsx b/app/[locale]/community/council/report/[id]/page.tsx index 2474a4b9..b46fcd40 100644 --- a/app/[locale]/community/council/report/[id]/page.tsx +++ b/app/[locale]/community/council/report/[id]/page.tsx @@ -31,7 +31,7 @@ export async function generateMetadata({ params }: Props) { export default async function CouncilReportPage({ params }: Props) { const { id, locale } = await params; - const t = await getTranslations('Content'); + const t = await getTranslations('Page.report'); const council = await getCouncilReport(id); const { title, description, sequence, name, createdAt } = council; @@ -44,10 +44,10 @@ export default async function CouncilReportPage({ params }: Props) {

{title}

- {t('작성자')}: {author} + {t('author')}: {author}

- {t('작성 날짜')}: {dateStr} + {t('createdDate')}: {dateStr}

diff --git a/app/[locale]/community/news/[id]/NewsViewer.tsx b/app/[locale]/community/news/[id]/NewsViewer.tsx index 393c0570..40295391 100644 --- a/app/[locale]/community/news/[id]/NewsViewer.tsx +++ b/app/[locale]/community/news/[id]/NewsViewer.tsx @@ -1,6 +1,8 @@ // TODO: searchParams를 사용했음에도 static rendering이 되는 것 같아 추가 export const dynamic = 'force-dynamic'; +import { getTranslations } from 'next-intl/server'; + import { deleteNewsAction } from '@/actions/news'; import { News } from '@/apis/types/news'; import PostFooter from '@/app/[locale]/community/components/PostFooter'; @@ -10,6 +12,7 @@ import Tags from '@/components/common/Tags'; import HTMLViewer from '@/components/form/html/HTMLViewer'; import { PAGE_PADDING_BOTTOM_TAILWIND } from '@/components/layout/pageLayout/paddings'; import { news } from '@/constants/segmentNode'; +import { NEWS_TAGS } from '@/constants/tag'; import { useDayjs } from '@/utils/hooks/useDayjs'; import { getPath } from '@/utils/page'; @@ -20,6 +23,12 @@ interface NewsPostPageProps { const newsPath = getPath(news); export default async function NewsViewer({ news }: NewsPostPageProps) { + const t = await getTranslations('Page.news.tag'); + const tags = NEWS_TAGS.filter((tag) => news.tags.includes(tag.id)).map((tag) => ({ + id: tag.id, + text: t(tag.transKey), + })); + return ( <>
@@ -37,7 +46,7 @@ export default async function NewsViewer({ news }: NewsPostPageProps) { wrapperClassName="mb-10" /> - +
{NEWS_TAGS.map((tag) => ( - + ))}
diff --git a/app/[locale]/community/news/components/NewsPageContent.tsx b/app/[locale]/community/news/components/NewsPageContent.tsx index dcfddfa8..0987fd8c 100644 --- a/app/[locale]/community/news/components/NewsPageContent.tsx +++ b/app/[locale]/community/news/components/NewsPageContent.tsx @@ -1,3 +1,7 @@ +'use client'; + +import { useTranslations } from 'next-intl'; + import { NewsPreviewList } from '@/apis/types/news'; import NewsRow from '@/app/[locale]/community/news/components/NewsRow'; import LoginVisible from '@/components/common/LoginVisible'; @@ -17,9 +21,15 @@ export default function NewsPageContent({ }: { data: NewsPreviewList; }) { + const t = useTranslations('Page.news.tag'); + const tags = NEWS_TAGS.map((tag) => ({ + id: tag.id, + text: t(tag.transKey), + })); + return ( <> - + {searchList.length > 0 ? (
@@ -29,7 +39,7 @@ export default function NewsPageContent({ href={`${newsPath}/${post.id}`} title={post.title} description={post.description} - tags={post.tags} + tagIds={post.tags} date={new Date(post.date)} imageURL={post.imageURL} /> diff --git a/app/[locale]/community/news/components/NewsRow.tsx b/app/[locale]/community/news/components/NewsRow.tsx index b595dbab..90f1b5c4 100644 --- a/app/[locale]/community/news/components/NewsRow.tsx +++ b/app/[locale]/community/news/components/NewsRow.tsx @@ -1,9 +1,12 @@ 'use client'; +import { useTranslations } from 'next-intl'; + import PaginatedLink from '@/app/[locale]/community/components/PaginatedLink'; import ImageWithFallback from '@/components/common/ImageWithFallback'; import Tags from '@/components/common/Tags'; import { news } from '@/constants/segmentNode'; +import { NEWS_TAGS } from '@/constants/tag'; import { useDayjs } from '@/utils/hooks/useDayjs'; import { getPath } from '@/utils/page'; @@ -11,7 +14,7 @@ interface NewsRowProps { href: string; title: string; description: string; - tags: string[]; + tagIds: string[]; date: Date; imageURL: string | null; @@ -28,13 +31,18 @@ export default function NewsRow({ href, title, description, - tags, + tagIds, date, imageURL, descriptionBold, hideDivider, }: NewsRowProps) { description += '...'; // clip이 안될정도로 화면이 좌우로 긴 경우 대비 + const t = useTranslations('Page.news.tag'); + const tags = NEWS_TAGS.filter((tag) => tagIds.includes(tag.id)).map((tag) => ({ + id: tag.id, + text: t(tag.transKey), + })); const formatDate = useDayjs(); diff --git a/app/[locale]/community/notice/NoticePageContent.tsx b/app/[locale]/community/notice/NoticePageContent.tsx index 7141f972..6db7bb09 100644 --- a/app/[locale]/community/notice/NoticePageContent.tsx +++ b/app/[locale]/community/notice/NoticePageContent.tsx @@ -1,5 +1,7 @@ 'use client'; +import { useTranslations } from 'next-intl'; + import { NoticePreviewList } from '@/apis/types/notice'; import AdminFeatures from '@/app/[locale]/community/notice/components/AdminFeatures'; import NoticeList from '@/app/[locale]/community/notice/components/NoticeList'; @@ -18,10 +20,15 @@ export default function NoticePageContent({ data: NoticePreviewList; }) { const { selectedIds, dispatchIds, editMode, toggleEditMode } = usePostSelect(); + const t = useTranslations('Page.notice.tag'); + const tags = NOTICE_TAGS.map((tag) => ({ + id: tag.id, + text: t(tag.transKey), + })); return ( <> - + notice.tags.includes(tag.id)).map((tag) => ({ + id: tag.id, + text: t(tag.transKey), + })); + return ( <>
@@ -28,7 +36,7 @@ export default async function NoticeViewer({ notice }: NoticePostPageProps) { - + { - const t = useTranslations('Content'); + const t = useTranslations('common'); const formatDate = useDayjs(); return ( @@ -58,10 +66,10 @@ const Header = ({

{title}

- {t('작성자')}: {author} + {t('author')}: {author}

- {t('작성 날짜')}: {formatDate ? formatDate({ date: createdAt, format: 'time' }) : ''} + {t('createdDate')}: {formatDate ? formatDate({ date: createdAt, format: 'time' }) : ''}

diff --git a/app/[locale]/community/notice/[id]/page.tsx b/app/[locale]/community/notice/[id]/page.tsx index 8edabb92..9a1afd47 100644 --- a/app/[locale]/community/notice/[id]/page.tsx +++ b/app/[locale]/community/notice/[id]/page.tsx @@ -47,6 +47,7 @@ export default async function NoticePostPage(props: NoticePostPageProps) { try { const notice = await getNoticePostDetail(id, searchParams); + return ( }> diff --git a/app/[locale]/community/notice/components/NoticeEditor.tsx b/app/[locale]/community/notice/components/NoticeEditor.tsx index 65e05882..e041a40a 100644 --- a/app/[locale]/community/notice/components/NoticeEditor.tsx +++ b/app/[locale]/community/notice/components/NoticeEditor.tsx @@ -69,7 +69,7 @@ export default function NoticeEditor({ defaultValues, onCancel, onSubmit, onDele
{NOTICE_TAGS.map((tag) => ( - + ))}
@@ -92,11 +92,7 @@ export default function NoticeEditor({ defaultValues, onCancel, onSubmit, onDele if (isImportant) setValue('isPrivate', false); }} /> - + - +
- {t('제목')} + {t('title')} {/* 아래 날짜들과 정렬하려면 너비를 직접 똑같이 맞춰줘야 함 */} - {t('날짜')} + {t('date')} ); diff --git a/app/[locale]/community/notice/create/page.tsx b/app/[locale]/community/notice/create/page.tsx index d99f3303..cb994467 100644 --- a/app/[locale]/community/notice/create/page.tsx +++ b/app/[locale]/community/notice/create/page.tsx @@ -32,7 +32,7 @@ export default function NoticeCreatePage() { isPrivate: content.isPrivate, isPinned: content.isPinned, isImportant: content.isImportant, - pinnedUntil: content.pinnedUntil.toISOString().slice(0, 10), + pinnedUntil: content.pinnedUntil.toISOString().slice(0, 10), importantUntil: content.importantUntil.toISOString().slice(0, 10), tags: content.tags, }), diff --git a/app/[locale]/community/seminar/[id]/SeminarViewer.tsx b/app/[locale]/community/seminar/[id]/SeminarViewer.tsx index 9825ef01..49948805 100644 --- a/app/[locale]/community/seminar/[id]/SeminarViewer.tsx +++ b/app/[locale]/community/seminar/[id]/SeminarViewer.tsx @@ -18,7 +18,7 @@ interface SeminarPostPageProps { } export default async function SeminarViewer({ seminarData }: SeminarPostPageProps) { - const t = await getTranslations('Content'); + const t = await getTranslations('Page.seminars'); return ( <> @@ -32,21 +32,21 @@ export default async function SeminarViewer({ seminarData }: SeminarPostPageProp
- {t('이름')}: {seminarData.name} + {t('name')}: {seminarData.name}
{seminarData.speakerTitle &&

직함: {seminarData.speakerTitle}

}
- {t('소속')}:{' '} + {t('affiliation')}:{' '} {seminarData.affiliation}
- {t('주최')}: {seminarData.host} + {t('host')}: {seminarData.host}
- {t('날짜')}: {formatStartEndDate(seminarData.startDate, seminarData.endDate)} + {t('date')}: {formatStartEndDate(seminarData.startDate, seminarData.endDate)}
- {t('위치')}: {seminarData.location} + {t('location')}: {seminarData.location}
@@ -61,14 +61,14 @@ export default async function SeminarViewer({ seminarData }: SeminarPostPageProp {seminarData.description && ( <> -
{t('요약')}
+
{t('summary')}
)} {seminarData.introduction && ( <> -
{t('연사 소개')}
+
{t('speakerInformation')}
)} diff --git a/app/[locale]/community/seminar/components/SeminarSearchBar.tsx b/app/[locale]/community/seminar/components/SeminarSearchBar.tsx index 42951049..9624bd8c 100644 --- a/app/[locale]/community/seminar/components/SeminarSearchBar.tsx +++ b/app/[locale]/community/seminar/components/SeminarSearchBar.tsx @@ -6,7 +6,7 @@ import { ChangeEventHandler, FormEvent, useState } from 'react'; import { useCustomSearchParams } from '@/utils/hooks/useCustomSearchParams'; export default function SeminarSearchBar() { - const t = useTranslations('Content'); + const t = useTranslations('common'); const { keyword: initKeyword, setSearchParams } = useCustomSearchParams(); const [text, setText] = useState(initKeyword ?? ''); @@ -23,7 +23,7 @@ export default function SeminarSearchBar() { return (

- {t(news.name)} + {t(`${news.engName}.title`)}

- {t('더보기')} + {t('home.viewMore')}
); diff --git a/app/[locale]/components/NoticeSection.tsx b/app/[locale]/components/NoticeSection.tsx index 2b85f1ae..2a41e4e6 100644 --- a/app/[locale]/components/NoticeSection.tsx +++ b/app/[locale]/components/NoticeSection.tsx @@ -15,8 +15,7 @@ import { getPath } from '@/utils/page'; export default function NoticeSection({ allMainNotice }: { allMainNotice: AllMainNotice }) { const [tag, setTag] = useState('all'); const { isMobile } = useResponsive(); - const t = useTranslations('Nav'); - const tTag = useTranslations('Tag'); + const t = useTranslations('Page'); const formatDate = useDayjs(); @@ -26,31 +25,31 @@ export default function NoticeSection({ allMainNotice }: { allMainNotice: AllMai
-

{t('공지사항')}

+

{t(`${notice.engName}.title`)}

setTag('all')}> - {tTag('전체')} + {t('home.all')} setTag('scholarship')} > - {tTag('장학')} + {t('home.scholarship')} setTag('undergraduate')} > - {tTag('학부')} + {t('home.undergraduate.lower')} setTag('graduate')}> - {tTag('대학원')} + {t('home.graduate')}
{!isMobile && ( - {t('더보기')} + {t('home.viewMore')} )}
@@ -74,7 +73,7 @@ export default function NoticeSection({ allMainNotice }: { allMainNotice: AllMai className="ml-auto mt-6 flex text-base font-normal text-[#E65817]" href={getPath(notice)} > - {t('더보기')} + {t('viewMore')} )}
diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx index 76a038a7..16a5dc25 100644 --- a/app/[locale]/layout.tsx +++ b/app/[locale]/layout.tsx @@ -28,13 +28,13 @@ export async function generateMetadata(props: { const { locale } = params; - const t = await getTranslations({ locale, namespace: 'Title' }); + const t = await getTranslations({ locale, namespace: 'common' }); return { metadataBase: new URL(PROD_URL), title: { - default: t('서울대학교 컴퓨터공학부'), - template: `%s | ${t('서울대학교 컴퓨터공학부')}`, + default: t('title'), + template: `%s | ${t('title')}`, }, description: '서울대학교 컴퓨터공학부 홈페이지입니다.', openGraph: { @@ -81,11 +81,7 @@ async function ContextProviders({ locale, children }: { locale: string; children const messages = await getMessages(); - return ( - - {children} - - ); + return {children}; } function BuildVersion() { diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx index 80e1b93c..f7ef74c5 100644 --- a/app/[locale]/page.tsx +++ b/app/[locale]/page.tsx @@ -93,13 +93,13 @@ const ImportantSectionArrow = () => ( ); const LinkSection = async () => { - const t = await getTranslations('Nav'); + const t = await getTranslations('Page.home'); return (

- {t('바로가기')} + {t('shortcuts')}

@@ -113,7 +113,9 @@ const LinkSection = async () => {
-

{t('학부')}

+

+ {t('undergraduate.cap')} +

; }) { - const t = useTranslations('Content'); + const t = useTranslations('Page.emeritusFaculty'); - const careerTimeStr = `${t('재직 기간')}: ${faculty.startDate} - ${faculty.endDate}`; + const careerTimeStr = `${t('employmentPeriod')}: ${faculty.startDate} - ${faculty.endDate}`; const handleDelete = async () => { try { @@ -53,12 +53,12 @@ export default function EmeritusFacultyMemberPageContent({
    {faculty.office && ( - {t('교수실')}: {faculty.office} + {t('office')}: {faculty.office} )} {faculty.email && ( - {t('이메일')}: + {t('email')}: - {t('웹사이트')}: + {t('website')}: {faculty.website} @@ -78,8 +78,8 @@ export default function EmeritusFacultyMemberPageContent({
)} - - + +
{careerTimeStr}
diff --git a/app/[locale]/people/faculty/[id]/FacultyMemberPageContent.tsx b/app/[locale]/people/faculty/[id]/FacultyMemberPageContent.tsx index 6652e263..81f659b4 100644 --- a/app/[locale]/people/faculty/[id]/FacultyMemberPageContent.tsx +++ b/app/[locale]/people/faculty/[id]/FacultyMemberPageContent.tsx @@ -30,7 +30,7 @@ export default function FacultyMemberPageContent({ faculty: Faculty; ids: WithLanguage; }) { - const t = useTranslations('Content'); + const t = useTranslations('Page.faculty'); const handleDelete = async () => { try { @@ -64,9 +64,9 @@ export default function FacultyMemberPageContent({
- - - + + +
diff --git a/app/[locale]/people/staff/[id]/StaffMemberPageContent.tsx b/app/[locale]/people/staff/[id]/StaffMemberPageContent.tsx index 17d2bfc6..b1adb164 100644 --- a/app/[locale]/people/staff/[id]/StaffMemberPageContent.tsx +++ b/app/[locale]/people/staff/[id]/StaffMemberPageContent.tsx @@ -29,7 +29,7 @@ export default function StaffMemberPageContent({ staff: Staff; ids: WithLanguage; }) { - const t = useTranslations('Content'); + const t = useTranslations('Page.staff'); const handleDelete = async () => { try { @@ -50,7 +50,7 @@ export default function StaffMemberPageContent({
-

{t('연락처')}

+

{t('contact')}

    {t('위치')}: {staff.office} diff --git a/app/[locale]/research/groups/ResearchGroupDetails.tsx b/app/[locale]/research/groups/ResearchGroupDetails.tsx index a27dd651..bf58e82e 100644 --- a/app/[locale]/research/groups/ResearchGroupDetails.tsx +++ b/app/[locale]/research/groups/ResearchGroupDetails.tsx @@ -23,7 +23,7 @@ interface ResearchGroupDetailsProps { const groupsPath = getPath(researchGroups); export default function ResearchGroupDetails({ group, ids }: ResearchGroupDetailsProps) { - const t = useTranslations('Content'); + const t = useTranslations('Page.streams'); const handleDelete = async () => { const resp = await deleteResearchGroupAction(ids); @@ -34,7 +34,7 @@ export default function ResearchGroupDetails({ group, ids }: ResearchGroupDetail

    - {group.name} {t('스트림')} + {group.name} {t('stream')}

    diff --git a/app/[locale]/research/groups/ResearchGroupLabs.tsx b/app/[locale]/research/groups/ResearchGroupLabs.tsx index 60ec8f17..396033b0 100644 --- a/app/[locale]/research/groups/ResearchGroupLabs.tsx +++ b/app/[locale]/research/groups/ResearchGroupLabs.tsx @@ -7,12 +7,12 @@ import { Link } from '@/i18n/routing'; import { getPath } from '@/utils/page'; export default function ResearchGroupLabs({ labs }: { labs: { id: number; name: string }[] }) { - const t = useTranslations('Content'); + const t = useTranslations('Page.streams'); return (

    - {t('연구실')} + {t('laboratories')}

      {labs.map((lab) => ( diff --git a/app/[locale]/research/labs/ResearchLabListHeader.tsx b/app/[locale]/research/labs/ResearchLabListHeader.tsx index d5f6c7fa..efe20942 100644 --- a/app/[locale]/research/labs/ResearchLabListHeader.tsx +++ b/app/[locale]/research/labs/ResearchLabListHeader.tsx @@ -5,16 +5,16 @@ import { useTranslations } from 'next-intl'; import { LAB_ROW_ITEM_WIDTH } from '@/app/[locale]/research/labs/ResearchLabListRow'; export default function ResearchLabListHeader() { - const t = useTranslations('Content'); + const t = useTranslations('Page.laboratories'); return (

      - {t('연구실')} - {t('지도교수')} - {t('연구실 위치')} - {t('전화')} - {t('약자')} - {t('소개 자료')} + {t('name')} + {t('supervisor')} + {t('location')} + {t('phone')} + {t('acronym')} + {t('information')}

      ); } diff --git a/app/[locale]/research/labs/[id]/ResearchLabDetailContent.tsx b/app/[locale]/research/labs/[id]/ResearchLabDetailContent.tsx index a1691926..97c60a47 100644 --- a/app/[locale]/research/labs/[id]/ResearchLabDetailContent.tsx +++ b/app/[locale]/research/labs/[id]/ResearchLabDetailContent.tsx @@ -62,7 +62,7 @@ const researchGroupsPath = getPath(researchGroups); const LENGTH_BOUNDARY = 10; function AffiliatedGroup({ groupName }: { groupName: string }) { - const t = useTranslations('Content'); + const t = useTranslations('Page.streams'); const width = groupName.length < LENGTH_BOUNDARY ? 'w-[10.875rem]' : 'w-[16.4375rem]'; const affiliatedGroupPath = `${researchGroupsPath}?selected=${replaceSpaceWithDash(groupName)}`; @@ -74,7 +74,7 @@ function AffiliatedGroup({ groupName }: { groupName: string }) { className={`absolute ${width} peer flex h-10 items-center justify-center pr-1 text-center text-sm duration-300 hover:text-white`} > - {groupName} {t('스트림')} + {groupName} {t('stream')}
      diff --git a/app/[locale]/research/top-conference-list/ConferenceListTable.tsx b/app/[locale]/research/top-conference-list/ConferenceListTable.tsx index a6c00c8f..e360db7c 100644 --- a/app/[locale]/research/top-conference-list/ConferenceListTable.tsx +++ b/app/[locale]/research/top-conference-list/ConferenceListTable.tsx @@ -16,15 +16,15 @@ export default function ConferenceListTable({ }: { conferenceList: ConferenceListTableProps[]; }) { - const t = useTranslations('Content'); + const t = useTranslations('Page.topConferenceList'); return (
      -
      {t('연번')}
      -
      {t('약칭')}
      -
      {t('학술대회 명칭')}
      +
      {t('number')}
      +
      {t('abbreviation')}
      +
      {t('conferenceName')}
      {conferenceList.map((conference, index) => ( diff --git a/app/[locale]/research/top-conference-list/page.tsx b/app/[locale]/research/top-conference-list/page.tsx index a7200bcf..97da53f1 100644 --- a/app/[locale]/research/top-conference-list/page.tsx +++ b/app/[locale]/research/top-conference-list/page.tsx @@ -16,7 +16,7 @@ export async function generateMetadata(props: { params: Promise<{ locale: string export default async function TopConferenceListPage() { const { modifiedAt, author, conferenceList } = await getTopConferenceList(); - const t = await getTranslations('Content'); + const t = await getTranslations('Page.topConferenceList'); // TODO: 왜 기본값이 한국이 아닌지 알아내기 const dateStr = new Date(modifiedAt).toLocaleDateString('ko-KR'); @@ -24,16 +24,12 @@ export default async function TopConferenceListPage() { return (
      -

      - {t('서울대학교 공과대학 컴퓨터공학부')} Top Conference List -

      +

      {t('subtitle')} Top Conference List

      +

      {t('disclaimerNotice')}.

      - {t('본 리스트는 시간과 상황의 변동에 따라 바뀔 수 있습니다')}. -

      -

      - {t('수정 날짜')}: {dateStr} + {t('lastUpdated')}: {dateStr}
      - {t('작성자')}: {author} + {t('author')}: {author}

      diff --git a/app/[locale]/reservations/[roomType]/[roomName]/page.tsx b/app/[locale]/reservations/[roomType]/[roomName]/page.tsx index d4649266..62e5aee2 100644 --- a/app/[locale]/reservations/[roomType]/[roomName]/page.tsx +++ b/app/[locale]/reservations/[roomType]/[roomName]/page.tsx @@ -1,4 +1,3 @@ -import { Metadata } from 'next'; import { Suspense } from 'react'; import ReservationCalendar from '@/app/[locale]/reservations/[roomType]/[roomName]/helper/ReservationCalendar'; @@ -8,7 +7,7 @@ import { getMetadata } from '@/utils/metadata'; export async function generateMetadata(props: { params: Promise<{ locale: string; roomName: string }>; -}): Promise { +}) { const params = await props.params; const { locale, roomName } = params; diff --git a/app/[locale]/reservations/introduction/page.tsx b/app/[locale]/reservations/introduction/page.tsx index 30d40032..1105a184 100644 --- a/app/[locale]/reservations/introduction/page.tsx +++ b/app/[locale]/reservations/introduction/page.tsx @@ -1,5 +1,3 @@ -import { Metadata } from 'next'; - import SelectionList from '@/components/common/selection/SelectionList'; import SelectionTitle from '@/components/common/selection/SelectionTitle'; import HTMLViewer from '@/components/form/html/HTMLViewer'; @@ -9,9 +7,7 @@ import { getMetadata } from '@/utils/metadata'; import { getPath } from '@/utils/page'; import { replaceDashWithSpace } from '@/utils/string'; -export async function generateMetadata(props: { - params: Promise<{ locale: string }>; -}): Promise { +export async function generateMetadata(props: { params: Promise<{ locale: string }> }) { const params = await props.params; const { locale } = params; diff --git a/app/[locale]/reservations/page.tsx b/app/[locale]/reservations/page.tsx index 78f09a80..5fcb8974 100644 --- a/app/[locale]/reservations/page.tsx +++ b/app/[locale]/reservations/page.tsx @@ -1,12 +1,8 @@ -import { Metadata } from 'next'; - import MajorCategoryPageLayout from '@/components/layout/pageLayout/MajorCategoryPageLayout'; import { reservations } from '@/constants/segmentNode'; import { getMetadata } from '@/utils/metadata'; -export async function generateMetadata(props: { - params: Promise<{ locale: string }>; -}): Promise { +export async function generateMetadata(props: { params: Promise<{ locale: string }> }) { const params = await props.params; const { locale } = params; diff --git a/app/[locale]/search/AboutSection.tsx b/app/[locale]/search/AboutSection.tsx index e52f4df3..80ef9125 100644 --- a/app/[locale]/search/AboutSection.tsx +++ b/app/[locale]/search/AboutSection.tsx @@ -1,7 +1,10 @@ +import { getTranslations } from 'next-intl/server'; + import { AboutPreview, AboutSearchResult } from '@/apis/types/search'; import BasicRow from '@/app/[locale]/search/helper/BasicRow'; import Section from '@/app/[locale]/search/helper/Section'; import { + about as aboutNode, contact, directions, facilities, @@ -14,8 +17,10 @@ import { import { getPath } from '@/utils/page'; export default async function AboutSection({ about }: { about: AboutSearchResult }) { + const t = await getTranslations(`Page.${aboutNode.engName}`); + return ( -
      +
      {about.results.map((result) => { const node = aboutPostTypeToNode(result.aboutPostType); diff --git a/app/[locale]/search/AcademicSection.tsx b/app/[locale]/search/AcademicSection.tsx index 75050c8c..859536cb 100644 --- a/app/[locale]/search/AcademicSection.tsx +++ b/app/[locale]/search/AcademicSection.tsx @@ -1,7 +1,10 @@ +import { getTranslations } from 'next-intl/server'; + import { Academic, AcademicsSearchResult } from '@/apis/types/search'; import BasicRow from '@/app/[locale]/search/helper/BasicRow'; import Section from '@/app/[locale]/search/helper/Section'; import { + academics, curriculum, degree, generalStudies, @@ -18,8 +21,10 @@ import { getPath } from '@/utils/page'; // TODO: 장학 제도 등 상세 페이지로 연결 export default async function AcademicSection({ academic }: { academic: AcademicsSearchResult }) { + const t = await getTranslations(`Page.${academics.engName}`); + return ( -
      +
      {academic.results.map((result) => { const node = toNode(result); diff --git a/app/[locale]/search/AdmissionSection.tsx b/app/[locale]/search/AdmissionSection.tsx index 78021031..1fb46a8e 100644 --- a/app/[locale]/search/AdmissionSection.tsx +++ b/app/[locale]/search/AdmissionSection.tsx @@ -1,3 +1,5 @@ +import { getTranslations } from 'next-intl/server'; + import { AdmissionsSearchResult } from '@/apis/types/search'; import BasicRow from '@/app/[locale]/search/helper/BasicRow'; import Section from '@/app/[locale]/search/helper/Section'; @@ -9,8 +11,10 @@ export default async function AdmissionSection({ }: { admission: AdmissionsSearchResult; }) { + const t = await getTranslations(`Page.${admissions.engName}`); + return ( -
      +
      {admission.admissions.map((result) => { const node = toNode(result.mainType, result.postType); diff --git a/app/[locale]/search/CommunitySection.tsx b/app/[locale]/search/CommunitySection.tsx index e11d37f5..a1d89ba9 100644 --- a/app/[locale]/search/CommunitySection.tsx +++ b/app/[locale]/search/CommunitySection.tsx @@ -1,4 +1,5 @@ import { useTranslations } from 'next-intl'; +import { getTranslations } from 'next-intl/server'; import { ReactNode } from 'react'; import { NewsSearchResult, NoticeSearchResult } from '@/apis/types/search'; @@ -9,7 +10,7 @@ import Divider from '@/app/[locale]/search/helper/Divider'; import NewsRow from '@/app/[locale]/search/helper/NewsRow'; import NoticeRow from '@/app/[locale]/search/helper/NoticeRow'; import Section from '@/app/[locale]/search/helper/Section'; -import { news, notice, seminar } from '@/constants/segmentNode'; +import { community, news, notice, seminar } from '@/constants/segmentNode'; import { Link } from '@/i18n/routing'; import { getPath } from '@/utils/page'; @@ -28,8 +29,10 @@ export default async function CommunitySection({ news: NewsSearchResult; seminar: SeminarPreviewList; }) { + const t = await getTranslations(`Page.${community.engName}`); + return ( -
      +
      { - const t = useTranslations('Nav'); + const t = useTranslations('Page.search'); return ( - {t('결과 더보기')} + {t('viewMore')} chevron_right ); diff --git a/app/[locale]/search/MemberSection.tsx b/app/[locale]/search/MemberSection.tsx index 644929ea..f4b941e6 100644 --- a/app/[locale]/search/MemberSection.tsx +++ b/app/[locale]/search/MemberSection.tsx @@ -1,19 +1,22 @@ +import { getTranslations } from 'next-intl/server'; + import { Member, MemberSearchResult } from '@/apis/types/search'; import CircleTitle from '@/app/[locale]/search/helper/CircleTitle'; import Divider from '@/app/[locale]/search/helper/Divider'; import Section from '@/app/[locale]/search/helper/Section'; import styles from '@/app/[locale]/search/style.module.css'; import ImageWithFallback from '@/components/common/ImageWithFallback'; -import { faculty, staff } from '@/constants/segmentNode'; +import { faculty, people, staff } from '@/constants/segmentNode'; import { Link } from '@/i18n/routing'; import { getPath } from '@/utils/page'; export default async function MemberSection({ member }: { member: MemberSearchResult }) { const professorList = member.results.filter((x) => x.memberType === 'PROFESSOR'); const staffList = member.results.filter((x) => x.memberType === 'STAFF'); + const t = await getTranslations(`Page.${people.engName}`); return ( -
      +
      {professorList.length !== 0 && ( <> diff --git a/app/[locale]/search/ResearchSection.tsx b/app/[locale]/search/ResearchSection.tsx index 4deac577..7ceccb41 100644 --- a/app/[locale]/search/ResearchSection.tsx +++ b/app/[locale]/search/ResearchSection.tsx @@ -1,7 +1,10 @@ +import { getTranslations } from 'next-intl/server'; + import { ResearchSearchResult, ResearchType } from '@/apis/types/search'; import BasicRow from '@/app/[locale]/search/helper/BasicRow'; import Section from '@/app/[locale]/search/helper/Section'; import { + research as researchNode, researchCenters, researchGroups, researchLabs, @@ -10,8 +13,10 @@ import { import { getPath } from '@/utils/page'; export default async function ResearchSection({ research }: { research: ResearchSearchResult }) { + const t = await getTranslations(`Page.${researchNode.engName}`); + return ( -
      +
      {research.results.map((result) => { const node = toNode(result.researchType); diff --git a/app/[locale]/search/helper/NoSearchResultError.tsx b/app/[locale]/search/helper/NoSearchResultError.tsx index 7d19b8da..cee4220b 100644 --- a/app/[locale]/search/helper/NoSearchResultError.tsx +++ b/app/[locale]/search/helper/NoSearchResultError.tsx @@ -1,9 +1,15 @@ +'use client'; + +import { useTranslations } from 'next-intl'; + import MagnificentGlass from '@/public/image/search/magnificent_glass.svg'; export default function NoSearchResultError() { + const t = useTranslations('Page.search'); + return (
      -

      검색 결과가 존재하지 않습니다

      +

      {t('noResults')}

      ); diff --git a/app/[locale]/search/helper/Section.tsx b/app/[locale]/search/helper/Section.tsx index 4c556691..d7bed35a 100644 --- a/app/[locale]/search/helper/Section.tsx +++ b/app/[locale]/search/helper/Section.tsx @@ -1,4 +1,3 @@ -import { useTranslations } from 'next-intl'; import { ReactNode } from 'react'; export default function Section({ @@ -10,14 +9,13 @@ export default function Section({ size: number; children: ReactNode; }) { - const t = useTranslations('Nav'); if (size === 0) return <>; return (

      - {t(title)}({size}) + {title}({size})

      diff --git a/app/[locale]/search/page.tsx b/app/[locale]/search/page.tsx index e426859d..4769246f 100644 --- a/app/[locale]/search/page.tsx +++ b/app/[locale]/search/page.tsx @@ -1,4 +1,3 @@ -import { Metadata } from 'next'; import { getTranslations } from 'next-intl/server'; import { ReactNode } from 'react'; @@ -18,19 +17,17 @@ import { SEARCH_TAGS } from '@/constants/tag'; import MagnificentGlass from '@/public/image/search/magnificent_glass.svg'; import { getMetadata } from '@/utils/metadata'; -export async function generateMetadata(props: { - params: Promise<{ locale: string }>; -}): Promise { +export async function generateMetadata(props: { params: Promise<{ locale: string }> }) { const params = await props.params; const { locale } = params; - const t = await getTranslations('Nav'); + const t = await getTranslations('Page.search'); return await getMetadata({ locale, metadata: { - title: t('통합 검색'), + title: t('title'), description: '서울대학교 컴퓨터공학부 통합 검색 페이지입니다.', }, }); @@ -94,14 +91,26 @@ export default async function SearchPage(props: { // TODO: page layout 사용한 방식으로 변경 // 현재는 SubNav의 차이 때문에 코드 복붙 -const SearchPageLayout = ({ node, children }: { node?: TreeNode[]; children?: ReactNode }) => { +const SearchPageLayout = async ({ + node, + children, +}: { + node?: TreeNode[]; + children?: ReactNode; +}) => { + const t = await getTranslations('Page.search.tag'); + const tags = SEARCH_TAGS.map((tag) => ({ + id: tag.id, + text: t(tag.transKey), + })); + return (
      {/* TODO: 임시로 넣은 main 교체 */}
      - + {children} {node !== undefined && }
      diff --git a/components/common/NoSearchResult.tsx b/components/common/NoSearchResult.tsx index 52720e99..50178fc5 100644 --- a/components/common/NoSearchResult.tsx +++ b/components/common/NoSearchResult.tsx @@ -1,3 +1,9 @@ +'use client'; + +import { useTranslations } from 'next-intl'; + export default function NoSearchResult() { - return

      검색 결과가 존재하지 않습니다.

      ; + const t = useTranslations('Page.search'); + + return

      {t('noResults')}

      ; } diff --git a/components/common/Tags.tsx b/components/common/Tags.tsx index f3ea9fc1..7c79792b 100644 --- a/components/common/Tags.tsx +++ b/components/common/Tags.tsx @@ -1,16 +1,14 @@ 'use client'; -import { useTranslations } from 'next-intl'; - import { Link } from '@/i18n/routing'; interface TagsProps { - tags: string[]; + tags: Tag[]; margin?: string; searchPath?: string; disabled?: boolean; - onClick?: (tag: string) => void; - onDelete?: (tag: string) => void; + onClick?: (tagId: string) => void; + onDelete?: (tagId: string) => void; } export default function Tags({ @@ -25,12 +23,12 @@ export default function Tags({
      {searchPath ? tags.map((tag) => ( - + )) : tags.map((tag) => ( - + ))}
      ); @@ -47,16 +45,21 @@ const HOVER_STYLE: { [key in HoverStyle]: string } = { const DEFAULT_STYLE: { [key in DefaultStyle]: string } = { orange: 'bg-white border-main-orange text-main-orange ', gray: 'bg-white border-neutral-400 text-neutral-400', - fill: ' bg-main-orange border-main-orange text-white', + fill: 'bg-main-orange border-main-orange text-white', }; +export interface Tag { + id: string; + text: string; +} + interface TagProps { - tag: string; + tag: Tag; hoverStyle?: HoverStyle; defaultStyle?: DefaultStyle; disabled?: boolean; - onClick?: (tag: string) => void; - onDelete?: (tag: string) => void; + onClick?: (tagId: string) => void; + onDelete?: (tagId: string) => void; } export function Tag({ @@ -72,16 +75,14 @@ export function Tag({ const defaultClass = DEFAULT_STYLE[defaultStyle]; const hoverClass = hoverStyle ? `${HOVER_STYLE[hoverStyle]} cursor-pointer` : ''; - const t = useTranslations('Tag'); - return ( - onClick?.(tag)}> - {t(tag)} + onClick?.(tag.id)}> + {tag.text} {onDelete && ( diff --git a/components/common/search/KeywordInput.tsx b/components/common/search/KeywordInput.tsx index 2ed5e0e7..9b9a06e3 100644 --- a/components/common/search/KeywordInput.tsx +++ b/components/common/search/KeywordInput.tsx @@ -8,11 +8,11 @@ interface KeywordInputProps { } export default function KeywordInput({ keyword, setKeyword, disabled = false }: KeywordInputProps) { - const t = useTranslations('Content'); + const t = useTranslations('common'); return (
      -
      {t('검색')}
      +
      {t('search')}
      ); diff --git a/components/common/search/SearchBox.tsx b/components/common/search/SearchBox.tsx index 5e35a6b9..9183d0bc 100644 --- a/components/common/search/SearchBox.tsx +++ b/components/common/search/SearchBox.tsx @@ -9,24 +9,25 @@ import TagFilter from '@/components/common/search/TagFilter'; import { useCustomSearchParams } from '@/utils/hooks/useCustomSearchParams'; interface SearchBoxProps { - tags: string[]; // 전체 태그(선택지) 목록 + allTags: { id: string; text: string }[]; // 전체 태그(선택지) 목록 disabled?: boolean; formOnly?: boolean; } -export default function SearchBox({ tags, disabled = false, formOnly = false }: SearchBoxProps) { - const { tags: initTags, keyword: initKeyword, setSearchParams } = useCustomSearchParams(); +export default function SearchBox({ allTags, disabled = false, formOnly = false }: SearchBoxProps) { + const { tags: initTagIds, keyword: initKeyword, setSearchParams } = useCustomSearchParams(); const [keyword, setKeyword] = useState(initKeyword ?? ''); const [, startTransition] = useTransition(); + const selectedTags = allTags.filter((tag) => initTagIds.includes(tag.id)); useEffect(() => { setKeyword(initKeyword ?? ''); }, [initKeyword]); - const search = (tags?: string[]) => { + const search = (tagIds?: string[]) => { // TODO: startTrnaisition이 의미있는지 확인 startTransition(() => { - setSearchParams({ purpose: 'search', keyword, tag: tags ?? initTags }); + setSearchParams({ purpose: 'search', keyword, tag: tagIds ?? initTagIds }); }); }; @@ -39,14 +40,19 @@ export default function SearchBox({ tags, disabled = false, formOnly = false }: search(); }} > - + {!formOnly && ( <> - + )}
      diff --git a/components/common/search/SelectedTags.tsx b/components/common/search/SelectedTags.tsx index 488d76df..56a70467 100644 --- a/components/common/search/SelectedTags.tsx +++ b/components/common/search/SelectedTags.tsx @@ -1,19 +1,20 @@ import { useTranslations } from 'next-intl'; -import Tags from '@/components/common/Tags'; +import Tags, { Tag } from '@/components/common/Tags'; interface SelectedTagsProps { - tags: string[]; + tags: Tag[]; search: (tags: string[]) => void; disabled: boolean; } export default function SelectedTags({ tags, search, disabled }: SelectedTagsProps) { + const t = useTranslations('common'); const isTagExist = tags.length > 0; - const deleteTag = (targetTag: string) => { - const filteredTags = tags.filter((tag) => tag !== targetTag); - search(filteredTags); + const deleteTag = (targetTagId: string) => { + const filteredTagIds = tags.filter((tag) => tag.id !== targetTagId).map((tag) => tag.id); + search(filteredTagIds); }; const resetTags = () => { @@ -23,7 +24,7 @@ export default function SelectedTags({ tags, search, disabled }: SelectedTagsPro return (
      @@ -38,7 +39,7 @@ interface TagResetButtonProps { } function TagResetButton({ disabled, onClick }: TagResetButtonProps) { - const t = useTranslations('Content'); + const t = useTranslations('common'); return ( ); } diff --git a/components/common/search/TagFilter.tsx b/components/common/search/TagFilter.tsx index 6f9ef52d..d965bfb5 100644 --- a/components/common/search/TagFilter.tsx +++ b/components/common/search/TagFilter.tsx @@ -4,14 +4,14 @@ import Checkbox from '@/components/form/legacy/Checkbox'; import useStyle from '@/utils/hooks/useStyle'; interface TagFilterProps { - tags: string[]; + tags: { id: string; text: string }[]; selectedTags: string[]; disabled: boolean; searchTags: (tags: string[]) => void; } export default function TagFilter({ tags, selectedTags, disabled, searchTags }: TagFilterProps) { - const t = useTranslations('Tag'); + const t = useTranslations('common'); const toggleCheck = (tag: string, isChecked: boolean) => { if (isChecked) { @@ -24,14 +24,14 @@ export default function TagFilter({ tags, selectedTags, disabled, searchTags }: const locale = useLocale(); return ( -
      -
      {t('태그')}
      +
      +
      {t('tag')}
      { style.gridTemplateColumns = `repeat(auto-fill, minmax(${calculateWidth( - tags.map((tag) => t(tag)), + tags.map((tag) => tag.text), locale, )}px, auto))`; }, @@ -40,10 +40,10 @@ export default function TagFilter({ tags, selectedTags, disabled, searchTags }: > {tags.map((tag) => ( toggleCheck(tag, !selectedTags.includes(tag))} + key={tag.id} + label={tag.text} + isChecked={selectedTags.includes(tag.id)} + toggleCheck={() => toggleCheck(tag.id, !selectedTags.includes(tag.text))} disabled={disabled} /> ))} diff --git a/components/layout/footer/Footer.tsx b/components/layout/footer/Footer.tsx index 4fc3e297..5247268a 100644 --- a/components/layout/footer/Footer.tsx +++ b/components/layout/footer/Footer.tsx @@ -72,14 +72,14 @@ function LinkGroup({ groupName, links, width, mode }: LinkGroupProps) {

      - {t(groupName)} + {groupName}

        {links.map((link, i) => (
      • - {t(link.title)} + {t(link.engTitle)}
      • ))} @@ -95,11 +95,11 @@ function FooterBottomLeft() { return (
        - {t('개인정보처리방침')} + {t('privacyPolicy')} | - {t('학부 연락처')} + {t('contactUs')} | - {t('찾아오시는 길')} + {t('directions')}
        {t('address')}
        diff --git a/components/layout/footer/constants.ts b/components/layout/footer/constants.ts index 05eff295..2d2128dc 100644 --- a/components/layout/footer/constants.ts +++ b/components/layout/footer/constants.ts @@ -10,7 +10,7 @@ import { reservationIntroduction, SegmentNode, seminar, - tentenProject, + tentenProposal, topConferenceList, undergraduateGuide, } from '@/constants/segmentNode'; @@ -19,11 +19,13 @@ import { getPath } from '@/utils/page'; export interface FooterLink { href: string; title: string; + engTitle: string; } const segmentNodeToLink = (node: SegmentNode): FooterLink => ({ href: getPath(node), title: node.name, + engTitle: node.engName, }); export const aboutLinks = [overview, faculty, undergraduateGuide, graduateGuide].map( @@ -34,32 +36,30 @@ export const resourcesLinks = [notice, seminar, reservationIntroduction].map(seg export const researchLinks = [ segmentNodeToLink(facultyRecruitment), - { - href: getPath(researchLabs), - title: '연구실 목록', - }, + segmentNodeToLink(researchLabs), segmentNodeToLink(topConferenceList), - { - href: getPath(tentenProject), - title: '10-10 Project', - }, + segmentNodeToLink(tentenProposal), ]; export const moreLinks: FooterLink[] = [ { title: '연합전공 인공지능(학사)', + engTitle: 'imai', href: 'https://imai.snu.ac.kr', }, { title: '지능형컴퓨팅사업단', + engTitle: 'bkcse', href: 'http://bkcse.snu.ac.kr', }, { title: '컴퓨터 연구소', + engTitle: 'ict', href: 'http://ict.snu.ac.kr', }, { title: '해동학술정보실', + engTitle: 'haedong', href: 'http://haedong.snu.ac.kr/', }, ]; diff --git a/components/layout/header/HeaderRight.tsx b/components/layout/header/HeaderRight.tsx index 5e34d7c9..c970bd9b 100644 --- a/components/layout/header/HeaderRight.tsx +++ b/components/layout/header/HeaderRight.tsx @@ -46,7 +46,7 @@ const ProdLogin = () => { const logout = useSessionStore((s) => s.logout); const t = useTranslations('Header'); - const authText = t(state === 'logout' ? '로그인' : '로그아웃'); + const authText = t(state === 'logout' ? 'login' : 'logout'); const onClickAuth = state === 'logout' ? login : logout; return ( diff --git a/components/layout/navbar/MobileNav.tsx b/components/layout/navbar/MobileNav.tsx index 0353a055..b6754601 100644 --- a/components/layout/navbar/MobileNav.tsx +++ b/components/layout/navbar/MobileNav.tsx @@ -26,12 +26,12 @@ export default function MobileNav() { } function MobileNavList() { - const navbarState = useNavbarStore((s) => s.navbarState); + const navbarState = useNavbarStore((s) => s.navbarState); const setNavbarState = useNavbarStore((s) => s.setNavbarState); const [search, setSearch] = useState(false); const cur = useCurrentSegmentNode(); - const t = useTranslations('Nav'); + const t = useTranslations('Page'); const shouldHighlight = (child: SegmentNode) => { return navbarState.type === 'hovered' @@ -50,7 +50,7 @@ function MobileNavList() { } cursor-pointer whitespace-nowrap leading-5`} onClick={() => setNavbarState({ type: 'hovered', segmentNode: child })} > - {t(child.name)} + {t(`${child.engName}.title`)} ))}
      diff --git a/components/layout/navbar/NavLabel.tsx b/components/layout/navbar/NavLabel.tsx index 677fa313..5f1cf80e 100644 --- a/components/layout/navbar/NavLabel.tsx +++ b/components/layout/navbar/NavLabel.tsx @@ -1,11 +1,7 @@ -import { useTranslations } from 'next-intl'; - // 예약 부분의 '301-417 (20석)'에서 괄호 부분이 작도록 처리합니다. export default function NavLabel({ text }: { text: string }) { - const t = useTranslations('Nav'); - const idx = text.indexOf('('); - if (idx === -1) return t(text); + if (idx === -1) return text; return ( <> diff --git a/components/layout/navbar/NavbarRoot.tsx b/components/layout/navbar/NavbarRoot.tsx index cf0cf18d..792cbb19 100644 --- a/components/layout/navbar/NavbarRoot.tsx +++ b/components/layout/navbar/NavbarRoot.tsx @@ -14,7 +14,7 @@ export const NAVBAR_EXPANDED_WIDTH_REM = 11; export default function NavbarRoot() { const navbarState = useNavbarStore((s) => s.navbarState); - const setNavbarState = useNavbarStore((s) => s.setNavbarState); + const setNavbarState = useNavbarStore((s) => s.setNavbarState); return ( // 상하로 화면이 좁은 경우를 대비해 overflow-scroll @@ -55,11 +55,11 @@ function DotList() { } function NavList() { - const navbarState = useNavbarStore((s) => s.navbarState); + const navbarState = useNavbarStore((s) => s.navbarState); const setNavbarState = useNavbarStore((s) => s.setNavbarState); const cur = useCurrentSegmentNode(); - const t = useTranslations('Nav'); + const t = useTranslations('Page'); const shouldHighlight = (child: SegmentNode) => { return navbarState.type === 'hovered' @@ -74,7 +74,7 @@ function NavList() { setNavbarState({ type: 'hovered', segmentNode: child })} /> diff --git a/components/layout/navbar/NavtreeRow.tsx b/components/layout/navbar/NavtreeRow.tsx index 32099bcf..9daf3995 100644 --- a/components/layout/navbar/NavtreeRow.tsx +++ b/components/layout/navbar/NavtreeRow.tsx @@ -1,5 +1,7 @@ 'use client'; +import { useTranslations } from 'next-intl'; + import { StraightNode } from '@/components/common/Nodes'; import NavLabel from '@/components/layout/navbar/NavLabel'; import { SegmentNode } from '@/constants/segmentNode'; @@ -20,6 +22,7 @@ export default function NavTreeLabel({ anchorClassName, }: NavTreeRowProps) { const href = getPath(segmentNode); + const t = useTranslations('Page'); if (highlight) { return ( @@ -28,7 +31,7 @@ export default function NavTreeLabel({ href={href} className={`mr-4 h-[1.0625rem] shrink-0 font-medium text-main-orange ${anchorClassName}`} > - +
      @@ -39,7 +42,7 @@ export default function NavTreeLabel({ href={href} className={`mb-6 block h-[1.0625rem] font-medium leading-5 text-white hover:text-main-orange ${anchorClassName} ${containerClassName}`} > - + ); } else { @@ -47,7 +50,7 @@ export default function NavTreeLabel({

      - +

      ); } diff --git a/components/layout/pageLayout/CategoryGrid.tsx b/components/layout/pageLayout/CategoryGrid.tsx index 907bf27c..5e7ef844 100644 --- a/components/layout/pageLayout/CategoryGrid.tsx +++ b/components/layout/pageLayout/CategoryGrid.tsx @@ -16,8 +16,6 @@ export default function CategoryGrid({ currentPage: SegmentNode; theme: 'light' | 'dark'; }) { - const t = useTranslations('Nav'); - // 학사 및 교과 등에서 소분류 선택 처리용 state const [selectedCategory, setSelectedCategory] = useState(null); const router = useRouter(); @@ -30,7 +28,7 @@ export default function CategoryGrid({ {currentPage.children!.map((subpage, index) => ( subpage.isPage ? router.push(getPath(subpage)) : setSelectedCategory(subpage) } @@ -46,7 +44,7 @@ export default function CategoryGrid({ {selectedCategory.children!.map((subpage, index) => ( router.push(getPath(subpage))} /> ))} @@ -112,6 +110,7 @@ function DetailItem({ borderColor, onClick, }: DetailItemProps) { + const t = useTranslations('Page'); const hoverBgColor = hoverColor ? `hover:${hoverColor}` : 'hover:bg-main-orange-dark'; return ( @@ -121,10 +120,10 @@ function DetailItem({ >

      - {title} + {t(`${title}.title`)}

      - {ENG_NAMES.Nav[title as keyof typeof ENG_NAMES.Nav] ?? ''} + {ENG_NAMES.Page[title as keyof typeof ENG_NAMES.Page].title ?? ''}

      {hasArrow && ( diff --git a/components/layout/pageLayout/MajorCategoryPageLayout.tsx b/components/layout/pageLayout/MajorCategoryPageLayout.tsx index 2ea9d03f..4a365169 100644 --- a/components/layout/pageLayout/MajorCategoryPageLayout.tsx +++ b/components/layout/pageLayout/MajorCategoryPageLayout.tsx @@ -18,11 +18,11 @@ export default function MajorCategoryPageLayout({ subtitle = '', description = '', }: GuidePageLayoutProps) { - const t = useTranslations('Nav'); + const t = useTranslations('Page'); const currentPage = useCurrentSegmentNode(); // TODO: messages.json에 번역 파일 추가 - title ||= t(currentPage.name); + title ||= t(`${currentPage.engName}.title`); return (
      diff --git a/components/layout/pageLayout/PageLayout.tsx b/components/layout/pageLayout/PageLayout.tsx index e6414733..744609a3 100644 --- a/components/layout/pageLayout/PageLayout.tsx +++ b/components/layout/pageLayout/PageLayout.tsx @@ -41,9 +41,9 @@ export default function PageLayout({ hideNavbar = false, children, }: PageLayoutProps) { - const t = useTranslations('Nav'); + const t = useTranslations('Page'); const currentPage = useCurrentSegmentNode(); - title ||= t(currentPage.name); + title ||= t(`${currentPage.engName}.title`); return (
      diff --git a/components/layout/pageLayout/PageTitle.tsx b/components/layout/pageLayout/PageTitle.tsx index c286b27c..17158f93 100644 --- a/components/layout/pageLayout/PageTitle.tsx +++ b/components/layout/pageLayout/PageTitle.tsx @@ -42,7 +42,7 @@ export default function PageTitle({ title, currentPage, titleType, margin }: Pag } function Breadcrumb({ currentPage }: { currentPage: SegmentNode }) { - const t = useTranslations('Nav'); + const t = useTranslations('Page'); const log: SegmentNode[] = getLocationLog(currentPage); const exactCurrentPagePathname = usePathname(); // 정확한 현재 페이지 주소 (e.g. 공지목록에서 하위 페이지로 들어간 경우 currentPage가 목록 페이지로 되어있음) @@ -54,7 +54,7 @@ function Breadcrumb({ currentPage }: { currentPage: SegmentNode }) {
    • diff --git a/components/layout/pageLayout/SubNavbar.tsx b/components/layout/pageLayout/SubNavbar.tsx index 1230d94d..224ac47f 100644 --- a/components/layout/pageLayout/SubNavbar.tsx +++ b/components/layout/pageLayout/SubNavbar.tsx @@ -11,7 +11,7 @@ const ITEM_HEIGHT = 33; const INDENTATION = 16; export default function SubNavbar({ currentTab }: { currentTab: SegmentNode }) { - const t = useTranslations('Nav'); + const t = useTranslations('Page'); const rootTab = getRootTab(currentTab); const subTabs = getAllSubTabs(rootTab).filter((tab) => !tab.hideInSubNav?.(currentTab)); @@ -26,7 +26,9 @@ export default function SubNavbar({ currentTab }: { currentTab: SegmentNode }) {
      -

      {t(rootTab.name)}

      +

      + {t(`${rootTab.engName}.title`)} +

        {subTabs.map((tab) => ( @@ -44,7 +46,7 @@ export default function SubNavbar({ currentTab }: { currentTab: SegmentNode }) { } function SubTab({ tab, isCurrent }: { tab: SegmentNode; isCurrent: boolean }) { - const t = useTranslations('Nav'); + const t = useTranslations('Page'); const marginLeft = `${(getDepth(tab) - 1) * INDENTATION}px`; return ( @@ -56,10 +58,10 @@ function SubTab({ tab, isCurrent }: { tab: SegmentNode; isCurrent: boolean }) { > {tab.isPage ? ( - + ) : ( - {t(tab.name)} + {t(`${tab.engName}.title`)} )} ); diff --git a/constants/segmentNode.ts b/constants/segmentNode.ts index 9319fc8d..805652c9 100644 --- a/constants/segmentNode.ts +++ b/constants/segmentNode.ts @@ -1,5 +1,6 @@ export interface SegmentNode { name: string; + engName: string; segment: string; isPage: boolean; children: SegmentNode[]; @@ -10,6 +11,7 @@ export interface SegmentNode { export const main: SegmentNode = { name: '홈', + engName: 'home', segment: '', isPage: true, parent: null, @@ -18,6 +20,7 @@ export const main: SegmentNode = { export const about: SegmentNode = { name: '소개', + engName: 'about', segment: 'about', isPage: true, parent: main, @@ -26,6 +29,7 @@ export const about: SegmentNode = { export const overview: SegmentNode = { name: '학부 소개', + engName: 'departmentOverview', segment: 'overview', isPage: true, parent: about, @@ -34,6 +38,7 @@ export const overview: SegmentNode = { export const greetings: SegmentNode = { name: '학부장 인사말', + engName: 'greetingsFromTheHead', segment: 'greetings', isPage: true, parent: about, @@ -42,6 +47,7 @@ export const greetings: SegmentNode = { export const history: SegmentNode = { name: '연혁', + engName: 'history', segment: 'history', isPage: true, parent: about, @@ -50,6 +56,7 @@ export const history: SegmentNode = { export const futureCareers: SegmentNode = { name: '졸업생 진로', + engName: 'futureCareers', segment: 'future-careers', isPage: true, parent: about, @@ -58,6 +65,7 @@ export const futureCareers: SegmentNode = { export const studentClubs: SegmentNode = { name: '동아리 소개', + engName: 'studentClubs', segment: 'student-clubs', isPage: true, parent: about, @@ -66,6 +74,7 @@ export const studentClubs: SegmentNode = { export const facilities: SegmentNode = { name: '시설 안내', + engName: 'facilities', segment: 'facilities', isPage: true, parent: about, @@ -74,6 +83,7 @@ export const facilities: SegmentNode = { export const contact: SegmentNode = { name: '연락처', + engName: 'contactUs', segment: 'contact', isPage: true, parent: about, @@ -82,14 +92,15 @@ export const contact: SegmentNode = { export const directions: SegmentNode = { name: '찾아오는 길', + engName: 'directions', segment: 'directions', isPage: true, parent: about, children: [], }; - export const community: SegmentNode = { name: '소식', + engName: 'community', segment: 'community', isPage: true, parent: main, @@ -98,6 +109,7 @@ export const community: SegmentNode = { export const notice: SegmentNode = { name: '공지사항', + engName: 'notice', segment: 'notice', isPage: true, parent: community, @@ -106,6 +118,7 @@ export const notice: SegmentNode = { export const news: SegmentNode = { name: '새 소식', + engName: 'news', segment: 'news', isPage: true, parent: community, @@ -114,6 +127,7 @@ export const news: SegmentNode = { export const seminar: SegmentNode = { name: '세미나', + engName: 'seminars', segment: 'seminar', isPage: true, parent: community, @@ -122,6 +136,7 @@ export const seminar: SegmentNode = { export const facultyRecruitment: SegmentNode = { name: '신임교수초빙', + engName: 'facultyRecruitment', segment: 'faculty-recruitment', isPage: true, parent: community, @@ -130,6 +145,7 @@ export const facultyRecruitment: SegmentNode = { export const council: SegmentNode = { name: '학생회', + engName: 'studentCouncil', segment: 'council', isPage: true, parent: community, @@ -138,6 +154,7 @@ export const council: SegmentNode = { export const councilIntro: SegmentNode = { name: '학생회 소개', + engName: 'introduction', segment: 'intro', isPage: true, parent: council, @@ -148,6 +165,7 @@ export const councilIntro: SegmentNode = { export const councilMinute: SegmentNode = { name: '학생회 회의록', + engName: 'minutes', segment: 'meeting-minute', isPage: true, parent: council, @@ -158,6 +176,7 @@ export const councilMinute: SegmentNode = { export const councilBylaws: SegmentNode = { name: '학생회칙 및 세칙', + engName: 'constitutionAndBylaws', segment: 'rules', isPage: true, parent: council, @@ -168,6 +187,7 @@ export const councilBylaws: SegmentNode = { export const councilReportList: SegmentNode = { name: '활동 보고', + engName: 'report', segment: 'report', isPage: true, parent: council, @@ -178,6 +198,7 @@ export const councilReportList: SegmentNode = { export const people: SegmentNode = { name: '구성원', + engName: 'people', segment: 'people', isPage: true, parent: main, @@ -186,6 +207,7 @@ export const people: SegmentNode = { export const faculty: SegmentNode = { name: '교수진', + engName: 'faculty', segment: 'faculty', isPage: true, parent: people, @@ -194,6 +216,7 @@ export const faculty: SegmentNode = { export const emeritusFaculty: SegmentNode = { name: '역대 교수진', + engName: 'emeritusFaculty', segment: 'emeritus-faculty', isPage: true, parent: people, @@ -202,6 +225,7 @@ export const emeritusFaculty: SegmentNode = { export const staff: SegmentNode = { name: '행정직원', + engName: 'staff', segment: 'staff', isPage: true, parent: people, @@ -210,6 +234,7 @@ export const staff: SegmentNode = { export const research: SegmentNode = { name: '연구·교육', + engName: 'research', segment: 'research', isPage: true, parent: main, @@ -218,6 +243,7 @@ export const research: SegmentNode = { export const researchGroups: SegmentNode = { name: '연구·교육 스트림', + engName: 'streams', segment: 'groups', isPage: true, parent: research, @@ -226,6 +252,7 @@ export const researchGroups: SegmentNode = { export const researchCenters: SegmentNode = { name: '연구 센터', + engName: 'centers', segment: 'centers', isPage: true, parent: research, @@ -234,6 +261,7 @@ export const researchCenters: SegmentNode = { export const researchLabs: SegmentNode = { name: '연구실 목록', + engName: 'laboratories', segment: 'labs', isPage: true, parent: research, @@ -242,6 +270,7 @@ export const researchLabs: SegmentNode = { export const topConferenceList: SegmentNode = { name: 'Top Conference List', + engName: 'topConferenceList', segment: 'top-conference-list', isPage: true, parent: research, @@ -250,6 +279,7 @@ export const topConferenceList: SegmentNode = { export const admissions: SegmentNode = { name: '입학', + engName: 'admissions', segment: 'admissions', isPage: true, parent: main, @@ -258,6 +288,7 @@ export const admissions: SegmentNode = { export const undergraduateAdmission: SegmentNode = { name: '학부', + engName: 'undergraduate', segment: 'undergraduate', isPage: false, parent: admissions, @@ -266,6 +297,7 @@ export const undergraduateAdmission: SegmentNode = { export const undergraduateEarlyAdmission: SegmentNode = { name: '수시 모집', + engName: 'earlyAdmission', segment: 'early-admission', isPage: true, parent: undergraduateAdmission, @@ -274,6 +306,7 @@ export const undergraduateEarlyAdmission: SegmentNode = { export const undergraduateRegularAdmission: SegmentNode = { name: '정시 모집', + engName: 'regularAdmission', segment: 'regular-admission', isPage: true, parent: undergraduateAdmission, @@ -282,6 +315,7 @@ export const undergraduateRegularAdmission: SegmentNode = { export const graduateAdmission: SegmentNode = { name: '대학원', + engName: 'graduate', segment: 'graduate', isPage: false, parent: admissions, @@ -290,6 +324,7 @@ export const graduateAdmission: SegmentNode = { export const graduateRegularAdmission: SegmentNode = { name: '전기/후기 모집', + engName: 'gradRegularAdmission', segment: 'regular-admission', isPage: true, parent: graduateAdmission, @@ -298,6 +333,7 @@ export const graduateRegularAdmission: SegmentNode = { export const internationalAdmission: SegmentNode = { name: 'International', + engName: 'international', segment: 'international', isPage: false, parent: admissions, @@ -306,6 +342,7 @@ export const internationalAdmission: SegmentNode = { export const internationalUndergraduateAdmission: SegmentNode = { name: 'Undergraduate', + engName: 'internationalUndergraduate', segment: 'undergraduate', isPage: true, parent: internationalAdmission, @@ -314,6 +351,7 @@ export const internationalUndergraduateAdmission: SegmentNode = { export const internationalGraduateAdmission: SegmentNode = { name: 'Graduate', + engName: 'internationalGraduate', segment: 'graduate', isPage: true, parent: internationalAdmission, @@ -322,6 +360,7 @@ export const internationalGraduateAdmission: SegmentNode = { export const exchangeVisitingProgram: SegmentNode = { name: 'Exchange/Visiting Program', + engName: 'exchangeVisitingProgram', segment: 'exchange', isPage: true, parent: internationalAdmission, @@ -330,6 +369,7 @@ export const exchangeVisitingProgram: SegmentNode = { export const internationalScholarships: SegmentNode = { name: 'Scholarships', + engName: 'scholarships', segment: 'scholarships', isPage: true, parent: internationalAdmission, @@ -338,6 +378,7 @@ export const internationalScholarships: SegmentNode = { export const academics: SegmentNode = { name: '학사 및 교과', + engName: 'academics', segment: 'academics', isPage: true, parent: main, @@ -346,6 +387,7 @@ export const academics: SegmentNode = { export const undergraduateAcademics: SegmentNode = { name: '학부', + engName: 'undergraduate', segment: 'undergraduate', isPage: false, parent: academics, @@ -354,6 +396,7 @@ export const undergraduateAcademics: SegmentNode = { export const undergraduateGuide: SegmentNode = { name: '학부 안내', + engName: 'undergraduateGuide', segment: 'guide', isPage: true, parent: undergraduateAcademics, @@ -362,6 +405,7 @@ export const undergraduateGuide: SegmentNode = { export const undergraduateCourses: SegmentNode = { name: '교과과정', + engName: 'courses', segment: 'courses', isPage: true, parent: undergraduateAcademics, @@ -370,6 +414,7 @@ export const undergraduateCourses: SegmentNode = { export const curriculum: SegmentNode = { name: '전공 이수 표준 형태', + engName: 'curriculum', segment: 'curriculum', isPage: true, parent: undergraduateAcademics, @@ -378,6 +423,7 @@ export const curriculum: SegmentNode = { export const generalStudies: SegmentNode = { name: '필수 교양 과목', + engName: 'generalStudiesRequirements', segment: 'general-studies-requirements', isPage: true, parent: undergraduateAcademics, @@ -386,6 +432,7 @@ export const generalStudies: SegmentNode = { export const degree: SegmentNode = { name: '졸업 규정', + engName: 'degreeRequirements', segment: 'degree-requirements', isPage: true, parent: undergraduateAcademics, @@ -394,6 +441,7 @@ export const degree: SegmentNode = { export const undergraduateCourseChanges: SegmentNode = { name: '교과목 변경 내역', + engName: 'courseChanges', segment: 'course-changes', isPage: true, parent: undergraduateAcademics, @@ -402,6 +450,7 @@ export const undergraduateCourseChanges: SegmentNode = { export const undergraduateScholarship: SegmentNode = { name: '장학 제도', + engName: 'scholarship', segment: 'scholarship', isPage: true, parent: undergraduateAcademics, @@ -410,6 +459,7 @@ export const undergraduateScholarship: SegmentNode = { export const graduateAcademics: SegmentNode = { name: '대학원', + engName: 'graduate', segment: 'graduate', isPage: false, parent: academics, @@ -418,6 +468,7 @@ export const graduateAcademics: SegmentNode = { export const graduateGuide: SegmentNode = { name: '대학원 안내', + engName: 'graduateGuide', segment: 'guide', isPage: true, parent: graduateAcademics, @@ -426,6 +477,7 @@ export const graduateGuide: SegmentNode = { export const graduateCourses: SegmentNode = { name: '교과과정', + engName: 'courses', segment: 'courses', isPage: true, parent: graduateAcademics, @@ -434,6 +486,7 @@ export const graduateCourses: SegmentNode = { export const graduateCourseChanges: SegmentNode = { name: '교과목 변경 내역', + engName: 'courseChanges', segment: 'course-changes', isPage: true, parent: graduateAcademics, @@ -442,6 +495,7 @@ export const graduateCourseChanges: SegmentNode = { export const graduateScholarship: SegmentNode = { name: '장학 제도', + engName: 'scholarship', segment: 'scholarship', isPage: true, parent: graduateAcademics, @@ -450,6 +504,7 @@ export const graduateScholarship: SegmentNode = { export const reservations: SegmentNode = { name: '시설 예약', + engName: 'reservations', segment: 'reservations', isPage: true, parent: main, @@ -458,6 +513,7 @@ export const reservations: SegmentNode = { export const reservationIntroduction: SegmentNode = { name: '시설 예약 안내', + engName: 'reservationIntro', segment: 'introduction', isPage: true, parent: reservations, @@ -466,6 +522,7 @@ export const reservationIntroduction: SegmentNode = { export const seminarRoom: SegmentNode = { name: '세미나실 예약', + engName: 'seminarRooms', segment: 'seminar-room', isPage: false, parent: reservations, @@ -474,6 +531,7 @@ export const seminarRoom: SegmentNode = { export const bldg301room417: SegmentNode = { name: '301-417 (20석)', + engName: '301_417', segment: '301-417', isPage: true, parent: seminarRoom, @@ -482,6 +540,7 @@ export const bldg301room417: SegmentNode = { export const bldg301Maldives: SegmentNode = { name: '301-MALDIVES (301-521, 11석)', + engName: '301_maldives', segment: '301-521', isPage: true, parent: seminarRoom, @@ -490,6 +549,7 @@ export const bldg301Maldives: SegmentNode = { export const bldg301Hawaii: SegmentNode = { name: '301-HAWAII (301-551-4, 20석)', + engName: '301_hawaii', segment: '301-551-4', isPage: true, parent: seminarRoom, @@ -498,6 +558,7 @@ export const bldg301Hawaii: SegmentNode = { export const bldg301Baekdu: SegmentNode = { name: '301-BAEKDU (301-552-1, 4석)', + engName: '301_baekdu', segment: '301-552-1', isPage: true, parent: seminarRoom, @@ -506,6 +567,7 @@ export const bldg301Baekdu: SegmentNode = { export const bldg301Alps: SegmentNode = { name: '301-ALPS (301-552-2, 5석)', + engName: '301_alps', segment: '301-552-2', isPage: true, parent: seminarRoom, @@ -514,6 +576,7 @@ export const bldg301Alps: SegmentNode = { export const bldg301Santorini: SegmentNode = { name: '301-SANTORINI (301-552-3, 4석)', + engName: '301_santorini', segment: '301-552-3', isPage: true, parent: seminarRoom, @@ -522,6 +585,7 @@ export const bldg301Santorini: SegmentNode = { export const bldg301Jeju: SegmentNode = { name: '301-JEJU (301-553-6, 6석)', + engName: '301_jeju', segment: '301-553-6', isPage: true, parent: seminarRoom, @@ -530,6 +594,7 @@ export const bldg301Jeju: SegmentNode = { export const bldg301ProfMeeting: SegmentNode = { name: '301-교수회의실 (301-317, 20석)', + engName: '301_facultyRoom', segment: '301-317', isPage: true, parent: seminarRoom, @@ -538,6 +603,7 @@ export const bldg301ProfMeeting: SegmentNode = { export const bldg302room308: SegmentNode = { name: '302-308 (46석)', + engName: '302_308', segment: '302-308', isPage: true, parent: seminarRoom, @@ -546,6 +612,7 @@ export const bldg302room308: SegmentNode = { export const bldg302room309first: SegmentNode = { name: '302-309-1 (48석)', + engName: '302_309_1', segment: '302-309-1', isPage: true, parent: seminarRoom, @@ -554,6 +621,7 @@ export const bldg302room309first: SegmentNode = { export const bldg302room309second: SegmentNode = { name: '302-309-2 (8석)', + engName: '302_309_2', segment: '302-309-2', isPage: true, parent: seminarRoom, @@ -562,6 +630,7 @@ export const bldg302room309second: SegmentNode = { export const bldg302room309third: SegmentNode = { name: '302-309-3 (8석)', + engName: '302_309_3', segment: '302-309-3', isPage: true, parent: seminarRoom, @@ -570,6 +639,7 @@ export const bldg302room309third: SegmentNode = { export const labRoom: SegmentNode = { name: '실습실 예약', + engName: 'labs', segment: 'lab', isPage: false, parent: reservations, @@ -578,6 +648,7 @@ export const labRoom: SegmentNode = { export const softwareLab: SegmentNode = { name: '소프트웨어 실습실 (302-311-1, 102석)', + engName: 'softwareLab', segment: '302-311-1', isPage: true, parent: labRoom, @@ -586,6 +657,7 @@ export const softwareLab: SegmentNode = { export const hardwareLab: SegmentNode = { name: '하드웨어 실습실 (302-310-2, 30석)', + engName: 'hardwareLab', segment: '302-310-2', isPage: true, parent: labRoom, @@ -594,6 +666,7 @@ export const hardwareLab: SegmentNode = { export const lectureRoom: SegmentNode = { name: '공과대학 강의실 예약', + engName: 'lectureRooms', segment: 'lecture-room', isPage: false, parent: reservations, @@ -602,6 +675,7 @@ export const lectureRoom: SegmentNode = { export const bldg302room208: SegmentNode = { name: '302-208 (116석)', + engName: '302_208', segment: '302-208', isPage: true, parent: lectureRoom, @@ -610,6 +684,7 @@ export const bldg302room208: SegmentNode = { export const bldg302room209: SegmentNode = { name: '302-209 (90석)', + engName: '302_209', segment: '302-209', isPage: true, parent: lectureRoom, @@ -680,6 +755,7 @@ lectureRoom.children = [bldg302room208, bldg302room209]; export const admin: SegmentNode = { name: '관련 페이지', // 관리자 페이지 사이드바는 상위 항목 이름이 '관련 페이지' + engName: 'admin', segment: 'admin', isPage: true, parent: null, @@ -690,14 +766,25 @@ export const admin: SegmentNode = { export const tentenProject: SegmentNode = { name: '10-10 Project', + engName: 'tenTenProject', segment: '10-10-project', isPage: true, parent: null, children: [], }; +export const tentenProposal: SegmentNode = { + name: 'Proposal', + engName: 'proposal', + segment: 'proposal', + isPage: true, + parent: tentenProject, + children: [], +}; + export const tentenManager: SegmentNode = { name: 'Manager', + engName: 'manager', segment: 'manager', isPage: true, parent: tentenProject, @@ -706,19 +793,12 @@ export const tentenManager: SegmentNode = { export const tentenParticipants: SegmentNode = { name: 'Participants(Professors)', + engName: 'participants', segment: 'participants', isPage: true, parent: tentenProject, children: [], }; -export const tentenProposal: SegmentNode = { - name: 'Proposal', - segment: 'proposal', - isPage: true, - parent: tentenProject, - children: [], -}; - -// 기존 홈페이지 푸터 링크가 propsal로 이동시는 등 proposal 내용이 우선순위라 판단되어 기존과 다르게 0번째로 배치 +// 기존 홈페이지 푸터 링크가 propsal로 이동하는 등 proposal 내용이 우선순위라 판단되어 기존과 다르게 0번째로 배치 tentenProject.children = [tentenProposal, tentenManager, tentenParticipants]; diff --git a/constants/tag.ts b/constants/tag.ts index a7661f2d..e13f468f 100644 --- a/constants/tag.ts +++ b/constants/tag.ts @@ -1,30 +1,43 @@ -export const NOTICE_TAGS = [ - '수업', - '장학', - '학사(학부)', - '학사(대학원)', - '다전공/전과', - '등록/복학/휴학/재입학', - '입학', - '졸업', - '채용정보', - '교환학생/유학', - '외부행사/프로그램', - '내부행사/프로그램', - 'international', +interface TagIdTransKeyPair { + id: string; // 태그 식별자 + transKey: string; // 태그 표시 텍스트 +} + +// messages/ko.json, messages/en.json에 있는 번역키와 일치해야 함 +export const NOTICE_TAGS: TagIdTransKeyPair[] = [ + { id: '수업', transKey: 'lecture' }, + { id: '장학', transKey: 'scholarship' }, + { id: '학사(학부)', transKey: 'undergraduate' }, + { id: '학사(대학원)', transKey: 'graduate' }, + { id: '다전공/전과', transKey: 'curriculumChange' }, + { id: '등록/복학/휴학/재입학', transKey: 'enrollmentStatus' }, + { id: '입학', transKey: 'admission' }, + { id: '졸업', transKey: 'graduation' }, + { id: '채용정보', transKey: 'recruitment' }, + { id: '교환학생/유학', transKey: 'exchange' }, + { id: '외부행사/프로그램', transKey: 'externalPrograms' }, + { id: '내부행사/프로그램', transKey: 'internalPrograms' }, + { id: 'international', transKey: 'international' }, ]; -export const NEWS_TAGS = [ - '행사', - '연구', - '수상', - '채용', - '칼럼', - '강연', - '교육', - '인터뷰', - '진로', - '과거 미분류', +export const NEWS_TAGS: TagIdTransKeyPair[] = [ + { id: '행사', transKey: 'event' }, + { id: '연구', transKey: 'research' }, + { id: '수상', transKey: 'award' }, + { id: '채용', transKey: 'recruitment' }, + { id: '칼럼', transKey: 'column' }, + { id: '강연', transKey: 'lecture' }, + { id: '교육', transKey: 'education' }, + { id: '인터뷰', transKey: 'interview' }, + { id: '진로', transKey: 'career' }, + { id: '과거 미분류', transKey: 'archive' }, ]; -export const SEARCH_TAGS = ['소개', '소식', '구성원', '연구', '입학', '학사 및 교과']; +export const SEARCH_TAGS: TagIdTransKeyPair[] = [ + { id: '소개', transKey: 'about' }, + { id: '소식', transKey: 'community' }, + { id: '구성원', transKey: 'people' }, + { id: '연구', transKey: 'research' }, + { id: '입학', transKey: 'admission' }, + { id: '학사 및 교과', transKey: 'academics' }, +]; diff --git a/messages/en.json b/messages/en.json index 82ccb568..29fbdc80 100644 --- a/messages/en.json +++ b/messages/en.json @@ -1,253 +1,269 @@ { - "Title": { - "서울대학교 컴퓨터공학부": "Dept. of Computer Science and Engineering, SNU" + "common": { + "title": "Dept. of Computer Science and Engineering, SNU", + "tag": "Tags", + "search": "Search", + "resetTags": "reset tags", + "list": "List", + "next": "Next", + "previous": "Prev", + "all": "all", + "createdDate": "Created", + "author": "Author" }, - "Nav": { - "홈": "Home", - - "소개": "About", - "소식": "Community", - "구성원": "People", - "연구·교육": "Research & Edu", - "입학": "Admissions", - "학사 및 교과": "Academics", - "시설 예약": "Reservations", - - "학부 소개": "Department Overview", - "학부장 인사말": "Greetings from the Head", - "연혁": "History", - "졸업생 진로": "Future Careers", - "동아리 소개": "Student Clubs", - "시설 안내": "Facilities", - "연락처": "Contact Us", - "찾아오는 길": "Directions", - - "공지사항": "Notice", - "새 소식": "News", - "세미나": "Seminars", - "신임교수초빙": "Faculty Recruitment", - "학생회": "Student Council", - "학생회 소개": "Introduction", - "학생회 회의록": "Minutes", - "학생회칙 및 세칙": "Constitution & Bylaws", - "활동 보고": "Report", - - "교수진": "Faculty", - "역대 교수진": "Emeritus Faculty", - "행정직원": "Staff", - - "연구·교육 스트림": "Research & Edu Streams", - "연구 센터": "Research Centers", - "연구실 목록": "Laboratories", - "Top Conference List": "Top Conference List", - - "학부": "Undergraduate", - "수시 모집": "Early Admission", - "정시 모집": "Regular Admission", - "대학원": "Graduate", - "전기/후기 모집": "Regular Admission", - "International": "International", - "Graduate": "Graduate", - "Undergraduate": "Undergraduate", - "Exchange/Visiting Program": "Exchange/Visiting Program", - "Scholarships": "Scholarships", - - "학부 안내": "Guide", - "교과과정": "Courses", - "전공 이수 표준 형태": "Curriculum", - "필수 교양 과목": "General Studies Requirements", - "졸업 규정": "Degree Requirements", - "교과목 변경 내역": "Course Changes", - "장학 제도": "Scholarship", - "대학원 안내": "Guide", - - "세미나실 예약": "Seminar Rooms", - "실습실 예약": "Laboratories", - "공과대학 강의실 예약": "Lecture Rooms", - "시설 예약 안내": "Introduction", - "301-417 (20석)": "301-417 (20 seats)", - "301-MALDIVES (301-521, 11석)": "301-MALDIVES (301-521, 11seats)", - "301-HAWAII (301-551-4, 20석)": "301-HAWAII (301-551-4, 20seats)", - "301-BAEKDU (301-552-1, 4석)": "301-BAEKDU (301-552-1, 4seats)", - "301-ALPS (301-552-2, 5석)": "301-ALPS (301-552-2, 5seats)", - "301-SANTORINI (301-552-3, 4석)": "301-SANTORINI (301-552-3, 4seats)", - "301-JEJU (301-553-6, 6석)": "301-JEJU (301-553-6, 6seats)", - "301-교수회의실 (301-317, 20석)": "301-faculty conference room (301-317, 20seats)", - "302-308 (46석)": "302-308 (46seats)", - "302-309-1 (48석)": "302-309-1 (48seats)", - "302-309-2 (8석)": "302-309-2 (8seats)", - "302-309-3 (8석)": "302-309-3 (8seats)", - "소프트웨어 실습실 (302-311-1, 102석)": "Software lab (302-311-1, 102seats)", - "하드웨어 실습실 (302-310-2, 30석)": "Hardware lab (302-310-2, 30seats)", - "302-208 (116석)": "302-208 (116seats)", - "302-209 (90석)": "302-209 (90seats)", - - "10-10 Project": "10-10 Project", - "Manager": "Manager", - "Participants(Professors)": "Participants(Professors)", - "Proposal": "Proposal", - - "관련 페이지": "관련 페이지", - - "검색": "Keyword", - "통합 검색": "Search", - "결과 더보기": "more results", - "전체": "Total", - - "더보기": "More", - "바로가기": "Shortcuts" + "Header": { + "login": "Login", + "logout": "Logout" }, - "Header": { - "로그인": "Login", - "로그아웃": "Logout" + "Page": { + "home": { + "title": "Home", + "viewMore": "More", + "shortcuts": "Shortcuts", + "all": "all", + "undergraduate": { + "cap": "undergraduate", + "lower": "undergraduate" + }, + "graduate": "graduate", + "scholarship": "scholarship" + }, + + "about": { "title": "About" }, + "departmentOverview": { + "title": "Department Overview", + "brochure": "CSE Brochure" + }, + "greetingsFromTheHead": { "title": "Greetings from the Head" }, + "history": { "title": "History" }, + "futureCareers": { + "title": "Future Careers", + "bachelor": "Bachelor", + "master": "Master", + "doctor": "Doctor", + "graduateCareerStatus": "Career Status", + "graduateStartups": "Startups", + "startupName": "Name", + "startupWebsite": "Website", + "startupYear": "Year Founded" + }, + "studentClubs": { "title": "Student Clubs" }, + "facilities": { "title": "Facilities" }, + "contactUs": { "title": "Contact Us" }, + "directions": { + "title": "Directions", + "description": "The Department is located in Building 301, SNU Gwanak Campus.", + "address": "Address", + "addressValue": "(08826) Seoul National University, College of Engineering, Dept. of Computer Science and Engineering, Building 301, Room 316", + "contact": "Telephone" + }, + + "community": { "title": "Community" }, + "notice": { + "title": "Notice", + "list": { + "title": "Title", + "date": "Date" + }, + "tag": { + "lecture": "lecture", + "scholarship": "scholarship", + "undergraduate": "bachelor(undergraduate)", + "graduate": "bachelor(graduate)", + "curriculumChange": "double major/transfer", + "enrollmentStatus": "registration/reinstatement/leave of absence/re-enrollment", + "admission": "admission", + "graduation": "graduation", + "recruitment": "recruitment", + "exchange": "exchange students/study abroad", + "externalPrograms": "external events/programs", + "internalPrograms": "internal events/programs", + "international": "international" + } + }, + "news": { + "title": "News", + "tag": { + "event": "event", + "research": "research", + "award": "award", + "recruitment": "recruitment", + "column": "column", + "lecture": "lecture", + "education": "education", + "interview": "interview", + "career": "career", + "archive": "archive" + } + }, + "seminars": { + "title": "Seminars", + "name": "Name", + "affiliation": "Affiliation", + "host": "Host", + "date": "Date", + "location": "Location", + "summary": "Summary", + "speakerInformation": "Speaker Introduction" + }, + "facultyRecruitment": { "title": "Faculty Recruitment" }, + "studentCouncil": { "title": "Student Council" }, + "introduction": { "title": "Introduction" }, + "minutes": { "title": "Minutes" }, + "constitutionAndBylaws": { "title": "Constitution & Bylaws" }, + "report": { "title": "Report" }, + + "people": { "title": "People" }, + "faculty": { + "title": "Faculty", + "education": "Education", + "researchAreas": "Research Areas", + "career": "Career" + }, + "emeritusFaculty": { + "title": "Emeritus Faculty", + "contact": "Contact", + "office": "Office", + "email": "Email", + "website": "Website", + "education": "Education", + "researchAreas": "Research Areas", + "employmentPeriod": "Employment Period" + }, + "staff": { + "title": "Staff", + "contact": "Contact", + "location": "Location", + "phone": "Telephone", + "email": "Email", + "mainTasks": "Main Tasks" + }, + + "research": { "title": "Research & Edu" }, + "streams": { + "title": "Research & Education Streams", + "stream": "Stream", + "labs": "Laboratories" + }, + "centers": { "title": "Research Centers" }, + "laboratories": { + "title": "Laboratories", + "name": "Name", + "supervisor": "Supervisor", + "location": "Location", + "phone": "Telephone", + "acronym": "Acronym", + "information": "Information" + }, + "topConferenceList": { + "title": "Top Conference List", + "subtitle": "Dept. of Computer Science and Engineering, SNU", + "number": "No.", + "abbreviation": "Abbreviation", + "conferenceName": "Title", + "disclaimerNotice": "This list is subject to change over time and depending on circumstances.", + "lastUpdated": "Last Updated", + "createdDate": "Created", + "author": "Author" + }, + + "admissions": { "title": "Admissions" }, + "undergraduate": { "title": "Undergraduate" }, + "earlyAdmission": { "title": "Early Admission" }, + "regularAdmission": { "title": "Regular Admission" }, + "graduate": { "title": "Graduate" }, + "gradRegularAdmission": { "title": "Regular Admission" }, + "international": { "title": "International" }, + "internationalUndergraduate": { "title": "Undergraduate" }, + "internationalGraduate": { "title": "Graduate" }, + "exchangeVisitingProgram": { "title": "Exchange/Visiting Program" }, + "scholarships": { "title": "Scholarships" }, + + "academics": { "title": "Academics" }, + "undergraduateGuide": { "title": "Guide" }, + "courses": { + "title": "Courses", + "courseInformation": "Course Information", + "cardView": "cards", + "listView": "list", + "name": "Title", + "classification": "Classification", + "code": "Code", + "credit": "{credit, plural, =0 {Credit} =1 {1 credit} other {# credits}}", + "grade": "{grade, plural, =0 {graduate} =1 {freshman} =2 {sophomore} =3 {junior} =4 {senior} =5 {Grade} other {#th year}}", + "graduate": "Graduate", + "undergraduate": "Undergraduate" + }, + "curriculum": { "title": "Curriculum" }, + "generalStudiesRequirements": { "title": "General Studies Requirements" }, + "degreeRequirements": { "title": "Degree Requirements" }, + "courseChanges": { "title": "Course Changes" }, + "scholarship": { "title": "Scholarship" }, + "graduateGuide": { "title": "Guide" }, + + "reservations": { "title": "Reservations" }, + "reservationIntro": { "title": "Introduction" }, + "seminarRooms": { "title": "Seminar Rooms" }, + "labs": { "title": "Laboratories" }, + "lectureRooms": { "title": "Lecture Rooms" }, + "301_417": { "title": "301-417 (20 seats)" }, + "301_maldives": { "title": "301-MALDIVES (301-521, 11seats)" }, + "301_hawaii": { "title": "301-HAWAII (301-551-4, 20seats)" }, + "301_baekdu": { "title": "301-BAEKDU (301-552-1, 4seats)" }, + "301_alps": { "title": "301-ALPS (301-552-2, 5seats)" }, + "301_santorini": { "title": "301-SANTORINI (301-552-3, 4seats)" }, + "301_jeju": { "title": "301-JEJU (301-553-6, 6seats)" }, + "301_facultyRoom": { "title": "301-faculty conference room (301-317, 20seats)" }, + "302_308": { "title": "302-308 (46seats)" }, + "302_309_1": { "title": "302-309-1 (48seats)" }, + "302_309_2": { "title": "302-309-2 (8seats)" }, + "302_309_3": { "title": "302-309-3 (8seats)" }, + "softwareLab": { "title": "Software lab (302-311-1, 102seats)" }, + "hardwareLab": { "title": "Hardware lab (302-310-2, 30seats)" }, + "302_208": { "title": "302-208 (116seats)" }, + "302_209": { "title": "302-209 (90seats)" }, + + "search": { + "title": "Search", + "noResults": "No search results found", + "searchResults": "{count} search results", + "viewMore": "more results", + "tag": { + "about": "about", + "community": "community", + "people": "people", + "research": "research", + "admission": "admission", + "academics": "academics" + } + } }, "Footer": { - "About": "About", - "학부 소개": "Department Overview", - "교수진": "Faculty", - "학부 안내": "Undergraduate Guide", - "대학원 안내": "Graduate Guide", - - "Resources": "Resources", - "공지사항": "Notice", - "세미나": "Seminars", - "시설 예약 안내": "Reservation", - - "Research": "Research", - "신임교수초빙": "Faculty Recruitment", - "연구실 목록": "Laboratories", - "Top Conference List": "Top Conference List", - "10-10 Project": "10-10 Project", - - "More": "More", - "연합전공 인공지능(학사)": "Interdisciplinary Major in Artificial Intelligence", - "지능형컴퓨팅사업단": "BK21 FOUR Intelligence Computing", - "컴퓨터 연구소": "Institute of Computer Technology", - "해동학술정보실": "HAEDONG Digital Library", - - "개인정보처리방침": "Privacy Policy", - "학부 연락처": "Contact Us", - "찾아오시는 길": "Directions", + "departmentOverview": "Department Overview", + "faculty": "Faculty", + "undergraduateGuide": "Undergraduate Guide", + "graduateGuide": "Graduate Guide", + + "notice": "Notice", + "seminars": "Seminars", + "reservationIntro": "Reservation", + + "facultyRecruitment": "Faculty Recruitment", + "laboratories": "Laboratories", + "topConferenceList": "Top Conference List", + "proposal": "10-10 Project", + + "imai": "Interdisciplinary Major in Artificial Intelligence", + "bkcse": "BK21 FOUR Intelligence Computing", + "ict": "Institute of Computer Technology", + "haedong": "HAEDONG Digital Library", + + "privacyPolicy": "Privacy Policy", + "contactUs": "Contact Us", + "directions": "Directions", "address": "College of Engineering, Department of Computer Science and Engineering 301-316, 1 Gwanak-ro, Gwanak-gu, Seoul 08826 Republic of Korea" - }, - - "Tag": { - "태그": "Tags", - "전체": "all", - - "소개": "about", - "소식": "community", - "구성원": "people", - "학사 및 교과": "academics", - - "수업": "lecture", - "장학": "scholarship", - "학사(학부)": "bachelor(undergraduate)", - "학사(대학원)": "bachelor(graduate)", - "다전공/전과": "double major/transfer", - "등록/복학/휴학/재입학": "registration/reinstatement/leave of absence/re-enrollment", - "입학": "admission", - "졸업": "graduation", - "채용정보": "job information", - "교환학생/유학": "exchange students/study abroad", - "외부행사/프로그램": "external events/programs", - "내부행사/프로그램": "internal events/programs", - "international": "international", - - "행사": "event", - "연구": "research", - "수상": "award", - "채용": "job", - "칼럼": "column", - "강연": "lecture", - "교육": "education", - "인터뷰": "interview", - "진로": "career", - "과거 미분류": "past uncategorized", - - "학년": "grade", - "교과목 구분": "classification", - "학점": "credit", - "대학원": "graduate", - "학부": "undergraduate", - - "1학년": "freshman", - "2학년": "sophomore", - "3학년": "junior", - "4학년": "senior", - - "1학점": "1 credit", - "2학점": "2 credits", - "3학점": "3 credits", - "4학점": "4 credits" - }, - - "Content": { - "서울대학교 공과대학 컴퓨터공학부": "SNU CSE", - - "학부 소개 책자": "CSE Brochure", - "졸업생 진로 현황": "Graduate Career Status", - "졸업생 창업 기업": "Graduate Startups", - "창업 기업명": "Name", - "홈페이지": "Website", - "창업연도": "Year Founded", - - "교과목 로드맵 보기": "Show Roadmap", - "교과목 로드맵": "Course Roadmap", - "선수 교과목": "prerequisite", - "교과목 정보": "Course Information", - "카드형": "cards", - "목록형": "list", - "교과목명": "Title", - "교과목 구분": "Classification", - "교과목 번호": "Code", - "학점": "Credit", - "학년": "Grade", - - "스트림": "Stream", - "연구실": "Laboratories", - "지도교수": "Professors", - "연구실 위치": "Location", - "전화": "Telephone", - "약자": "Acronym", - "소개 자료": "Information", - "연번": "No.", - "약칭": "Abbreviation", - "학술대회 명칭": "Title", - - "본 리스트는 시간과 상황의 변동에 따라 바뀔 수 있습니다": "This list is subject to change over time and depending on circumstances", - - "수정 날짜": "Last Updated", - "작성 날짜": "Created", - "작성자": "Author", - - "주소": "Address", - "제목": "Title", - "날짜": "Date", - "검색": "Search", - "태그 초기화": "reset tags", - "목록": "List", - "다음글": "Next", - "이전글": "Prev", - - "이름": "Name", - "소속": "Affiliation", - "주최": "Host", - "위치": "Location", - "요약": "Summary", - "연사 소개": "Speaker Introduction", - - "학력": "Education", - "연구 분야": "Research Areas", - "경력": "Career", - "이메일": "Email", - "웹사이트": "Website", - "연락처": "Contact", - "재직 기간": "Employment Period", - "교수실": "Location", - "주요 업무": "Main Tasks" } } diff --git a/messages/ko.json b/messages/ko.json index dd8e2586..9ce5a88c 100644 --- a/messages/ko.json +++ b/messages/ko.json @@ -1,252 +1,270 @@ { - "Title": { - "서울대학교 컴퓨터공학부": "서울대학교 컴퓨터공학부" + "common": { + "title": "서울대학교 컴퓨터공학부", + "tag": "태그", + "search": "검색", + "resetTags": "태그 초기화", + "list": "목록", + "next": "다음글", + "previous": "이전글", + "all": "전체", + "createdDate": "작성 날짜", + "author": "작성자" }, - "Nav": { - "홈": "홈", - - "소개": "소개", - "소식": "소식", - "구성원": "구성원", - "연구·교육": "연구·교육", - "입학": "입학", - "학사 및 교과": "학사 및 교과", - "시설 예약": "시설 예약", - - "학부 소개": "학부 소개", - "학부장 인사말": "학부장 인사말", - "연혁": "연혁", - "졸업생 진로": "졸업생 진로", - "동아리 소개": "동아리 소개", - "시설 안내": "시설 안내", - "연락처": "연락처", - "찾아오는 길": "찾아오는 길", - - "공지사항": "공지사항", - "새 소식": "새 소식", - "세미나": "세미나", - "신임교수초빙": "신임교수초빙", - "학생회": "학생회", - "학생회 소개": "학생회 소개", - "학생회 회의록": "학생회 회의록", - "학생회칙 및 세칙": "학생회칙 및 세칙", - "활동 보고": "활동 보고", - - "교수진": "교수진", - "역대 교수진": "역대 교수진", - "행정직원": "행정직원", - - "연구·교육 스트림": "연구·교육 스트림", - "연구 센터": "연구 센터", - "연구실 목록": "연구실 목록", - "Top Conference List": "Top Conference List", - - "학부": "학부", - "수시 모집": "수시 모집", - "정시 모집": "정시 모집", - "대학원": "대학원", - "전기/후기 모집": "전기/후기 모집", - "International": "International", - "Graduate": "Graduate", - "Undergraduate": "Undergraduate", - "Exchange/Visiting Program": "Exchange/Visiting Program", - "Scholarships": "Scholarships", - - "학부 안내": "학부 안내", - "교과과정": "교과과정", - "전공 이수 표준 형태": "전공 이수 표준 형태", - "필수 교양 과목": "필수 교양 과목", - "졸업 규정": "졸업 규정", - "교과목 변경 내역": "교과목 변경 내역", - "장학 제도": "장학 제도", - "대학원 안내": "대학원 안내", - - "세미나실 예약": "세미나실 예약", - "실습실 예약": "실습실 예약", - "공과대학 강의실 예약": "공과대학 강의실 예약", - "시설 예약 안내": "시설 예약 안내", - "301-417 (20석)": "301-417 (20석)", - "301-MALDIVES (301-521, 11석)": "301-MALDIVES (301-521, 11석)", - "301-HAWAII (301-551-4, 20석)": "301-HAWAII (301-551-4, 20석)", - "301-BAEKDU (301-552-1, 4석)": "301-BAEKDU (301-552-1, 4석)", - "301-ALPS (301-552-2, 5석)": "301-ALPS (301-552-2, 5석)", - "301-SANTORINI (301-552-3, 4석)": "301-SANTORINI (301-552-3, 4석)", - "301-JEJU (301-553-6, 6석)": "301-JEJU (301-553-6, 6석)", - "301-교수회의실 (301-317, 20석)": "301-교수회의실 (301-317, 20석)", - "302-308 (46석)": "302-308 (46석)", - "302-309-1 (48석)": "302-309-1 (48석)", - "302-309-2 (8석)": "302-309-2 (8석)", - "302-309-3 (8석)": "302-309-3 (8석)", - "소프트웨어 실습실 (302-311-1, 102석)": "소프트웨어 실습실 (302-311-1, 102석)", - "하드웨어 실습실 (302-310-2, 30석)": "하드웨어 실습실 (302-310-2, 30석)", - "302-208 (116석)": "302-208 (116석)", - "302-209 (90석)": "302-209 (90석)", - - "10-10 Project": "10-10 Project", - "Manager": "Manager", - "Participants(Professors)": "Participants(Professors)", - "Proposal": "Proposal", - - "관련 페이지": "관련 페이지", - - "검색": "검색", - "통합 검색": "통합 검색", - "결과 더보기": "결과 더보기", - "전체": "전체", - - "더보기": "더보기", - "바로가기": "바로가기" + "Header": { + "login": "로그인", + "logout": "로그아웃" }, - "Header": { - "로그인": "로그인", - "로그아웃": "로그아웃" + "Page": { + "home": { + "title": "홈", + "viewMore": "더보기", + "shortcuts": "바로가기", + "all": "전체", + "undergraduate": { + "cap": "학부", + "lower": "학부" + }, + "graduate": "대학원", + "scholarship": "장학" + }, + + "about": { "title": "소개" }, + "departmentOverview": { + "title": "학부 소개", + "brochure": "학부 소개 책자" + }, + "greetingsFromTheHead": { "title": "학부장 인사말" }, + "history": { "title": "연혁" }, + "futureCareers": { + "title": "졸업생 진로", + "bachelor": "학사", + "master": "석사", + "doctor": "박사", + "graduateCareerStatus": "졸업생 진로 현황", + "graduateStartups": "졸업생 창업 기업", + "startupName": "창업 기업명", + "startupWebsite": "홈페이지", + "startupYear": "창업 연도" + }, + "studentClubs": { "title": "동아리 소개" }, + "facilities": { "title": "시설 안내" }, + "contactUs": { "title": "연락처" }, + "directions": { + "title": "찾아오는 길", + "description": "컴퓨터공학부는 서울대학교 관악 301동(신공학관1)에 있습니다.", + "address": "주소", + "addressValue": "(08826) 서울특별시 관악구 관악로 1 서울대학교 공과대학 컴퓨터공학부 행정실(301동 316호)", + "contact": "전화" + }, + + "community": { "title": "소식" }, + "notice": { + "title": "공지사항", + "list": { + "title": "제목", + "date": "날짜" + }, + "tag": { + "lecture": "수업", + "scholarship": "장학", + "undergraduate": "학사(학부)", + "graduate": "학사(대학원)", + "curriculumChange": "다전공/전과", + "enrollmentStatus": "등록/복학/휴학/재입학", + "admission": "입학", + "graduation": "졸업", + "recruitment": "채용정보", + "exchange": "교환학생/유학", + "externalPrograms": "외부행사/프로그램", + "internalPrograms": "내부행사/프로그램", + "international": "international" + } + }, + "news": { + "title": "새소식", + "tag": { + "event": "행사", + "research": "연구", + "award": "수상", + "recruitment": "채용", + "column": "칼럼", + "lecture": "강연", + "education": "교육", + "interview": "인터뷰", + "career": "진로", + "archive": "과거 미분류" + } + }, + "seminars": { + "title": "세미나", + "name": "이름", + "affiliation": "소속", + "host": "주최", + "date": "날짜", + "location": "위치", + "summary": "요약", + "speakerInformation": "연사 소개" + }, + "facultyRecruitment": { "title": "신임교수초빙" }, + "studentCouncil": { "title": "학생회" }, + "introduction": { "title": "학생회 소개" }, + "minutes": { "title": "학생회 회의록" }, + "constitutionAndBylaws": { "title": "학생회칙 및 세칙" }, + "report": { "title": "활동 보고" }, + + "people": { "title": "구성원" }, + "faculty": { + "title": "교수진", + "education": "학력", + "researchAreas": "연구 분야", + "career": "경력" + }, + "emeritusFaculty": { + "title": "역대 교수진", + "contact": "연락처", + "office": "교수실", + "email": "이메일", + "website": "웹사이트", + "education": "학력", + "researchAreas": "연구 분야", + "employmentPeriod": "재직 기간" + }, + "staff": { + "title": "행정직원", + "contact": "연락처", + "location": "위치", + "phone": "전화", + "email": "이메일", + "mainTasks": "주요 업무" + }, + + "research": { "title": "연구·교육" }, + "streams": { + "title": "연구·교육 스트림", + "stream": "스트림", + "labs": "연구실" + }, + "centers": { "title": "연구 센터" }, + "laboratories": { + "title": "연구실 목록", + "name": "연구실", + "supervisor": "지도교수", + "location": "연구실 위치", + "phone": "전화", + "acronym": "약자", + "information": "소개 자료" + }, + "topConferenceList": { + "title": "Top Conference List", + "subtitle": "서울대학교 공과대학 컴퓨터공학부", + "number": "연번", + "abbreviation": "약칭", + "conferenceName": "학술대회 명칭", + "disclaimerNotice": "본 리스트는 시간과 상황의 변동에 따라 바뀔 수 있습니다.", + "lastUpdated": "수정 날짜", + "createdDate": "작성 날짜", + "author": "작성자" + }, + + "admissions": { "title": "입학" }, + "undergraduate": { "title": "학부" }, + "earlyAdmission": { "title": "수시 모집" }, + "regularAdmission": { "title": "정시 모집" }, + "graduate": { "title": "대학원" }, + "gradRegularAdmission": { "title": "전기/후기 모집" }, + "international": { "title": "International" }, + "internationalUndergraduate": { "title": "Undergraduate" }, + "internationalGraduate": { "title": "Graduate" }, + "exchangeVisitingProgram": { "title": "Exchange/Visiting Program" }, + "scholarships": { "title": "Scholarships" }, + + "academics": { "title": "학사 및 교과" }, + "undergraduateGuide": { "title": "학부 안내" }, + "courses": { + "title": "교과과정", + "courseInformation": "교과목 정보", + "cardView": "카드형", + "listView": "목록형", + "name": "교과목명", + "classification": "교과목 구분", + "code": "교과목 번호", + "credit": "{credit, plural, =0 {학점} other {#학점}}", + "grade": "{grade, plural, =0 {대학원} =5 {학년} other {#학년}}", + + "graduate": "대학원", + "undergraduate": "학부" + }, + "curriculum": { "title": "전공 이수 표준 형태" }, + "generalStudiesRequirements": { "title": "필수 교양 과목" }, + "degreeRequirements": { "title": "졸업 규정" }, + "courseChanges": { "title": "교과목 변경 내역" }, + "scholarship": { "title": "장학 제도" }, + "graduateGuide": { "title": "대학원 안내" }, + + "reservations": { "title": "시설 예약" }, + "reservationIntro": { "title": "시설 예약 안내" }, + "seminarRooms": { "title": "세미나실 예약" }, + "labs": { "title": "실습실 예약" }, + "lectureRooms": { "title": "공과대학 강의실 예약" }, + "301_417": { "title": "301-417 (20석)" }, + "301_maldives": { "title": "301-MALDIVES (301-521, 11석)" }, + "301_hawaii": { "title": "301-HAWAII (301-551-4, 20석)" }, + "301_baekdu": { "title": "301-BAEKDU (301-552-1, 4석)" }, + "301_alps": { "title": "301-ALPS (301-552-2, 5석)" }, + "301_santorini": { "title": "301-SANTORINI (301-552-3, 4석)" }, + "301_jeju": { "title": "301-JEJU (301-553-6, 6석)" }, + "301_facultyRoom": { "title": "301-교수회의실 (301-317, 20석)" }, + "302_308": { "title": "302-308 (46석)" }, + "302_309_1": { "title": "302-309-1 (48석)" }, + "302_309_2": { "title": "302-309-2 (8석)" }, + "302_309_3": { "title": "302-309-3 (8석)" }, + "softwareLab": { "title": "소프트웨어 실습실 (302-311-1, 102석)" }, + "hardwareLab": { "title": "하드웨어 실습실 (302-310-2, 30석)" }, + "302_208": { "title": "302-208 (116석)" }, + "302_209": { "title": "302-209 (90석)" }, + + "search": { + "title": "검색", + "noResults": "검색 결과가 존재하지 않습니다", + "searchResults": "{count}개의 검색 결과", + "viewMore": "결과 더보기", + "tag": { + "about": "소개", + "community": "소식", + "people": "구성원", + "research": "연구", + "admission": "입학", + "academics": "학사 및 교과" + } + } }, "Footer": { - "About": "About", - "학부 소개": "학부 소개", - "교수진": "교수진", - "학부 안내": "학부 안내", - "대학원 안내": "대학원 안내", - - "Resources": "Resources", - "공지사항": "공지사항", - "세미나": "세미나", - "시설 예약 안내": "시설 예약 안내", - - "Research": "Research", - "신임교수초빙": "신임교수초빙", - "연구실 목록": "연구실 목록", - "Top Conference List": "Top Conference List", - "10-10 Project": "10-10 Project", - - "More": "More", - "연합전공 인공지능(학사)": "연합전공 인공지능(학사)", - "지능형컴퓨팅사업단": "지능형컴퓨팅사업단", - "컴퓨터 연구소": "컴퓨터 연구소", - "해동학술정보실": "해동학술정보실", - - "개인정보처리방침": "개인정보처리방침", - "학부 연락처": "학부 연락처", - "찾아오시는 길": "찾아오시는 길", + "departmentOverview": "학부 소개", + "faculty": "교수진", + "undergraduateGuide": "학부 안내", + "graduateGuide": "대학원 안내", + + "notice": "공지사항", + "seminars": "세미나", + "reservationIntro": "시설 예약 안내", + + "facultyRecruitment": "신임교수초빙", + "laboratories": "연구실 목록", + "topConferenceList": "Top Conference List", + "proposal": "10-10 Project", + + "imai": "연합전공 인공지능(학사)", + "bkcse": "지능형컴퓨팅사업단", + "ict": "컴퓨터 연구소", + "haedong": "해동학술정보실", + + "privacyPolicy": "개인정보처리방침", + "contactUs": "학부 연락처", + "directions": "찾아오시는 길", "address": "08826 서울특별시 관악구 관악로 1 서울대학교 공과대학 컴퓨터공학부 행정실(301동 316호)" - }, - - "Tag": { - "태그": "태그", - "전체": "전체", - - "소개": "소개", - "소식": "소식", - "구성원": "구성원", - "학사 및 교과": "학사 및 교과", - - "수업": "수업", - "장학": "장학", - "학사(학부)": "학사(학부)", - "학사(대학원)": "학사(대학원)", - "다전공/전과": "다전공/전과", - "등록/복학/휴학/재입학": "등록/복학/휴학/재입학", - "입학": "입학", - "졸업": "졸업", - "채용정보": "채용정보", - "교환학생/유학": "교환학생/유학", - "외부행사/프로그램": "외부행사/프로그램", - "내부행사/프로그램": "내부행사/프로그램", - "international": "international", - - "행사": "행사", - "연구": "연구", - "수상": "수상", - "채용": "채용", - "칼럼": "칼럼", - "강연": "강연", - "교육": "교육", - "인터뷰": "인터뷰", - "진로": "진로", - "과거 미분류": "과거 미분류", - - "학년": "학년", - "교과목 구분": "교과목 구분", - "학점": "학점", - "대학원": "대학원", - "학부": "학부", - - "1학년": "1학년", - "2학년": "2학년", - "3학년": "3학년", - "4학년": "4학년", - - "1학점": "1학점", - "2학점": "2학점", - "3학점": "3학점", - "4학점": "4학점" - }, - - "Content": { - "서울대학교 공과대학 컴퓨터공학부": "서울대학교 공과대학 컴퓨터공학부", - - "학부 소개 책자": "학부 소개 책자", - "졸업생 진로 현황": "졸업생 진로 현황", - "졸업생 창업 기업": "졸업생 창업 기업", - "창업 기업명": "창업 기업명", - "홈페이지": "홈페이지", - "창업연도": "창업 연도", - - "교과목 로드맵 보기": "교과목 로드맵 보기", - "교과목 로드맵": "교과목 로드맵", - "선수 교과목": "선수 교과목", - "교과목 정보": "교과목 정보", - "카드형": "카드형", - "목록형": "목록형", - "교과목명": "교과목명", - "교과목 구분": "교과목 구분", - "교과목 번호": "교과목 번호", - "학점": "학점", - "학년": "학년", - - "스트림": "스트림", - "연구실": "연구실", - "지도교수": "지도교수", - "연구실 위치": "연구실 위치", - "전화": "전화", - "약자": "약자", - "소개 자료": "소개 자료", - "연번": "연번", - "약칭": "약칭", - "학술대회 명칭": "학술대회 명칭", - "수정 날짜": "수정 날짜", - "작성 날짜": "작성 날짜", - "작성자": "작성자", - "본 리스트는 시간과 상황의 변동에 따라 바뀔 수 있습니다": "본 리스트는 시간과 상황의 변동에 따라 바뀔 수 있습니다", - - "주소": "주소", - - "제목": "제목", - "날짜": "날짜", - "검색": "검색", - "태그 초기화": "태그 초기화", - "목록": "목록", - "다음글": "다음글", - "이전글": "이전글", - - "이름": "이름", - "소속": "소속", - "주최": "주최", - "위치": "위치", - "요약": "요약", - "연사 소개": "연사 소개", - - "학력": "학력", - "연구 분야": "연구 분야", - "경력": "경력", - "이메일": "이메일", - "웹사이트": "웹사이트", - "연락처": "연락처", - "재직 기간": "재직 기간", - "교수실": "교수실", - "주요 업무": "주요 업무" } } diff --git a/utils/metadata.ts b/utils/metadata.ts index 9c577d0d..3172ac35 100644 --- a/utils/metadata.ts +++ b/utils/metadata.ts @@ -18,11 +18,11 @@ export const getMetadata = async ({ node?: SegmentNode; metadata?: Metadata; }): Promise => { - const t = await getTranslations({ locale, namespace: 'Nav' }); + const t = await getTranslations({ locale, namespace: 'Page' }); return { ...metadata, - title: metadata?.title || (node ? t(node.name) : undefined), + title: metadata?.title || (node ? t(`${node.engName}.title`) : undefined), description: metadata?.description || (node ? `서울대학교 컴퓨터공학부 ${node.name} 페이지입니다.` : undefined), diff --git a/utils/validateSearchParams.ts b/utils/validateSearchParams.ts index a0039ffa..48bee792 100644 --- a/utils/validateSearchParams.ts +++ b/utils/validateSearchParams.ts @@ -14,7 +14,7 @@ export const validateTag = (category: 'notice' | 'news', tag?: string | string[] if (!tag) return true; const availableTags = category === 'notice' ? NOTICE_TAGS : NEWS_TAGS; - const isTagValid = (singleTag: string) => availableTags.includes(singleTag); + const isTagValid = (singleTag: string) => availableTags.some((tag) => tag.id === singleTag); return Array.isArray(tag) ? tag.every(isTagValid) : isTagValid(tag); };