diff --git a/EATSSU/App/Resources/en.lproj/Localizable.strings b/EATSSU/App/Resources/en.lproj/Localizable.strings
new file mode 100644
index 00000000..6b8944e4
--- /dev/null
+++ b/EATSSU/App/Resources/en.lproj/Localizable.strings
@@ -0,0 +1,461 @@
+//
+// Localizable.strings
+// EATSSU
+//
+// Created by jeongminji on 5/3/26.
+//
+
+// MARK: - Common
+
+/// "숭실대에서 먹자"
+"common.logoSubTitle" = "Eat at Soongsil";
+/// "확인"
+"common.confirm" = "Confirm";
+/// "취소"
+"common.cancel" = "Cancel";
+/// "취소하기"
+"common.cancelDark" = "Cancel";
+/// "삭제하기"
+"common.delete" = "Delete";
+/// "수정하기"
+"common.fix" = "Edit";
+/// "로그인이 필요한 서비스입니다"
+"common.needLogin" = "Login is required to use this service.";
+/// "로그인 하시겠습니까?"
+"common.askLogin" = "Would you like to log in?";
+/// "설정으로 이동"
+"common.moveToSetting" = "Go to Settings";
+/// "탈퇴 처리가 완료되었습니다."
+"common.withdrawComplete" = "Your account has been deleted.";
+/// "잠시 후 다시 시도해주세요."
+"common.tryAgain" = "Please try again later.";
+/// "세션이 만료되었습니다. 다시 로그인해주세요."
+"common.sessionExpired" = "Your session has expired. Please log in again.";
+/// "에러가 발생했습니다"
+"common.errorOccured" = "An error occurred.";
+/// "다시 시도하세요"
+"common.retry" = "Try Again";
+
+
+// MARK: - TabBar
+
+/// "학식"
+"tabBar.meal" = "Cafeteria";
+/// "지도"
+"tabBar.map" = "Map";
+/// "나만아니면돼~"
+"tabBar.coffee" = "Lucky Game";
+/// "마이"
+"tabBar.my" = "My";
+
+
+// MARK: - Auth
+
+/// "닉네임을 입력해주세요"
+"auth.inputNickName" = "Please enter a nickname";
+/// "Apple로 로그인"
+"auth.signInWithApple" = "Sign in with Apple";
+/// "카카오 로그인"
+"auth.signInWithKakao" = "Login with Kakao";
+/// "둘러보기"
+"auth.lookingWithNoSignIn" = "Browse Without Signing In";
+/// "최근에 로그인했어요"
+"auth.lastLoginTooltip" = "Recently used";
+/// LoginVC - "카카오톡으로 생성된 계정입니다."
+"auth.kakaoAccount" = "This account was created with KakaoTalk.";
+/// LoginVC - "Apple로 생성된 계정입니다."
+"auth.appleAccount" = "This account was created with Apple.";
+/// SetNickNameView - "닉네임 설정"
+"auth.setNickname" = "Edit Nickname";
+/// SetNickNameView - "중복 확인"
+"auth.checkDuplicate" = "Check Availability";
+/// SetNickNameView - "소속 설정"
+"auth.setCollege" = "Set Affiliation";
+/// SetNickNameView - "단과대"
+"auth.college" = "College";
+/// SetNickNameView - "학과"
+"auth.department" = "Department";
+/// SetNickNameView - "연결된 계정"
+"auth.linkedAccount" = "Linked Account";
+/// SetNickNameView - "없음"
+"auth.empty" = "None";
+/// SetNickNameView - "저장하기"
+"auth.save" = "Save";
+/// SetNickNameView - "카카오"
+"auth.kakao" = "Kakao";
+/// SetNickNameView - "APPLE"
+"auth.apple" = "APPLE";
+/// SetNickNameVC - "변경된 정보가 없습니다."
+"auth.noChanges" = "No information has changed.";
+/// SetNickNameVC - "유효하지 않은 학과 정보입니다."
+"auth.invalidDepartment" = "Invalid department information.";
+/// SetNickNameVC - "정보 업데이트 중 오류가 발생했습니다."
+"auth.updateError" = "An error occurred while updating your information.";
+/// SetNickNameVC - "내 정보가 수정되었어요."
+"auth.updateSuccess" = "Your information has been updated.";
+/// NIcknameTextFieldResultType - "필수 입력 사항입니다"
+"auth.requiredInput" = "Required field";
+/// NIcknameTextFieldResultType - "중복 확인을 진행해주세요."
+"auth.needCheckDuplicate" = "Please check availability.";
+/// NIcknameTextFieldResultType - "이미 사용 중인 닉네임이에요."
+"auth.duplicatedNickname" = "This nickname is already in use.";
+/// NIcknameTextFieldResultType - "사용가능한 닉네임이에요"
+"auth.availableNickname" = "This nickname is available.";
+/// NIcknameTextFieldResultType - "2~16글자를 입력해 주세요."
+"auth.nicknameLength" = "Enter 2–16 characters";
+/// NIcknameTextFieldResultType - "특수문자로 시작/끝나는 닉네임은 사용할 수 없어요."
+"auth.specialCharNickname" = "Nicknames cannot start or end with a special character.";
+/// NIcknameTextFieldResultType - "연속된 특수문자(--, __)는 사용할 수 없어요."
+"auth.continuousSpecialChar" = "Consecutive special characters (--, __) are not allowed.";
+/// NIcknameTextFieldResultType - "숫자만으로 된 닉네임은 사용할 수 없어요."
+"auth.numberOnlyNickname" = "Nicknames made only of numbers are not allowed.";
+/// NIcknameTextFieldResultType - "허용 문자(한글/영문/숫자)만 사용할 수 있어요."
+"auth.allowedChar" = "Only Korean/English letters and numbers are allowed.";
+/// NIcknameTextFieldResultType - "사용할 수 없는 단어가 포함되어 있어요."
+"auth.bannedWord" = "This nickname contains a word that cannot be used.";
+/// NIcknameTextFieldResultType - "띄어쓰기로 시작/끝나는 닉네임은 사용할 수 없어요."
+"auth.spaceNickname" = "Nicknames cannot start or end with a space.";
+/// NIcknameTextFieldResultType - "연속된 띄어쓰기는 사용할 수 없어요."
+"auth.continuousSpace" = "Consecutive spaces are not allowed.";
+/// NIcknameTextFieldResultType - "이모지, 특수문자는 사용할 수 없어요."
+"auth.emojiSpecialChar" = "Emoji and special characters are not allowed.";
+/// NIcknameTextFieldResultType - "관리자로 혼동될 수 있는 닉네임은 사용할 수 없어요."
+"auth.adminNickname" = "Nicknames that may be confused with an admin are not allowed.";
+/// NIcknameTextFieldResultType - "서비스명 단독 닉네임은 사용할 수 없어요."
+"auth.serviceNameNickname" = "You cannot use the service name by itself as a nickname.";
+/// NIcknameTextFieldResultType - "욕설, 비속어 등의 표현이 포함된 닉네임은 사용할 수 없어요."
+"auth.slangNickname" = "Nicknames containing profanity or slang are not allowed.";
+
+
+// MARK: - Home
+/// Home - "오늘의 메뉴"
+"home.todayMenu" = "Today's Menu";
+/// Home - "가격"
+"home.price" = "Price";
+/// Home - "평점"
+"home.rating" = "Rating";
+/// Home - " -"
+"home.emptyRating" = " -";
+/// Home - "제공되는 메뉴가 없습니다"
+"home.noMenuProvidedMessage" = "No menu is available.";
+/// CustomTimeTabController - "아침"
+"home.morning" = "Breakfast";
+/// CustomTimeTabController - "점심"
+"home.lunch" = "Lunch";
+/// CustomTimeTabController - "저녁"
+"home.dinner" = "Dinner";
+/// RestaurantInfoView - "학생 식당"
+"home.studentRestaurant" = "Student Restaurant";
+/// RestaurantInfoView - "식당 위치"
+"home.restaurantLocation" = "Location";
+/// RestaurantInfoView - "식당 사진"
+"home.restaurantPicture" = "Photos";
+/// RestaurantInfoView - "숭실대학교"
+"home.soongsilUniversity" = "Soongsil University";
+/// RestaurantInfoView - "영업 시간"
+"home.businessHour" = "Hours";
+/// RestaurantInfoView - "비고"
+"home.note" = "Notes";
+/// RestaurantInfoView - "아시안푸드, 돈까스, 샐러드, 국밥 등\n카페"
+"home.dodamEtc" = "Asian food, pork cutlet, salad, gukbap, etc.\nCafe";
+/// RestaurantMenuGroupCell - "영업 시간이 아니에요."
+"home.notBusinessHour" = "Closed now";
+/// RestaurantTableViewHeader - "기숙사 식당"
+"home.dormitoryRestaurant" = "Dormitory Restaurant";
+
+
+// MARK: - Map
+
+/// MainMapVC - "제휴 지도"
+"map.map" = "Partnership Map";
+/// MainMapView - "전체"
+"map.all" = "All";
+/// MainMapView - "내 제휴"
+"map.myPartner" = "My Partnerships";
+/// NoDepartmentSheetVC - "학과를 입력하고\n나만의 제휴를 확인해보세요!"
+"map.inputDepartment" = "Enter your department\nand check your own partnerships!";
+/// NoDepartmentSheetVC - "학과 입력하기"
+"map.inputDepartmentButton" = "Enter Department";
+/// PartnershipDetailSheetVC - "음식점"
+"map.restaurant" = "Restaurant";
+/// PartnershipDetailSheetVC - "카페"
+"map.cafe" = "Cafe";
+/// PartnershipDetailSheetVC - "주점"
+"map.pub" = "Pub";
+/// PartnershipDetailSheetVC - "학과 정보 없음"
+"map.noDepartmentInfo" = "No Department Info";
+/// MainMapVC+Location - "위치 권한 필요"
+"map.needLocationAuth" = "Location Permission Required";
+/// MainMapVC+Location - "지도에서 내 위치를 바로 확인하고, 현재 위치 주변의 제휴점들을 손쉽게 찾아볼 수 있도록 위치 권한을 허용해 주세요."
+"map.locationAuthDescription" = "Please allow location permission so you can check your location on the map and easily find nearby partners.";
+
+
+// MARK: - MyPage
+
+/// "마이페이지"
+"myPage.myPage" = "My Page";
+/// UserWithdrawVC - "회원탈퇴"
+"myPage.withdraw" = "Delete Account";
+/// MyPageVC - "EAT-SSU 수신 동의"
+"myPage.agreeNoti" = "EAT-SSU notifications enabled (%@)";
+/// MyPageVC - "EAT-SSU 수신 거절"
+"myPage.disagreeNoti" = "EAT-SSU notifications disabled (%@)";
+
+// MARK: - MyPageSection: 알림 및 활동
+/// MyPageSectionVC - "알림 및 활동"
+"myPage.activitySection" = "Notifications & Activity";
+/// "내 정보"
+"myPage.myInfo" = "My Info";
+/// "내 리뷰"
+"myPage.myReview" = "My Reviews";
+/// NotificationSettingTableViewCell - "푸시 알림 설정"
+"myPage.pushNotificationSetting" = "Push Notification Settings";
+/// NotificationSettingTableViewCell - "매일 오전 11시에 알림을 보내드려요"
+"myPage.pushNotificationDescription" = "We'll send you a notification every day at 11 AM.";
+/// MyPageVC - "알림 설정 중 오류가 발생했습니다."
+"myPage.notiSettingError" = "An error occurred while updating notification settings.";
+
+// MARK: - MyPageSection: 서비스 정보
+// MyPageSectionVC - "서비스 정보"
+"myPage.serviceInfoSection" = "Service Info";
+/// MyPageVC - "문의하기"
+"myPage.inquiry" = "Contact Us";
+/// CreatorVC - "만든 사람들"
+"myPage.creators" = "Creators";
+/// MyPageVC - "EAT-SSU 인스타그램"
+"myPage.instagram" = "EAT-SSU Instagram";
+
+// MARK: - MyPageSection: 기타
+// MyPageSectionVC - "기타"
+"myPage.etcSection" = "Other";
+/// MyPageVC - "언어 설정"
+"myPage.languageSetting" = "Language";
+/// MyPageVC - "지원 언어: 한국어"
+"myPage.language.korean" = "Korean";
+/// MyPageVC - "지원 언어: 영어"
+"myPage.language.english" = "English";
+/// MyPageVC - "약관 및 정책"
+"myPage.termsAndPolicy" = "Terms & Policies";
+/// MyPageVC - "서비스 이용약관"
+"myPage.termsOfUse" = "Terms of Service";
+/// MyPageVC - "개인정보처리방침"
+"myPage.privacyTermsOfUse" = "Privacy Policy";
+/// MyPageVC - "로그아웃"
+"myPage.logout" = "Log Out";
+/// MyPageVC - "정말 로그아웃 하시겠습니까?"
+"myPage.askLogout" = "Are you sure you want to log out?";
+
+/// MyReviewVC - "리뷰 수정 혹은 삭제"
+"myPage.fixOrDeleteReview" = "Edit or Delete Review";
+/// MyReviewVC - "작성하신 리뷰를 수정 또는 삭제하시겠습니까?"
+"myPage.askFixOrDeleteReview" = "Would you like to edit or delete this review?";
+/// MyReviewVC - "리뷰 삭제하기"
+"myPage.deleteMyReview" = "Delete Review";
+/// MyReviewVC - "해당 리뷰를 삭제할까요?"
+"myPage.askDeleteMyReview" = "Delete this review?";
+/// MyReviewVC - "리뷰가 성공적으로 삭제되었습니다."
+"myPage.deleteMyReviewSuccess" = "Your review has been deleted successfully.";
+/// MyPageView - "다시 시도해주세요"
+"myPage.retry" = "Please try again.";
+/// MyPageView - "앱 버전"
+"myPage.appVersion" = "App Version";
+/// MyPageView - "탈퇴하기"
+"myPage.withdrawButton" = "Delete Account";
+/// MyPageView - "알 수 없음"
+"myPage.unknownUser" = "Unknown";
+/// ProvisionVC - "이용약관"
+"myPage.defaultTerms" = "Terms of Service";
+/// UserWithdrawView - "정말 탈퇴하시겠습니까?"
+"myPage.confirmWithdrawal" = "Are you sure you want to delete your account?";
+/// UserWithdrawView - "작성한 리뷰 게시글은 삭제되지 않으며, (알수없음)으로 표시됩니다.\n자세한 내용은 서비스이용약관 및 개인정보처리방침을 확인해 주세요."
+"myPage.withdrawalNotice" = "Your posted reviews will not be deleted and will be shown as (Unknown).\nFor details, please check the Terms of Service and Privacy Policy.";
+/// UserWithdrawView - "올바른 입력입니다."
+"myPage.validInputMessage" = "Valid input.";
+/// UserWithdrawView - "올바르지 않은 닉네임입니다"
+"myPage.invalidNicknameMessage" = "Invalid nickname.";
+
+
+// MARK: - Review
+
+/// ReportVC - "EAT SSU 팀에게 보내기"
+"review.sendToTeam" = "Send to the EAT SSU Team";
+/// ReportVC - "신고하기"
+"review.report" = "Report";
+/// ReportVC - "사유를 선택해주세요!"
+"review.selectReason" = "Please select a reason!";
+/// ReportVC - "신고가 성공적으로 접수되었어요!"
+"review.reportSuccess" = "Your report has been submitted successfully!";
+/// ReportVC, ReportView - "메뉴와 관련없는 내용"
+"review.unrelatedMenu" = "Content unrelated to the menu";
+/// ReportVC, ReportView - "음란성, 욕설 등 부적절한 내용"
+"review.inappropriateContent" = "Inappropriate content such as profanity or explicit content";
+/// ReportVC, ReportView - "부적절한 홍보 또는 광고"
+"review.inappropriateAd" = "Inappropriate promotion or advertisement";
+/// ReportVC, ReportView - "리뷰 작성 취지에 맞지 않는 내용 (복사글 등)"
+"review.notReviewFormat" = "Content that does not fit the purpose of a review (e.g. copied text)";
+/// ReportVC, ReportView - "저작권 도용 의심 (사진 등)"
+"review.copyright" = "Suspected copyright infringement (e.g. photos)";
+/// ReportVC, ReportView - "기타 (하단 내용 작성)"
+"review.etc" = "Other (write details below)";
+/// ReportView - "리뷰 신고 사유를 알려주세요"
+"review.reportReason" = "Tell us why you are reporting this review";
+/// ReportView - "하나의 리뷰에 대해 24시간 내 한 번만 신고 가능합니다."
+"review.reportGuide" = "You can report the same review only once every 24 hours.";
+/// ReportView - "리뷰 신고 사유를 작성해 주세요"
+"review.inputReportReason" = "Please enter the reason for reporting this review.";
+/// ReviewVC - "리뷰 작성하기"
+"review.writeReview" = "Write a Review";
+/// ReviewVC - "리뷰가 성공적으로 등록되었습니다."
+"review.registerReviewSuccess" = "Your review has been posted successfully.";
+/// ReviewVC - "리뷰"
+"review.review" = "Reviews";
+/// ReviewVC - "리뷰 삭제"
+"review.deleteReview" = "Delete Review";
+/// ReviewVC - "해당 리뷰를 삭제할까요?"
+"review.askDeleteReview" = "Delete this review?";
+/// ReviewVC - "리뷰 신고하기"
+"review.reportReview" = "Report Review";
+/// ReviewVC - "해당 리뷰를 신고하시겠습니까?"
+"review.askReportReview" = "Would you like to report this review?";
+/// ReviewVC - "리뷰가 성공적으로 삭제되었습니다."
+"review.deleteReviewSuccess" = "Your review has been deleted successfully.";
+/// ReviewVC - "리뷰 삭제에 실패했습니다."
+"review.deleteReviewFail" = "Failed to delete the review.";
+/// SetRateVC - "리뷰 수정하기"
+"review.fixReview" = "Edit Review";
+/// SetRateVC - "리뷰 남기기"
+"review.leaveReview" = "Submit Review";
+/// 메뉴 이름의 받침 유무에 따라 '을/를'을 동적으로 붙여 추천 문장을 생성합니다.
+"review.recommendMenu.default" = "Would you recommend %@?";
+/// 메뉴 이름의 받침 유무에 따라 '을/를'을 동적으로 붙여 추천 문장을 생성합니다.
+"review.recommendMenu.withJongseong" = "Would you recommend %@?";
+/// 메뉴 이름의 받침 유무에 따라 '을/를'을 동적으로 붙여 추천 문장을 생성합니다.
+"review.recommendMenu.withoutJongseong" = "Would you recommend %@?";
+/// SetRateVC - "메뉴를 추천하시겠어요?"
+"review.recommendMenuTitle" = "Would you recommend this menu?";
+/// SetRateVC - "리뷰 수정 완료하기"
+"review.fixReviewComplete" = "Complete Review Edit";
+/// SetRateVC - "완료하기"
+"review.complete" = "Complete";
+/// SetRateVC - "별점을 입력해주세요!"
+"review.inputRating" = "Please enter a rating!";
+/// SetRateVC - "메뉴 목록 조회에 실패했습니다."
+"review.loadMenuListFail" = "Failed to load the menu list.";
+/// SetRateVC - "수정할 리뷰 정보가 없습니다."
+"review.noReviewInfoForFix" = "There is no review information to edit.";
+/// SetRateVC - "리뷰가 성공적으로 수정되었습니다."
+"review.fixReviewSuccess" = "Your review has been updated successfully.";
+/// SetRateVC - "리뷰 수정에 실패했습니다."
+"review.fixReviewFail" = "Failed to update the review.";
+/// SetRateVC - "식단 정보가 없습니다."
+"review.noMealInfo" = "No meal information is available.";
+/// SetRateVC - "리뷰 업로드에 실패했습니다."
+"review.uploadReviewFail" = "Failed to upload the review.";
+/// SetRateVC - "메뉴 정보가 없습니다."
+"review.noMenuInfo" = "No menu information is available.";
+/// SetRateVC - "메뉴에 대한 상세한 리뷰를 작성해주세요"
+"review.inputDetailReview" = "Please write a detailed review of the menu.";
+/// SetRateVC - "나가시겠어요?"
+"review.askLeave" = "Leave this page?";
+/// SetRateVC - "지금 나가면 작성한 내용이 저장되지 않습니다."
+"review.leaveWarning" = "Your changes will not be saved if you leave now.";
+/// SetRateVC - "나가기"
+"review.leave" = "Leave";
+/// SetRateVC - "계속 작성"
+"review.continueWriting" = "Keep Writing";
+/// ReviewEmptyViewCell - "아직 작성된 리뷰가 없어요!"
+"review.noReview" = "No reviews have been written yet!";
+/// ReviewEmptyViewCell - "메뉴에 가장 먼저 리뷰를 남겨주세요!"
+"review.beFirstReviewer" = "Be the first to leave a review for this menu!";
+/// ReviewEmptyViewCell - "로그인이 필요합니다"
+"review.needLogin" = "Login is required.";
+/// ReviewEmptyViewCell - "로그인 후 리뷰를 확인하세요"
+"review.checkReviewAfterLogin" = "Log in to view reviews.";
+/// ReviewEmptyViewCell - "아직 작성한 리뷰가 없어요"
+"review.noWrittenReview" = "You haven't written any reviews yet.";
+/// ReviewEmptyViewCell - "첫 리뷰를 남겨 주세요!"
+"review.writeFirstReview" = "Leave your first review!";
+/// ReviewDividerCell - "리뷰"
+"review.reviewCount" = "%d Reviews";
+/// ReviewRateViewCell - "오늘의 메뉴"
+"review.todayMenu" = "Today's Menu";
+/// ReviewRateViewCell - "5점"
+"review.fiveStars" = "5 Stars";
+/// ReviewRateViewCell - "4점"
+"review.fourStars" = "4 Stars";
+/// ReviewRateViewCell - "3점"
+"review.threeStars" = "3 Stars";
+/// ReviewRateViewCell - "2점"
+"review.twoStars" = "2 Stars";
+/// ReviewRateViewCell - "1점"
+"review.oneStar" = "1 Star";
+/// SetRateView - "오늘의 식사는 어떠셨나요?"
+"review.rateTodayMeal" = "How was your meal today?";
+/// SetRateView - "추천하고 싶은 메뉴가 있나요?"
+"review.recommendMenu" = "Any dishes you'd recommend?";
+/// SetRateView - "사진 추가 (0/1)"
+"review.addPhoto" = "Add Photo (%d/1)";
+/// character count
+"review.characterCount" = "%d / %d";
+
+
+// MARK: - Coffee
+
+/// "나가시겠어요?"
+"coffee.askLeave" = "Leave this page?";
+/// "지금 나가면 진행 상황이\n저장되지 않습니다."
+"coffee.leaveWarning" = "Your progress will not be saved\nif you leave now.";
+/// "나가기"
+"coffee.leave" = "Leave";
+/// "계속하기"
+"coffee.continueEvent" = "Continue";
+
+
+// MARK: - Splash
+
+/// NoticeSplashVC - "긴급 서버 점검 안내"
+"splash.serverInspection" = "Emergency Server Maintenance Notice";
+
+
+// MARK: - PromotionPopup
+
+/// 03. 16(월)~03. 27(금)
+"promotionPopup.period" = "03. 16(Mon)~03. 27(Fri)";
+/// EAT-SSU 인스타그램 바로가기
+"promotionPopup.instagramButtonTitle" = "Go to EAT-SSU Instagram";
+/// 자세한 내용은 EAT-SSU 인스타그램을 확인해 주세요
+"promotionPopup.guideMessage" = "Please check EAT-SSU Instagram for more details.";
+/// 다시 보지 않기
+"promotionPopup.neverShowAgain" = "Don't show again";
+/// 닫기
+"promotionPopup.close" = "Close";
+
+
+// MARK: - Notification
+
+/// 🤔 오늘 밥 뭐 먹지…
+"notification.dailyWeekdayNotificationTitle" = "🤔 What should I eat today…";
+/// 오늘의 학식을 확인해보세요!
+"notification.dailyWeekdayNotificationBody" = "Check today's cafeteria menu!";
+/// 알림 권한 필요
+"notification_error_permission_denied_message" = "Notification Permission Required";
+/// 알림을 받으려면 설정에서 알림 권한을 허용해주세요.
+"notification_error_permission_denied_description" = "Please allow notification permission in Settings to receive notifications.";
+/// 알 수 없는 오류
+"notification_error_unknown_message" = "Unknown Error";
+/// 다시 시도해주세요.
+"notification_error_unknown_description" = "Please try again.";
+
+
+// MARK: - Restaurant
+
+/// "기숙사 식당"
+"restaurant.dormitoryRestaurant" = "Dormitory Restaurant";
+/// "도담 식당"
+"restaurant.dodamRestaurant" = "Dodam Restaurant";
+/// "학생 식당"
+"restaurant.studentRestaurant" = "Student Restaurant";
+/// "스낵 코너"
+"restaurant.snackCorner" = "Snack Corner";
+/// "FACULTY (교직원 전용)"
+"restaurant.facultyRestaurant" = "FACULTY (Faculty Only)";
diff --git a/EATSSU/App/Resources/ko.lproj/Localizable.strings b/EATSSU/App/Resources/ko.lproj/Localizable.strings
new file mode 100644
index 00000000..775190d3
--- /dev/null
+++ b/EATSSU/App/Resources/ko.lproj/Localizable.strings
@@ -0,0 +1,462 @@
+//
+// Localizable.strings
+// EATSSU
+//
+// Created by jeongminji on 5/3/26.
+//
+
+// MARK: - Common
+
+/// "숭실대에서 먹자"
+"common.logoSubTitle" = "숭실대에서 먹자";
+/// "확인"
+"common.confirm" = "확인";
+/// "취소"
+"common.cancel" = "취소";
+/// "취소하기"
+"common.cancelDark" = "취소하기";
+/// "삭제하기"
+"common.delete" = "삭제하기";
+/// "수정하기"
+"common.fix" = "수정하기";
+/// "로그인이 필요한 서비스입니다"
+"common.needLogin" = "로그인이 필요한 서비스입니다";
+/// "로그인 하시겠습니까?"
+"common.askLogin" = "로그인 하시겠습니까?";
+/// "설정으로 이동"
+"common.moveToSetting" = "설정으로 이동";
+/// "탈퇴 처리가 완료되었습니다."
+"common.withdrawComplete" = "탈퇴 처리가 완료되었습니다.";
+/// "잠시 후 다시 시도해주세요."
+"common.tryAgain" = "잠시 후 다시 시도해주세요.";
+/// "세션이 만료되었습니다. 다시 로그인해주세요."
+"common.sessionExpired" = "세션이 만료되었습니다. 다시 로그인해주세요.";
+/// "에러가 발생했습니다"
+"common.errorOccured" = "에러가 발생했습니다";
+/// "다시 시도하세요"
+"common.retry" = "다시 시도하세요";
+
+
+// MARK: - TabBar
+
+/// "학식"
+"tabBar.meal" = "학식";
+/// "지도"
+"tabBar.map" = "지도";
+/// "나만아니면돼~"
+"tabBar.coffee" = "나만아니면돼~";
+/// "마이"
+"tabBar.my" = "마이";
+
+
+// MARK: - Auth
+
+/// "닉네임을 입력해주세요"
+"auth.inputNickName" = "닉네임을 입력해주세요";
+/// "Apple로 로그인"
+"auth.signInWithApple" = "Apple로 로그인";
+/// "카카오 로그인"
+"auth.signInWithKakao" = "카카오 로그인";
+/// "둘러보기"
+"auth.lookingWithNoSignIn" = "둘러보기";
+/// "최근에 로그인했어요"
+"auth.lastLoginTooltip" = "최근에 로그인했어요";
+/// LoginVC - "카카오톡으로 생성된 계정입니다."
+"auth.kakaoAccount" = "카카오톡으로 생성된 계정입니다.";
+/// LoginVC - "Apple로 생성된 계정입니다."
+"auth.appleAccount" = "Apple로 생성된 계정입니다.";
+/// SetNickNameView - "닉네임 설정"
+"auth.setNickname" = "닉네임 설정";
+/// SetNickNameView - "중복 확인"
+"auth.checkDuplicate" = "중복 확인";
+/// SetNickNameView - "소속 설정"
+"auth.setCollege" = "소속 설정";
+/// SetNickNameView - "단과대"
+"auth.college" = "단과대";
+/// SetNickNameView - "학과"
+"auth.department" = "학과";
+/// SetNickNameView - "연결된 계정"
+"auth.linkedAccount" = "연결된 계정";
+/// SetNickNameView - "없음"
+"auth.empty" = "없음";
+/// SetNickNameView - "저장하기"
+"auth.save" = "저장하기";
+/// SetNickNameView - "카카오"
+"auth.kakao" = "카카오";
+/// SetNickNameView - "APPLE"
+"auth.apple" = "APPLE";
+/// SetNickNameVC - "변경된 정보가 없습니다."
+"auth.noChanges" = "변경된 정보가 없습니다.";
+/// SetNickNameVC - "유효하지 않은 학과 정보입니다."
+"auth.invalidDepartment" = "유효하지 않은 학과 정보입니다.";
+/// SetNickNameVC - "정보 업데이트 중 오류가 발생했습니다."
+"auth.updateError" = "정보 업데이트 중 오류가 발생했습니다.";
+/// SetNickNameVC - "내 정보가 수정되었어요."
+"auth.updateSuccess" = "내 정보가 수정되었어요.";
+/// NIcknameTextFieldResultType - "필수 입력 사항입니다"
+"auth.requiredInput" = "필수 입력 사항입니다";
+/// NIcknameTextFieldResultType - "중복 확인을 진행해주세요."
+"auth.needCheckDuplicate" = "중복 확인을 진행해주세요.";
+/// NIcknameTextFieldResultType - "이미 사용 중인 닉네임이에요."
+"auth.duplicatedNickname" = "이미 사용 중인 닉네임이에요.";
+/// NIcknameTextFieldResultType - "사용가능한 닉네임이에요"
+"auth.availableNickname" = "사용가능한 닉네임이에요";
+/// NIcknameTextFieldResultType - "2~16글자를 입력해 주세요."
+"auth.nicknameLength" = "2~16글자를 입력해 주세요.";
+/// NIcknameTextFieldResultType - "특수문자로 시작/끝나는 닉네임은 사용할 수 없어요."
+"auth.specialCharNickname" = "특수문자로 시작/끝나는 닉네임은 사용할 수 없어요.";
+/// NIcknameTextFieldResultType - "연속된 특수문자(--, __)는 사용할 수 없어요."
+"auth.continuousSpecialChar" = "연속된 특수문자(--, __)는 사용할 수 없어요.";
+/// NIcknameTextFieldResultType - "숫자만으로 된 닉네임은 사용할 수 없어요."
+"auth.numberOnlyNickname" = "숫자만으로 된 닉네임은 사용할 수 없어요.";
+/// NIcknameTextFieldResultType - "허용 문자(한글/영문/숫자)만 사용할 수 있어요."
+"auth.allowedChar" = "허용 문자(한글/영문/숫자)만 사용할 수 있어요.";
+/// NIcknameTextFieldResultType - "사용할 수 없는 단어가 포함되어 있어요."
+"auth.bannedWord" = "사용할 수 없는 단어가 포함되어 있어요.";
+/// NIcknameTextFieldResultType - "띄어쓰기로 시작/끝나는 닉네임은 사용할 수 없어요."
+"auth.spaceNickname" = "띄어쓰기로 시작/끝나는 닉네임은 사용할 수 없어요.";
+/// NIcknameTextFieldResultType - "연속된 띄어쓰기는 사용할 수 없어요."
+"auth.continuousSpace" = "연속된 띄어쓰기는 사용할 수 없어요.";
+/// NIcknameTextFieldResultType - "이모지, 특수문자는 사용할 수 없어요."
+"auth.emojiSpecialChar" = "이모지, 특수문자는 사용할 수 없어요.";
+/// NIcknameTextFieldResultType - "관리자로 혼동될 수 있는 닉네임은 사용할 수 없어요."
+"auth.adminNickname" = "관리자로 혼동될 수 있는 닉네임은 사용할 수 없어요.";
+/// NIcknameTextFieldResultType - "서비스명 단독 닉네임은 사용할 수 없어요."
+"auth.serviceNameNickname" = "서비스명 단독 닉네임은 사용할 수 없어요.";
+/// NIcknameTextFieldResultType - "욕설, 비속어 등의 표현이 포함된 닉네임은 사용할 수 없어요."
+"auth.slangNickname" = "욕설, 비속어 등의 표현이 포함된 닉네임은 사용할 수 없어요.";
+
+
+// MARK: - Home
+
+/// Home - "오늘의 메뉴"
+"home.todayMenu" = "오늘의 메뉴";
+/// Home - "가격"
+"home.price" = "가격";
+/// Home - "평점"
+"home.rating" = "평점";
+/// Home - " -"
+"home.emptyRating" = " -";
+/// Home - "제공되는 메뉴가 없습니다"
+"home.noMenuProvidedMessage" = "제공되는 메뉴가 없습니다";
+/// CustomTimeTabController - "아침"
+"home.morning" = "아침";
+/// CustomTimeTabController - "점심"
+"home.lunch" = "점심";
+/// CustomTimeTabController - "저녁"
+"home.dinner" = "저녁";
+/// RestaurantInfoView - "학생 식당"
+"home.studentRestaurant" = "학생 식당";
+/// RestaurantInfoView - "식당 위치"
+"home.restaurantLocation" = "식당 위치";
+/// RestaurantInfoView - "식당 사진"
+"home.restaurantPicture" = "식당 사진";
+/// RestaurantInfoView - "숭실대학교"
+"home.soongsilUniversity" = "숭실대학교";
+/// RestaurantInfoView - "영업 시간"
+"home.businessHour" = "영업 시간";
+/// RestaurantInfoView - "비고"
+"home.note" = "비고";
+/// RestaurantInfoView - "아시안푸드, 돈까스, 샐러드, 국밥 등\n카페"
+"home.dodamEtc" = "아시안푸드, 돈까스, 샐러드, 국밥 등\n카페";
+/// RestaurantMenuGroupCell - "영업 시간이 아니에요."
+"home.notBusinessHour" = "영업 시간이 아니에요.";
+/// RestaurantTableViewHeader - "기숙사 식당"
+"home.dormitoryRestaurant" = "기숙사 식당";
+
+
+// MARK: - Map
+
+/// MainMapVC - "제휴 지도"
+"map.map" = "제휴 지도";
+/// MainMapView - "전체"
+"map.all" = "전체";
+/// MainMapView - "내 제휴"
+"map.myPartner" = "내 제휴";
+/// NoDepartmentSheetVC - "학과를 입력하고\n나만의 제휴를 확인해보세요!"
+"map.inputDepartment" = "학과를 입력하고\n나만의 제휴를 확인해보세요!";
+/// NoDepartmentSheetVC - "학과 입력하기"
+"map.inputDepartmentButton" = "학과 입력하기";
+/// PartnershipDetailSheetVC - "음식점"
+"map.restaurant" = "음식점";
+/// PartnershipDetailSheetVC - "카페"
+"map.cafe" = "카페";
+/// PartnershipDetailSheetVC - "주점"
+"map.pub" = "주점";
+/// PartnershipDetailSheetVC - "학과 정보 없음"
+"map.noDepartmentInfo" = "학과 정보 없음";
+/// MainMapVC+Location - "위치 권한 필요"
+"map.needLocationAuth" = "위치 권한 필요";
+/// MainMapVC+Location - "지도에서 내 위치를 바로 확인하고, 현재 위치 주변의 제휴점들을 손쉽게 찾아볼 수 있도록 위치 권한을 허용해 주세요."
+"map.locationAuthDescription" = "지도에서 내 위치를 바로 확인하고, 현재 위치 주변의 제휴점들을 손쉽게 찾아볼 수 있도록 위치 권한을 허용해 주세요.";
+
+
+// MARK: - MyPage
+
+/// "마이페이지"
+"myPage.myPage" = "마이페이지";
+/// UserWithdrawVC - "회원탈퇴"
+"myPage.withdraw" = "회원탈퇴";
+/// MyPageVC - "EAT-SSU 수신 동의"
+"myPage.agreeNoti" = "EAT-SSU 수신 동의 (%@)";
+/// MyPageVC - "EAT-SSU 수신 거절"
+"myPage.disagreeNoti" = "EAT-SSU 수신 거절 (%@)";
+
+// MARK: - MyPageSection: 알림 및 활동
+/// MyPageSectionVC - "알림 및 활동"
+"myPage.activitySection" = "알림 및 활동";
+/// "내 정보"
+"myPage.myInfo" = "내 정보";
+/// "내 리뷰"
+"myPage.myReview" = "내 리뷰";
+/// NotificationSettingTableViewCell - "푸시 알림 설정"
+"myPage.pushNotificationSetting" = "푸시 알림 설정";
+/// NotificationSettingTableViewCell - "매일 오전 11시에 알림을 보내드려요"
+"myPage.pushNotificationDescription" = "매일 오전 11시에 알림을 보내드려요";
+/// MyPageVC - "알림 설정 중 오류가 발생했습니다."
+"myPage.notiSettingError" = "알림 설정 중 오류가 발생했습니다.";
+
+// MARK: - MyPageSection: 서비스 정보
+// MyPageSectionVC - "서비스 정보"
+"myPage.serviceInfoSection" = "서비스 정보";
+/// MyPageVC - "문의하기"
+"myPage.inquiry" = "문의하기";
+/// CreatorVC - "만든 사람들"
+"myPage.creators" = "만든 사람들";
+/// MyPageVC - "EAT-SSU 인스타그램"
+"myPage.instagram" = "EAT-SSU 인스타그램";
+
+// MARK: - MyPageSection: 기타
+// MyPageSectionVC - "기타"
+"myPage.etcSection" = "기타";
+/// MyPageVC - "언어 설정"
+"myPage.languageSetting" = "언어 설정";
+/// MyPageVC - "지원 언어: 한국어"
+"myPage.language.korean" = "한국어";
+/// MyPageVC - "지원 언어: 영어"
+"myPage.language.english" = "English";
+/// MyPageVC - "약관 및 정책"
+"myPage.termsAndPolicy" = "약관 및 정책";
+/// MyPageVC - "서비스 이용약관"
+"myPage.termsOfUse" = "서비스 이용약관";
+/// MyPageVC - "개인정보처리방침"
+"myPage.privacyTermsOfUse" = "개인정보처리방침";
+/// MyPageVC - "로그아웃"
+"myPage.logout" = "로그아웃";
+/// MyPageVC - "정말 로그아웃 하시겠습니까?"
+"myPage.askLogout" = "정말 로그아웃 하시겠습니까?";
+
+/// MyReviewVC - "리뷰 수정 혹은 삭제"
+"myPage.fixOrDeleteReview" = "리뷰 수정 혹은 삭제";
+/// MyReviewVC - "작성하신 리뷰를 수정 또는 삭제하시겠습니까?"
+"myPage.askFixOrDeleteReview" = "작성하신 리뷰를 수정 또는 삭제하시겠습니까?";
+/// MyReviewVC - "리뷰 삭제하기"
+"myPage.deleteMyReview" = "리뷰 삭제하기";
+/// MyReviewVC - "해당 리뷰를 삭제할까요?"
+"myPage.askDeleteMyReview" = "해당 리뷰를 삭제할까요?";
+/// MyReviewVC - "리뷰가 성공적으로 삭제되었습니다."
+"myPage.deleteMyReviewSuccess" = "리뷰가 성공적으로 삭제되었습니다.";
+/// MyPageView - "다시 시도해주세요"
+"myPage.retry" = "다시 시도해주세요";
+/// MyPageView - "앱 버전"
+"myPage.appVersion" = "앱 버전";
+/// MyPageView - "탈퇴하기"
+"myPage.withdrawButton" = "탈퇴하기";
+/// MyPageView - "알 수 없음"
+"myPage.unknownUser" = "알 수 없음";
+/// ProvisionVC - "이용약관"
+"myPage.defaultTerms" = "이용약관";
+/// UserWithdrawView - "정말 탈퇴하시겠습니까?"
+"myPage.confirmWithdrawal" = "정말 탈퇴하시겠습니까?";
+/// UserWithdrawView - "작성한 리뷰 게시글은 삭제되지 않으며, (알수없음)으로 표시됩니다.\n자세한 내용은 서비스이용약관 및 개인정보처리방침을 확인해 주세요."
+"myPage.withdrawalNotice" = "작성한 리뷰 게시글은 삭제되지 않으며, (알수없음)으로 표시됩니다.\n자세한 내용은 서비스이용약관 및 개인정보처리방침을 확인해 주세요.";
+/// UserWithdrawView - "올바른 입력입니다."
+"myPage.validInputMessage" = "올바른 입력입니다";
+/// UserWithdrawView - "올바르지 않은 닉네임입니다"
+"myPage.invalidNicknameMessage" = "올바르지 않은 닉네임입니다";
+
+
+// MARK: - Review
+
+/// ReportVC - "EAT SSU 팀에게 보내기"
+"review.sendToTeam" = "EAT SSU 팀에게 보내기";
+/// ReportVC - "신고하기"
+"review.report" = "신고하기";
+/// ReportVC - "사유를 선택해주세요!"
+"review.selectReason" = "사유를 선택해주세요!";
+/// ReportVC - "신고가 성공적으로 접수되었어요!"
+"review.reportSuccess" = "신고가 성공적으로 접수되었어요!";
+/// ReportVC, ReportView - "메뉴와 관련없는 내용"
+"review.unrelatedMenu" = "메뉴와 관련없는 내용";
+/// ReportVC, ReportView - "음란성, 욕설 등 부적절한 내용"
+"review.inappropriateContent" = "음란성, 욕설 등 부적절한 내용";
+/// ReportVC, ReportView - "부적절한 홍보 또는 광고"
+"review.inappropriateAd" = "부적절한 홍보 또는 광고";
+/// ReportVC, ReportView - "리뷰 작성 취지에 맞지 않는 내용 (복사글 등)"
+"review.notReviewFormat" = "리뷰 작성 취지에 맞지 않는 내용 (복사글 등)";
+/// ReportVC, ReportView - "저작권 도용 의심 (사진 등)"
+"review.copyright" = "저작권 도용 의심 (사진 등)";
+/// ReportVC, ReportView - "기타 (하단 내용 작성)"
+"review.etc" = "기타 (하단 내용 작성)";
+/// ReportView - "리뷰 신고 사유를 알려주세요"
+"review.reportReason" = "리뷰 신고 사유를 알려주세요";
+/// ReportView - "하나의 리뷰에 대해 24시간 내 한 번만 신고 가능합니다."
+"review.reportGuide" = "하나의 리뷰에 대해 24시간 내 한 번만 신고 가능합니다.";
+/// ReportView - "리뷰 신고 사유를 작성해 주세요"
+"review.inputReportReason" = "리뷰 신고 사유를 작성해 주세요";
+/// ReviewVC - "리뷰 작성하기"
+"review.writeReview" = "리뷰 작성하기";
+/// ReviewVC - "리뷰가 성공적으로 등록되었습니다."
+"review.registerReviewSuccess" = "리뷰가 성공적으로 등록되었습니다.";
+/// ReviewVC - "리뷰"
+"review.review" = "리뷰";
+/// ReviewVC - "리뷰 삭제"
+"review.deleteReview" = "리뷰 삭제";
+/// ReviewVC - "해당 리뷰를 삭제할까요?"
+"review.askDeleteReview" = "해당 리뷰를 삭제할까요?";
+/// ReviewVC - "리뷰 신고하기"
+"review.reportReview" = "리뷰 신고하기";
+/// ReviewVC - "해당 리뷰를 신고하시겠습니까?"
+"review.askReportReview" = "해당 리뷰를 신고하시겠습니까?";
+/// ReviewVC - "리뷰가 성공적으로 삭제되었습니다."
+"review.deleteReviewSuccess" = "리뷰가 성공적으로 삭제되었습니다.";
+/// ReviewVC - "리뷰 삭제에 실패했습니다."
+"review.deleteReviewFail" = "리뷰 삭제에 실패했습니다.";
+/// SetRateVC - "리뷰 수정하기"
+"review.fixReview" = "리뷰 수정하기";
+/// SetRateVC - "리뷰 남기기"
+"review.leaveReview" = "리뷰 남기기";
+/// 메뉴 이름의 받침 유무에 따라 '을/를'을 동적으로 붙여 추천 문장을 생성합니다.
+"review.recommendMenu.default" = "%@을(를) 추천하시겠어요?";
+/// 메뉴 이름의 받침 유무에 따라 '을/를'을 동적으로 붙여 추천 문장을 생성합니다.
+"review.recommendMenu.withJongseong" = "%@을 추천하시겠어요?";
+/// 메뉴 이름의 받침 유무에 따라 '을/를'을 동적으로 붙여 추천 문장을 생성합니다.
+"review.recommendMenu.withoutJongseong" = "%@를 추천하시겠어요?";
+/// SetRateVC - "메뉴를 추천하시겠어요?"
+"review.recommendMenuTitle" = "메뉴를 추천하시겠어요?";
+/// SetRateVC - "리뷰 수정 완료하기"
+"review.fixReviewComplete" = "리뷰 수정 완료하기";
+/// SetRateVC - "완료하기"
+"review.complete" = "완료하기";
+/// SetRateVC - "별점을 입력해주세요!"
+"review.inputRating" = "별점을 입력해주세요!";
+/// SetRateVC - "메뉴 목록 조회에 실패했습니다."
+"review.loadMenuListFail" = "메뉴 목록 조회에 실패했습니다.";
+/// SetRateVC - "수정할 리뷰 정보가 없습니다."
+"review.noReviewInfoForFix" = "수정할 리뷰 정보가 없습니다.";
+/// SetRateVC - "리뷰가 성공적으로 수정되었습니다."
+"review.fixReviewSuccess" = "리뷰가 성공적으로 수정되었습니다.";
+/// SetRateVC - "리뷰 수정에 실패했습니다."
+"review.fixReviewFail" = "리뷰 수정에 실패했습니다.";
+/// SetRateVC - "식단 정보가 없습니다."
+"review.noMealInfo" = "식단 정보가 없습니다.";
+/// SetRateVC - "리뷰 업로드에 실패했습니다."
+"review.uploadReviewFail" = "리뷰 업로드에 실패했습니다.";
+/// SetRateVC - "메뉴 정보가 없습니다."
+"review.noMenuInfo" = "메뉴 정보가 없습니다.";
+/// SetRateVC - "메뉴에 대한 상세한 리뷰를 작성해주세요"
+"review.inputDetailReview" = "메뉴에 대한 상세한 리뷰를 작성해주세요";
+/// SetRateVC - "나가시겠어요?"
+"review.askLeave" = "나가시겠어요?";
+/// SetRateVC - "지금 나가면 작성한 내용이 저장되지 않습니다."
+"review.leaveWarning" = "지금 나가면 작성한 내용이 저장되지 않습니다.";
+/// SetRateVC - "나가기"
+"review.leave" = "나가기";
+/// SetRateVC - "계속 작성"
+"review.continueWriting" = "계속 작성";
+/// ReviewEmptyViewCell - "아직 작성된 리뷰가 없어요!"
+"review.noReview" = "아직 작성된 리뷰가 없어요!";
+/// ReviewEmptyViewCell - "메뉴에 가장 먼저 리뷰를 남겨주세요!"
+"review.beFirstReviewer" = "메뉴에 가장 먼저 리뷰를 남겨주세요!";
+/// ReviewEmptyViewCell - "로그인이 필요합니다"
+"review.needLogin" = "로그인이 필요합니다";
+/// ReviewEmptyViewCell - "로그인 후 리뷰를 확인하세요"
+"review.checkReviewAfterLogin" = "로그인 후 리뷰를 확인하세요";
+/// ReviewEmptyViewCell - "아직 작성한 리뷰가 없어요"
+"review.noWrittenReview" = "아직 작성한 리뷰가 없어요";
+/// ReviewEmptyViewCell - "첫 리뷰를 남겨 주세요!"
+"review.writeFirstReview" = "첫 리뷰를 남겨 주세요!";
+/// ReviewDividerCell - "리뷰"
+"review.reviewCount" = "리뷰 %d";
+/// ReviewRateViewCell - "오늘의 메뉴"
+"review.todayMenu" = "오늘의 메뉴";
+/// ReviewRateViewCell - "5점"
+"review.fiveStars" = "5점";
+/// ReviewRateViewCell - "4점"
+"review.fourStars" = "4점";
+/// ReviewRateViewCell - "3점"
+"review.threeStars" = "3점";
+/// ReviewRateViewCell - "2점"
+"review.twoStars" = "2점";
+/// ReviewRateViewCell - "1점"
+"review.oneStar" = "1점";
+/// SetRateView - "오늘의 식사는 어떠셨나요?"
+"review.rateTodayMeal" = "오늘의 식사는 어떠셨나요?";
+/// SetRateView - "추천하고 싶은 메뉴가 있나요?"
+"review.recommendMenu" = "추천하고 싶은 메뉴가 있나요?";
+/// SetRateView - "사진 추가 (0/1)"
+"review.addPhoto" = "사진 추가 (%d/1)";
+/// character count
+"review.characterCount" = "%d / %d";
+
+
+// MARK: - Coffee
+
+/// "나가시겠어요?"
+"coffee.askLeave" = "나가시겠어요?";
+/// "지금 나가면 진행 상황이\n저장되지 않습니다."
+"coffee.leaveWarning" = "지금 나가면 진행 상황이\n저장되지 않습니다.";
+/// "나가기"
+"coffee.leave" = "나가기";
+/// "계속하기"
+"coffee.continueEvent" = "계속하기";
+
+
+// MARK: - Splash
+
+/// NoticeSplashVC - "긴급 서버 점검 안내"
+"splash.serverInspection" = "긴급 서버 점검 안내";
+
+
+// MARK: - PromotionPopup
+
+/// 03. 16(월)~03. 27(금)
+"promotionPopup.period" = "03. 16(월)~03. 27(금)";
+/// EAT-SSU 인스타그램 바로가기
+"promotionPopup.instagramButtonTitle" = "EAT-SSU 인스타그램 바로가기";
+/// 자세한 내용은 EAT-SSU 인스타그램을 확인해 주세요
+"promotionPopup.guideMessage" = "자세한 내용은 EAT-SSU 인스타그램을 확인해 주세요";
+/// 다시 보지 않기
+"promotionPopup.neverShowAgain" = "다시 보지 않기";
+/// 닫기
+"promotionPopup.close" = "닫기";
+
+
+// MARK: - Notification
+
+/// 🤔 오늘 밥 뭐 먹지…
+"notification.dailyWeekdayNotificationTitle" = "🤔 오늘 밥 뭐 먹지…";
+/// 오늘의 학식을 확인해보세요!
+"notification.dailyWeekdayNotificationBody" = "오늘의 학식을 확인해보세요!";
+/// 알림 권한 필요
+"notification_error_permission_denied_message" = "알림 권한 필요";
+/// 알림을 받으려면 설정에서 알림 권한을 허용해주세요.
+"notification_error_permission_denied_description" = "알림을 받으려면 설정에서 알림 권한을 허용해주세요.";
+/// 알 수 없는 오류
+"notification_error_unknown_message" = "알 수 없는 오류";
+/// 다시 시도해주세요.
+"notification_error_unknown_description" = "다시 시도해주세요.";
+
+
+// MARK: - Restaurant
+
+/// "기숙사 식당"
+"restaurant.dormitoryRestaurant" = "기숙사 식당";
+/// "도담 식당"
+"restaurant.dodamRestaurant" = "도담 식당";
+/// "학생 식당"
+"restaurant.studentRestaurant" = "학생 식당";
+/// "스낵 코너"
+"restaurant.snackCorner" = "스낵 코너";
+/// "FACULTY (교직원 전용)"
+"restaurant.facultyRestaurant" = "FACULTY (교직원 전용)";
diff --git a/EATSSU/App/Sources/Notification/NotificationManager.swift b/EATSSU/App/Sources/Notification/NotificationManager.swift
index bc585862..aee3b253 100644
--- a/EATSSU/App/Sources/Notification/NotificationManager.swift
+++ b/EATSSU/App/Sources/Notification/NotificationManager.swift
@@ -119,25 +119,26 @@ class NotificationManager {
}
// MARK: - Error Types
+
enum NotificationError: Error {
case permissionDenied
case unknown
-
+
var message: String {
switch self {
case .permissionDenied:
- return "알림 권한 필요"
+ return TextLiteral.Notification.permissionDeniedMessage
case .unknown:
- return "알 수 없는 오류"
+ return TextLiteral.Notification.unknownErrorMessage
}
}
-
+
var description: String {
switch self {
case .permissionDenied:
- return "알림을 받으려면 설정에서 알림 권한을 허용해주세요."
+ return TextLiteral.Notification.permissionDeniedDescription
case .unknown:
- return "다시 시도해주세요."
+ return TextLiteral.Notification.unknownErrorDescription
}
}
}
diff --git a/EATSSU/App/Sources/Presentation/Auth/View/LoginView.swift b/EATSSU/App/Sources/Presentation/Auth/View/LoginView.swift
index 7ff3b734..401a7a49 100644
--- a/EATSSU/App/Sources/Presentation/Auth/View/LoginView.swift
+++ b/EATSSU/App/Sources/Presentation/Auth/View/LoginView.swift
@@ -19,31 +19,30 @@ final class LoginView: BaseUIView {
imageView.image = EATSSUDesignAsset.Images.authLogo.image
return imageView
}()
-
- private let logoSubTitle: UIImageView = {
- let imageView = UIImageView()
- imageView.image = EATSSUDesignAsset.Images.authSubTitle.image
- return imageView
- }()
-
- let appleLoginButton: UIButton = {
- let button = UIButton()
- button.setImage(EATSSUDesignAsset.Images.appleLoginButton.image, for: .normal)
- return button
- }()
-
- let kakaoLoginButton: UIButton = {
- let button = UIButton()
- button.setImage(EATSSUDesignAsset.Images.kakaoLoginButton.image, for: .normal)
- return button
+
+ private let logoSubTitle: UILabel = {
+ let label = UILabel()
+ label.font = .header2
+ label.attributedText = TextLiteral.Common.logoSubTitle.logoHighlightedLastWord(
+ baseColor: .black,
+ highlightColor: .primary
+ )
+ return label
}()
-
+
+ let appleLoginButton = SocialLoginButton(type: .apple)
+
+ let kakaoLoginButton = SocialLoginButton(type: .kakao)
+
let lookingWithNoSignInButton: UIButton = {
- let button = UIButton()
- button.setImage(EATSSUDesignAsset.Images.lookAroundButton.image, for: .normal)
+ let button = UIButton(type: .system)
+ button.setTitle(TextLiteral.Auth.lookingWithNoSignIn, for: .normal)
+ button.setTitleColor(.gray400, for: .normal)
+ button.titleLabel?.font = .body2
+ button.backgroundColor = .clear
return button
}()
-
+
private var lastLoginTooltipView: LastLoginTooltipView?
override func configureUI() {
@@ -69,11 +68,13 @@ final class LoginView: BaseUIView {
appleLoginButton.snp.makeConstraints {
$0.centerX.equalToSuperview()
+ $0.horizontalEdges.equalToSuperview().inset(45)
$0.bottom.equalTo(self.safeAreaLayoutGuide).inset(151)
}
kakaoLoginButton.snp.makeConstraints {
$0.centerX.equalToSuperview()
+ $0.horizontalEdges.equalToSuperview().inset(45)
$0.bottom.equalTo(self.safeAreaLayoutGuide).inset(90)
}
diff --git a/EATSSU/App/Sources/Presentation/MyPage/Enum/MyPageLabels.swift b/EATSSU/App/Sources/Presentation/MyPage/Enum/MyPageLabels.swift
index eda3f837..e59f8474 100644
--- a/EATSSU/App/Sources/Presentation/MyPage/Enum/MyPageLabels.swift
+++ b/EATSSU/App/Sources/Presentation/MyPage/Enum/MyPageLabels.swift
@@ -7,29 +7,65 @@
import Foundation
-/// "마이파이지"에서 확인할 수 있는 서비스 리스트
-enum MyPageLabels: Int {
- /// 푸시 알림 설정
- case NotificationSetting = 0
-
- /// 내 정보
- case MyInfo
+/// "마이페이지"에서 확인할 수 있는 서비스 리스트
+enum MyPageLabels {
+ case notificationSetting
+ case myInfo
+ case myReview
+ case inquiry
+ case creators
+ case instagram
+ case languageSetting
+ case termsAndPolicy
+ case logout
- /// 내 리뷰
- case MyReview
-
- /// 문의하기
- case Inquiry
-
- /// 서비스 이용약관
- case TermsOfUse
-
- /// 개인정보 이용약관
- case PrivacyTermsOfUse
-
- /// 만든사람들
- case Creator
-
- /// 로그아웃
- case Logout
+ var title: String {
+ switch self {
+ case .notificationSetting:
+ return TextLiteral.MyPage.pushNotificationSetting
+ case .myInfo:
+ return TextLiteral.MyPage.myInfo
+ case .myReview:
+ return TextLiteral.MyPage.myReview
+ case .inquiry:
+ return TextLiteral.MyPage.inquiry
+ case .creators:
+ return TextLiteral.MyPage.creators
+ case .instagram:
+ return TextLiteral.MyPage.instagram
+ case .languageSetting:
+ return TextLiteral.MyPage.languageSetting
+ case .termsAndPolicy:
+ return TextLiteral.MyPage.termsAndPolicy
+ case .logout:
+ return TextLiteral.MyPage.logout
+ }
+ }
+
+ var subtitle: String? {
+ switch self {
+ case .notificationSetting:
+ return TextLiteral.MyPage.pushNotificationDescription
+ default:
+ return nil
+ }
+ }
+
+ var rightText: String? {
+ switch self {
+ case .languageSetting:
+ return TextLiteral.MyPage.currentLanguage
+ default:
+ return nil
+ }
+ }
+
+ var showsDisclosure: Bool {
+ switch self {
+ case .notificationSetting, .logout:
+ return false
+ default:
+ return true
+ }
+ }
}
diff --git a/EATSSU/App/Sources/Presentation/MyPage/Enum/MyPageTableMetric.swift b/EATSSU/App/Sources/Presentation/MyPage/Enum/MyPageTableMetric.swift
new file mode 100644
index 00000000..b412f414
--- /dev/null
+++ b/EATSSU/App/Sources/Presentation/MyPage/Enum/MyPageTableMetric.swift
@@ -0,0 +1,25 @@
+//
+// MyPageTableMetric.swift
+// EATSSU
+//
+// Created by jeongminji on 5/3/26.
+//
+
+import Foundation
+
+enum MyPageTableMetric {
+ static let normalRowHeight: CGFloat = 48
+ static let notificationRowHeight: CGFloat = 74
+ static let headerHeight: CGFloat = 18
+ static let footerHeight: CGFloat = 16
+
+ static func rowHeight(for item: MyPageLabels) -> CGFloat {
+ switch item {
+ case .notificationSetting:
+ return notificationRowHeight
+
+ default:
+ return normalRowHeight
+ }
+ }
+}
diff --git a/EATSSU/App/Sources/Presentation/MyPage/Model/MyPageLocalData.swift b/EATSSU/App/Sources/Presentation/MyPage/Model/MyPageLocalData.swift
deleted file mode 100644
index 77010e88..00000000
--- a/EATSSU/App/Sources/Presentation/MyPage/Model/MyPageLocalData.swift
+++ /dev/null
@@ -1,42 +0,0 @@
-//
-// MyPageLocalData.swift
-// EATSSU_MVC
-//
-// Created by Jiwoong CHOI on 9/19/24.
-//
-
-import Foundation
-
-// TODO: 구조체에 익스텐션으로 코드를 작성하는 이유에 대해서 알아보기
-
-struct MyPageLocalData {
- let titleLabel: String
-}
-
-extension MyPageLocalData {
- static let myPageTableLabelList = [
- // "푸시 알림 설정"
- MyPageLocalData(titleLabel: TextLiteral.MyPage.pushNotificationSetting),
-
- // "내 정보"
- MyPageLocalData(titleLabel: TextLiteral.MyPage.myInfo),
-
- // "내 리뷰"
- MyPageLocalData(titleLabel: TextLiteral.MyPage.myReview),
-
- // "문의하기"
- MyPageLocalData(titleLabel: TextLiteral.MyPage.inquiry),
-
- // "서비스 이용약관"
- MyPageLocalData(titleLabel: TextLiteral.MyPage.termsOfUse),
-
- // "개인정보 이용약관"
- MyPageLocalData(titleLabel: TextLiteral.MyPage.privacyTermsOfUse),
-
- // "만든 사람들"
- MyPageLocalData(titleLabel: TextLiteral.MyPage.creators),
-
- // "로그아웃"
- MyPageLocalData(titleLabel: TextLiteral.MyPage.logout),
- ]
-}
diff --git a/EATSSU/App/Sources/Presentation/MyPage/Model/MyPageRightItemData.swift b/EATSSU/App/Sources/Presentation/MyPage/Model/MyPageRightItemData.swift
deleted file mode 100644
index 7bf44c29..00000000
--- a/EATSSU/App/Sources/Presentation/MyPage/Model/MyPageRightItemData.swift
+++ /dev/null
@@ -1,20 +0,0 @@
-//
-// MyPageRightItemData.swift
-// EATSSU_MVC
-//
-// Created by Jiwoong CHOI on 9/19/24.
-//
-
-import Foundation
-
-/// "마이페이지"에서 사용하는 셀의 오른쪽 데이터
-enum MyPageRightItemData {
- /// 앱의 배포 버전
- static var version: String? {
- if let info = Bundle.main.infoDictionary, let version = info["CFBundleShortVersionString"] as? String {
- version
- } else {
- nil
- }
- }
-}
diff --git a/EATSSU/App/Sources/Presentation/MyPage/Model/MyPageSectionData.swift b/EATSSU/App/Sources/Presentation/MyPage/Model/MyPageSectionData.swift
new file mode 100644
index 00000000..87507008
--- /dev/null
+++ b/EATSSU/App/Sources/Presentation/MyPage/Model/MyPageSectionData.swift
@@ -0,0 +1,44 @@
+//
+// MyPageSectionData.swift
+// EATSSU
+//
+// Created by jeongminji on 5/3/26.
+//
+
+import Foundation
+
+struct MyPageSectionData {
+ let headerTitle: String
+ let items: [MyPageLabels]
+}
+
+extension MyPageSectionData {
+ static var sections: [MyPageSectionData] {
+ [
+ MyPageSectionData(
+ headerTitle: TextLiteral.MyPage.activitySection,
+ items: [
+ .notificationSetting,
+ .myInfo,
+ .myReview
+ ]
+ ),
+ MyPageSectionData(
+ headerTitle: TextLiteral.MyPage.serviceInfoSection,
+ items: [
+ .inquiry,
+ .creators,
+ .instagram
+ ]
+ ),
+ MyPageSectionData(
+ headerTitle: TextLiteral.MyPage.etcSection,
+ items: [
+ .languageSetting,
+ .termsAndPolicy,
+ .logout
+ ]
+ )
+ ]
+ }
+}
diff --git a/EATSSU/App/Sources/Presentation/MyPage/View/MyPageView/Cell/MyPageTableDefaultCell.swift b/EATSSU/App/Sources/Presentation/MyPage/View/MyPageView/Cell/MyPageTableDefaultCell.swift
index a1de498b..e2e9a7c0 100644
--- a/EATSSU/App/Sources/Presentation/MyPage/View/MyPageView/Cell/MyPageTableDefaultCell.swift
+++ b/EATSSU/App/Sources/Presentation/MyPage/View/MyPageView/Cell/MyPageTableDefaultCell.swift
@@ -13,56 +13,109 @@ import EATSSUDesign
final class MyPageTableDefaultCell: UITableViewCell {
// MARK: - Properties
-
+
static let identifier = "MyPageTableDefaultCell"
-
+
// MARK: - UI Components
-
+
let serviceLabel: UILabel = {
let label = UILabel()
label.font = .body1
- label.textColor = .gray700Basic
+ label.textColor = .black
return label
}()
-
+
+ private let rightTextLabel: UILabel = {
+ let label = UILabel()
+ label.font = .body2
+ label.textColor = .gray600
+ label.textAlignment = .right
+ return label
+ }()
+
let rigthChevronImage: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(systemName: "chevron.right")
imageView.tintColor = .gray300
return imageView
}()
-
+
// MARK: - Initializer
-
+
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
-
+
configureUI()
setLayout()
}
-
+
@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
-
+
// MARK: - Functions
-
+
private func configureUI() {
addSubviews(
serviceLabel,
+ rightTextLabel,
rigthChevronImage
)
}
-
+
private func setLayout() {
serviceLabel.snp.makeConstraints {
$0.leading.equalToSuperview().offset(24)
$0.centerY.equalToSuperview()
}
+ rightTextLabel.snp.makeConstraints {
+ $0.trailing.equalToSuperview().inset(46)
+ $0.centerY.equalToSuperview()
+ }
rigthChevronImage.snp.makeConstraints {
$0.trailing.equalToSuperview().inset(24)
$0.centerY.equalToSuperview()
}
}
+
+ /// 마이페이지 메뉴 셀 설정
+ /// - MyPageLabels enum을 그대로 받아서 title, rightText, disclosure 표시 여부를 설정합니다.
+ /// - MyPageViewController에서 사용하는 기본 설정 함수입니다.
+ func configure(with item: MyPageLabels) {
+ serviceLabel.text = item.title
+
+ if let rightText = item.rightText {
+ rightTextLabel.text = rightText
+ rightTextLabel.isHidden = false
+ } else {
+ rightTextLabel.text = nil
+ rightTextLabel.isHidden = true
+ }
+
+ rigthChevronImage.isHidden = !item.showsDisclosure
+ }
+
+ /// 일반 텍스트 기반 셀 설정
+ /// - Parameters:
+ /// - title: 왼쪽에 표시할 메인 텍스트
+ /// - rightText: 오른쪽에 표시할 보조 텍스트. nil이면 숨김 처리됩니다.
+ /// - showsDisclosure: 오른쪽 chevron 표시 여부
+ func configure(
+ title: String,
+ rightText: String? = nil,
+ showsDisclosure: Bool = true
+ ) {
+ serviceLabel.text = title
+
+ if let rightText {
+ rightTextLabel.text = rightText
+ rightTextLabel.isHidden = false
+ } else {
+ rightTextLabel.text = nil
+ rightTextLabel.isHidden = true
+ }
+
+ rigthChevronImage.isHidden = !showsDisclosure
+ }
}
diff --git a/EATSSU/App/Sources/Presentation/MyPage/View/MyPageView/Cell/NotificationSettingTableViewCell.swift b/EATSSU/App/Sources/Presentation/MyPage/View/MyPageView/Cell/NotificationSettingTableViewCell.swift
index 68bb8391..b2369e4d 100644
--- a/EATSSU/App/Sources/Presentation/MyPage/View/MyPageView/Cell/NotificationSettingTableViewCell.swift
+++ b/EATSSU/App/Sources/Presentation/MyPage/View/MyPageView/Cell/NotificationSettingTableViewCell.swift
@@ -11,7 +11,7 @@ import SnapKit
import EATSSUDesign
-class NotificationSettingTableViewCell: UITableViewCell {
+final class NotificationSettingTableViewCell: UITableViewCell {
// MARK: - Properties
static let identifier = "NotificationSettingTableViewCell"
@@ -87,4 +87,9 @@ class NotificationSettingTableViewCell: UITableViewCell {
make.centerY.equalToSuperview()
}
}
+
+ func configure(with item: MyPageLabels) {
+ pushNotificationTitleLabel.text = item.title
+ dailyNotificationInfoLabel.text = item.subtitle
+ }
}
diff --git a/EATSSU/App/Sources/Presentation/MyPage/View/MyPageView/Cell/RadioSelectionTableViewCell.swift b/EATSSU/App/Sources/Presentation/MyPage/View/MyPageView/Cell/RadioSelectionTableViewCell.swift
new file mode 100644
index 00000000..cf4afd8d
--- /dev/null
+++ b/EATSSU/App/Sources/Presentation/MyPage/View/MyPageView/Cell/RadioSelectionTableViewCell.swift
@@ -0,0 +1,77 @@
+//
+// RadioSelectionTableViewCell.swift
+// EATSSU
+//
+// Created by jeongminji on 5/3/26.
+//
+
+import UIKit
+
+import SnapKit
+
+import EATSSUDesign
+
+final class RadioSelectionTableViewCell: UITableViewCell {
+ // MARK: - Properties
+
+ static let identifier = "RadioSelectionTableViewCell"
+
+ // MARK: - UI Components
+
+ private let titleLabel: UILabel = {
+ let label = UILabel()
+ label.font = .body1
+ label.textColor = .black
+ return label
+ }()
+
+ private let radioButton: CustomRadioButton = {
+ let button = CustomRadioButton()
+ button.isUserInteractionEnabled = false
+ return button
+ }()
+
+ // MARK: - Initializer
+
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+
+ configureUI()
+ setLayout()
+ }
+
+ @available(*, unavailable)
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ // MARK: - Functions
+
+ private func configureUI() {
+ contentView.addSubviews(
+ titleLabel,
+ radioButton
+ )
+ }
+
+ private func setLayout() {
+ titleLabel.snp.makeConstraints {
+ $0.leading.equalToSuperview().inset(24)
+ $0.centerY.equalToSuperview()
+ }
+
+ radioButton.snp.makeConstraints {
+ $0.trailing.equalToSuperview().inset(24)
+ $0.centerY.equalToSuperview()
+ $0.width.height.equalTo(20)
+ }
+ }
+
+ func configure(
+ title: String,
+ isSelected: Bool
+ ) {
+ titleLabel.text = title
+ radioButton.updateState(isSelected: isSelected)
+ }
+}
diff --git a/EATSSU/App/Sources/Presentation/MyPage/View/MyPageView/MyPageView.swift b/EATSSU/App/Sources/Presentation/MyPage/View/MyPageView/MyPageView.swift
index b645b53f..edb29939 100644
--- a/EATSSU/App/Sources/Presentation/MyPage/View/MyPageView/MyPageView.swift
+++ b/EATSSU/App/Sources/Presentation/MyPage/View/MyPageView/MyPageView.swift
@@ -12,37 +12,73 @@ import SnapKit
import EATSSUDesign
final class MyPageView: BaseUIView {
- // MARK: - UI Components
+ // MARK: - Properties
+ private static var appVersion: String {
+ guard let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else {
+ return "-"
+ }
+
+ return version
+ }
+
+ private var myPageTableViewHeight: CGFloat {
+ let rowTotalHeight = MyPageSectionData.sections.reduce(CGFloat(0)) { result, section in
+ let sectionRowHeight = section.items.reduce(CGFloat(0)) { rowResult, item in
+ return rowResult + MyPageTableMetric.rowHeight(for: item)
+ }
+
+ return result + sectionRowHeight
+ }
+ let sectionCount = MyPageSectionData.sections.count
+ let headerTotalHeight = CGFloat(sectionCount) * MyPageTableMetric.headerHeight
+
+ let footerCount = max(sectionCount - 1, 0)
+ let footerTotalHeight = CGFloat(footerCount) * MyPageTableMetric.footerHeight
+
+ return rowTotalHeight + headerTotalHeight + footerTotalHeight
+ }
+
+ // MARK: - UI Components
+
/// MyPageView 전체 스크롤뷰
private let scrollView = UIScrollView()
-
+
/// 스크롤뷰 안에 들어갈 콘텐츠 뷰
private let contentView = UIView()
-
+
// 사용자 이미지
var userImage: UIImageView = {
let imageView = UIImageView()
imageView.image = EATSSUDesignAsset.Images.profile.image
return imageView
}()
-
- // 닉네임이 들어간 닉네임 변경 버튼
+
+ // 유저 닉네임
var userNicknameLabel: UILabel = {
let label = UILabel()
label.text = TextLiteral.MyPage.retry
label.textColor = .gray700Basic
- label.font = .header1
+ label.font = .subtitle2
return label
}()
-
+
+ // 유저 소속
+ var userAffiliationLabel: UILabel = {
+ let label = UILabel()
+ label.textColor = .gray600
+ label.font = .body3
+ label.numberOfLines = 1
+ return label
+ }()
+
let myPageTableView: UITableView = {
let tableView = UITableView()
tableView.separatorStyle = .none
tableView.isScrollEnabled = false
return tableView
}()
-
+
// "앱 버전" 레이블
private let appVersionStringLabel: UILabel = {
let label = UILabel()
@@ -51,16 +87,16 @@ final class MyPageView: BaseUIView {
label.textColor = .gray400
return label
}()
-
+
// 현재 배포된 앱의 버전
private let appVersionLabel: UILabel = {
let label = UILabel()
- label.text = MyPageRightItemData.version
+ label.text = appVersion
label.font = .caption2
label.textColor = .gray400
return label
}()
-
+
/// "탈퇴하기" 레이블과 탈퇴하기 아이콘
let userWithdrawButton: UIButton = {
let button = UIButton()
@@ -69,33 +105,35 @@ final class MyPageView: BaseUIView {
button.setTitleColor(.gray400, for: .normal)
button.titleLabel?.font = .caption2
button.tintColor = .red
+ button.semanticContentAttribute = .forceRightToLeft
return button
}()
-
+
/// "탈퇴하기" 레이블 underline
private let underLineView: UIView = {
let view = UIView()
view.backgroundColor = .gray400
return view
}()
-
+
// MARK: - Intializer
-
+
override init(frame: CGRect) {
super.init(frame: frame)
-
+
registerTableViewCells()
}
-
+
// MARK: - Functions
-
+
override func configureUI() {
addSubview(scrollView)
scrollView.addSubview(contentView)
-
+
contentView.addSubviews(
userImage,
userNicknameLabel,
+ userAffiliationLabel,
myPageTableView,
appVersionStringLabel,
appVersionLabel,
@@ -103,62 +141,64 @@ final class MyPageView: BaseUIView {
underLineView
)
}
-
+
override func setLayout() {
scrollView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
-
+
contentView.snp.makeConstraints {
$0.edges.equalToSuperview()
$0.width.equalTo(scrollView)
}
-
+
userImage.snp.makeConstraints {
- $0.top.equalToSuperview().offset(24)
- $0.centerX.equalToSuperview()
- $0.height.width.equalTo(100)
+ $0.top.equalToSuperview().offset(12)
+ $0.leading.equalToSuperview().inset(24)
+ $0.height.width.equalTo(48)
}
-
+
userNicknameLabel.snp.makeConstraints {
- $0.top.equalTo(userImage.snp.bottom).offset(6)
- $0.centerX.equalTo(userImage)
- $0.height.equalTo(40)
+ $0.leading.equalTo(userImage.snp.trailing).offset(12)
+ $0.bottom.equalTo(userImage.snp.centerY)
}
-
+
+ userAffiliationLabel.snp.makeConstraints {
+ $0.top.equalTo(userNicknameLabel.snp.bottom)
+ $0.leading.equalTo(userNicknameLabel.snp.leading)
+ }
+
myPageTableView.snp.makeConstraints {
- $0.top.equalTo(userNicknameLabel.snp.bottom).offset(16)
+ $0.top.equalTo(userImage.snp.bottom).offset(24)
$0.leading.trailing.equalToSuperview()
- let cellHeight = 60
- let totalHeight = MyPageLocalData.myPageTableLabelList.count * cellHeight
- $0.height.equalTo(totalHeight)
+ $0.height.equalTo(myPageTableViewHeight)
$0.width.equalToSuperview()
}
-
+
appVersionStringLabel.snp.makeConstraints { make in
make.top.equalTo(myPageTableView.snp.bottom).offset(6)
make.leading.equalToSuperview().inset(24)
}
-
+
appVersionLabel.snp.makeConstraints { make in
make.top.equalTo(myPageTableView.snp.bottom).offset(6)
make.trailing.equalToSuperview().inset(24)
}
-
+
// TODO: withdrawStackView를 프로퍼티로 선언할 때, lazy를 사용하면 레이아웃이 한 타임 늦게 잡히는 문제로 인해서 여기에서 스택 안에 들어갈 뷰를 추가함. 개선 방법이 없는지 확인.
userWithdrawButton.snp.makeConstraints { make in
make.top.equalTo(appVersionLabel.snp.bottom).offset(16)
make.trailing.equalToSuperview().inset(24)
make.bottom.equalToSuperview().inset(70)
}
-
+
underLineView.snp.makeConstraints {
$0.top.equalTo(userWithdrawButton.snp.bottom)
$0.leading.trailing.equalTo(userWithdrawButton)
$0.height.equalTo(0.5)
}
}
-
+
private func registerTableViewCells() {
myPageTableView.register(
MyPageTableDefaultCell.self,
@@ -169,8 +209,20 @@ final class MyPageView: BaseUIView {
forCellReuseIdentifier: NotificationSettingTableViewCell.identifier
)
}
-
- public func setUserInfo(nickname: String) {
+
+ public func setUserInfo(
+ nickname: String,
+ collegeName: String?,
+ departmentName: String?
+ ) {
userNicknameLabel.text = nickname
+
+ let affiliationText = [collegeName, departmentName]
+ .compactMap { $0 }
+ .filter { !$0.isEmpty }
+ .joined(separator: " ")
+
+ userAffiliationLabel.text = affiliationText
+ userAffiliationLabel.isHidden = affiliationText.isEmpty
}
}
diff --git a/EATSSU/App/Sources/Presentation/MyPage/ViewController/LanguageSettingViewController.swift b/EATSSU/App/Sources/Presentation/MyPage/ViewController/LanguageSettingViewController.swift
new file mode 100644
index 00000000..3c54718d
--- /dev/null
+++ b/EATSSU/App/Sources/Presentation/MyPage/ViewController/LanguageSettingViewController.swift
@@ -0,0 +1,208 @@
+//
+// LanguageSettingViewController.swift
+// EATSSU
+//
+// Created by jeongminji on 5/3/26.
+//
+
+import UIKit
+
+import SnapKit
+import EATSSUDesign
+
+final class LanguageSettingViewController: BaseViewController {
+ override var shouldHideTabBar: Bool { true }
+
+ // MARK: - Properties
+
+ /// 언어 설정 화면에서 실제로 언어가 변경되었는지 여부
+ /// - true이면 뒤로가기 시 앱 전체 화면을 새 언어 기준으로 다시 구성
+ private var didChangeLanguage = false
+
+ /// 버튼 뒤로가기와 swipe back에서 root 재구성이 중복 호출되는 것을 방지
+ private var isResettingRootViewController = false
+
+ private var selectedLanguage: AppLanguage {
+ return AppLanguageManager.shared.currentLanguage
+ }
+
+ // MARK: - UI Components
+
+ private let tableView: UITableView = {
+ let tableView = UITableView()
+ tableView.separatorStyle = .none
+ tableView.rowHeight = 48
+ tableView.backgroundColor = .white
+ return tableView
+ }()
+
+ // MARK: - Life Cycles
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ setTableViewDelegate()
+ registerTableViewCells()
+ setInteractivePopGesture()
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ super.viewWillDisappear(animated)
+
+ guard didChangeLanguage,
+ isMovingFromParent,
+ !isResettingRootViewController else {
+ return
+ }
+
+ resetRootAfterLanguageChangeIfNeeded()
+ }
+
+ // MARK: - Functions
+
+ override func setCustomNavigationBar() {
+ super.setCustomNavigationBar()
+
+ navigationItem.title = TextLiteral.MyPage.languageSetting
+
+ let backButton = UIBarButtonItem(
+ image: UIImage(systemName: "chevron.left"),
+ style: .plain,
+ target: self,
+ action: #selector(backButtonDidTap)
+ )
+
+ backButton.tintColor = .gray500
+ navigationItem.leftBarButtonItem = backButton
+ }
+
+ override func configureUI() {
+ view.backgroundColor = .white
+ view.addSubview(tableView)
+ }
+
+ override func setLayout() {
+ tableView.snp.makeConstraints {
+ $0.top.equalTo(view.safeAreaLayoutGuide)
+ $0.leading.trailing.bottom.equalToSuperview()
+ }
+ }
+
+ private func setTableViewDelegate() {
+ tableView.dataSource = self
+ tableView.delegate = self
+ }
+
+ private func registerTableViewCells() {
+ tableView.register(
+ RadioSelectionTableViewCell.self,
+ forCellReuseIdentifier: RadioSelectionTableViewCell.identifier
+ )
+ }
+
+ private func setInteractivePopGesture() {
+ navigationController?.interactivePopGestureRecognizer?.delegate = self
+ navigationController?.interactivePopGestureRecognizer?.isEnabled = true
+ }
+
+ @objc
+ private func backButtonDidTap() {
+ if didChangeLanguage {
+ resetRootAfterLanguageChangeIfNeeded()
+ } else {
+ navigationController?.popViewController(animated: true)
+ }
+ }
+
+ private func changeLanguage(to language: AppLanguage) {
+ guard language != AppLanguageManager.shared.currentLanguage else {
+ return
+ }
+
+ AppLanguageManager.shared.changeLanguage(to: language)
+ didChangeLanguage = true
+
+ updateLocalizedTexts()
+ }
+
+ private func updateLocalizedTexts() {
+ navigationItem.title = TextLiteral.MyPage.languageSetting
+ tableView.reloadData()
+ }
+
+ private func resetRootAfterLanguageChangeIfNeeded() {
+ guard !isResettingRootViewController else { return }
+
+ isResettingRootViewController = true
+ resetRootViewController()
+ }
+
+ private func resetRootViewController() {
+ guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
+ let keyWindow = windowScene.windows.first(where: { $0.isKeyWindow }) else {
+ return
+ }
+
+ let customTabBarController = CustomTabBarContainerController()
+
+ _ = customTabBarController.view
+ customTabBarController.setTab(index: 3)
+
+ keyWindow.replaceRootViewController(customTabBarController)
+ }
+}
+
+// MARK: - UITableViewDataSource
+
+extension LanguageSettingViewController: UITableViewDataSource {
+ func tableView(
+ _ tableView: UITableView,
+ numberOfRowsInSection section: Int
+ ) -> Int {
+ return AppLanguage.allCases.count
+ }
+
+ func tableView(
+ _ tableView: UITableView,
+ cellForRowAt indexPath: IndexPath
+ ) -> UITableViewCell {
+ guard let cell = tableView.dequeueReusableCell(
+ withIdentifier: RadioSelectionTableViewCell.identifier,
+ for: indexPath
+ ) as? RadioSelectionTableViewCell else {
+ return UITableViewCell()
+ }
+
+ let language = AppLanguage.allCases[indexPath.row]
+
+ cell.configure(
+ title: language.title,
+ isSelected: language == selectedLanguage
+ )
+
+ return cell
+ }
+}
+
+// MARK: - UITableViewDelegate
+
+extension LanguageSettingViewController: UITableViewDelegate {
+ func tableView(
+ _ tableView: UITableView,
+ didSelectRowAt indexPath: IndexPath
+ ) {
+ tableView.deselectRow(at: indexPath, animated: true)
+
+ let language = AppLanguage.allCases[indexPath.row]
+
+ changeLanguage(to: language)
+ }
+}
+
+// MARK: - UIGestureRecognizerDelegate
+
+extension LanguageSettingViewController: UIGestureRecognizerDelegate {
+ func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
+ return navigationController?.viewControllers.count ?? 0 > 1
+ }
+}
diff --git a/EATSSU/App/Sources/Presentation/MyPage/ViewController/MyPageViewController.swift b/EATSSU/App/Sources/Presentation/MyPage/ViewController/MyPageViewController.swift
index d93632d2..ffe96a55 100644
--- a/EATSSU/App/Sources/Presentation/MyPage/ViewController/MyPageViewController.swift
+++ b/EATSSU/App/Sources/Presentation/MyPage/ViewController/MyPageViewController.swift
@@ -16,20 +16,23 @@ import SnapKit
final class MyPageViewController: BaseViewController {
// MARK: - Properties
-
+ private enum URLConstants {
+ static let instagram = "https://www.instagram.com/eatssu.official/"
+ }
+
private var nickName = ""
private var switchState = false
- private let myPageTableLabelList = MyPageLocalData.myPageTableLabelList
-
+ private let sections = MyPageSectionData.sections
+
// MARK: - UI Components
-
+
let mypageView = MyPageView()
-
+
// MARK: - Life Cycles
-
+
override func viewDidLoad() {
super.viewDidLoad()
-
+
setTableViewDelegate()
loadSwitchStateFromUserDefaults()
}
@@ -39,36 +42,43 @@ final class MyPageViewController: BaseViewController {
logScreenView(screenID: FirebaseScreenID.MyPage.mypage1)
}
-
+
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
+
+ let userInfo = UserInfoManager.shared.getCurrentUserInfo()
- nickName = UserInfoManager.shared.getCurrentUserInfo()?.nickname ?? TextLiteral.MyPage.unknownUser
- mypageView.setUserInfo(nickname: nickName)
- }
+ nickName = userInfo?.nickname ?? TextLiteral.MyPage.unknownUser
+ mypageView.setUserInfo(
+ nickname: nickName,
+ collegeName: userInfo?.collegeName,
+ departmentName: userInfo?.departmentName
+ )
+ }
+
// MARK: - Functions
-
+
override func setCustomNavigationBar() {
super.setCustomNavigationBar()
navigationItem.title = TextLiteral.MyPage.myPage
}
-
+
override func configureUI() {
view.addSubviews(mypageView)
}
-
+
override func setLayout() {
mypageView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
}
-
+
override func setButtonEvent() {
mypageView.userWithdrawButton
.addTarget(self, action: #selector(userWithdrawButtonTapped), for: .touchUpInside)
}
-
+
private func setFirebaseTask() {
FirebaseRemoteConfig.shared.fetchRestaurantInfo()
}
@@ -79,70 +89,95 @@ final class MyPageViewController: BaseViewController {
let userWithdrawViewController = UserWithdrawViewController(nickName: nickName)
navigationController?.pushViewController(userWithdrawViewController, animated: true)
}
-
+
/// TableViewDelegate & DataSource를 해당 클래스로 할당합니다.
private func setTableViewDelegate() {
mypageView.myPageTableView.dataSource = self
mypageView.myPageTableView.delegate = self
+
+ if #available(iOS 15.0, *) {
+ mypageView.myPageTableView.sectionHeaderTopPadding = 0
+ }
}
-
+
/// 로그아웃 Alert를 스크린에 표시하는 메소드
private func logoutShowAlert() {
let alert = UIAlertController(title: TextLiteral.MyPage.logout,
message: TextLiteral.MyPage.askLogout,
preferredStyle: UIAlertController.Style.alert)
-
+
let cancelAction = UIAlertAction(title: TextLiteral.Common.cancelDark,
style: .default,
handler: nil)
-
+
let fixAction = UIAlertAction(title: TextLiteral.MyPage.logout,
style: .default,
handler: { _ in
- RealmService.shared.resetDB()
-
- let loginViewController = LoginViewController()
- if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
- let keyWindow = windowScene.windows.first(where: { $0.isKeyWindow })
- {
- keyWindow.replaceRootViewController(UINavigationController(rootViewController: loginViewController))
- }
- })
-
+ RealmService.shared.resetDB()
+
+ let loginViewController = LoginViewController()
+ if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
+ let keyWindow = windowScene.windows.first(where: { $0.isKeyWindow })
+ {
+ keyWindow.replaceRootViewController(UINavigationController(rootViewController: loginViewController))
+ }
+ })
+
alert.addAction(cancelAction)
alert.addAction(fixAction)
-
+
present(alert, animated: true, completion: nil)
}
-
+
/// UserDefaults에 스위치 상태 저장
private func saveSwitchStateToUserDefaults() {
print("사용자 푸시 알림 값을 앱 저장소에 보관합니다.")
UserDefaults.standard.set(switchState, forKey: TextLiteral.MyPage.pushNotificationUserSettingKey)
}
-
+
/// UserDefaults에서 스위치 상태 불러오기
private func loadSwitchStateFromUserDefaults() {
print("사용자 푸시 알림 값을 앱 저장소에서 불러옵니다.")
switchState = UserDefaults.standard.bool(forKey: TextLiteral.MyPage.pushNotificationUserSettingKey)
}
+
+ /// indexPath로 현재 item을 가져오기
+ private func item(at indexPath: IndexPath) -> MyPageLabels {
+ return sections[indexPath.section].items[indexPath.row]
+ }
}
// MARK: - TableView DataSource
extension MyPageViewController: UITableViewDataSource {
- func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
- myPageTableLabelList.count
+ func numberOfSections(in tableView: UITableView) -> Int {
+ return sections.count
}
- func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
- if indexPath.row == MyPageLabels.NotificationSetting.rawValue {
- let cell = tableView
- .dequeueReusableCell(
- withIdentifier: NotificationSettingTableViewCell.identifier,
- for: indexPath
- ) as! NotificationSettingTableViewCell
+
+ func tableView(
+ _ tableView: UITableView,
+ numberOfRowsInSection section: Int
+ ) -> Int {
+ return sections[section].items.count
+ }
+
+ func tableView(
+ _ tableView: UITableView,
+ cellForRowAt indexPath: IndexPath
+ ) -> UITableViewCell {
+ let item = item(at: indexPath)
+
+ switch item {
+ case .notificationSetting:
+ guard let cell = tableView.dequeueReusableCell(
+ withIdentifier: NotificationSettingTableViewCell.identifier,
+ for: indexPath
+ ) as? NotificationSettingTableViewCell else {
+ return UITableViewCell()
+ }
+
+ cell.configure(with: item)
- // Task로 비동기 작업 처리
_Concurrency.Task {
let settings = await NotificationManager.shared.checkNotificationSetting()
@@ -157,16 +192,19 @@ extension MyPageViewController: UITableViewDataSource {
}
}
}
+
return cell
- } else {
- let cell = tableView
- .dequeueReusableCell(
- withIdentifier: MyPageTableDefaultCell.identifier,
- for: indexPath
- ) as! MyPageTableDefaultCell
- let title = myPageTableLabelList[indexPath.row].titleLabel
- cell.serviceLabel.text = title
+ default:
+ guard let cell = tableView.dequeueReusableCell(
+ withIdentifier: MyPageTableDefaultCell.identifier,
+ for: indexPath
+ ) as? MyPageTableDefaultCell else {
+ return UITableViewCell()
+ }
+
+ cell.configure(with: item)
+
return cell
}
}
@@ -175,34 +213,72 @@ extension MyPageViewController: UITableViewDataSource {
// MARK: - UITableView Delegate
extension MyPageViewController: UITableViewDelegate {
- func tableView(_: UITableView, heightForRowAt _: IndexPath) -> CGFloat {
- 60
+ func tableView(
+ _ tableView: UITableView,
+ heightForRowAt indexPath: IndexPath
+ ) -> CGFloat {
+ let item = item(at: indexPath)
+ return MyPageTableMetric.rowHeight(for: item)
}
-
- func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ func tableView(
+ _ tableView: UITableView,
+ heightForHeaderInSection section: Int
+ ) -> CGFloat {
+ return MyPageTableMetric.headerHeight
+ }
+
+ func tableView(
+ _ tableView: UITableView,
+ viewForHeaderInSection section: Int
+ ) -> UIView? {
+ let headerView = UIView()
+ headerView.backgroundColor = .white
+
+ let titleLabel = UILabel()
+ titleLabel.text = sections[section].headerTitle
+ titleLabel.font = .caption1
+ titleLabel.textColor = .gray500
+
+ headerView.addSubview(titleLabel)
+
+ titleLabel.snp.makeConstraints {
+ $0.leading.equalToSuperview().inset(24)
+ $0.centerY.equalToSuperview()
+ }
+
+ return headerView
+ }
+
+ func tableView(
+ _ tableView: UITableView,
+ didSelectRowAt indexPath: IndexPath
+ ) {
tableView.deselectRow(at: indexPath, animated: true)
-
- switch indexPath.row {
- // "푸시 알림 설정" 스위치 토글
- case MyPageLabels.NotificationSetting.rawValue:
+
+ let item = item(at: indexPath)
+
+ switch item {
+ // "푸시 알림 설정" 스위치 토글
+ case .notificationSetting:
AnalyticsService.logEvent("click_mypage_menu", parameters: ["menu": "notification_setting"])
handleNotificationSettingToggle(at: indexPath)
-
- // "내 정보" 스크린으로 이동
- case MyPageLabels.MyInfo.rawValue:
+
+ // "내 정보" 스크린으로 이동
+ case .myInfo:
AnalyticsService.logEvent("click_mypage_menu", parameters: ["menu": "my_info"])
let setNickNameVC = SetNickNameViewController()
setNickNameVC.source = .signup
navigationController?.pushViewController(setNickNameVC, animated: true)
-
- // "내 리뷰" 스크린으로 이동
- case MyPageLabels.MyReview.rawValue:
+
+ // "내 리뷰" 스크린으로 이동
+ case .myReview:
AnalyticsService.logEvent("click_mypage_menu", parameters: ["menu": "my_review"])
let myReviewViewController = MyReviewViewController(nickname: nickName)
navigationController?.pushViewController(myReviewViewController, animated: true)
-
- // "문의하기" 스크린으로 이동
- case MyPageLabels.Inquiry.rawValue:
+
+ // "문의하기" 스크린으로 이동
+ case .inquiry:
AnalyticsService.logEvent("click_mypage_menu", parameters: ["menu": "inquiry"])
TalkApi.shared.chatChannel(channelPublicId: TextLiteral.KakaoChannel.id) { [weak self] error in
if error != nil {
@@ -219,34 +295,69 @@ extension MyPageViewController: UITableViewDelegate {
// TODO: 카카오톡 채널 채팅방으로 연결 성공했을 때, 앱에서 동작되어야 하는 로직 고민
}
}
-
- // "서비스 이용약관" 스크린으로 이동
- case MyPageLabels.TermsOfUse.rawValue:
- AnalyticsService.logEvent("click_mypage_menu", parameters: ["menu": "terms_of_use"])
- let provisionViewController = ProvisionViewController(agreementType: .termsOfService)
- provisionViewController.navigationTitle = TextLiteral.MyPage.termsOfUse
- navigationController?.pushViewController(provisionViewController, animated: true)
-
- // "개인정보 이용약관" 스크린으로 이동
- case MyPageLabels.PrivacyTermsOfUse.rawValue:
- AnalyticsService.logEvent("click_mypage_menu", parameters: ["menu": "privacy_policy"])
- let provisionViewController = ProvisionViewController(agreementType: .privacyPolicy)
- provisionViewController.navigationTitle = TextLiteral.MyPage.privacyTermsOfUse
- navigationController?.pushViewController(provisionViewController, animated: true)
-
+
// "만든사람들" 스크린으로 이동
- case MyPageLabels.Creator.rawValue:
+ case .creators:
AnalyticsService.logEvent("click_mypage_menu", parameters: ["menu": "creator"])
let creatorViewController = CreatorViewController()
navigationController?.pushViewController(creatorViewController, animated: true)
+
+ // 잇슈 인스타그램 이동
+ case .instagram:
+ // TODO: 실제 로그 이름 통일
+ //AnalyticsService.logEvent("click_mypage_menu", parameters: ["menu": "instagram"])
+
+ if let instagramURL = URL(string: URLConstants.instagram) {
+ UIApplication.shared.open(instagramURL)
+ }
+
+ case .languageSetting:
+ // TODO: 실제 로그 이름 통일
+ // AnalyticsService.logEvent("click_mypage_menu", parameters: ["menu": "language_setting"])
- // "로그아웃" 팝업알림 표시
- case MyPageLabels.Logout.rawValue:
+ let languageSettingViewController = LanguageSettingViewController()
+ navigationController?.pushViewController(languageSettingViewController, animated: true)
+
+ // "약관 및 정책" 스크린으로 이동
+ case .termsAndPolicy:
+ let termsAndPolicyViewController = TermsAndPolicyViewController()
+ navigationController?.pushViewController(termsAndPolicyViewController, animated: true)
+
+ // "로그아웃" 팝업알림 표시
+ case .logout:
logoutShowAlert()
+ }
+ }
+
+ func tableView(
+ _ tableView: UITableView,
+ heightForFooterInSection section: Int
+ ) -> CGFloat {
+ return section == sections.count - 1 ? CGFloat.leastNormalMagnitude : MyPageTableMetric.footerHeight
+ }
- default:
- return
+ func tableView(
+ _ tableView: UITableView,
+ viewForFooterInSection section: Int
+ ) -> UIView? {
+ guard section != sections.count - 1 else {
+ return nil
}
+
+ let footerView = UIView()
+
+ let dividerView = UIView()
+ dividerView.backgroundColor = .gray300
+
+ footerView.addSubview(dividerView)
+
+ dividerView.snp.makeConstraints {
+ $0.height.equalTo(1)
+ $0.horizontalEdges.equalToSuperview()
+ $0.centerY.equalToSuperview()
+ }
+
+ return footerView
}
/// 알림 설정 토글 처리
@@ -272,8 +383,8 @@ extension MyPageViewController: UITableViewDelegate {
let formattedDate = dateFormatter.string(from: Date())
let message = newState
- ? TextLiteral.MyPage.agreeNoti(date: formattedDate)
- : TextLiteral.MyPage.disagreeNoti(date: formattedDate)
+ ? TextLiteral.MyPage.agreeNoti(date: formattedDate)
+ : TextLiteral.MyPage.disagreeNoti(date: formattedDate)
self.showToast(message: message, type: .info)
}
@@ -302,7 +413,7 @@ extension MyPageViewController: UITableViewDelegate {
)
let settingsAction = UIAlertAction(title: TextLiteral.Common.moveToSetting, style: .default) { _ in
- NotificationManager.shared.openNotificationSettings()
+ NotificationManager.shared.openNotificationSettings()
}
let cancelAction = UIAlertAction(title: TextLiteral.Common.cancel, style: .cancel)
diff --git a/EATSSU/App/Sources/Presentation/MyPage/ViewController/TermsAndPolicyViewController.swift b/EATSSU/App/Sources/Presentation/MyPage/ViewController/TermsAndPolicyViewController.swift
new file mode 100644
index 00000000..06719e48
--- /dev/null
+++ b/EATSSU/App/Sources/Presentation/MyPage/ViewController/TermsAndPolicyViewController.swift
@@ -0,0 +1,144 @@
+//
+// TermsAndPolicyViewController.swift
+// EATSSU
+//
+// Created by jeongminji on 5/3/26.
+//
+
+import UIKit
+
+import SnapKit
+
+final class TermsAndPolicyViewController: BaseViewController {
+ override var shouldHideTabBar: Bool { true }
+ // MARK: - Properties
+
+ enum TermsAndPolicyType: CaseIterable {
+ case termsOfUse
+ case privacyTermsOfUse
+
+ var title: String {
+ switch self {
+ case .termsOfUse:
+ return TextLiteral.MyPage.termsOfUse
+
+ case .privacyTermsOfUse:
+ return TextLiteral.MyPage.privacyTermsOfUse
+ }
+ }
+ }
+
+ private let termsAndPolicyItems = TermsAndPolicyType.allCases
+
+ // MARK: - UI Components
+
+ private let tableView: UITableView = {
+ let tableView = UITableView()
+ tableView.separatorStyle = .none
+ tableView.rowHeight = 48
+ tableView.backgroundColor = .white
+ return tableView
+ }()
+
+ // MARK: - Life Cycles
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ setTableViewDelegate()
+ registerTableViewCells()
+ }
+
+ // MARK: - Functions
+
+ override func setCustomNavigationBar() {
+ super.setCustomNavigationBar()
+
+ navigationItem.title = TextLiteral.MyPage.termsAndPolicy
+ }
+
+ override func configureUI() {
+ view.backgroundColor = .white
+ view.addSubview(tableView)
+ }
+
+ override func setLayout() {
+ tableView.snp.makeConstraints {
+ $0.top.equalTo(view.safeAreaLayoutGuide)
+ $0.leading.trailing.bottom.equalToSuperview()
+ }
+ }
+
+ private func setTableViewDelegate() {
+ tableView.dataSource = self
+ tableView.delegate = self
+ }
+
+ private func registerTableViewCells() {
+ tableView.register(
+ MyPageTableDefaultCell.self,
+ forCellReuseIdentifier: MyPageTableDefaultCell.identifier
+ )
+ }
+
+ private func pushProvisionViewController(with item: TermsAndPolicyType) {
+ let provisionViewController: ProvisionViewController
+
+ switch item {
+ case .termsOfUse:
+ AnalyticsService.logEvent("click_mypage_menu", parameters: ["menu": "terms_of_use"])
+ provisionViewController = ProvisionViewController(agreementType: .termsOfService)
+
+ case .privacyTermsOfUse:
+ AnalyticsService.logEvent("click_mypage_menu", parameters: ["menu": "privacy_policy"])
+ provisionViewController = ProvisionViewController(agreementType: .privacyPolicy)
+ }
+
+ provisionViewController.navigationTitle = item.title
+
+ navigationController?.pushViewController(
+ provisionViewController,
+ animated: true
+ )
+ }
+}
+
+// MARK: - UITableViewDataSource
+extension TermsAndPolicyViewController: UITableViewDataSource {
+ func tableView(
+ _ tableView: UITableView,
+ numberOfRowsInSection section: Int
+ ) -> Int {
+ return termsAndPolicyItems.count
+ }
+
+ func tableView(
+ _ tableView: UITableView,
+ cellForRowAt indexPath: IndexPath
+ ) -> UITableViewCell {
+ guard let cell = tableView.dequeueReusableCell(
+ withIdentifier: MyPageTableDefaultCell.identifier,
+ for: indexPath
+ ) as? MyPageTableDefaultCell else {
+ return UITableViewCell()
+ }
+
+ let item = termsAndPolicyItems[indexPath.row]
+ cell.configure(title: item.title)
+
+ return cell
+ }
+}
+
+// MARK: - UITableViewDelegate
+extension TermsAndPolicyViewController: UITableViewDelegate {
+ func tableView(
+ _ tableView: UITableView,
+ didSelectRowAt indexPath: IndexPath
+ ) {
+ tableView.deselectRow(at: indexPath, animated: true)
+
+ let item = termsAndPolicyItems[indexPath.row]
+ pushProvisionViewController(with: item)
+ }
+}
diff --git a/EATSSU/App/Sources/Utility/Extension/String+.swift b/EATSSU/App/Sources/Utility/Extension/String+.swift
new file mode 100644
index 00000000..dce0e050
--- /dev/null
+++ b/EATSSU/App/Sources/Utility/Extension/String+.swift
@@ -0,0 +1,44 @@
+//
+// String+.swift
+// EATSSU
+//
+// Created by jeongminji on 5/4/26.
+//
+
+import Foundation
+
+import UIKit
+
+extension String {
+ /// 문자열에서 마지막 단어만 지정한 색상으로 강조한 attributed string을 반환
+ /// - Parameters:
+ /// - baseColor: 전체 문자열에 기본으로 적용할 색상
+ /// - highlightColor: 마지막 단어에 적용할 강조 색상
+ /// - Returns: 마지막 단어만 강조 색상이 적용된 `NSAttributedString`
+ func logoHighlightedLastWord(
+ baseColor: UIColor,
+ highlightColor: UIColor
+ ) -> NSAttributedString {
+ let attributedString = NSMutableAttributedString(
+ string: self,
+ attributes: [
+ .foregroundColor: baseColor
+ ]
+ )
+
+ guard let lastWord = self.split(separator: " ").last else {
+ return attributedString
+ }
+
+ let nsString = self as NSString
+ let range = nsString.range(of: String(lastWord), options: .backwards)
+
+ attributedString.addAttribute(
+ .foregroundColor,
+ value: highlightColor,
+ range: range
+ )
+
+ return attributedString
+ }
+}
diff --git a/EATSSU/App/Sources/Utility/Literal/AppLanguage.swift b/EATSSU/App/Sources/Utility/Literal/AppLanguage.swift
new file mode 100644
index 00000000..3dcc9f6f
--- /dev/null
+++ b/EATSSU/App/Sources/Utility/Literal/AppLanguage.swift
@@ -0,0 +1,22 @@
+//
+// AppLanguage.swift
+// EATSSU
+//
+// Created by jeongminji on 5/3/26.
+//
+
+import Foundation
+
+enum AppLanguage: String, CaseIterable {
+ case korean = "ko"
+ case english = "en"
+
+ var title: String {
+ switch self {
+ case .korean:
+ return "한국어"
+ case .english:
+ return "English"
+ }
+ }
+}
diff --git a/EATSSU/App/Sources/Utility/Literal/AppLanguageManager.swift b/EATSSU/App/Sources/Utility/Literal/AppLanguageManager.swift
new file mode 100644
index 00000000..77d5f1f8
--- /dev/null
+++ b/EATSSU/App/Sources/Utility/Literal/AppLanguageManager.swift
@@ -0,0 +1,71 @@
+//
+// AppLanguageManager.swift
+// EATSSU
+//
+// Created by jeongminji on 5/3/26.
+//
+
+import Foundation
+
+final class AppLanguageManager {
+ static let shared = AppLanguageManager()
+
+ private init() {}
+
+ private enum Constant {
+ /// 사용자가 직접 고른 언어 저장 key
+ static let selectedLanguageKey = "selectedAppLanguage"
+
+ /// 사용자가 앱에서 직접 언어를 바꾼 적 있는지 저장 key
+ static let didSelectLanguageManuallyKey = "didSelectLanguageManually"
+ }
+
+ // MARK: - Current Language
+
+ /// 1순위: 사용자가 직접 선택한 언어가 있으면 그걸 우선 사용
+ /// 2순위: 사용자가 직접 선택한 적이 없으면 휴대폰 언어 사용
+ var currentLanguage: AppLanguage {
+ if didSelectLanguageManually,
+ let savedLanguageCode = UserDefaults.standard.string(forKey: Constant.selectedLanguageKey),
+ let savedLanguage = AppLanguage(rawValue: savedLanguageCode) {
+ return savedLanguage
+ }
+
+ return deviceLanguage
+ }
+
+ var didSelectLanguageManually: Bool {
+ return UserDefaults.standard.bool(forKey: Constant.didSelectLanguageManuallyKey)
+ }
+
+ private var deviceLanguage: AppLanguage {
+ let preferredCode = Locale.preferredLanguages.first ?? "ko"
+
+ if preferredCode.hasPrefix("en") {
+ return .english
+ } else {
+ return .korean
+ }
+ }
+
+ // MARK: - Bundle
+
+ var bundle: Bundle {
+ guard let path = Bundle.main.path(
+ forResource: currentLanguage.rawValue,
+ ofType: "lproj"
+ ),
+ let bundle = Bundle(path: path) else {
+ return .main
+ }
+
+ return bundle
+ }
+
+ // MARK: - Change Language
+
+ func changeLanguage(to language: AppLanguage) {
+ UserDefaults.standard.set(language.rawValue, forKey: Constant.selectedLanguageKey)
+ UserDefaults.standard.set(true, forKey: Constant.didSelectLanguageManuallyKey)
+ }
+}
diff --git a/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift b/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift
index 25497efe..767382d0 100644
--- a/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift
+++ b/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift
@@ -7,9 +7,38 @@
import Foundation
-enum TextLiteral {
+private enum Localization {
+ static func localized(
+ _ key: String,
+ fallback: String
+ ) -> String {
+ let value = AppLanguageManager.shared.bundle.localizedString(
+ forKey: key,
+ value: fallback,
+ table: nil
+ )
+
+ return value
+ }
+ static func formatted(
+ _ key: String,
+ fallback: String,
+ _ arguments: CVarArg...
+ ) -> String {
+ let format = localized(key, fallback: fallback)
+
+ return String(
+ format: format,
+ locale: Locale(identifier: AppLanguageManager.shared.currentLanguage.rawValue),
+ arguments: arguments
+ )
+ }
+}
+
+enum TextLiteral {
// MARK: - KakaoChannel
+
enum KakaoChannel {
/// EATSSU 카카오 채널 ID
static let id: String = "_ZlVAn"
@@ -19,569 +48,944 @@ enum TextLiteral {
enum Common {
/// "확인"
- static let confirm: String = "확인"
+ static var logoSubTitle: String {
+ Localization.localized("common.logoSubTitle", fallback: "숭실대에서 먹자")
+ }
+ /// "확인"
+ static var confirm: String {
+ Localization.localized("common.confirm", fallback: "확인")
+ }
/// "취소"
- static let cancel: String = "취소"
+ static var cancel: String {
+ Localization.localized("common.cancel", fallback: "취소")
+ }
/// "취소하기"
- static let cancelDark: String = "취소하기"
+ static var cancelDark: String {
+ Localization.localized("common.cancelDark", fallback: "취소하기")
+ }
/// "삭제하기"
- static let delete: String = "삭제하기"
+ static var delete: String {
+ Localization.localized("common.delete", fallback: "삭제하기")
+ }
/// "수정하기"
- static let fix: String = "수정하기"
+ static var fix: String {
+ Localization.localized("common.fix", fallback: "수정하기")
+ }
/// "로그인이 필요한 서비스입니다"
- static let needLogin: String = "로그인이 필요한 서비스입니다"
+ static var needLogin: String {
+ Localization.localized("common.needLogin", fallback: "로그인이 필요한 서비스입니다")
+ }
/// "로그인 하시겠습니까?"
- static let askLogin: String = "로그인 하시겠습니까?"
+ static var askLogin: String {
+ Localization.localized("common.askLogin", fallback: "로그인 하시겠습니까?")
+ }
/// "설정으로 이동"
- static let moveToSetting: String = "설정으로 이동"
+ static var moveToSetting: String {
+ Localization.localized("common.moveToSetting", fallback: "설정으로 이동")
+ }
/// "탈퇴 처리가 완료되었습니다."
- static let withdrawComplete: String = "탈퇴 처리가 완료되었습니다."
+ static var withdrawComplete: String {
+ Localization.localized("common.withdrawComplete", fallback: "탈퇴 처리가 완료되었습니다.")
+ }
/// "잠시 후 다시 시도해주세요."
- static let tryAgain: String = "잠시 후 다시 시도해주세요."
+ static var tryAgain: String {
+ Localization.localized("common.tryAgain", fallback: "잠시 후 다시 시도해주세요.")
+ }
/// "세션이 만료되었습니다. 다시 로그인해주세요."
- static let sessionExpired: String = "세션이 만료되었습니다. 다시 로그인해주세요."
+ static var sessionExpired: String {
+ Localization.localized("common.sessionExpired", fallback: "세션이 만료되었습니다. 다시 로그인해주세요.")
+ }
/// "에러가 발생했습니다"
- static let errorOccured: String = "에러가 발생했습니다"
+ static var errorOccured: String {
+ Localization.localized("common.errorOccured", fallback: "에러가 발생했습니다")
+ }
/// "다시 시도하세요"
- static let retry: String = "다시 시도하세요"
+ static var retry: String {
+ Localization.localized("common.retry", fallback: "다시 시도하세요")
+ }
}
// MARK: - TabBar
enum TabBar {
/// "학식"
- static let meal: String = "학식"
+ static var meal: String {
+ Localization.localized("tabBar.meal", fallback: "학식")
+ }
/// "지도"
- static let map: String = "지도"
+ static var map: String {
+ Localization.localized("tabBar.map", fallback: "지도")
+ }
/// "나만아니면돼~"
- static let coffee: String = "나만아니면돼~"
+ static var coffee: String {
+ Localization.localized("tabBar.coffee", fallback: "나만아니면돼~")
+ }
/// "마이"
- static let my: String = "마이"
+ static var my: String {
+ Localization.localized("tabBar.my", fallback: "마이")
+ }
}
// MARK: - Auth
enum Auth {
/// "닉네임을 입력해주세요"
- static let inputNickName: String = "닉네임을 입력해주세요"
+ static var inputNickName: String {
+ Localization.localized("auth.inputNickName", fallback: "닉네임을 입력해주세요")
+ }
/// "Apple로 로그인"
- static let signInWithApple: String = "Apple로 로그인"
+ static var signInWithApple: String {
+ Localization.localized("auth.signInWithApple", fallback: "Apple로 로그인")
+ }
/// "카카오 로그인"
- static let signInWithKakao: String = "카카오 로그인"
+ static var signInWithKakao: String {
+ Localization.localized("auth.signInWithKakao", fallback: "카카오 로그인")
+ }
/// "둘러보기"
- static let lookingWithNoSignIn: String = "둘러보기"
+ static var lookingWithNoSignIn: String {
+ Localization.localized("auth.lookingWithNoSignIn", fallback: "둘러보기")
+ }
/// UserDefaults key for last login provider
static let lastLoginProviderKey: String = "lastLoginProvider"
/// "최근에 로그인했어요"
- static let lastLoginTooltip: String = "최근에 로그인했어요"
+ static var lastLoginTooltip: String {
+ Localization.localized("auth.lastLoginTooltip", fallback: "최근에 로그인했어요")
+ }
/// LoginVC - "카카오톡으로 생성된 계정입니다."
- static let kakaoAccount: String = "카카오톡으로 생성된 계정입니다."
+ static var kakaoAccount: String {
+ Localization.localized("auth.kakaoAccount", fallback: "카카오톡으로 생성된 계정입니다.")
+ }
/// LoginVC - "Apple로 생성된 계정입니다."
- static let appleAccount: String = "Apple로 생성된 계정입니다."
+ static var appleAccount: String {
+ Localization.localized("auth.appleAccount", fallback: "Apple로 생성된 계정입니다.")
+ }
/// SetNickNameView - "닉네임 설정"
- static let setNickname: String = "닉네임 설정"
+ static var setNickname: String {
+ Localization.localized("auth.setNickname", fallback: "닉네임 설정")
+ }
/// SetNickNameView - "중복 확인"
- static let checkDuplicate: String = "중복 확인"
+ static var checkDuplicate: String {
+ Localization.localized("auth.checkDuplicate", fallback: "중복 확인")
+ }
/// SetNickNameView - "소속 설정"
- static let setCollege: String = "소속 설정"
+ static var setCollege: String {
+ Localization.localized("auth.setCollege", fallback: "소속 설정")
+ }
/// SetNickNameView - "단과대"
- static let college: String = "단과대"
+ static var college: String {
+ Localization.localized("auth.college", fallback: "단과대")
+ }
/// SetNickNameView - "학과"
- static let department: String = "학과"
+ static var department: String {
+ Localization.localized("auth.department", fallback: "학과")
+ }
/// SetNickNameView - "연결된 계정"
- static let linkedAccount: String = "연결된 계정"
+ static var linkedAccount: String {
+ Localization.localized("auth.linkedAccount", fallback: "연결된 계정")
+ }
/// SetNickNameView - "없음"
- static let empty: String = "없음"
+ static var empty: String {
+ Localization.localized("auth.empty", fallback: "없음")
+ }
/// SetNickNameView - "저장하기"
- static let save: String = "저장하기"
+ static var save: String {
+ Localization.localized("auth.save", fallback: "저장하기")
+ }
/// SetNickNameView - "카카오"
- static let kakao: String = "카카오"
+ static var kakao: String {
+ Localization.localized("auth.kakao", fallback: "카카오")
+ }
/// SetNickNameView - "APPLE"
- static let apple: String = "APPLE"
+ static var apple: String {
+ Localization.localized("auth.apple", fallback: "APPLE")
+ }
/// SetNickNameVC - "변경된 정보가 없습니다."
- static let noChanges: String = "변경된 정보가 없습니다."
+ static var noChanges: String {
+ Localization.localized("auth.noChanges", fallback: "변경된 정보가 없습니다.")
+ }
/// SetNickNameVC - "유효하지 않은 학과 정보입니다."
- static let invalidDepartment: String = "유효하지 않은 학과 정보입니다."
+ static var invalidDepartment: String {
+ Localization.localized("auth.invalidDepartment", fallback: "유효하지 않은 학과 정보입니다.")
+ }
/// SetNickNameVC - "정보 업데이트 중 오류가 발생했습니다."
- static let updateError: String = "정보 업데이트 중 오류가 발생했습니다."
+ static var updateError: String {
+ Localization.localized("auth.updateError", fallback: "정보 업데이트 중 오류가 발생했습니다.")
+ }
/// SetNickNameVC - "내 정보가 수정되었어요."
- static let updateSuccess: String = "내 정보가 수정되었어요."
+ static var updateSuccess: String {
+ Localization.localized("auth.updateSuccess", fallback: "내 정보가 수정되었어요.")
+ }
/// NIcknameTextFieldResultType - "필수 입력 사항입니다"
- static let requiredInput: String = "필수 입력 사항입니다"
+ static var requiredInput: String {
+ Localization.localized("auth.requiredInput", fallback: "필수 입력 사항입니다")
+ }
/// NIcknameTextFieldResultType - "중복 확인을 진행해주세요."
- static let needCheckDuplicate: String = "중복 확인을 진행해주세요."
+ static var needCheckDuplicate: String {
+ Localization.localized("auth.needCheckDuplicate", fallback: "중복 확인을 진행해주세요.")
+ }
/// NIcknameTextFieldResultType - "이미 사용 중인 닉네임이에요."
- static let duplicatedNickname: String = "이미 사용 중인 닉네임이에요."
+ static var duplicatedNickname: String {
+ Localization.localized("auth.duplicatedNickname", fallback: "이미 사용 중인 닉네임이에요.")
+ }
/// NIcknameTextFieldResultType - "사용가능한 닉네임이에요"
- static let availableNickname: String = "사용가능한 닉네임이에요"
+ static var availableNickname: String {
+ Localization.localized("auth.availableNickname", fallback: "사용가능한 닉네임이에요")
+ }
/// NIcknameTextFieldResultType - "2~16글자를 입력해 주세요."
- static let nicknameLength: String = "2~16글자를 입력해 주세요."
+ static var nicknameLength: String {
+ Localization.localized("auth.nicknameLength", fallback: "2~16글자를 입력해 주세요.")
+ }
/// NIcknameTextFieldResultType - "특수문자로 시작/끝나는 닉네임은 사용할 수 없어요."
- static let specialCharNickname: String = "특수문자로 시작/끝나는 닉네임은 사용할 수 없어요."
+ static var specialCharNickname: String {
+ Localization.localized("auth.specialCharNickname", fallback: "특수문자로 시작/끝나는 닉네임은 사용할 수 없어요.")
+ }
/// NIcknameTextFieldResultType - "연속된 특수문자(--, __)는 사용할 수 없어요."
- static let continuousSpecialChar: String = "연속된 특수문자(--, __)는 사용할 수 없어요."
+ static var continuousSpecialChar: String {
+ Localization.localized("auth.continuousSpecialChar", fallback: "연속된 특수문자(--, __)는 사용할 수 없어요.")
+ }
/// NIcknameTextFieldResultType - "숫자만으로 된 닉네임은 사용할 수 없어요."
- static let numberOnlyNickname: String = "숫자만으로 된 닉네임은 사용할 수 없어요."
+ static var numberOnlyNickname: String {
+ Localization.localized("auth.numberOnlyNickname", fallback: "숫자만으로 된 닉네임은 사용할 수 없어요.")
+ }
/// NIcknameTextFieldResultType - "허용 문자(한글/영문/숫자)만 사용할 수 있어요."
- static let allowedChar: String = "허용 문자(한글/영문/숫자)만 사용할 수 있어요."
+ static var allowedChar: String {
+ Localization.localized("auth.allowedChar", fallback: "허용 문자(한글/영문/숫자)만 사용할 수 있어요.")
+ }
/// NIcknameTextFieldResultType - "사용할 수 없는 단어가 포함되어 있어요."
- static let bannedWord: String = "사용할 수 없는 단어가 포함되어 있어요."
+ static var bannedWord: String {
+ Localization.localized("auth.bannedWord", fallback: "사용할 수 없는 단어가 포함되어 있어요.")
+ }
/// NIcknameTextFieldResultType - "띄어쓰기로 시작/끝나는 닉네임은 사용할 수 없어요."
- static let spaceNickname: String = "띄어쓰기로 시작/끝나는 닉네임은 사용할 수 없어요."
+ static var spaceNickname: String {
+ Localization.localized("auth.spaceNickname", fallback: "띄어쓰기로 시작/끝나는 닉네임은 사용할 수 없어요.")
+ }
/// NIcknameTextFieldResultType - "연속된 띄어쓰기는 사용할 수 없어요."
- static let continuousSpace: String = "연속된 띄어쓰기는 사용할 수 없어요."
+ static var continuousSpace: String {
+ Localization.localized("auth.continuousSpace", fallback: "연속된 띄어쓰기는 사용할 수 없어요.")
+ }
/// NIcknameTextFieldResultType - "이모지, 특수문자는 사용할 수 없어요."
- static let emojiSpecialChar: String = "이모지, 특수문자는 사용할 수 없어요."
+ static var emojiSpecialChar: String {
+ Localization.localized("auth.emojiSpecialChar", fallback: "이모지, 특수문자는 사용할 수 없어요.")
+ }
/// NIcknameTextFieldResultType - "관리자로 혼동될 수 있는 닉네임은 사용할 수 없어요."
- static let adminNickname: String = "관리자로 혼동될 수 있는 닉네임은 사용할 수 없어요."
+ static var adminNickname: String {
+ Localization.localized("auth.adminNickname", fallback: "관리자로 혼동될 수 있는 닉네임은 사용할 수 없어요.")
+ }
/// NIcknameTextFieldResultType - "서비스명 단독 닉네임은 사용할 수 없어요."
- static let serviceNameNickname: String = "서비스명 단독 닉네임은 사용할 수 없어요."
+ static var serviceNameNickname: String {
+ Localization.localized("auth.serviceNameNickname", fallback: "서비스명 단독 닉네임은 사용할 수 없어요.")
+ }
/// NIcknameTextFieldResultType - "욕설, 비속어 등의 표현이 포함된 닉네임은 사용할 수 없어요."
- static let slangNickname: String = "욕설, 비속어 등의 표현이 포함된 닉네임은 사용할 수 없어요."
+ static var slangNickname: String {
+ Localization.localized("auth.slangNickname", fallback: "욕설, 비속어 등의 표현이 포함된 닉네임은 사용할 수 없어요.")
+ }
}
// MARK: - Home
enum Home {
/// Home - "오늘의 메뉴"
- static let todayMenu: String = "오늘의 메뉴"
+ static var todayMenu: String {
+ Localization.localized("home.todayMenu", fallback: "오늘의 메뉴")
+ }
/// Home - "가격"
- static let price: String = "가격"
+ static var price: String {
+ Localization.localized("home.price", fallback: "가격")
+ }
/// Home - "평점"
- static let rating: String = "평점"
+ static var rating: String {
+ Localization.localized("home.rating", fallback: "평점")
+ }
/// Home - " -"
- static let emptyRating: String = " -"
+ static var emptyRating: String {
+ Localization.localized("home.emptyRating", fallback: " -")
+ }
/// Home - "제공되는 메뉴가 없습니다"
- static let noMenuProvidedMessage: String = "제공되는 메뉴가 없습니다"
+ static var noMenuProvidedMessage: String {
+ Localization.localized("home.noMenuProvidedMessage", fallback: "제공되는 메뉴가 없습니다")
+ }
/// CustomTimeTabController - "아침"
- static let morning: String = "아침"
+ static var morning: String {
+ Localization.localized("home.morning", fallback: "아침")
+ }
/// CustomTimeTabController - "점심"
- static let lunch: String = "점심"
+ static var lunch: String {
+ Localization.localized("home.lunch", fallback: "점심")
+ }
/// CustomTimeTabController - "저녁"
- static let dinner: String = "저녁"
+ static var dinner: String {
+ Localization.localized("home.dinner", fallback: "저녁")
+ }
/// RestaurantInfoView - "학생 식당"
- static let studentRestaurant: String = "학생 식당"
+ static var studentRestaurant: String {
+ Localization.localized("home.studentRestaurant", fallback: "학생 식당")
+ }
/// RestaurantInfoView - "식당 위치"
- static let restaurantLocation: String = "식당 위치"
+ static var restaurantLocation: String {
+ Localization.localized("home.restaurantLocation", fallback: "식당 위치")
+ }
/// RestaurantInfoView - "식당 사진"
- static let restaurantPicture: String = "식당 사진"
+ static var restaurantPicture: String {
+ Localization.localized("home.restaurantPicture", fallback: "식당 사진")
+ }
/// RestaurantInfoView - "숭실대학교"
- static let soongsilUniversity: String = "숭실대학교"
+ static var soongsilUniversity: String {
+ Localization.localized("home.soongsilUniversity", fallback: "숭실대학교")
+ }
/// RestaurantInfoView - "영업 시간"
- static let businessHour: String = "영업 시간"
+ static var businessHour: String {
+ Localization.localized("home.businessHour", fallback: "영업 시간")
+ }
/// RestaurantInfoView - "비고"
- static let note: String = "비고"
+ static var note: String {
+ Localization.localized("home.note", fallback: "비고")
+ }
/// RestaurantInfoView - "아시안푸드, 돈까스, 샐러드, 국밥 등\n카페"
- static let dodamEtc: String = "아시안푸드, 돈까스, 샐러드, 국밥 등\n카페"
+ static var dodamEtc: String {
+ Localization.localized("home.dodamEtc", fallback: "아시안푸드, 돈까스, 샐러드, 국밥 등\n카페")
+ }
/// RestaurantMenuGroupCell - "영업 시간이 아니에요."
- static let notBusinessHour: String = "영업 시간이 아니에요."
+ static var notBusinessHour: String {
+ Localization.localized("home.notBusinessHour", fallback: "영업 시간이 아니에요.")
+ }
/// RestaurantTableViewHeader - "기숙사 식당"
- static let dormitoryRestaurant: String = "기숙사 식당"
+ static var dormitoryRestaurant: String {
+ Localization.localized("home.dormitoryRestaurant", fallback: "기숙사 식당")
+ }
}
// MARK: - Map
enum Map {
/// MainMapVC - "제휴 지도"
- static let map: String = "제휴 지도"
+ static var map: String {
+ Localization.localized("map.map", fallback: "제휴 지도")
+ }
/// MainMapView - "전체"
- static let all: String = "전체"
+ static var all: String {
+ Localization.localized("map.all", fallback: "전체")
+ }
/// MainMapView - "내 제휴"
- static let myPartner: String = "내 제휴"
+ static var myPartner: String {
+ Localization.localized("map.myPartner", fallback: "내 제휴")
+ }
/// NoDepartmentSheetVC - "학과를 입력하고\n나만의 제휴를 확인해보세요!"
- static let inputDepartment: String = "학과를 입력하고\n나만의 제휴를 확인해보세요!"
+ static var inputDepartment: String {
+ Localization.localized("map.inputDepartment", fallback: "학과를 입력하고\n나만의 제휴를 확인해보세요!")
+ }
/// NoDepartmentSheetVC - "학과 입력하기"
- static let inputDepartmentButton: String = "학과 입력하기"
+ static var inputDepartmentButton: String {
+ Localization.localized("map.inputDepartmentButton", fallback: "학과 입력하기")
+ }
/// PartnershipDetailSheetVC - "음식점"
- static let restaurant: String = "음식점"
+ static var restaurant: String {
+ Localization.localized("map.restaurant", fallback: "음식점")
+ }
/// PartnershipDetailSheetVC - "카페"
- static let cafe: String = "카페"
+ static var cafe: String {
+ Localization.localized("map.cafe", fallback: "카페")
+ }
/// PartnershipDetailSheetVC - "주점"
- static let pub: String = "주점"
+ static var pub: String {
+ Localization.localized("map.pub", fallback: "주점")
+ }
/// PartnershipDetailSheetVC - "학과 정보 없음"
- static let noDepartmentInfo: String = "학과 정보 없음"
+ static var noDepartmentInfo: String {
+ Localization.localized("map.noDepartmentInfo", fallback: "학과 정보 없음")
+ }
/// MainMapVC+Location - "위치 권한 필요"
- static let needLocationAuth: String = "위치 권한 필요"
+ static var needLocationAuth: String {
+ Localization.localized("map.needLocationAuth", fallback: "위치 권한 필요")
+ }
/// MainMapVC+Location - "지도에서 내 위치를 바로 확인하고, 현재 위치 주변의 제휴점들을 손쉽게 찾아볼 수 있도록 위치 권한을 허용해 주세요."
- static let locationAuthDescription: String = "지도에서 내 위치를 바로 확인하고, 현재 위치 주변의 제휴점들을 손쉽게 찾아볼 수 있도록 위치 권한을 허용해 주세요."
+ static var locationAuthDescription: String {
+ Localization.localized(
+ "map.locationAuthDescription",
+ fallback: "지도에서 내 위치를 바로 확인하고, 현재 위치 주변의 제휴점들을 손쉽게 찾아볼 수 있도록 위치 권한을 허용해 주세요."
+ )
+ }
}
// MARK: - MyPage
enum MyPage {
/// "마이페이지"
- static let myPage: String = "마이페이지"
-
- /// "내 정보"
- static let myInfo: String = "내 정보"
+ static var myPage: String {
+ Localization.localized("myPage.myPage", fallback: "마이페이지")
+ }
- /// "내 리뷰"
- static let myReview: String = "내 리뷰"
-
/// UserWithdrawVC - "회원탈퇴"
- static let withdraw: String = "회원탈퇴"
-
- /// MyPageVC - "로그아웃"
- static let logout: String = "로그아웃"
-
- /// MyPageVC - "정말 로그아웃 하시겠습니까?"
- static let askLogout: String = "정말 로그아웃 하시겠습니까?"
-
+ static var withdraw: String {
+ Localization.localized("myPage.withdraw", fallback: "회원탈퇴")
+ }
+
/// MyPageVC - "EAT-SSU 수신 동의"
static func agreeNoti(date: String) -> String {
- return "EAT-SSU 수신 동의 (\(date))"
+ return Localization.formatted("myPage.agreeNoti", fallback: "EAT-SSU 수신 동의 (%@)", date)
}
/// MyPageVC - "EAT-SSU 수신 거절"
static func disagreeNoti(date: String) -> String {
- return "EAT-SSU 수신 거절 (\(date))"
+ return Localization.formatted("myPage.disagreeNoti", fallback: "EAT-SSU 수신 거절 (%@)", date)
+ }
+
+ // MARK: - MyPageSection: 알림 및 활동
+
+ /// MyPageSectionVC - "알림 및 활동"
+ static var activitySection: String {
+ Localization.localized("myPage.activitySection", fallback: "알림 및 활동")
+ }
+
+ /// "내 정보"
+ static var myInfo: String {
+ Localization.localized("myPage.myInfo", fallback: "내 정보")
+ }
+
+ /// "내 리뷰"
+ static var myReview: String {
+ Localization.localized("myPage.myReview", fallback: "내 리뷰")
+ }
+
+ /// NotificationSettingTableViewCell - "푸시 알림 설정"
+ static var pushNotificationSetting: String {
+ Localization.localized("myPage.pushNotificationSetting", fallback: "푸시 알림 설정")
}
+
+ /// Push Notification key for UserDefaults
+ static let pushNotificationUserSettingKey: String = "pushNotificationUserSettingKey"
+ /// NotificationSettingTableViewCell - "매일 오전 11시에 알림을 보내드려요"
+ static var pushNotificationDescription: String {
+ Localization.localized("myPage.pushNotificationDescription", fallback: "매일 오전 11시에 알림을 보내드려요")
+ }
+
/// MyPageVC - "알림 설정 중 오류가 발생했습니다."
- static let notiSettingError: String = "알림 설정 중 오류가 발생했습니다."
+ static var notiSettingError: String {
+ Localization.localized("myPage.notiSettingError", fallback: "알림 설정 중 오류가 발생했습니다.")
+ }
+
+ // MARK: - MyPageSection: 서비스 정보
+
+ /// MyPageSectionVC - "서비스 정보"
+ static var serviceInfoSection: String {
+ Localization.localized("myPage.serviceInfoSection", fallback: "서비스 정보")
+ }
+
+ /// MyPageVC - "문의하기"
+ static var inquiry: String {
+ Localization.localized("myPage.inquiry", fallback: "문의하기")
+ }
/// CreatorVC - "만든 사람들"
- static let creators: String = "만든 사람들"
+ static var creators: String {
+ Localization.localized("myPage.creators", fallback: "만든 사람들")
+ }
+
+ /// MyPageVC - "EAT-SSU 인스타그램"
+ static var instagram: String {
+ Localization.localized("myPage.instagram", fallback: "EAT-SSU 인스타그램")
+ }
+
+ // MARK: - MyPageSection: 기타
+
+ /// MyPageSectionVC - "기타"
+ static var etcSection: String {
+ Localization.localized("myPage.etcSection", fallback: "기타")
+ }
+
+ /// MyPageVC - "언어 설정"
+ static var languageSetting: String {
+ Localization.localized("myPage.languageSetting", fallback: "언어 설정")
+ }
+
+ /// MyPageVC - "현재 언어"
+ static var currentLanguage: String {
+ return AppLanguageManager.shared.currentLanguage.title
+ }
+
+ /// MyPageVC - "약관 및 정책"
+ static var termsAndPolicy: String {
+ Localization.localized("myPage.termsAndPolicy", fallback: "약관 및 정책")
+ }
+
+ /// MyPageVC - "서비스 이용약관"
+ static var termsOfUse: String {
+ Localization.localized("myPage.termsOfUse", fallback: "서비스 이용약관")
+ }
+
+ /// MyPageVC - "개인정보처리방침"
+ static var privacyTermsOfUse: String {
+ Localization.localized("myPage.privacyTermsOfUse", fallback: "개인정보처리방침")
+ }
+
+ /// MyPageVC - "로그아웃"
+ static var logout: String {
+ Localization.localized("myPage.logout", fallback: "로그아웃")
+ }
+
+ /// MyPageVC - "정말 로그아웃 하시겠습니까?"
+ static var askLogout: String {
+ Localization.localized("myPage.askLogout", fallback: "정말 로그아웃 하시겠습니까?")
+ }
/// MyReviewVC - "리뷰 수정 혹은 삭제"
- static let fixOrDeleteReview: String = "리뷰 수정 혹은 삭제"
+ static var fixOrDeleteReview: String {
+ Localization.localized("myPage.fixOrDeleteReview", fallback: "리뷰 수정 혹은 삭제")
+ }
/// MyReviewVC - "작성하신 리뷰를 수정 또는 삭제하시겠습니까?"
- static let askFixOrDeleteReview: String = "작성하신 리뷰를 수정 또는 삭제하시겠습니까?"
+ static var askFixOrDeleteReview: String {
+ Localization.localized("myPage.askFixOrDeleteReview", fallback: "작성하신 리뷰를 수정 또는 삭제하시겠습니까?")
+ }
/// MyReviewVC - "리뷰 삭제하기"
- static let deleteMyReview: String = "리뷰 삭제하기"
+ static var deleteMyReview: String {
+ Localization.localized("myPage.deleteMyReview", fallback: "리뷰 삭제하기")
+ }
/// MyReviewVC - "해당 리뷰를 삭제할까요?"
- static let askDeleteMyReview: String = "해당 리뷰를 삭제할까요?"
+ static var askDeleteMyReview: String {
+ Localization.localized("myPage.askDeleteMyReview", fallback: "해당 리뷰를 삭제할까요?")
+ }
/// MyReviewVC - "리뷰가 성공적으로 삭제되었습니다."
- static let deleteMyReviewSuccess: String = "리뷰가 성공적으로 삭제되었습니다."
+ static var deleteMyReviewSuccess: String {
+ Localization.localized("myPage.deleteMyReviewSuccess", fallback: "리뷰가 성공적으로 삭제되었습니다.")
+ }
/// MyPageView - "다시 시도해주세요"
- static let retry: String = "다시 시도해주세요"
+ static var retry: String {
+ Localization.localized("myPage.retry", fallback: "다시 시도해주세요")
+ }
/// MyPageView - "앱 버전"
- static let appVersion: String = "앱 버전"
+ static var appVersion: String {
+ Localization.localized("myPage.appVersion", fallback: "앱 버전")
+ }
/// MyPageView - "탈퇴하기"
- static let withdrawButton: String = "탈퇴하기"
+ static var withdrawButton: String {
+ Localization.localized("myPage.withdrawButton", fallback: "탈퇴하기")
+ }
/// MyPageView - "알 수 없음"
- static let unknownUser: String = "알 수 없음"
-
- /// NotificationSettingTableViewCell - "푸시 알림 설정"
- static let pushNotificationSetting: String = "푸시 알림 설정"
-
- /// Push Notification key for UserDefaults
- static let pushNotificationUserSettingKey: String = "pushNotificationUserSettingKey"
-
- /// NotificationSettingTableViewCell - "매일 오전 11시에 알림을 보내드려요"
- static let pushNotificationDescription: String = "매일 오전 11시에 알림을 보내드려요"
-
- /// MyPageVC - "문의하기"
- static let inquiry: String = "문의하기"
-
- /// MyPageVC - "서비스 이용약관"
- static let termsOfUse: String = "서비스 이용약관"
-
- /// MyPageVC - "개인정보 이용약관"
- static let privacyTermsOfUse: String = "개인정보 이용약관"
+ static var unknownUser: String {
+ Localization.localized("myPage.unknownUser", fallback: "알 수 없음")
+ }
/// ProvisionVC - "이용약관"
- static let defaultTerms: String = "이용약관"
-
+ static var defaultTerms: String {
+ Localization.localized("myPage.defaultTerms", fallback: "이용약관")
+ }
+
/// UserWithdrawView - "정말 탈퇴하시겠습니까?"
- static let confirmWithdrawal: String = "정말 탈퇴하시겠습니까?"
+ static var confirmWithdrawal: String {
+ Localization.localized("myPage.confirmWithdrawal", fallback: "정말 탈퇴하시겠습니까?")
+ }
/// UserWithdrawView - "작성한 리뷰 게시글은 삭제되지 않으며, (알수없음)으로 표시됩니다.\n자세한 내용은 서비스이용약관 및 개인정보처리방침을 확인해 주세요."
- static let withdrawalNotice: String = "작성한 리뷰 게시글은 삭제되지 않으며, (알수없음)으로 표시됩니다.\n자세한 내용은 서비스이용약관 및 개인정보처리방침을 확인해 주세요."
+ static var withdrawalNotice: String {
+ Localization.localized(
+ "myPage.withdrawalNotice",
+ fallback: "작성한 리뷰 게시글은 삭제되지 않으며, (알수없음)으로 표시됩니다.\n자세한 내용은 서비스이용약관 및 개인정보처리방침을 확인해 주세요."
+ )
+ }
/// UserWithdrawView - "올바른 입력입니다."
- static let validInputMessage: String = "올바른 입력입니다"
+ static var validInputMessage: String {
+ Localization.localized("myPage.validInputMessage", fallback: "올바른 입력입니다")
+ }
/// UserWithdrawView - "올바르지 않은 닉네임입니다"
- static let invalidNicknameMessage: String = "올바르지 않은 닉네임입니다"
+ static var invalidNicknameMessage: String {
+ Localization.localized("myPage.invalidNicknameMessage", fallback: "올바르지 않은 닉네임입니다")
+ }
}
// MARK: - Review
enum Review {
/// ReportVC - "EAT SSU 팀에게 보내기"
- static let sendToTeam: String = "EAT SSU 팀에게 보내기"
+ static var sendToTeam: String {
+ Localization.localized("review.sendToTeam", fallback: "EAT SSU 팀에게 보내기")
+ }
/// ReportVC - "신고하기"
- static let report: String = "신고하기"
+ static var report: String {
+ Localization.localized("review.report", fallback: "신고하기")
+ }
/// ReportVC - "사유를 선택해주세요!"
- static let selectReason: String = "사유를 선택해주세요!"
+ static var selectReason: String {
+ Localization.localized("review.selectReason", fallback: "사유를 선택해주세요!")
+ }
/// ReportVC - "신고가 성공적으로 접수되었어요!"
- static let reportSuccess: String = "신고가 성공적으로 접수되었어요!"
+ static var reportSuccess: String {
+ Localization.localized("review.reportSuccess", fallback: "신고가 성공적으로 접수되었어요!")
+ }
/// ReportVC, ReportView - "메뉴와 관련없는 내용"
- static let unrelatedMenu: String = "메뉴와 관련없는 내용"
+ static var unrelatedMenu: String {
+ Localization.localized("review.unrelatedMenu", fallback: "메뉴와 관련없는 내용")
+ }
/// ReportVC, ReportView - "음란성, 욕설 등 부적절한 내용"
- static let inappropriateContent: String = "음란성, 욕설 등 부적절한 내용"
+ static var inappropriateContent: String {
+ Localization.localized("review.inappropriateContent", fallback: "음란성, 욕설 등 부적절한 내용")
+ }
/// ReportVC, ReportView - "부적절한 홍보 또는 광고"
- static let inappropriateAd: String = "부적절한 홍보 또는 광고"
+ static var inappropriateAd: String {
+ Localization.localized("review.inappropriateAd", fallback: "부적절한 홍보 또는 광고")
+ }
/// ReportVC, ReportView - "리뷰 작성 취지에 맞지 않는 내용 (복사글 등)"
- static let notReviewFormat: String = "리뷰 작성 취지에 맞지 않는 내용 (복사글 등)"
+ static var notReviewFormat: String {
+ Localization.localized("review.notReviewFormat", fallback: "리뷰 작성 취지에 맞지 않는 내용 (복사글 등)")
+ }
/// ReportVC, ReportView - "저작권 도용 의심 (사진 등)"
- static let copyright: String = "저작권 도용 의심 (사진 등)"
+ static var copyright: String {
+ Localization.localized("review.copyright", fallback: "저작권 도용 의심 (사진 등)")
+ }
/// ReportVC, ReportView - "기타 (하단 내용 작성)"
- static let etc: String = "기타 (하단 내용 작성)"
+ static var etc: String {
+ Localization.localized("review.etc", fallback: "기타 (하단 내용 작성)")
+ }
/// ReportView - "리뷰 신고 사유를 알려주세요"
- static let reportReason: String = "리뷰 신고 사유를 알려주세요"
+ static var reportReason: String {
+ Localization.localized("review.reportReason", fallback: "리뷰 신고 사유를 알려주세요")
+ }
/// ReportView - "하나의 리뷰에 대해 24시간 내 한 번만 신고 가능합니다."
- static let reportGuide: String = "하나의 리뷰에 대해 24시간 내 한 번만 신고 가능합니다."
+ static var reportGuide: String {
+ Localization.localized("review.reportGuide", fallback: "하나의 리뷰에 대해 24시간 내 한 번만 신고 가능합니다.")
+ }
/// ReportView - "리뷰 신고 사유를 작성해 주세요"
- static let inputReportReason: String = "리뷰 신고 사유를 작성해 주세요"
+ static var inputReportReason: String {
+ Localization.localized("review.inputReportReason", fallback: "리뷰 신고 사유를 작성해 주세요")
+ }
/// ReviewVC - "리뷰 작성하기"
- static let writeReview: String = "리뷰 작성하기"
+ static var writeReview: String {
+ Localization.localized("review.writeReview", fallback: "리뷰 작성하기")
+ }
/// ReviewVC - "리뷰가 성공적으로 등록되었습니다."
- static let registerReviewSuccess: String = "리뷰가 성공적으로 등록되었습니다."
+ static var registerReviewSuccess: String {
+ Localization.localized("review.registerReviewSuccess", fallback: "리뷰가 성공적으로 등록되었습니다.")
+ }
/// ReviewVC - "리뷰"
- static let review: String = "리뷰"
+ static var review: String {
+ Localization.localized("review.review", fallback: "리뷰")
+ }
/// ReviewVC - "리뷰 삭제"
- static let deleteReview: String = "리뷰 삭제"
+ static var deleteReview: String {
+ Localization.localized("review.deleteReview", fallback: "리뷰 삭제")
+ }
/// ReviewVC - "해당 리뷰를 삭제할까요?"
- static let askDeleteReview: String = "해당 리뷰를 삭제할까요?"
+ static var askDeleteReview: String {
+ Localization.localized("review.askDeleteReview", fallback: "해당 리뷰를 삭제할까요?")
+ }
/// ReviewVC - "리뷰 신고하기"
- static let reportReview: String = "리뷰 신고하기"
+ static var reportReview: String {
+ Localization.localized("review.reportReview", fallback: "리뷰 신고하기")
+ }
/// ReviewVC - "해당 리뷰를 신고하시겠습니까?"
- static let askReportReview: String = "해당 리뷰를 신고하시겠습니까?"
+ static var askReportReview: String {
+ Localization.localized("review.askReportReview", fallback: "해당 리뷰를 신고하시겠습니까?")
+ }
/// ReviewVC - "리뷰가 성공적으로 삭제되었습니다."
- static let deleteReviewSuccess: String = "리뷰가 성공적으로 삭제되었습니다."
+ static var deleteReviewSuccess: String {
+ Localization.localized("review.deleteReviewSuccess", fallback: "리뷰가 성공적으로 삭제되었습니다.")
+ }
/// ReviewVC - "리뷰 삭제에 실패했습니다."
- static let deleteReviewFail: String = "리뷰 삭제에 실패했습니다."
+ static var deleteReviewFail: String {
+ Localization.localized("review.deleteReviewFail", fallback: "리뷰 삭제에 실패했습니다.")
+ }
/// SetRateVC - "리뷰 수정하기"
- static let fixReview: String = "리뷰 수정하기"
+ static var fixReview: String {
+ Localization.localized("review.fixReview", fallback: "리뷰 수정하기")
+ }
/// SetRateVC - "리뷰 남기기"
- static let leaveReview: String = "리뷰 남기기"
+ static var leaveReview: String {
+ Localization.localized("review.leaveReview", fallback: "리뷰 남기기")
+ }
/// 메뉴 이름의 받침 유무에 따라 '을/를'을 동적으로 붙여 추천 문장을 생성합니다.
static func recommendMenu(name: String) -> String {
guard let lastChar = name.last,
let lastScalar = lastChar.unicodeScalars.first else {
- return "\(name)을(를) 추천하시겠어요?" // 예외 처리
+ return Localization.formatted("review.recommendMenu.default", fallback: "%@을(를) 추천하시겠어요?", name)
}
- // '가' ~ '힣' 사이의 한글 유니코드 범위
let hangulStart: UInt32 = 0xAC00
let hangulEnd: UInt32 = 0xD7A3
- // 받침이 있는지 계산 (종성 코드 확인)
if lastScalar.value >= hangulStart && lastScalar.value <= hangulEnd {
let hasJongseong = (lastScalar.value - hangulStart) % 28 != 0
if hasJongseong {
- return "\(name)을 추천하시겠어요?" // 받침 있음
+ return Localization.formatted("review.recommendMenu.withJongseong", fallback: "%@을 추천하시겠어요?", name)
}
}
- return "\(name)를 추천하시겠어요?" // 받침 없음
+ return Localization.formatted("review.recommendMenu.withoutJongseong", fallback: "%@를 추천하시겠어요?", name)
}
/// SetRateVC - "메뉴를 추천하시겠어요?"
- static let recommendMenuTitle: String = "메뉴를 추천하시겠어요?"
+ static var recommendMenuTitle: String {
+ Localization.localized("review.recommendMenuTitle", fallback: "메뉴를 추천하시겠어요?")
+ }
/// SetRateVC - "리뷰 수정 완료하기"
- static let fixReviewComplete: String = "리뷰 수정 완료하기"
+ static var fixReviewComplete: String {
+ Localization.localized("review.fixReviewComplete", fallback: "리뷰 수정 완료하기")
+ }
/// SetRateVC - "완료하기"
- static let complete: String = "완료하기"
+ static var complete: String {
+ Localization.localized("review.complete", fallback: "완료하기")
+ }
/// SetRateVC - "별점을 입력해주세요!"
- static let inputRating: String = "별점을 입력해주세요!"
+ static var inputRating: String {
+ Localization.localized("review.inputRating", fallback: "별점을 입력해주세요!")
+ }
/// SetRateVC - "메뉴 목록 조회에 실패했습니다."
- static let loadMenuListFail: String = "메뉴 목록 조회에 실패했습니다."
+ static var loadMenuListFail: String {
+ Localization.localized("review.loadMenuListFail", fallback: "메뉴 목록 조회에 실패했습니다.")
+ }
/// SetRateVC - "수정할 리뷰 정보가 없습니다."
- static let noReviewInfoForFix: String = "수정할 리뷰 정보가 없습니다."
+ static var noReviewInfoForFix: String {
+ Localization.localized("review.noReviewInfoForFix", fallback: "수정할 리뷰 정보가 없습니다.")
+ }
/// SetRateVC - "리뷰가 성공적으로 수정되었습니다."
- static let fixReviewSuccess: String = "리뷰가 성공적으로 수정되었습니다."
+ static var fixReviewSuccess: String {
+ Localization.localized("review.fixReviewSuccess", fallback: "리뷰가 성공적으로 수정되었습니다.")
+ }
/// SetRateVC - "리뷰 수정에 실패했습니다."
- static let fixReviewFail: String = "리뷰 수정에 실패했습니다."
+ static var fixReviewFail: String {
+ Localization.localized("review.fixReviewFail", fallback: "리뷰 수정에 실패했습니다.")
+ }
/// SetRateVC - "식단 정보가 없습니다."
- static let noMealInfo: String = "식단 정보가 없습니다."
+ static var noMealInfo: String {
+ Localization.localized("review.noMealInfo", fallback: "식단 정보가 없습니다.")
+ }
/// SetRateVC - "리뷰 업로드에 실패했습니다."
- static let uploadReviewFail: String = "리뷰 업로드에 실패했습니다."
+ static var uploadReviewFail: String {
+ Localization.localized("review.uploadReviewFail", fallback: "리뷰 업로드에 실패했습니다.")
+ }
/// SetRateVC - "메뉴 정보가 없습니다."
- static let noMenuInfo: String = "메뉴 정보가 없습니다."
+ static var noMenuInfo: String {
+ Localization.localized("review.noMenuInfo", fallback: "메뉴 정보가 없습니다.")
+ }
/// SetRateVC - "메뉴에 대한 상세한 리뷰를 작성해주세요"
- static let inputDetailReview: String = "메뉴에 대한 상세한 리뷰를 작성해주세요"
+ static var inputDetailReview: String {
+ Localization.localized("review.inputDetailReview", fallback: "메뉴에 대한 상세한 리뷰를 작성해주세요")
+ }
/// SetRateVC - "나가시겠어요?"
- static let askLeave: String = "나가시겠어요?"
+ static var askLeave: String {
+ Localization.localized("review.askLeave", fallback: "나가시겠어요?")
+ }
/// SetRateVC - "지금 나가면 작성한 내용이 저장되지 않습니다."
- static let leaveWarning: String = "지금 나가면 작성한 내용이 저장되지 않습니다."
+ static var leaveWarning: String {
+ Localization.localized("review.leaveWarning", fallback: "지금 나가면 작성한 내용이 저장되지 않습니다.")
+ }
/// SetRateVC - "나가기"
- static let leave: String = "나가기"
+ static var leave: String {
+ Localization.localized("review.leave", fallback: "나가기")
+ }
/// SetRateVC - "계속 작성"
- static let continueWriting: String = "계속 작성"
+ static var continueWriting: String {
+ Localization.localized("review.continueWriting", fallback: "계속 작성")
+ }
/// ReviewEmptyViewCell - "아직 작성된 리뷰가 없어요!"
- static let noReview: String = "아직 작성된 리뷰가 없어요!"
+ static var noReview: String {
+ Localization.localized("review.noReview", fallback: "아직 작성된 리뷰가 없어요!")
+ }
/// ReviewEmptyViewCell - "메뉴에 가장 먼저 리뷰를 남겨주세요!"
- static let beFirstReviewer: String = "메뉴에 가장 먼저 리뷰를 남겨주세요!"
+ static var beFirstReviewer: String {
+ Localization.localized("review.beFirstReviewer", fallback: "메뉴에 가장 먼저 리뷰를 남겨주세요!")
+ }
/// ReviewEmptyViewCell - "로그인이 필요합니다"
- static let needLogin: String = "로그인이 필요합니다"
+ static var needLogin: String {
+ Localization.localized("review.needLogin", fallback: "로그인이 필요합니다")
+ }
/// ReviewEmptyViewCell - "로그인 후 리뷰를 확인하세요"
- static let checkReviewAfterLogin: String = "로그인 후 리뷰를 확인하세요"
+ static var checkReviewAfterLogin: String {
+ Localization.localized("review.checkReviewAfterLogin", fallback: "로그인 후 리뷰를 확인하세요")
+ }
/// ReviewEmptyViewCell - "아직 작성한 리뷰가 없어요"
- static let noWrittenReview: String = "아직 작성한 리뷰가 없어요"
+ static var noWrittenReview: String {
+ Localization.localized("review.noWrittenReview", fallback: "아직 작성한 리뷰가 없어요")
+ }
/// ReviewEmptyViewCell - "첫 리뷰를 남겨 주세요!"
- static let writeFirstReview: String = "첫 리뷰를 남겨 주세요!"
+ static var writeFirstReview: String {
+ Localization.localized("review.writeFirstReview", fallback: "첫 리뷰를 남겨 주세요!")
+ }
/// ReviewDividerCell - "리뷰"
static func reviewCount(_ count: Int) -> String {
- return "리뷰 \(count)"
+ return Localization.formatted("review.reviewCount", fallback: "리뷰 %d", count)
}
/// ReviewRateViewCell - "오늘의 메뉴"
- static let todayMenu: String = "오늘의 메뉴"
+ static var todayMenu: String {
+ Localization.localized("review.todayMenu", fallback: "오늘의 메뉴")
+ }
/// ReviewRateViewCell - "5점"
- static let fiveStars: String = "5점"
+ static var fiveStars: String {
+ Localization.localized("review.fiveStars", fallback: "5점")
+ }
/// ReviewRateViewCell - "4점"
- static let fourStars: String = "4점"
+ static var fourStars: String {
+ Localization.localized("review.fourStars", fallback: "4점")
+ }
/// ReviewRateViewCell - "3점"
- static let threeStars: String = "3점"
+ static var threeStars: String {
+ Localization.localized("review.threeStars", fallback: "3점")
+ }
/// ReviewRateViewCell - "2점"
- static let twoStars: String = "2점"
+ static var twoStars: String {
+ Localization.localized("review.twoStars", fallback: "2점")
+ }
/// ReviewRateViewCell - "1점"
- static let oneStar: String = "1점"
+ static var oneStar: String {
+ Localization.localized("review.oneStar", fallback: "1점")
+ }
/// SetRateView - "오늘의 식사는 어떠셨나요?"
- static let rateTodayMeal: String = "오늘의 식사는 어떠셨나요?"
+ static var rateTodayMeal: String {
+ Localization.localized("review.rateTodayMeal", fallback: "오늘의 식사는 어떠셨나요?")
+ }
/// SetRateView - "추천하고 싶은 메뉴가 있나요?"
- static let recommendMenu: String = "추천하고 싶은 메뉴가 있나요?"
+ static var recommendMenu: String {
+ Localization.localized("review.recommendMenu", fallback: "추천하고 싶은 메뉴가 있나요?")
+ }
/// SetRateView - "사진 추가 (0/1)"
static func addPhoto(count: Int) -> String {
- return "사진 추가 (\(count)/1)"
+ return Localization.formatted("review.addPhoto", fallback: "사진 추가 (%d/1)", count)
}
/// character count
static func characterCount(current: Int, max: Int) -> String {
- return "\(current) / \(max)"
+ return Localization.formatted("review.characterCount", fallback: "%d / %d", current, max)
}
}
@@ -589,66 +993,132 @@ enum TextLiteral {
enum Coffee {
/// "나가시겠어요?"
- static let askLeave: String = "나가시겠어요?"
+ static var askLeave: String {
+ Localization.localized("coffee.askLeave", fallback: "나가시겠어요?")
+ }
/// "지금 나가면 진행 상황이\n저장되지 않습니다."
- static let leaveWarning: String = "지금 나가면 진행 상황이\n저장되지 않습니다."
+ static var leaveWarning: String {
+ Localization.localized("coffee.leaveWarning", fallback: "지금 나가면 진행 상황이\n저장되지 않습니다.")
+ }
/// "나가기"
- static let leave: String = "나가기"
+ static var leave: String {
+ Localization.localized("coffee.leave", fallback: "나가기")
+ }
/// "계속하기"
- static let continueEvent: String = "계속하기"
+ static var continueEvent: String {
+ Localization.localized("coffee.continueEvent", fallback: "계속하기")
+ }
}
// MARK: - Splash
enum Splash {
/// NoticeSplashVC - "긴급 서버 점검 안내"
- static let serverInspection: String = "긴급 서버 점검 안내"
+ static var serverInspection: String {
+ Localization.localized("splash.serverInspection", fallback: "긴급 서버 점검 안내")
+ }
}
// MARK: - PromotionPopup
enum PromotionPopup {
/// 03. 16(월)~03. 27(금)
- static let period: String = "03. 16(월)~03. 27(금)"
+ static var period: String {
+ Localization.localized("promotionPopup.period", fallback: "03. 16(월)~03. 27(금)")
+ }
/// EAT-SSU 인스타그램 바로가기
- static let instagramButtonTitle: String = "EAT-SSU 인스타그램 바로가기"
+ static var instagramButtonTitle: String {
+ Localization.localized("promotionPopup.instagramButtonTitle", fallback: "EAT-SSU 인스타그램 바로가기")
+ }
/// 자세한 내용은 EAT-SSU 인스타그램을 확인해 주세요
- static let guideMessage: String = "자세한 내용은 EAT-SSU 인스타그램을 확인해 주세요"
+ static var guideMessage: String {
+ Localization.localized("promotionPopup.guideMessage", fallback: "자세한 내용은 EAT-SSU 인스타그램을 확인해 주세요")
+ }
/// 다시 보지 않기
- static let neverShowAgain: String = "다시 보지 않기"
+ static var neverShowAgain: String {
+ Localization.localized("promotionPopup.neverShowAgain", fallback: "다시 보지 않기")
+ }
/// 닫기
- static let close: String = "닫기"
+ static var close: String {
+ Localization.localized("promotionPopup.close", fallback: "닫기")
+ }
}
// MARK: - Notification
enum Notification {
/// 🤔 오늘 밥 뭐 먹지…
- static let dailyWeekdayNotificationTitle: String = "🤔 오늘 밥 뭐 먹지…"
+ static var dailyWeekdayNotificationTitle: String {
+ Localization.localized("notification.dailyWeekdayNotificationTitle", fallback: "🤔 오늘 밥 뭐 먹지…")
+ }
/// 오늘의 학식을 확인해보세요!
- static let dailyWeekdayNotificationBody: String = "오늘의 학식을 확인해보세요!"
+ static var dailyWeekdayNotificationBody: String {
+ Localization.localized("notification.dailyWeekdayNotificationBody", fallback: "오늘의 학식을 확인해보세요!")
+ }
+ /// 알림 권한 필요
+ static var permissionDeniedMessage: String {
+ Localization.localized(
+ "notification_error_permission_denied_message",
+ fallback: "알림 권한 필요"
+ )
+ }
+ /// 알림을 받으려면 설정에서 알림 권한을 허용해주세요.
+ static var permissionDeniedDescription: String {
+ Localization.localized(
+ "notification_error_permission_denied_description",
+ fallback: "알림을 받으려면 설정에서 알림 권한을 허용해주세요."
+ )
+ }
+ /// 알 수 없는 오류
+ static var unknownErrorMessage: String {
+ Localization.localized(
+ "notification_error_unknown_message",
+ fallback: "알 수 없는 오류"
+ )
+ }
+ /// 다시 시도해주세요.
+ static var unknownErrorDescription: String {
+ Localization.localized(
+ "notification_error_unknown_description",
+ fallback: "다시 시도해주세요."
+ )
+ }
}
// MARK: - Restaurant
enum Restaurant {
/// "기숙사 식당"
- static let dormitoryRestaurant: String = "기숙사 식당"
+ static var dormitoryRestaurant: String {
+ Localization.localized("restaurant.dormitoryRestaurant", fallback: "기숙사 식당")
+ }
+
/// "도담 식당"
- static let dodamRestaurant: String = "도담 식당"
+ static var dodamRestaurant: String {
+ Localization.localized("restaurant.dodamRestaurant", fallback: "도담 식당")
+ }
+
/// "학생 식당"
- static let studentRestaurant: String = "학생 식당"
+ static var studentRestaurant: String {
+ Localization.localized("restaurant.studentRestaurant", fallback: "학생 식당")
+ }
+
/// "스낵 코너"
- static let snackCorner: String = "스낵 코너"
+ static var snackCorner: String {
+ Localization.localized("restaurant.snackCorner", fallback: "스낵 코너")
+ }
+
/// "FACULTY (교직원 전용)"
- static let facultyRestaurant: String = "FACULTY (교직원 전용)"
+ static var facultyRestaurant: String {
+ Localization.localized("restaurant.facultyRestaurant", fallback: "FACULTY (교직원 전용)")
+ }
}
}
diff --git a/EATSSU/App/Sources/Utility/UIComponent/CustomRadioButton.swift b/EATSSU/App/Sources/Utility/UIComponent/CustomRadioButton.swift
new file mode 100644
index 00000000..3c7efec1
--- /dev/null
+++ b/EATSSU/App/Sources/Utility/UIComponent/CustomRadioButton.swift
@@ -0,0 +1,53 @@
+//
+// CustomRadioButton.swift
+// EATSSU
+//
+// Created by jeongminji on 5/3/26.
+//
+
+import UIKit
+
+import EATSSUDesign
+
+final class CustomRadioButton: UIButton {
+
+ // MARK: - Initializer
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+
+ configureUI()
+ updateState(isSelected: false)
+ }
+
+ @available(*, unavailable)
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func layoutSubviews() {
+ super.layoutSubviews()
+
+ layer.cornerRadius = bounds.width / 2
+ }
+
+ // MARK: - Function
+
+ private func configureUI() {
+ backgroundColor = .clear
+ layer.borderWidth = 2
+ layer.borderColor = UIColor.gray400.cgColor
+ }
+
+ func updateState(isSelected: Bool) {
+ self.isSelected = isSelected
+
+ layer.borderWidth = isSelected ? 6 : 2
+ layer.borderColor = isSelected
+ ? UIColor.primary.cgColor
+ : UIColor.gray400.cgColor
+
+ backgroundColor = .clear
+ setNeedsLayout()
+ }
+}
diff --git a/EATSSU/App/Sources/Utility/UIComponent/SocialLoginButton.swift b/EATSSU/App/Sources/Utility/UIComponent/SocialLoginButton.swift
new file mode 100644
index 00000000..49dcc845
--- /dev/null
+++ b/EATSSU/App/Sources/Utility/UIComponent/SocialLoginButton.swift
@@ -0,0 +1,154 @@
+//
+// SocialLoginButton.swift
+// EATSSU
+//
+// Created by jeongminji on 5/4/26.
+//
+
+import UIKit
+
+import SnapKit
+
+import EATSSUDesign
+
+// MARK: - extension UIColor: 카카오 로그인 버튼 규격
+
+private extension UIColor {
+ static let kakaoContainer = UIColor(
+ red: 254 / 255,
+ green: 229 / 255,
+ blue: 0 / 255,
+ alpha: 1.0
+ )
+
+ static let kakaoSymbol = UIColor.black
+
+ static let kakaoLabel = UIColor.black.withAlphaComponent(0.85)
+}
+
+final class SocialLoginButton: UIButton {
+
+ // MARK: - Properties
+
+ enum LoginType {
+ case apple
+ case kakao
+
+ var title: String {
+ switch self {
+ case .apple:
+ return TextLiteral.Auth.signInWithApple
+ case .kakao:
+ return TextLiteral.Auth.signInWithKakao
+ }
+ }
+
+ var backgroundColor: UIColor {
+ switch self {
+ case .apple:
+ return .black
+ case .kakao:
+ return .kakaoContainer
+ }
+ }
+
+ var titleColor: UIColor {
+ switch self {
+ case .apple:
+ return .white
+ case .kakao:
+ return .kakaoLabel
+ }
+ }
+
+ var icon: UIImage {
+ switch self {
+ case .apple:
+ return EATSSUDesignAsset.Images.appleLoginLogo.image
+ case .kakao:
+ return EATSSUDesignAsset.Images.kakaoLoginLogo.image
+ }
+ }
+
+ var iconVerticalInset: CGFloat {
+ switch self {
+ case .apple:
+ return 11
+ case .kakao:
+ return 14
+ }
+ }
+
+ var iconAspectRatio: CGFloat {
+ return icon.size.width / icon.size.height
+ }
+ }
+
+ private enum Constant {
+ static let buttonAspectRatio = 45.0 / 300.0
+ }
+
+ private let type: LoginType
+
+ // MARK: - UI Components
+
+ private let iconImageView: UIImageView = {
+ let imageView = UIImageView()
+ imageView.contentMode = .scaleAspectFit
+ return imageView
+ }()
+
+ private let loginTitleLabel: UILabel = {
+ let label = UILabel()
+ label.font = .systemFont(ofSize: 15, weight: .regular)
+ label.textAlignment = .center
+ return label
+ }()
+
+ // MARK: - Initializer
+
+ init(type: LoginType) {
+ self.type = type
+ super.init(frame: .zero)
+
+ configureUI()
+ setLayout()
+ configure()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ // MARK: - Functions
+
+ private func configureUI() {
+ layer.cornerRadius = 5
+ clipsToBounds = true
+
+ addSubviews(iconImageView, loginTitleLabel)
+ }
+
+ private func setLayout() {
+ snp.makeConstraints {
+ $0.height.equalTo(snp.width).multipliedBy(Constant.buttonAspectRatio)
+ }
+
+ iconImageView.snp.makeConstraints {
+ $0.leading.equalToSuperview().inset(15)
+ $0.verticalEdges.equalToSuperview().inset(type.iconVerticalInset)
+ $0.width.equalTo(iconImageView.snp.height).multipliedBy(type.iconAspectRatio)
+ }
+
+ loginTitleLabel.snp.makeConstraints {
+ $0.center.equalToSuperview()
+ }
+ }
+
+ private func configure() {
+ backgroundColor = type.backgroundColor
+ iconImageView.image = type.icon
+ loginTitleLabel.text = type.title
+ loginTitleLabel.textColor = type.titleColor
+ }
+}
diff --git a/EATSSU/Project.swift b/EATSSU/Project.swift
index b86afd34..a181d045 100644
--- a/EATSSU/Project.swift
+++ b/EATSSU/Project.swift
@@ -99,7 +99,7 @@ let widgetDeploymentTarget: DeploymentTargets = .iOS("17.0")
let project = Project(
name: "EATSSU",
options: .options(
- defaultKnownRegions: ["ko"],
+ defaultKnownRegions: ["ko", "en"],
developmentRegion: "ko"
),
settings: projectSettings,
diff --git a/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/AppleLoginButton.imageset/AppleLoginButton.svg b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/AppleLoginButton.imageset/AppleLoginButton.svg
deleted file mode 100644
index 2177a536..00000000
--- a/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/AppleLoginButton.imageset/AppleLoginButton.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/AppleLoginButton.imageset/Contents.json b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/AppleLoginButton.imageset/Contents.json
deleted file mode 100644
index f1a1f5b2..00000000
--- a/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/AppleLoginButton.imageset/Contents.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "images" : [
- {
- "idiom" : "universal",
- "scale" : "1x"
- },
- {
- "filename" : "AppleLoginButton.svg",
- "idiom" : "universal",
- "scale" : "2x"
- },
- {
- "idiom" : "universal",
- "scale" : "3x"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
diff --git a/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/AppleLoginLogo.imageset/AppleLoginLogo.svg b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/AppleLoginLogo.imageset/AppleLoginLogo.svg
new file mode 100644
index 00000000..31f0adeb
--- /dev/null
+++ b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/AppleLoginLogo.imageset/AppleLoginLogo.svg
@@ -0,0 +1,3 @@
+
diff --git a/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoLoginButton.imageset/Contents.json b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/AppleLoginLogo.imageset/Contents.json
similarity index 74%
rename from EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoLoginButton.imageset/Contents.json
rename to EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/AppleLoginLogo.imageset/Contents.json
index 1cb770d4..7d70c8a6 100644
--- a/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoLoginButton.imageset/Contents.json
+++ b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/AppleLoginLogo.imageset/Contents.json
@@ -1,7 +1,7 @@
{
"images" : [
{
- "filename" : "KakaoLoginButton.svg",
+ "filename" : "AppleLoginLogo.svg",
"idiom" : "universal"
}
],
diff --git a/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoLoginButton.imageset/KakaoLoginButton.svg b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoLoginButton.imageset/KakaoLoginButton.svg
deleted file mode 100644
index d6710f9b..00000000
--- a/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoLoginButton.imageset/KakaoLoginButton.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
diff --git a/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/LookAroundButton.imageset/Contents.json b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoLoginLogo.imageset/Contents.json
similarity index 74%
rename from EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/LookAroundButton.imageset/Contents.json
rename to EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoLoginLogo.imageset/Contents.json
index 0e0074c0..4823ae8d 100644
--- a/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/LookAroundButton.imageset/Contents.json
+++ b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoLoginLogo.imageset/Contents.json
@@ -1,7 +1,7 @@
{
"images" : [
{
- "filename" : "LookAroundButton.svg",
+ "filename" : "KakaoLoginLogo.svg",
"idiom" : "universal"
}
],
diff --git a/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoLoginLogo.imageset/KakaoLoginLogo.svg b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoLoginLogo.imageset/KakaoLoginLogo.svg
new file mode 100644
index 00000000..636993a2
--- /dev/null
+++ b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoLoginLogo.imageset/KakaoLoginLogo.svg
@@ -0,0 +1,3 @@
+
diff --git a/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/LookAroundButton.imageset/LookAroundButton.svg b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/LookAroundButton.imageset/LookAroundButton.svg
deleted file mode 100644
index 2824bdd5..00000000
--- a/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/LookAroundButton.imageset/LookAroundButton.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-