From 4582e95ede52e289460813ab8470e05dfae634c5 Mon Sep 17 00:00:00 2001 From: mina-gwak Date: Tue, 3 Jun 2025 20:18:34 +0900 Subject: [PATCH 1/8] =?UTF-8?q?Feat:=20PD-279=20=EC=A7=80=EC=9B=90?= =?UTF-8?q?=EC=9E=90=20=EA=B4=80=EB=A6=AC=20=EC=83=81=EC=84=B8=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=8B=A4=EA=B5=AD=EC=96=B4=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/update-job-post-resume-memo.ts | 3 +- .../api/update-job-post-resume-status.ts | 3 +- .../ui/application-status-select/index.tsx | 33 +++++++---- .../application-side-panel/ui/side-panel.tsx | 14 +++-- .../ui/submit-button.tsx | 57 ++++++++++++++----- .../download-file-resume-relation-button.tsx | 7 ++- .../ui/download-resume-relation-button.tsx | 7 ++- .../locales/en/applicant-management.json | 27 +++++++++ .../locales/en/field.json | 26 +++++++++ .../locales/ko/applicant-management.json | 27 +++++++++ .../locales/ko/field.json | 26 +++++++++ src/widgets/application-resume/ui/header.tsx | 11 +++- .../ui/personal-information.tsx | 36 +++++++++--- 13 files changed, 228 insertions(+), 49 deletions(-) diff --git a/src/entities/job-post-resume-relation/api/update-job-post-resume-memo.ts b/src/entities/job-post-resume-relation/api/update-job-post-resume-memo.ts index 7928b4f..7fd76e2 100644 --- a/src/entities/job-post-resume-relation/api/update-job-post-resume-memo.ts +++ b/src/entities/job-post-resume-relation/api/update-job-post-resume-memo.ts @@ -13,8 +13,9 @@ const handleError = (error: Error): ServerError => { const isHttpError = error instanceof HttpError if (!isHttpError) throw error - return errorHandler.toast('Failed to update job post resume memo', { + return errorHandler.toast('applicant-management-detail.error.update', { error, + translate: true, }) } diff --git a/src/entities/job-post-resume-relation/api/update-job-post-resume-status.ts b/src/entities/job-post-resume-relation/api/update-job-post-resume-status.ts index 9de0ad3..53bf9a9 100644 --- a/src/entities/job-post-resume-relation/api/update-job-post-resume-status.ts +++ b/src/entities/job-post-resume-relation/api/update-job-post-resume-status.ts @@ -15,8 +15,9 @@ const handleError = (error: Error): ServerError => { const isHttpError = error instanceof HttpError if (!isHttpError) throw error - return errorHandler.toast('Failed to update job post resume status', { + return errorHandler.toast('applicant-management-detail.error.update', { error, + translate: true, }) } diff --git a/src/entities/job-post-resume-relation/ui/application-status-select/index.tsx b/src/entities/job-post-resume-relation/ui/application-status-select/index.tsx index 7b4716c..99be7b3 100644 --- a/src/entities/job-post-resume-relation/ui/application-status-select/index.tsx +++ b/src/entities/job-post-resume-relation/ui/application-status-select/index.tsx @@ -1,29 +1,42 @@ +import { useLocale, useTranslations } from 'next-intl' + import { ApplicationStatus } from 'entities/job-post-resume-relation' import { Form, FormSelectProps } from 'shared/form' +import { cn } from 'shared/lib' export const StatusLabel = { - [ApplicationStatus.SUBMITTED]: '접수', - [ApplicationStatus.REVIEWED]: '검토', - [ApplicationStatus.ACCEPTED]: '합격', - [ApplicationStatus.REJECTED]: '불합격', + [ApplicationStatus.SUBMITTED]: 'submitted', + [ApplicationStatus.REVIEWED]: 'reviewed', + [ApplicationStatus.ACCEPTED]: 'accepted', + [ApplicationStatus.REJECTED]: 'rejected', } export const ApplicationStatusSelect = (props: FormSelectProps) => { + const locale = useLocale() + const t = useTranslations('field.application-status.option') + return ( StatusLabel[value as ApplicationStatus]} + className={cn({ + 'w-[85px]': locale === 'ko', + 'w-[120px]': locale === 'en', + })} + displayValue={value => t(StatusLabel[value as ApplicationStatus])} {...props} > - 접수 + {t('submitted')} + + + {t('reviewed')} + + + {t('accepted')} - 검토 - 합격 - 불합격 + {t('rejected')} ) diff --git a/src/features/application-side-panel/ui/side-panel.tsx b/src/features/application-side-panel/ui/side-panel.tsx index d8fc172..980ec77 100644 --- a/src/features/application-side-panel/ui/side-panel.tsx +++ b/src/features/application-side-panel/ui/side-panel.tsx @@ -1,6 +1,7 @@ 'use client' import { useParams } from 'next/navigation' +import { useTranslations } from 'next-intl' import { useForm, useWatch } from 'react-hook-form' import { toast } from 'react-toastify' @@ -16,7 +17,6 @@ import { Separator } from 'shared/ui' import { SubmitButton } from './submit-button' import { FormValues } from '../model/form-values' - type Params = { jobPostResumeRelationId: string } @@ -26,6 +26,8 @@ type Props = { } export const ApplicationSidePanel = ({ values }: Props) => { + const t = useTranslations('applicant-management-detail') + const params = useParams() const jobPostResumeRelationId = params?.jobPostResumeRelationId as string @@ -95,7 +97,7 @@ export const ApplicationSidePanel = ({ values }: Props) => { } if (success) { - toast.success('내용을 저장했어요') + toast.success(t('success.save')) } } @@ -105,23 +107,23 @@ export const ApplicationSidePanel = ({ values }: Props) => {

- 채용 단계를 변경하면 지원자에게 알림으로 결과를 알려줘요 + {t('side-panel.status-description')}

diff --git a/src/features/application-side-panel/ui/submit-button.tsx b/src/features/application-side-panel/ui/submit-button.tsx index 3849139..10903dd 100644 --- a/src/features/application-side-panel/ui/submit-button.tsx +++ b/src/features/application-side-panel/ui/submit-button.tsx @@ -1,5 +1,6 @@ 'use client' +import { useTranslations } from 'next-intl' import { useState } from 'react' import { useFormContext } from 'react-hook-form' @@ -22,6 +23,8 @@ type Props = { } export const SubmitButton = ({ hasToOpenModal, onSubmit, ...props }: Props) => { + const t = useTranslations() + const [isOpen, setIsOpen] = useState(false) const form = useFormContext() @@ -47,29 +50,53 @@ export const SubmitButton = ({ hasToOpenModal, onSubmit, ...props }: Props) => { onClick={handleSubmitButtonClick} {...props} > - 저장하기 + {t('applicant-management-detail.button.save')} - 채용 단계 변경 + + {t('applicant-management-detail.status-update-modal.title')} +

- 채용 단계를 ' - - {StatusLabel[props.status.prev]} - - '에서 ' - - {StatusLabel[props.status.next]} - - '으로 변경했어요. + {t.rich( + 'applicant-management-detail.status-update-modal.description1', + { + prev: () => ( + <> + " + + {t( + `field.application-status.option.${StatusLabel[props.status.prev]}`, + )} + + " + + ), + next: () => ( + <> + " + + {t( + `field.application-status.option.${StatusLabel[props.status.next]}`, + )} + + " + + ), + }, + )} +

+

+ {t('applicant-management-detail.status-update-modal.description2')} +

+

+ {t('applicant-management-detail.status-update-modal.description3')}

-

저장하면 지원자에게 결과가 전달돼요.

-

변경 사항을 저장할까요?

@@ -79,7 +106,7 @@ export const SubmitButton = ({ hasToOpenModal, onSubmit, ...props }: Props) => { fullWidth onClick={handleConfirmButtonClick} > - 저장하기 + {t('applicant-management-detail.button.save')}
diff --git a/src/features/download-resume/ui/download-file-resume-relation-button.tsx b/src/features/download-resume/ui/download-file-resume-relation-button.tsx index bbf9369..40b1a19 100644 --- a/src/features/download-resume/ui/download-file-resume-relation-button.tsx +++ b/src/features/download-resume/ui/download-file-resume-relation-button.tsx @@ -1,5 +1,6 @@ 'use client' +import { useTranslations } from 'next-intl' import { useState, MouseEvent } from 'react' import { toast } from 'react-toastify' @@ -22,6 +23,8 @@ type Props = { const ELEMENT_ID = 'cover-letter-pdf' export const DownloadFileResumeRelationButton = ({ resumeRelation }: Props) => { + const t = useTranslations('applicant-management-detail') + const [isLoading, setIsLoading] = useState(false) const fetchResumePDF = async () => { @@ -81,7 +84,7 @@ export const DownloadFileResumeRelationButton = ({ resumeRelation }: Props) => { await downloadResumePDF() } } catch (error) { - toast.error('Failed to download resume') + toast.error(t('error.download-resume')) } finally { setIsLoading(false) } @@ -95,7 +98,7 @@ export const DownloadFileResumeRelationButton = ({ resumeRelation }: Props) => { className="body-large flex gap-0.5 text-gray-700" > - {isLoading ? '다운로드 중...' : 'PDF로 다운받기'} + {isLoading ? t('button.downloading') : t('button.download-as-pdf')} ) } diff --git a/src/features/download-resume/ui/download-resume-relation-button.tsx b/src/features/download-resume/ui/download-resume-relation-button.tsx index abfdfff..1c5574d 100644 --- a/src/features/download-resume/ui/download-resume-relation-button.tsx +++ b/src/features/download-resume/ui/download-resume-relation-button.tsx @@ -1,5 +1,6 @@ 'use client' +import { useTranslations } from 'next-intl' import { useState, MouseEvent } from 'react' import { toast } from 'react-toastify' @@ -17,6 +18,8 @@ type Props = { } export const DownloadResumeRelationButton = ({ resumeRelation }: Props) => { + const t = useTranslations('applicant-management-detail') + const [isLoading, setIsLoading] = useState(false) const handleDownload = async (event: MouseEvent) => { @@ -49,7 +52,7 @@ export const DownloadResumeRelationButton = ({ resumeRelation }: Props) => { containers.forEach(container => document.body.removeChild(container)) } catch (error) { - toast.error('Failed to download resume') + toast.error(t('error.download-resume')) } finally { setIsLoading(false) } @@ -63,7 +66,7 @@ export const DownloadResumeRelationButton = ({ resumeRelation }: Props) => { className="body-large flex gap-0.5 text-gray-700" > - {isLoading ? '다운로드 중...' : 'PDF로 다운받기'} + {isLoading ? t('button.downloading') : t('button.download-as-pdf')} ) } diff --git a/src/shared/config/internationalization/locales/en/applicant-management.json b/src/shared/config/internationalization/locales/en/applicant-management.json index f3fcd0a..d822f21 100644 --- a/src/shared/config/internationalization/locales/en/applicant-management.json +++ b/src/shared/config/internationalization/locales/en/applicant-management.json @@ -14,5 +14,32 @@ "application-date": "Application Date", "no-data": "No applications have been received yet" } + }, + "applicant-management-detail": { + "side-panel": { + "status": "Status", + "status-description": "Changing the status will notify the applicant of the update", + "memo": "Memo", + "memo-placeholder": "Please note any details you'd like to remember about the applicant (up to 100 characters)" + }, + "status-update-modal": { + "title": "Update Status", + "description1": "Changed the hiring stage from to .", + "description2": "Saving will notify the applicant of the outcome.", + "description3": "Would you like to save these changes?" + }, + "button": { + "cancel": "Cancel", + "save": "Save", + "downloading": "Downloading...", + "download-as-pdf": "Download as PDF" + }, + "success": { + "save": "Updated successfully" + }, + "error": { + "download-resume": "Failed to download resume", + "update": "Failed to update" + } } } diff --git a/src/shared/config/internationalization/locales/en/field.json b/src/shared/config/internationalization/locales/en/field.json index f2da470..f3c0ce1 100644 --- a/src/shared/config/internationalization/locales/en/field.json +++ b/src/shared/config/internationalization/locales/en/field.json @@ -70,6 +70,32 @@ "gyeongnam": "Gyeongsangnam-do", "jeju": "Jeju" } + }, + "application-date": { + "label": "Application Date" + }, + "nationality": { + "label": "Nationality" + }, + "residence-country": { + "label": "Current Country of Residence" + }, + "degree": { + "label": "Degree" + }, + "visa": { + "label": "Visa" + }, + "student-type": { + "label": "Student Type" + }, + "application-status": { + "option": { + "submitted": "Submitted", + "reviewed": "Reviewed", + "accepted": "Accepted", + "rejected": "Rejected" + } } } } diff --git a/src/shared/config/internationalization/locales/ko/applicant-management.json b/src/shared/config/internationalization/locales/ko/applicant-management.json index 88b7218..d0c7bd5 100644 --- a/src/shared/config/internationalization/locales/ko/applicant-management.json +++ b/src/shared/config/internationalization/locales/ko/applicant-management.json @@ -14,5 +14,32 @@ "application-date": "지원 일자", "no-data": "아직 접수된 지원자가 없습니다" } + }, + "applicant-management-detail": { + "side-panel": { + "status": "현재 채용 단계", + "status-description": "채용 단계를 변경하면 지원자에게 알림으로 결과를 알려줘요", + "memo": "한 줄 메모", + "memo-placeholder": "지원자에 대해 기억하고 싶은 점이 있다면 적어주세요 (최대 100자)" + }, + "status-update-modal": { + "title": "채용 단계 변경", + "description1": "채용 단계를 에서 (으)로 변경했어요.", + "description2": "저장하면 지원자에게 결과가 전달돼요.", + "description3": "변경 사항을 저장할까요?" + }, + "button": { + "cancel": "취소", + "save": "저장하기", + "downloading": "다운로드 중...", + "download-as-pdf": "PDF로 다운받기" + }, + "success": { + "save": "내용을 저장했어요" + }, + "error": { + "download-resume": "이력서 다운로드 중 오류가 발생했습니다", + "update": "내용을 저장하는 중 오류가 발생했습니다" + } } } diff --git a/src/shared/config/internationalization/locales/ko/field.json b/src/shared/config/internationalization/locales/ko/field.json index 8b4660f..aca57bd 100644 --- a/src/shared/config/internationalization/locales/ko/field.json +++ b/src/shared/config/internationalization/locales/ko/field.json @@ -70,6 +70,32 @@ "gyeongnam": "경남", "jeju": "제주" } + }, + "application-date": { + "label": "지원 일자" + }, + "nationality": { + "label": "국적" + }, + "residence-country": { + "label": "거주 지역" + }, + "degree": { + "label": "학력" + }, + "visa": { + "label": "비자" + }, + "student-type": { + "label": "학생 유형" + }, + "application-status": { + "option": { + "submitted": "접수", + "reviewed": "검토", + "accepted": "합격", + "rejected": "불합격" + } } } } diff --git a/src/widgets/application-resume/ui/header.tsx b/src/widgets/application-resume/ui/header.tsx index 06e3943..88e8fab 100644 --- a/src/widgets/application-resume/ui/header.tsx +++ b/src/widgets/application-resume/ui/header.tsx @@ -1,14 +1,19 @@ +import { getTranslations } from 'next-intl/server' + import { JobPostRelationDetail } from 'entities/job-post-resume-relation' import { DownloadFileResumeRelationButton, DownloadResumeRelationButton, } from 'features/download-resume' +import { formatDate } from 'shared/lib' type Props = { resumeRelation: JobPostRelationDetail } -export const Header = ({ resumeRelation }: Props) => { +export const Header = async ({ resumeRelation }: Props) => { + const t = await getTranslations('field') + const hasFile = resumeRelation.filePath !== null return ( @@ -18,8 +23,8 @@ export const Header = ({ resumeRelation }: Props) => {

- 지원 일자: - {resumeRelation.submittedDate} + {t('application-date.label')}: + {formatDate(resumeRelation.submittedDate)}

{hasFile ? ( diff --git a/src/widgets/application-resume/ui/personal-information.tsx b/src/widgets/application-resume/ui/personal-information.tsx index 5b7c9ab..2415870 100644 --- a/src/widgets/application-resume/ui/personal-information.tsx +++ b/src/widgets/application-resume/ui/personal-information.tsx @@ -1,13 +1,19 @@ +import { capitalize } from 'lodash-es' +import { getTranslations } from 'next-intl/server' + import { convertStudentTypeToArray } from 'entities/job-post' import { JobPostRelationDetail } from 'entities/job-post-resume-relation' import { colors } from 'shared/config' +import { formatDate } from 'shared/lib' import { Image, Icon } from 'shared/ui' type Props = { jobPostResumeRelation: JobPostRelationDetail } -export const PersonalInformation = ({ jobPostResumeRelation }: Props) => { +export const PersonalInformation = async ({ jobPostResumeRelation }: Props) => { + const t = await getTranslations() + const studentType = convertStudentTypeToArray(jobPostResumeRelation) return ( @@ -33,28 +39,34 @@ export const PersonalInformation = ({ jobPostResumeRelation }: Props) => { {jobPostResumeRelation.firstName} {jobPostResumeRelation.lastName}

- {jobPostResumeRelation.birthDate} + {formatDate(jobPostResumeRelation.birthDate)} - {jobPostResumeRelation.genderType} + {capitalize(jobPostResumeRelation.genderType)}

-
이메일
+
+ {t('field.email.label')} +
{jobPostResumeRelation.email}
-
국적
+
+ {t('field.nationality.label')} +
{jobPostResumeRelation.countryNameEn}
-
거주 지역
+
+ {t('field.residence-country.label')} +
{jobPostResumeRelation.residenceCountryNameEn}
@@ -62,7 +74,9 @@ export const PersonalInformation = ({ jobPostResumeRelation }: Props) => {
-
학력
+
+ {t('field.degree.label')} +
{jobPostResumeRelation.degree} {jobPostResumeRelation.major && @@ -70,7 +84,9 @@ export const PersonalInformation = ({ jobPostResumeRelation }: Props) => {
-
비자
+
+ {t('field.visa.label')} +
{jobPostResumeRelation.hasVisa ? jobPostResumeRelation.visaType @@ -81,7 +97,9 @@ export const PersonalInformation = ({ jobPostResumeRelation }: Props) => {
-
학생 유형
+
+ {t('field.student-type.label')} +
{studentType.map(type => ( {type} From 3a5aa8f272eae6479b4e51a405f569c9252b5d8a Mon Sep 17 00:00:00 2001 From: mina-gwak Date: Tue, 3 Jun 2025 20:31:04 +0900 Subject: [PATCH 2/8] =?UTF-8?q?Feat:=20PD-279=20=EC=B1=84=EC=9A=A9=20?= =?UTF-8?q?=EA=B3=B5=EA=B3=A0=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=8B=A4=EA=B5=AD=EC=96=B4=20=EB=B3=B4?= =?UTF-8?q?=EC=99=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../business-job-posting/ui/job-posting-list-page.tsx | 7 +++++-- .../internationalization/locales/en/job-posting.json | 2 ++ .../internationalization/locales/ko/job-posting.json | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/pages/business-job-posting/ui/job-posting-list-page.tsx b/src/pages/business-job-posting/ui/job-posting-list-page.tsx index 92e2d2a..4a2902a 100644 --- a/src/pages/business-job-posting/ui/job-posting-list-page.tsx +++ b/src/pages/business-job-posting/ui/job-posting-list-page.tsx @@ -142,11 +142,14 @@ export const BusinessJobPostingListPage = () => { })} > {jobPost.title} - {jobPost.resumeCount}명 + + {jobPost.resumeCount} + {t('application-count')} + {formatCurrency({ number: jobPost.salary, - code: '만원', + code: t('currency-unit'), })} Date: Tue, 3 Jun 2025 22:13:33 +0900 Subject: [PATCH 3/8] =?UTF-8?q?Feat:=20PD-279=20=EC=B1=84=EC=9A=A9=20?= =?UTF-8?q?=EA=B3=B5=EA=B3=A0=20=EC=A7=80=EC=9B=90=EC=9E=90=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=8B=A4=EA=B5=AD?= =?UTF-8?q?=EC=96=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/application.ts | 1 + .../ui/copy-job-posting-button.tsx | 6 ++++-- .../ui/preview-job-posting-button.tsx | 7 +++++-- .../job-post-applicant-management-list-page.tsx | 16 ++++++++++------ .../locales/en/applicant-management.json | 7 +++++++ .../locales/ko/applicant-management.json | 7 +++++++ 6 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/entities/job-post-resume-relation/model/application.ts b/src/entities/job-post-resume-relation/model/application.ts index 704564e..a5713f7 100644 --- a/src/entities/job-post-resume-relation/model/application.ts +++ b/src/entities/job-post-resume-relation/model/application.ts @@ -13,6 +13,7 @@ export type JobPostRelation = { academyId: number academyName: string academyMemo: string | null + countryNameEn: string } export type JobPostRelationDetail = { diff --git a/src/features/copy-job-posting/ui/copy-job-posting-button.tsx b/src/features/copy-job-posting/ui/copy-job-posting-button.tsx index 862ff99..de40285 100644 --- a/src/features/copy-job-posting/ui/copy-job-posting-button.tsx +++ b/src/features/copy-job-posting/ui/copy-job-posting-button.tsx @@ -1,7 +1,7 @@ +import { useTranslations } from 'next-intl' import { MouseEvent } from 'react' import { colors } from 'shared/config' -import { cn } from 'shared/lib' import { Button, Icon } from 'shared/ui' import { useCopyJobPosting } from '../lib/copy-job-posting' @@ -19,6 +19,8 @@ export const CopyJobPostingButton = ({ onSuccess, disabled, }: Props) => { + const t = useTranslations('applicant-management-list') + const { isLoading, copyJobPosting } = useCopyJobPosting() const handleCopyButtonClick = async ( @@ -58,7 +60,7 @@ export const CopyJobPostingButton = ({ disabled={isLoading} > - {isLoading ? '복사 중...' : '공고 복사'} + {isLoading ? t('button.copying') : t('button.copy')} ) } diff --git a/src/features/preview-job-posting/ui/preview-job-posting-button.tsx b/src/features/preview-job-posting/ui/preview-job-posting-button.tsx index 61d597b..04a6b84 100644 --- a/src/features/preview-job-posting/ui/preview-job-posting-button.tsx +++ b/src/features/preview-job-posting/ui/preview-job-posting-button.tsx @@ -1,3 +1,4 @@ +import { useTranslations } from 'next-intl' import { MouseEvent } from 'react' import { getJobPost } from 'entities/job-post' @@ -23,6 +24,8 @@ export const PreviewJobPostingButton = ({ disabled, className, }: Props) => { + const t = useTranslations('applicant-management-list') + const handlePreviewButtonClick = async ( event: MouseEvent, ) => { @@ -63,7 +66,7 @@ export const PreviewJobPostingButton = ({ disabled={disabled} onClick={handlePreviewButtonClick} > - 미리 보기 + {t('button.preview')} ) } @@ -78,7 +81,7 @@ export const PreviewJobPostingButton = ({ className={className} > - 미리 보기 + {t('button.preview')} ) } diff --git a/src/pages/business-job-posting/ui/job-post-applicant-management-list-page.tsx b/src/pages/business-job-posting/ui/job-post-applicant-management-list-page.tsx index 0e15d1a..b78373c 100644 --- a/src/pages/business-job-posting/ui/job-post-applicant-management-list-page.tsx +++ b/src/pages/business-job-posting/ui/job-post-applicant-management-list-page.tsx @@ -7,7 +7,7 @@ import { useState } from 'react' import { ApplicationStatus } from 'entities/job-post-resume-relation' import { CopyJobPostingButton } from 'features/copy-job-posting' import { PreviewJobPostingButton } from 'features/preview-job-posting' -import { cn } from 'shared/lib' +import { cn, formatDate } from 'shared/lib' import { Layout, Tabs, Table, Pagination, Button } from 'shared/ui' import { useJobPostRelations } from '../api/use-job-post-relations' @@ -88,7 +88,7 @@ export const JobPostApplicantManagementListPage = ({ disabled={isExpired} > - 공고 수정 + {t('button.edit')} - {t('table.job-title')} + {t('table.nationality')} {t('table.memo')} @@ -134,11 +134,15 @@ export const JobPostApplicantManagementListPage = ({ {application.resumeFirstName}{' '} {application.resumeLastName} - {application.jobPostTitle} - {application?.academyMemo ?? ''} + {application?.countryNameEn ?? '-'} + + + {application?.academyMemo ?? '-'} + + + {formatDate(application.submittedDate)} - {application.submittedDate} ))} diff --git a/src/shared/config/internationalization/locales/en/applicant-management.json b/src/shared/config/internationalization/locales/en/applicant-management.json index d822f21..0dded5e 100644 --- a/src/shared/config/internationalization/locales/en/applicant-management.json +++ b/src/shared/config/internationalization/locales/en/applicant-management.json @@ -7,8 +7,15 @@ "accepted": "Accepted", "rejected": "Rejected" }, + "button": { + "edit": "Edit", + "copy": "Copy", + "preview": "Preview", + "copying": "Copying..." + }, "table": { "applicant": "Applicant", + "nationality": "Nationality", "job-title": "Job Title", "memo": "Memo", "application-date": "Application Date", diff --git a/src/shared/config/internationalization/locales/ko/applicant-management.json b/src/shared/config/internationalization/locales/ko/applicant-management.json index d0c7bd5..1d6219a 100644 --- a/src/shared/config/internationalization/locales/ko/applicant-management.json +++ b/src/shared/config/internationalization/locales/ko/applicant-management.json @@ -7,8 +7,15 @@ "accepted": "합격", "rejected": "불합격" }, + "button": { + "edit": "공고 수정", + "copy": "공고 복사", + "preview": "미리보기", + "copying": "복사 중..." + }, "table": { "applicant": "지원자", + "nationality": "국적", "job-title": "공고 제목", "memo": "메모", "application-date": "지원 일자", From 5d413145caa56167b286b0b89b7094c6f2356adb Mon Sep 17 00:00:00 2001 From: mina-gwak Date: Tue, 3 Jun 2025 23:04:46 +0900 Subject: [PATCH 4/8] =?UTF-8?q?Feat:=20PD-279=20=EA=B3=B5=EA=B3=A0=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D/=EC=88=98=EC=A0=95=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EB=8B=A4=EA=B5=AD=EC=96=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../job-post/api/create-job-post-draft.ts | 10 +- src/entities/job-post/api/create-job-post.ts | 3 +- .../job-post/api/update-job-post-draft.ts | 10 +- src/entities/job-post/api/update-job-post.ts | 3 +- .../job-posting-form/ui/job-posting-form.tsx | 91 +++++++++++++------ .../job-posting-form/ui/side-panel.tsx | 14 +-- .../ui/create-job-posting-page.tsx | 9 +- .../ui/job-posting-list-page.tsx | 4 +- .../ui/update-job-posting-draft-page.tsx | 15 ++- .../ui/update-job-posting-page.tsx | 7 +- .../locales/en/field.json | 52 +++++++++++ .../locales/en/job-posting.json | 25 +++++ .../locales/ko/field.json | 52 +++++++++++ .../locales/ko/job-posting.json | 25 +++++ 14 files changed, 267 insertions(+), 53 deletions(-) diff --git a/src/entities/job-post/api/create-job-post-draft.ts b/src/entities/job-post/api/create-job-post-draft.ts index bd50242..468b1cc 100644 --- a/src/entities/job-post/api/create-job-post-draft.ts +++ b/src/entities/job-post/api/create-job-post-draft.ts @@ -15,9 +15,13 @@ const handleError = (error: Error) => { const isHttpError = error instanceof HttpError if (!isHttpError) throw error - return errorHandler.toast('An error occurred while creating job post draft', { - error, - }) + return errorHandler.toast( + 'create-job-posting.error.job-posting-draft-register', + { + error, + translate: true, + }, + ) } export const createJobPostDraft = async (jobPost: CreateJobPost) => { diff --git a/src/entities/job-post/api/create-job-post.ts b/src/entities/job-post/api/create-job-post.ts index 82756ff..f5d5ed9 100644 --- a/src/entities/job-post/api/create-job-post.ts +++ b/src/entities/job-post/api/create-job-post.ts @@ -15,8 +15,9 @@ const handleError = (error: Error) => { const isHttpError = error instanceof HttpError if (!isHttpError) throw error - return errorHandler.toast('An error occurred while creating job post', { + return errorHandler.toast('create-job-posting.error.job-posting-register', { error, + translate: true, }) } diff --git a/src/entities/job-post/api/update-job-post-draft.ts b/src/entities/job-post/api/update-job-post-draft.ts index 4bf635f..611ea5e 100644 --- a/src/entities/job-post/api/update-job-post-draft.ts +++ b/src/entities/job-post/api/update-job-post-draft.ts @@ -20,9 +20,13 @@ const handleError = (error: Error) => { const isHttpError = error instanceof HttpError if (!isHttpError) throw error - return errorHandler.toast('An error occurred while updating job post draft', { - error, - }) + return errorHandler.toast( + 'create-job-posting.error.job-posting-draft-update', + { + error, + translate: true, + }, + ) } export const updateJobPostDraft = async ({ diff --git a/src/entities/job-post/api/update-job-post.ts b/src/entities/job-post/api/update-job-post.ts index e166da9..44bd6ea 100644 --- a/src/entities/job-post/api/update-job-post.ts +++ b/src/entities/job-post/api/update-job-post.ts @@ -20,8 +20,9 @@ const handleError = (error: Error) => { const isHttpError = error instanceof HttpError if (!isHttpError) throw error - return errorHandler.toast('An error occurred while updating job post', { + return errorHandler.toast('create-job-posting.error.job-posting-update', { error, + translate: true, }) } diff --git a/src/features/job-posting-form/ui/job-posting-form.tsx b/src/features/job-posting-form/ui/job-posting-form.tsx index 5fcd9e8..0543e13 100644 --- a/src/features/job-posting-form/ui/job-posting-form.tsx +++ b/src/features/job-posting-form/ui/job-posting-form.tsx @@ -1,4 +1,5 @@ import { isEqual } from 'lodash-es' +import { useLocale, useTranslations } from 'next-intl' import { useFormContext, useWatch } from 'react-hook-form' import { fieldCss, Form } from 'shared/form' @@ -13,6 +14,9 @@ type Props = { } export const JobPostingForm = ({ className }: Props) => { + const locale = useLocale() + const t = useTranslations() + const { control, setValue, @@ -58,20 +62,20 @@ export const JobPostingForm = ({ className }: Props) => { return (
- +
- + @@ -79,13 +83,15 @@ export const JobPostingForm = ({ className }: Props) => {
- + @@ -93,13 +99,15 @@ export const JobPostingForm = ({ className }: Props) => {
- + @@ -107,10 +115,10 @@ export const JobPostingForm = ({ className }: Props) => {
- + @@ -118,52 +126,81 @@ export const JobPostingForm = ({ className }: Props) => {
- + - + - 만원 + + {t('field.currency-unit.label')} + - +
- -
+ +
- - - - - + + + + +
- + @@ -172,10 +209,10 @@ export const JobPostingForm = ({ className }: Props) => {
- + { onChange={handleExpirationDateChange} > diff --git a/src/features/job-posting-form/ui/side-panel.tsx b/src/features/job-posting-form/ui/side-panel.tsx index 94c0137..0af718d 100644 --- a/src/features/job-posting-form/ui/side-panel.tsx +++ b/src/features/job-posting-form/ui/side-panel.tsx @@ -1,4 +1,5 @@ import { isEqual } from 'lodash-es' +import { useTranslations } from 'next-intl' import { useFormContext, useWatch } from 'react-hook-form' import { Button } from 'shared/ui' @@ -16,6 +17,7 @@ type Props = { } export const SidePanel = ({ type, onRegister, onSave }: Props) => { + const t = useTranslations('create-job-posting') const { handleSubmit, control, getValues } = useFormContext() const [ @@ -45,15 +47,13 @@ export const SidePanel = ({ type, onRegister, onSave }: Props) => { return (
-

- 공고 내용은 영어로 작성해주세요. -

+

{t('side-panel.title')}

- 입력한 정보는 검색에 반영돼요. + {t('side-panel.description1')}

- 중요한 정보를 빠뜨리지 않았는지 확인해 주세요. + {t('side-panel.description2')}

@@ -73,7 +73,7 @@ export const SidePanel = ({ type, onRegister, onSave }: Props) => { }) } > - 등록하기 + {t('button.register-job-posting')} {type === 'register' && ( )}
diff --git a/src/pages/business-job-posting/ui/create-job-posting-page.tsx b/src/pages/business-job-posting/ui/create-job-posting-page.tsx index 227b51c..216075c 100644 --- a/src/pages/business-job-posting/ui/create-job-posting-page.tsx +++ b/src/pages/business-job-posting/ui/create-job-posting-page.tsx @@ -2,6 +2,7 @@ import { useQuery } from '@tanstack/react-query' import { useRouter } from 'next/navigation' +import { useTranslations } from 'next-intl' import { useForm } from 'react-hook-form' import { toast } from 'react-toastify' @@ -23,6 +24,8 @@ import { Form } from 'shared/form' import { Layout } from 'shared/ui' export const CreateJobPostingPage = () => { + const t = useTranslations() + const router = useRouter() const { data: academyMe } = useQuery(academyQueries.me()) @@ -43,7 +46,7 @@ export const CreateJobPostingPage = () => { const handleRegisterJobPostingSuccess = () => { router.push('/business/job-posting') - toast.success('공고를 등록했어요') + toast.success(t('create-job-posting.success.job-posting-register')) } const registerJobPosting = async () => { @@ -60,7 +63,7 @@ export const CreateJobPostingPage = () => { const handleSaveJobPostingDraftSuccess = () => { router.push('/business/job-posting') - toast.success('공고를 임시 저장했어요') + toast.success(t('create-job-posting.success.job-posting-draft-register')) } const saveJobPostingDraft = async () => { @@ -78,7 +81,7 @@ export const CreateJobPostingPage = () => { return (

- 공고 등록 + {t('create-job-posting.create-title')}

diff --git a/src/pages/business-job-posting/ui/job-posting-list-page.tsx b/src/pages/business-job-posting/ui/job-posting-list-page.tsx index 4a2902a..8c63911 100644 --- a/src/pages/business-job-posting/ui/job-posting-list-page.tsx +++ b/src/pages/business-job-posting/ui/job-posting-list-page.tsx @@ -141,7 +141,7 @@ export const BusinessJobPostingListPage = () => { 'hover:bg-white': status === JobFilter.SAVED, })} > - {jobPost.title} + {jobPost.title ?? '-'} {jobPost.resumeCount} {t('application-count')} @@ -150,7 +150,7 @@ export const BusinessJobPostingListPage = () => { {formatCurrency({ number: jobPost.salary, code: t('currency-unit'), - })} + }) ?? '-'} { + const t = useTranslations() + const router = useRouter() const { data: academyMe } = useQuery(academyQueries.me()) @@ -52,13 +56,16 @@ export const UpdateJobPostingDraftPage = ({ const handleRegisterJobPostingSuccess = () => { router.push('/business/job-posting') - toast.success('공고를 등록했어요') + toast.success(t('create-job-posting.success.job-posting-register')) } const registerJobPosting = async () => { const values = form.getValues() - const response = await createJobPost(convertToCreateJobPostDTO(values)) + const response = await updateJobPost({ + jobPostId, + jobPost: convertToCreateJobPostDTO(values), + }) if (isServerError(response)) { handleServerError(response) @@ -69,7 +76,7 @@ export const UpdateJobPostingDraftPage = ({ const handleSaveJobPostingDraftSuccess = () => { router.push('/business/job-posting') - toast.success('임시 저장된 공고를 수정했어요') + toast.success(t('create-job-posting.success.job-posting-draft-update')) } const saveJobPostingDraft = async () => { @@ -90,7 +97,7 @@ export const UpdateJobPostingDraftPage = ({ return (

- 공고 등록 + {t('create-job-posting.create-title')}

diff --git a/src/pages/business-job-posting/ui/update-job-posting-page.tsx b/src/pages/business-job-posting/ui/update-job-posting-page.tsx index 230318d..9d83c5b 100644 --- a/src/pages/business-job-posting/ui/update-job-posting-page.tsx +++ b/src/pages/business-job-posting/ui/update-job-posting-page.tsx @@ -2,6 +2,7 @@ import { useQuery } from '@tanstack/react-query' import { useRouter } from 'next/navigation' +import { useTranslations } from 'next-intl' import { useForm } from 'react-hook-form' import { toast } from 'react-toastify' @@ -25,6 +26,8 @@ type Props = { } export const UpdateJobPostingPage = ({ jobPostId, jobPostDetail }: Props) => { + const t = useTranslations() + const router = useRouter() const { data: academyMe } = useQuery(academyQueries.me()) @@ -45,7 +48,7 @@ export const UpdateJobPostingPage = ({ jobPostId, jobPostDetail }: Props) => { const handleUpdateJobPostingSuccess = () => { router.push('/business/job-posting') - toast.success('공고를 수정했어요') + toast.success(t('create-job-posting.success.job-posting-update')) } const updateJobPosting = async () => { @@ -66,7 +69,7 @@ export const UpdateJobPostingPage = ({ jobPostId, jobPostDetail }: Props) => { return (

- 공고 수정 + {t('create-job-posting.update-title')}

diff --git a/src/shared/config/internationalization/locales/en/field.json b/src/shared/config/internationalization/locales/en/field.json index f3c0ce1..65332d5 100644 --- a/src/shared/config/internationalization/locales/en/field.json +++ b/src/shared/config/internationalization/locales/en/field.json @@ -96,6 +96,58 @@ "accepted": "Accepted", "rejected": "Rejected" } + }, + "job-posting-title": { + "label": "Job Title", + "placeholder": "Please enter concisely and clearly. ex) Full-Time ESL Instructor" + }, + "job-posting-description": { + "label": "Job Description", + "placeholder": "Please provide detailed information about the responsibilities after joining.\nex) Teach [elementary-level] English classes [30 hours per week]. Develop lesson plans and class materials." + }, + "job-posting-required-qualification": { + "label": "Required Qualifications", + "placeholder": "Please enter the necessary requirements for the applicant.\nex) Native or native-level English speaker. Valid TEFL/TESOL/CELTA certification (preferred)." + }, + "job-posting-preferred-qualification": { + "label": "Preferred Qualifications", + "placeholder": "If there are any preferred qualifications, please enter them.\nex) Basic Korean language skills for simple communication.\nExperience teaching [elementary students/IELTS/TOEFL]." + }, + "job-posting-benefits": { + "label": "Benefits", + "placeholder": "Please describe the benefits provided to employees.\nex) Provided housing or housing allowance. Paid vacation days (ex. 10 days) + national holidays. Round-trip airfare reimbursement (depending on contract)." + }, + "job-posting-salary": { + "label": "Salary", + "placeholder": "Please enter numbers only" + }, + "job-posting-salary-negotiable": { + "label": "Negotiable" + }, + "job-posting-target-student": { + "label": "Target Student", + "option": { + "all": "All", + "kindergarten": "Kindergarten", + "elementary": "Elementary", + "middle-school": "Middle School", + "high-school": "High School", + "adult": "Adult" + } + }, + "job-posting-start-date": { + "label": "Start Date", + "placeholder": "Please select the start date" + }, + "job-posting-due-date": { + "label": "Expiration Date", + "placeholder": "Please select the expiration date" + }, + "job-posting-no-expiration-date": { + "label": "Open Recruitment" + }, + "currency-unit": { + "label": "in 10,000 KRW" } } } diff --git a/src/shared/config/internationalization/locales/en/job-posting.json b/src/shared/config/internationalization/locales/en/job-posting.json index 911f4d0..539e64d 100644 --- a/src/shared/config/internationalization/locales/en/job-posting.json +++ b/src/shared/config/internationalization/locales/en/job-posting.json @@ -24,5 +24,30 @@ "success": { "job-posting-copy": "Job posting copied successfully" } + }, + "create-job-posting": { + "create-title": "Register Job Posting", + "update-title": "Edit Job Posting", + "side-panel": { + "title": "Please write the job posting in English.", + "description1": "The information you enter will be reflected in search results.", + "description2": "Please make sure you haven't missed any important details." + }, + "button": { + "register-job-posting": "Register", + "save-job-posting-draft": "Save" + }, + "error": { + "job-posting-register": "An error occurred while registering the job posting", + "job-posting-draft-register": "An error occurred while saving the job posting as a draft", + "job-posting-draft-update": "An error occurred while updating the job posting draft", + "job-posting-update": "An error occurred while updating the job posting" + }, + "success": { + "job-posting-draft-register": "Job posting saved as a draft successfully", + "job-posting-register": "Job posting registered successfully", + "job-posting-draft-update": "Job posting draft updated successfully", + "job-posting-update": "Job posting updated successfully" + } } } diff --git a/src/shared/config/internationalization/locales/ko/field.json b/src/shared/config/internationalization/locales/ko/field.json index aca57bd..d1b0876 100644 --- a/src/shared/config/internationalization/locales/ko/field.json +++ b/src/shared/config/internationalization/locales/ko/field.json @@ -96,6 +96,58 @@ "accepted": "합격", "rejected": "불합격" } + }, + "job-posting-title": { + "label": "공고 제목", + "placeholder": "간결하고 명확하게 입력해 주세요. ex) Full-Time ESL Instructor" + }, + "job-posting-description": { + "label": "주요 업무", + "placeholder": "입사 후 맡게되는 업무에 대해 자세히 알려주세요\nex) Teach [elementary-level] English classes [30 hours per week]. Develop lesson plans and class materials." + }, + "job-posting-required-qualification": { + "label": "자격 요건", + "placeholder": "지원자의 필요 조건을 입력해 주세요\nex) Native or native-level English speaker. Valid TEFL/TESOL/CELTA certification (preferred)." + }, + "job-posting-preferred-qualification": { + "label": "우대 사항", + "placeholder": "채용시 우대되는 사항이 있다면 입력해 주세요\nex) Basic Korean language skills for simple communication.\nExperience teaching [elementary students/IELTS/TOEFL]." + }, + "job-posting-benefits": { + "label": "혜택/복지", + "placeholder": "근무자에게 제공되는 혜택을 알려주세요\nex) Provided housing or housing allowance. Paid vacation days (ex. 10 days) + national holidays. Round-trip airfare reimbursement (depending on contract)." + }, + "job-posting-salary": { + "label": "월급", + "placeholder": "숫자만 입력해 주세요" + }, + "job-posting-salary-negotiable": { + "label": "협의 가능" + }, + "job-posting-target-student": { + "label": "대상 학생", + "option": { + "all": "전체", + "kindergarten": "유치원", + "elementary": "초등학생", + "middle-school": "중학생", + "high-school": "고등학생", + "adult": "성인" + } + }, + "job-posting-start-date": { + "label": "업무 시작 가능 날짜", + "placeholder": "업무 시작 날짜를 선택해 주세요" + }, + "job-posting-due-date": { + "label": "공고 마감일", + "placeholder": "공고 마감 날짜를 선택해 주세요" + }, + "job-posting-no-expiration-date": { + "label": "상시 채용" + }, + "currency-unit": { + "label": "만원" } } } diff --git a/src/shared/config/internationalization/locales/ko/job-posting.json b/src/shared/config/internationalization/locales/ko/job-posting.json index 5d7fac4..d3f920e 100644 --- a/src/shared/config/internationalization/locales/ko/job-posting.json +++ b/src/shared/config/internationalization/locales/ko/job-posting.json @@ -24,5 +24,30 @@ "success": { "job-posting-copy": "공고를 복사했어요" } + }, + "create-job-posting": { + "create-title": "공고 등록", + "update-title": "공고 수정", + "side-panel": { + "title": "공고 내용은 영어로 작성해주세요.", + "description1": "입력한 정보는 검색에 반영돼요.", + "description2": "중요한 정보를 빠뜨리지 않았는지 확인해 주세요." + }, + "button": { + "register-job-posting": "등록하기", + "save-job-posting-draft": "임시 저장" + }, + "error": { + "job-posting-register": "공고를 등록하는 중 오류가 발생했습니다", + "job-posting-draft-register": "공고를 임시 저장하는 중 오류가 발생했습니다", + "job-posting-draft-update": "임시 저장된 공고를 수정하는 중 오류가 발생했습니다", + "job-posting-update": "공고를 수정하는 중 오류가 발생했습니다" + }, + "success": { + "job-posting-draft-register": "공고를 임시 저장했어요", + "job-posting-register": "공고를 등록했어요", + "job-posting-draft-update": "임시 저장된 공고를 수정했어요", + "job-posting-update": "공고를 수정했어요" + } } } From db44121a1ed2a48ab93da1cbc7e84a569df90fe4 Mon Sep 17 00:00:00 2001 From: mina-gwak Date: Tue, 3 Jun 2025 23:37:53 +0900 Subject: [PATCH 5/8] =?UTF-8?q?Feat:=20PD-279=20=ED=95=99=EC=9B=90=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A0=95=EB=B3=B4=20=EB=8B=A4=EA=B5=AD?= =?UTF-8?q?=EC=96=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/academy/api/update-academy-me.ts | 3 +- .../academy-detail-form/model/rules.ts | 6 +-- .../academy-detail-form/ui/field/address.tsx | 2 +- .../ui/field/description.tsx | 8 +++- .../academy-detail-form/ui/field/images.tsx | 7 +++- .../ui/field/student-type.tsx | 42 +++++++++++++++---- .../academy-detail-form/ui/image-uploader.tsx | 7 +++- .../academy-detail-form/ui/side-panel.tsx | 13 +++--- .../job-posting-form/ui/job-posting-form.tsx | 14 +++---- .../academy-detail/ui/academy-detail-page.tsx | 5 ++- .../locales/en/academy-detail.json | 19 +++++++++ .../locales/en/field.json | 14 ++++++- .../locales/en/validation.json | 7 ++++ .../locales/ko/academy-detail.json | 19 +++++++++ .../locales/ko/field.json | 14 ++++++- .../locales/ko/validation.json | 7 ++++ .../config/internationalization/request.ts | 1 + 17 files changed, 153 insertions(+), 35 deletions(-) create mode 100644 src/shared/config/internationalization/locales/en/academy-detail.json create mode 100644 src/shared/config/internationalization/locales/ko/academy-detail.json diff --git a/src/entities/academy/api/update-academy-me.ts b/src/entities/academy/api/update-academy-me.ts index 69904e1..5f41abf 100644 --- a/src/entities/academy/api/update-academy-me.ts +++ b/src/entities/academy/api/update-academy-me.ts @@ -21,8 +21,9 @@ const handleError = (error: Error): ServerError => { const isHttpError = error instanceof HttpError if (!isHttpError) throw error - return errorHandler.toast('업데이트에 실패했어요', { + return errorHandler.toast('academy-detail.error.register', { error, + translate: true, }) } diff --git a/src/features/academy-detail-form/model/rules.ts b/src/features/academy-detail-form/model/rules.ts index b963592..94046ce 100644 --- a/src/features/academy-detail-form/model/rules.ts +++ b/src/features/academy-detail-form/model/rules.ts @@ -27,7 +27,7 @@ export const academyNameEn = { } export const address = { - required: 'validation.address.required', + required: true, } export const detailedAddress = { @@ -55,10 +55,10 @@ export const businessRegistrationNumber = { } export const description = { - required: 'validation.description.required', + required: 'validation.academyDescription.required', maxLength: { value: 1000, - message: 'validation.description.maxLength', + message: 'validation.academyDescription.maxLength', }, } diff --git a/src/features/academy-detail-form/ui/field/address.tsx b/src/features/academy-detail-form/ui/field/address.tsx index af12cce..92294de 100644 --- a/src/features/academy-detail-form/ui/field/address.tsx +++ b/src/features/academy-detail-form/ui/field/address.tsx @@ -53,7 +53,7 @@ export const Address = () => { className="w-[120px] shrink-0" onClick={handleButtonClick} > - 주소 검색 + {t('field.address.button')} { + const t = useTranslations('field') + return (
- + diff --git a/src/features/academy-detail-form/ui/field/images.tsx b/src/features/academy-detail-form/ui/field/images.tsx index 83d9752..19947cc 100644 --- a/src/features/academy-detail-form/ui/field/images.tsx +++ b/src/features/academy-detail-form/ui/field/images.tsx @@ -1,3 +1,4 @@ +import { useTranslations } from 'next-intl' import { useFieldArray, useFormContext } from 'react-hook-form' import { Label } from 'shared/ui' @@ -6,6 +7,8 @@ import { FormValues } from '../../model/form-values' import { ImageUploader } from '../image-uploader' export const Images = () => { + const t = useTranslations('field') + const { control, formState: { errors }, @@ -39,9 +42,9 @@ export const Images = () => { return (
- +

- 소개 이미지는 최소 1장 이상 등록해 주세요. + {t('introduction-image.description')}

    {fields.map((field, index) => ( diff --git a/src/features/academy-detail-form/ui/field/student-type.tsx b/src/features/academy-detail-form/ui/field/student-type.tsx index bf53b6b..39fea9e 100644 --- a/src/features/academy-detail-form/ui/field/student-type.tsx +++ b/src/features/academy-detail-form/ui/field/student-type.tsx @@ -1,13 +1,18 @@ import { isEqual } from 'lodash-es' +import { useLocale, useTranslations } from 'next-intl' import { useFormContext, useWatch } from 'react-hook-form' import { fieldCss, Form } from 'shared/form' +import { cn } from 'shared/lib' import { Checkbox, Label } from 'shared/ui' import { FormValues } from '../../model/form-values' import * as rules from '../../model/rules' export const StudentType = () => { + const locale = useLocale() + const t = useTranslations('field') + const { control, setValue, @@ -42,25 +47,46 @@ export const StudentType = () => { return (
    - -
    + +
    - - - - - + + + + +
    diff --git a/src/features/academy-detail-form/ui/image-uploader.tsx b/src/features/academy-detail-form/ui/image-uploader.tsx index 01d4a6e..7f237ff 100644 --- a/src/features/academy-detail-form/ui/image-uploader.tsx +++ b/src/features/academy-detail-form/ui/image-uploader.tsx @@ -1,5 +1,6 @@ 'use client' +import { useTranslations } from 'next-intl' import { useRef, useState } from 'react' import { ImageUploadInput } from 'features/upload-image' @@ -20,6 +21,8 @@ export const ImageUploader = ({ onDelete, className, }: Props) => { + const t = useTranslations('field') + const fileInputRef = useRef(null) const [isHovering, setIsHovering] = useState(false) @@ -104,7 +107,9 @@ export const ImageUploader = ({
    -

    이미지 추가

    +

    + {t('introduction-image.placeholder')} +

    ) })()} diff --git a/src/features/academy-detail-form/ui/side-panel.tsx b/src/features/academy-detail-form/ui/side-panel.tsx index d7aa91d..58817b8 100644 --- a/src/features/academy-detail-form/ui/side-panel.tsx +++ b/src/features/academy-detail-form/ui/side-panel.tsx @@ -1,3 +1,4 @@ +import { useTranslations } from 'next-intl' import { useFormContext, useWatch } from 'react-hook-form' import { toast } from 'react-toastify' @@ -12,6 +13,8 @@ import { } from '../model/form-values' export const SidePanel = () => { + const t = useTranslations('academy-detail') + const { handleSubmit, control } = useFormContext() const { handleServerError } = useServerErrorHandler() @@ -40,7 +43,7 @@ export const SidePanel = () => { }) const handleRegisterSuccess = () => { - toast.success('정보를 저장했어요') + toast.success(t('success.register')) } const submitForm = async (data: FormValues) => { @@ -55,13 +58,13 @@ export const SidePanel = () => { return (
    -

    작성에 유의해 주세요

    +

    {t('side-panel.title')}

    - 입력한 정보는 검색에 반영돼요. + {t('side-panel.description1')}

    - 중요한 정보를 빠뜨리지 않았는지 확인해 주세요. + {t('side-panel.description2')}

    @@ -83,7 +86,7 @@ export const SidePanel = () => { }) } > - 저장하기 + {t('button.save')}
    diff --git a/src/features/job-posting-form/ui/job-posting-form.tsx b/src/features/job-posting-form/ui/job-posting-form.tsx index 0543e13..e3a4e39 100644 --- a/src/features/job-posting-form/ui/job-posting-form.tsx +++ b/src/features/job-posting-form/ui/job-posting-form.tsx @@ -152,7 +152,7 @@ export const JobPostingForm = ({ className }: Props) => {
    - +
    { options={studentTypeOptions} > diff --git a/src/pages/academy-detail/ui/academy-detail-page.tsx b/src/pages/academy-detail/ui/academy-detail-page.tsx index 1488eae..21842af 100644 --- a/src/pages/academy-detail/ui/academy-detail-page.tsx +++ b/src/pages/academy-detail/ui/academy-detail-page.tsx @@ -1,5 +1,6 @@ 'use client' +import { useTranslations } from 'next-intl' import { useForm } from 'react-hook-form' import { AcademyDetail } from 'entities/academy' @@ -16,6 +17,8 @@ type Props = { } export const AcademyDetailPage = ({ academyDetail }: Props) => { + const t = useTranslations() + const form = useForm({ defaultValues: convertToFormValues(academyDetail), }) @@ -23,7 +26,7 @@ export const AcademyDetailPage = ({ academyDetail }: Props) => { return (

    - 학원 상세 정보 + {t('academy-detail.title')}

    diff --git a/src/shared/config/internationalization/locales/en/academy-detail.json b/src/shared/config/internationalization/locales/en/academy-detail.json new file mode 100644 index 0000000..48d5c00 --- /dev/null +++ b/src/shared/config/internationalization/locales/en/academy-detail.json @@ -0,0 +1,19 @@ +{ + "academy-detail": { + "title": "Academy", + "side-panel": { + "title": "Please fill out carefully", + "description1": "The information you enter will be reflected in search results.", + "description2": "Please make sure you haven't missed any important details." + }, + "button": { + "save": "Save" + }, + "success": { + "register": "Information saved" + }, + "error": { + "register": "An error occurred while saving information" + } + } +} diff --git a/src/shared/config/internationalization/locales/en/field.json b/src/shared/config/internationalization/locales/en/field.json index 65332d5..cf4c138 100644 --- a/src/shared/config/internationalization/locales/en/field.json +++ b/src/shared/config/internationalization/locales/en/field.json @@ -42,9 +42,19 @@ "label": "Academy Name (English)", "placeholder": "Enter the academy name (English)" }, + "academy-description": { + "label": "Academy Description", + "placeholder": "Please enter in English" + }, "address": { "label": "Academy Address", - "placeholder": "Enter the remaining address" + "placeholder": "Enter the remaining address", + "button": "Search" + }, + "introduction-image": { + "label": "Introduction Image", + "placeholder": "Upload Image", + "description": "Please upload at least one introduction image." }, "business-registration-number": { "label": "Business Registration Number", @@ -124,7 +134,7 @@ "job-posting-salary-negotiable": { "label": "Negotiable" }, - "job-posting-target-student": { + "target-student": { "label": "Target Student", "option": { "all": "All", diff --git a/src/shared/config/internationalization/locales/en/validation.json b/src/shared/config/internationalization/locales/en/validation.json index 0ec1942..778dca2 100644 --- a/src/shared/config/internationalization/locales/en/validation.json +++ b/src/shared/config/internationalization/locales/en/validation.json @@ -54,6 +54,13 @@ "required": "Please enter the detailed address", "maxLength": "Please enter less than 100 characters" }, + "academyDescription": { + "required": "Please enter the academy description", + "maxLength": "Please enter less than 1000 characters" + }, + "images": { + "required": "Please upload at least one introduction image" + }, "businessRegistrationNumber": { "required": "Please enter the business registration number", "length": "Please enter 10 digits", diff --git a/src/shared/config/internationalization/locales/ko/academy-detail.json b/src/shared/config/internationalization/locales/ko/academy-detail.json new file mode 100644 index 0000000..6d98428 --- /dev/null +++ b/src/shared/config/internationalization/locales/ko/academy-detail.json @@ -0,0 +1,19 @@ +{ + "academy-detail": { + "title": "학원 상세 정보", + "side-panel": { + "title": "작성에 유의해 주세요", + "description1": "입력한 정보는 검색에 반영돼요.", + "description2": "중요한 정보를 빠뜨리지 않았는지 확인해 주세요." + }, + "button": { + "save": "저장하기" + }, + "success": { + "register": "정보를 저장했어요" + }, + "error": { + "register": "정보를 저장하는 중 오류가 발생했습니다" + } + } +} diff --git a/src/shared/config/internationalization/locales/ko/field.json b/src/shared/config/internationalization/locales/ko/field.json index d1b0876..c5439a7 100644 --- a/src/shared/config/internationalization/locales/ko/field.json +++ b/src/shared/config/internationalization/locales/ko/field.json @@ -42,9 +42,19 @@ "label": "학원 이름 (영문)", "placeholder": "영어로 입력해 주세요" }, + "academy-description": { + "label": "학원 소개", + "placeholder": "영어로 입력해 주세요" + }, "address": { "label": "학원 주소", - "placeholder": "상세 주소 입력" + "placeholder": "상세 주소 입력", + "button": "주소 검색" + }, + "introduction-image": { + "label": "소개 이미지", + "placeholder": "이미지 추가", + "description": "소개 이미지는 최소 1장 이상 등록해 주세요." }, "business-registration-number": { "label": "사업자 등록번호", @@ -124,7 +134,7 @@ "job-posting-salary-negotiable": { "label": "협의 가능" }, - "job-posting-target-student": { + "target-student": { "label": "대상 학생", "option": { "all": "전체", diff --git a/src/shared/config/internationalization/locales/ko/validation.json b/src/shared/config/internationalization/locales/ko/validation.json index 6ff93d5..69c4dae 100644 --- a/src/shared/config/internationalization/locales/ko/validation.json +++ b/src/shared/config/internationalization/locales/ko/validation.json @@ -54,6 +54,13 @@ "required": "상세 주소를 입력해 주세요", "maxLength": "상세 주소는 최대 100자를 초과할 수 없습니다" }, + "academyDescription": { + "required": "학원 소개를 입력해 주세요", + "maxLength": "학원 소개는 최대 1000자를 초과할 수 없습니다" + }, + "images": { + "required": "소개 이미지를 최소 1장 이상 등록해 주세요" + }, "businessRegistrationNumber": { "required": "사업자 등록번호를 입력해 주세요", "length": "10자를 입력해주세요", diff --git a/src/shared/config/internationalization/request.ts b/src/shared/config/internationalization/request.ts index df36998..45fd867 100644 --- a/src/shared/config/internationalization/request.ts +++ b/src/shared/config/internationalization/request.ts @@ -13,6 +13,7 @@ export default getRequestConfig(async () => { ...(await import(`./locales/${locale}/gnb.json`)).default, ...(await import(`./locales/${locale}/job-posting.json`)).default, ...(await import(`./locales/${locale}/validation.json`)).default, + ...(await import(`./locales/${locale}/academy-detail.json`)).default, } return { From d406c743f2a3dc0e2748ace59aace10b89bd1020 Mon Sep 17 00:00:00 2001 From: mina-gwak Date: Wed, 4 Jun 2025 00:19:29 +0900 Subject: [PATCH 6/8] =?UTF-8?q?Feat:=20PD-279=20=ED=95=99=EC=9B=90=20?= =?UTF-8?q?=EB=A7=88=EC=9D=B4=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=8B=A4?= =?UTF-8?q?=EA=B5=AD=EC=96=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/user/api/change-password.ts | 25 ++++++++++---- src/entities/user/api/delete-user-me.ts | 4 +-- src/entities/user/api/update-user-me.ts | 3 +- src/entities/user/model/user.ts | 1 + .../ui/delete-account-button.tsx | 5 ++- .../ui/delete-account-success.tsx | 9 +++-- .../delete-account/ui/delete-account.tsx | 14 ++++---- .../my-academy/ui/change-password-page.tsx | 13 +++++--- src/pages/my-academy/ui/my-academy.tsx | 2 +- .../ui/personal-information-page.tsx | 9 +++-- .../internationalization/locales/en/auth.json | 33 ++++++++++++++++++- .../internationalization/locales/ko/auth.json | 33 ++++++++++++++++++- 12 files changed, 120 insertions(+), 31 deletions(-) diff --git a/src/entities/user/api/change-password.ts b/src/entities/user/api/change-password.ts index f36067b..d4ff170 100644 --- a/src/entities/user/api/change-password.ts +++ b/src/entities/user/api/change-password.ts @@ -19,19 +19,30 @@ const handleError = (error: HttpError) => { if (!isHttpError) throw error if (error.code === AuthExceptionCode.PW_NOT_CORRECT) { - return errorHandler.form({ - currentPassword: 'The current password is incorrect', - }) + return errorHandler.form( + { + currentPassword: 'reset-password.error.incorrect', + }, + { + translate: true, + }, + ) } else if ( error.code === InvalidInputValueExceptionCode.INVALID_INPUT_VALUE ) { - return errorHandler.form({ - currentPassword: 'The current password is incorrect', - }) + return errorHandler.form( + { + currentPassword: 'reset-password.error.incorrect', + }, + { + translate: true, + }, + ) } - return errorHandler.toast('An error occurred while changing the password', { + return errorHandler.toast('reset-password.error.reset-password', { error, + translate: true, }) } diff --git a/src/entities/user/api/delete-user-me.ts b/src/entities/user/api/delete-user-me.ts index dcc55e0..7088d3c 100644 --- a/src/entities/user/api/delete-user-me.ts +++ b/src/entities/user/api/delete-user-me.ts @@ -17,9 +17,9 @@ const handleError = (error: Error) => { if (!isHttpError) throw error if (error.code === ResourceNotFoundExceptionCode.USER_NOT_FOUND) { - return errorHandler.toast('This account has been deactivated') + return errorHandler.toast('my-account.error.account-deactivated') } else { - return errorHandler.toast('An error occurred while deleting the account', { + return errorHandler.toast('my-account.error.delete-account', { error, }) } diff --git a/src/entities/user/api/update-user-me.ts b/src/entities/user/api/update-user-me.ts index 54a2e34..5f7a059 100644 --- a/src/entities/user/api/update-user-me.ts +++ b/src/entities/user/api/update-user-me.ts @@ -15,8 +15,9 @@ const handleError = (error: Error) => { const isHttpError = error instanceof HttpError if (!isHttpError) throw error - return errorHandler.toast('An error occurred while updating the user', { + return errorHandler.toast('my-account.error.save', { error, + translate: true, }) } diff --git a/src/entities/user/model/user.ts b/src/entities/user/model/user.ts index ca0a46f..3910d25 100644 --- a/src/entities/user/model/user.ts +++ b/src/entities/user/model/user.ts @@ -2,6 +2,7 @@ export type User = { id: number firstName: string lastName: string + fullName?: string genderType: 'MALE' | 'FEMALE' birthDate: string email: string diff --git a/src/features/delete-account/ui/delete-account-button.tsx b/src/features/delete-account/ui/delete-account-button.tsx index f35f55c..e0dbf8e 100644 --- a/src/features/delete-account/ui/delete-account-button.tsx +++ b/src/features/delete-account/ui/delete-account-button.tsx @@ -1,5 +1,6 @@ 'use client' +import { useTranslations } from 'next-intl' import { useState } from 'react' import { Modal } from 'shared/ui' @@ -7,6 +8,8 @@ import { Modal } from 'shared/ui' import { DeleteUserModal } from './delete-account-modal' export const DeleteUserButton = () => { + const t = useTranslations('my-account') + const [isOpen, setIsOpen] = useState(false) const handleButtonClick = () => { @@ -19,7 +22,7 @@ export const DeleteUserButton = () => { className="body-large ml-auto block text-gray-400 transition-all hover:text-gray-500" onClick={handleButtonClick} > - Delete Account + {t('button.delete-account')} diff --git a/src/features/delete-account/ui/delete-account-success.tsx b/src/features/delete-account/ui/delete-account-success.tsx index 6c33dcf..def62b2 100644 --- a/src/features/delete-account/ui/delete-account-success.tsx +++ b/src/features/delete-account/ui/delete-account-success.tsx @@ -3,11 +3,14 @@ import { useQueryClient } from '@tanstack/react-query' import { useRouter } from 'next/navigation' import { signOut } from 'next-auth/react' +import { useTranslations } from 'next-intl' import { teacherSignOut } from 'entities/auth' import { Button, Modal } from 'shared/ui' export const DeleteUserSuccess = () => { + const t = useTranslations('my-account') + const queryClient = useQueryClient() const router = useRouter() @@ -26,14 +29,14 @@ export const DeleteUserSuccess = () => { onClose={handleCloseButtonClick} > - Account Deletion Completed + {t('success.delete-account.title')} - Thank you for joining Plus82. We support you on your journey ahead! + {t('success.delete-account.description')}
    - +
    diff --git a/src/features/delete-account/ui/delete-account.tsx b/src/features/delete-account/ui/delete-account.tsx index 4e0acb9..07a4877 100644 --- a/src/features/delete-account/ui/delete-account.tsx +++ b/src/features/delete-account/ui/delete-account.tsx @@ -2,6 +2,7 @@ import { useQueryClient } from '@tanstack/react-query' import { isNull } from 'lodash-es' +import { useTranslations } from 'next-intl' import { useActionState, useEffect } from 'react' import { deleteUserMe } from 'entities/user' @@ -13,6 +14,8 @@ type Props = { } export const DeleteUser = ({ onSucceed }: Props) => { + const t = useTranslations('my-account') + const queryClient = useQueryClient() const [state, formAction, isPending] = useActionState(deleteUserMe, null) @@ -31,26 +34,25 @@ export const DeleteUser = ({ onSucceed }: Props) => { return ( - Delete Account + {t('delete-account-modal.title')}
    - If you cancel your membership, all the resumes you have created, your - application history and any offers received will be permanently lost. + {t('delete-account-modal.description1')} - Do you still want to proceed with the cancellation? + {t('delete-account-modal.description2')}
    diff --git a/src/pages/my-academy/ui/change-password-page.tsx b/src/pages/my-academy/ui/change-password-page.tsx index 2d30331..301f027 100644 --- a/src/pages/my-academy/ui/change-password-page.tsx +++ b/src/pages/my-academy/ui/change-password-page.tsx @@ -3,6 +3,7 @@ import { useQueryClient } from '@tanstack/react-query' import { useRouter } from 'next/navigation' import { signOut } from 'next-auth/react' +import { useTranslations } from 'next-intl' import { useForm, useWatch } from 'react-hook-form' import { toast } from 'react-toastify' @@ -24,6 +25,8 @@ import { } from '../model/form-values' export const ChangePasswordPage = () => { + const t = useTranslations('reset-password') + const queryClient = useQueryClient() const router = useRouter() @@ -42,7 +45,7 @@ export const ChangePasswordPage = () => { !isEmptyString(currentPassword) && !isEmptyString(newPassword) const handleChangePasswordSuccess = async () => { - toast.success('비밀번호가 변경되었어요. 다시 로그인 해주세요.') + toast.success(t('success.update-password')) queryClient.removeQueries() await businessSignOut() @@ -64,11 +67,11 @@ export const ChangePasswordPage = () => { return (
    -

    비밀번호 변경

    +

    {t('title')}

    - + @@ -76,7 +79,7 @@ export const ChangePasswordPage = () => {
    - +
    { onClick={form.handleSubmit(submitForm)} disabled={!canSubmit} > - 변경하기 + {t('button.reset-password')}
    diff --git a/src/pages/my-academy/ui/my-academy.tsx b/src/pages/my-academy/ui/my-academy.tsx index 03d9c41..f04b380 100644 --- a/src/pages/my-academy/ui/my-academy.tsx +++ b/src/pages/my-academy/ui/my-academy.tsx @@ -12,7 +12,7 @@ export const MyAcademyPage = async () => {

    {academy.name}

    담당자

    -

    {user.firstName}

    +

    {user?.fullName}

    diff --git a/src/pages/my-academy/ui/personal-information-page.tsx b/src/pages/my-academy/ui/personal-information-page.tsx index 629388c..ad4d248 100644 --- a/src/pages/my-academy/ui/personal-information-page.tsx +++ b/src/pages/my-academy/ui/personal-information-page.tsx @@ -1,5 +1,6 @@ 'use client' +import { useTranslations } from 'next-intl' import { useForm } from 'react-hook-form' import { toast } from 'react-toastify' @@ -21,6 +22,8 @@ type Props = { } export const PersonalInformationPage = ({ user }: Props) => { + const t = useTranslations('my-account') + const form = useForm({ defaultValues: convertToUpdateUserMeFormValues(user), reValidateMode: 'onSubmit', @@ -29,7 +32,7 @@ export const PersonalInformationPage = ({ user }: Props) => { const { handleServerError } = useServerErrorHandler() const handleSuccess = () => { - toast.success('Your personal information has been updated') + toast.success(t('success.save')) } const submitForm = (data: UpdateUserMeFormValues) => { @@ -46,7 +49,7 @@ export const PersonalInformationPage = ({ user }: Props) => {

    - 개인 정보 + {t('title')}

    @@ -59,7 +62,7 @@ export const PersonalInformationPage = ({ user }: Props) => { fullWidth onClick={form.handleSubmit(submitForm)} > - 저장하기 + {t('button.save')} diff --git a/src/shared/config/internationalization/locales/en/auth.json b/src/shared/config/internationalization/locales/en/auth.json index 10e1d18..10d2fe0 100644 --- a/src/shared/config/internationalization/locales/en/auth.json +++ b/src/shared/config/internationalization/locales/en/auth.json @@ -43,6 +43,7 @@ "reset-password": { "title": "Reset Password", "label": { + "current-password": "Current password", "new-password": "New password", "confirm-new-password": "Confirm password" }, @@ -54,9 +55,11 @@ "reset-password": "Reset Password" }, "success": { - "reset-password": "Your password has been changed successfully" + "reset-password": "Your password has been changed successfully", + "update-password": "Your password has been changed successfully. Please sign in again." }, "error": { + "incorrect": "The current password is incorrect", "reset-password": "An error occurred while resetting the password" } }, @@ -89,5 +92,33 @@ "email-not-verified": "This email address is not verified", "email-verification-code-not-checked": "Please click the verify button to complete email verification" } + }, + "my-account": { + "title": "Personal Information", + "delete-account-modal": { + "title": "Delete Account", + "description1": "If you cancel your membership, all the resumes you have created, your application history and any offers received will be permanently lost.", + "description2": "Do you still want to proceed with the cancellation?" + }, + "button": { + "save": "Save", + "delete-account": "Delete Account", + "close": "Close", + "cancel": "Cancel", + "delete": "Delete", + "confirm": "Confirm" + }, + "success": { + "save": "Your personal information has been updated", + "delete-account": { + "title": "Account Deletion Completed", + "description": "Thank you for joining Plus82. We support you on your journey ahead!" + } + }, + "error": { + "save": "An error occurred while updating your personal information", + "account-deactivated": "This account has been deactivated", + "delete-account": "An error occurred while deleting the account" + } } } diff --git a/src/shared/config/internationalization/locales/ko/auth.json b/src/shared/config/internationalization/locales/ko/auth.json index 9ebfb3d..830c038 100644 --- a/src/shared/config/internationalization/locales/ko/auth.json +++ b/src/shared/config/internationalization/locales/ko/auth.json @@ -43,6 +43,7 @@ "reset-password": { "title": "비밀번호 변경", "label": { + "current-password": "현재 비밀번호", "new-password": "새 비밀번호", "confirm-new-password": "새 비밀번호 확인" }, @@ -54,9 +55,11 @@ "reset-password": "변경하기" }, "success": { - "reset-password": "비밀번호가 변경되었습니다" + "reset-password": "비밀번호가 변경되었습니다", + "update-password": "비밀번호가 변경되었어요. 다시 로그인 해주세요." }, "error": { + "incorrect": "현재 비밀번호가 일치하지 않습니다", "reset-password": "비밀번호 변경 중 오류가 발생했습니다" } }, @@ -89,5 +92,33 @@ "email-not-verified": "이메일 주소가 인증되지 않았습니다", "email-verification-code-not-checked": "이메일 인증 확인 버튼을 눌러주세요" } + }, + "my-account": { + "title": "개인 정보", + "delete-account-modal": { + "title": "회원 탈퇴", + "description1": "만약 회원 탈퇴를 하시면, 등록하신 모든 이력서와 지원 이력이 삭제됩니다. 또한 Plus82는 이력 삭제에 대해 책임지지 않습니다.", + "description2": "회원 탈퇴를 진행하시겠습니까?" + }, + "button": { + "save": "저장하기", + "delete-account": "회원 탈퇴", + "close": "닫기", + "cancel": "취소", + "delete": "회원 탈퇴", + "confirm": "확인" + }, + "success": { + "save": "정보를 저장했어요", + "delete-account": { + "title": "회원 탈퇴가 완료되었습니다", + "description": "그동안 Plus82를 이용해주셔서 감사합니다. 당신의 여정을 응원합니다!" + } + }, + "error": { + "save": "정보를 저장하는 중 오류가 발생했습니다", + "account-deactivated": "이 계정은 비활성화되었습니다", + "delete-account": "회원 탈퇴 중 오류가 발생했습니다" + } } } From 2e8e6f81e8a5ce6061eeb7a1d982cd4ec26e4b82 Mon Sep 17 00:00:00 2001 From: mina-gwak Date: Wed, 4 Jun 2025 00:27:10 +0900 Subject: [PATCH 7/8] =?UTF-8?q?Feat:=20PD-279=20GNB=20=EB=8B=A4=EA=B5=AD?= =?UTF-8?q?=EC=96=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/ui/gnb/business-button.tsx | 63 +++++++++++++++------------ src/shared/ui/gnb/business-gnb.tsx | 4 +- src/shared/ui/gnb/gnb.tsx | 6 +-- src/shared/ui/gnb/notification.tsx | 6 ++- src/shared/ui/gnb/user-button.tsx | 11 +++-- 5 files changed, 54 insertions(+), 36 deletions(-) diff --git a/src/shared/ui/gnb/business-button.tsx b/src/shared/ui/gnb/business-button.tsx index f7ba62b..0449835 100644 --- a/src/shared/ui/gnb/business-button.tsx +++ b/src/shared/ui/gnb/business-button.tsx @@ -1,7 +1,7 @@ import { useQueryClient } from '@tanstack/react-query' import { useRouter } from 'next/navigation' import { signOut } from 'next-auth/react' -import { useLocale } from 'next-intl' +import { useLocale, useTranslations } from 'next-intl' import { startTransition } from 'react' import { businessSignOut } from 'entities/auth' @@ -17,6 +17,9 @@ export const BusinessButton = () => { const queryClient = useQueryClient() const locale = useLocale() + const t = useTranslations() + + const isDev = process.env.NODE_ENV === 'development' const { isOpen, toggleIsOpen, close, dropdownRef } = useDropdown() const { @@ -81,33 +84,39 @@ export const BusinessButton = () => { className="w-[140px] shadow-[0px_2px_8px_0px_rgba(0,0,0,0.08)]" scrollable={false} > - My Page - -
    - Language - {isSubMenuOpen && ( - - - English - - - Korean - - - )} -
    + + {t('dropdown.my-page')} + + {isDev && ( + +
    + Language + {isSubMenuOpen && ( + + + English + + + Korean + + + )} +
    +
    + )} + + {t('dropdown.sign-out')} - Sign Out )}
    diff --git a/src/shared/ui/gnb/business-gnb.tsx b/src/shared/ui/gnb/business-gnb.tsx index 6fd9dca..4ce25f2 100644 --- a/src/shared/ui/gnb/business-gnb.tsx +++ b/src/shared/ui/gnb/business-gnb.tsx @@ -35,9 +35,9 @@ export const BusinessGNB = () => { {isDev && ( - + {/* {t('tab.find-teacher')} - + */} {t('tab.applicant-management')} diff --git a/src/shared/ui/gnb/gnb.tsx b/src/shared/ui/gnb/gnb.tsx index cdee512..bb55b92 100644 --- a/src/shared/ui/gnb/gnb.tsx +++ b/src/shared/ui/gnb/gnb.tsx @@ -41,11 +41,11 @@ export const GNB = () => { {notAuthenticated ? (
    ) : ( @@ -55,7 +55,7 @@ export const GNB = () => {
    )}
    diff --git a/src/shared/ui/gnb/notification.tsx b/src/shared/ui/gnb/notification.tsx index ca3cf74..cb62f05 100644 --- a/src/shared/ui/gnb/notification.tsx +++ b/src/shared/ui/gnb/notification.tsx @@ -1,3 +1,5 @@ +import { useTranslations } from 'next-intl' + import { colors } from 'shared/config' import { useDropdown } from 'shared/lib' @@ -5,6 +7,8 @@ import { Dropdown } from '../dropdown' import { Icon } from '../icon' export const NotificationButton = () => { + const t = useTranslations() + const { isOpen, toggleIsOpen, dropdownRef } = useDropdown() const handleClick = () => { @@ -34,7 +38,7 @@ export const NotificationButton = () => { className="relative -top-px" />

    - You have no notifications yet + {t('notification.empty')}

    diff --git a/src/shared/ui/gnb/user-button.tsx b/src/shared/ui/gnb/user-button.tsx index 3a6ad6b..94bb94a 100644 --- a/src/shared/ui/gnb/user-button.tsx +++ b/src/shared/ui/gnb/user-button.tsx @@ -1,7 +1,7 @@ import { useQueryClient } from '@tanstack/react-query' import { useRouter } from 'next/navigation' import { signOut } from 'next-auth/react' -import { useLocale } from 'next-intl' +import { useLocale, useTranslations } from 'next-intl' import { startTransition } from 'react' import { teacherSignOut } from 'entities/auth' @@ -17,6 +17,7 @@ export const UserButton = () => { const queryClient = useQueryClient() const locale = useLocale() + const t = useTranslations() const isDev = process.env.NODE_ENV === 'development' @@ -83,7 +84,9 @@ export const UserButton = () => { className="w-[140px] shadow-[0px_2px_8px_0px_rgba(0,0,0,0.08)]" scrollable={false} > - My Page + + {t('dropdown.my-page')} + {isDev && ( {
    )} - Sign Out + + {t('dropdown.sign-out')} + )}
    From 308078f9ec1e1a959688a964f71dadefa3d43a20 Mon Sep 17 00:00:00 2001 From: mina-gwak Date: Wed, 4 Jun 2025 00:30:16 +0900 Subject: [PATCH 8/8] =?UTF-8?q?Fix:=20=EC=83=81=EC=84=B8=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EC=97=90=EC=84=9C=20=EC=84=B8=EC=85=98=20?= =?UTF-8?q?=EC=97=86=EC=9D=B4=EB=8F=84=20=EC=A0=91=EA=B7=BC=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(user)/job-board/[jobPostId]/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/(user)/job-board/[jobPostId]/page.tsx b/app/(user)/job-board/[jobPostId]/page.tsx index 207922e..a67bcf6 100644 --- a/app/(user)/job-board/[jobPostId]/page.tsx +++ b/app/(user)/job-board/[jobPostId]/page.tsx @@ -1,4 +1,4 @@ -import { getTeacherSession } from 'entities/auth' +import { getNullableTeacherSession } from 'entities/auth' import { getJobPost, getTeacherApplicationStatus } from 'entities/job-post' import { getResumeCount } from 'entities/resume' import { JobPostingDetailPage as _JobPostingDetailPage } from 'pages/job-post' @@ -14,7 +14,7 @@ const JobPostingDetailPage = async ({ }) => { const { jobPostId } = await params - const session = await getTeacherSession() + const session = await getNullableTeacherSession() const resumeCount = await getResumeCount() const jobPostDetail = await getJobPost({ jobPostId: Number(jobPostId) })