From e47040552dedaff99863202c366103864ea21313 Mon Sep 17 00:00:00 2001 From: jeongminji Date: Sun, 3 May 2026 16:05:08 +0900 Subject: [PATCH 01/16] =?UTF-8?q?[#275]=20Localizing=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Utility/Literal/TextLiteral.swift | 387 ++++++++-------- .../Literal/en.lproj/Localizable.strings | 428 +++++++++++++++++ .../Literal/ko.lproj/Localizable.strings | 429 ++++++++++++++++++ 3 files changed, 1056 insertions(+), 188 deletions(-) create mode 100644 EATSSU/App/Sources/Utility/Literal/en.lproj/Localizable.strings create mode 100644 EATSSU/App/Sources/Utility/Literal/ko.lproj/Localizable.strings diff --git a/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift b/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift index 25497efe..394e4c76 100644 --- a/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift +++ b/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift @@ -7,6 +7,17 @@ import Foundation +private enum Localization { + static func localized(_ key: String, fallback: String) -> String { + NSLocalizedString(key, tableName: "Localizable", bundle: .main, value: fallback, comment: "") + } + + static func formatted(_ key: String, fallback: String, _ arguments: CVarArg...) -> String { + let format = localized(key, fallback: fallback) + return String(format: format, locale: Locale.current, arguments: arguments) + } +} + enum TextLiteral { // MARK: - KakaoChannel @@ -19,449 +30,449 @@ enum TextLiteral { enum Common { /// "확인" - static let confirm: String = "확인" + static let confirm: String = Localization.localized("common.confirm", fallback: "확인") /// "취소" - static let cancel: String = "취소" + static let cancel: String = Localization.localized("common.cancel", fallback: "취소") /// "취소하기" - static let cancelDark: String = "취소하기" + static let cancelDark: String = Localization.localized("common.cancelDark", fallback: "취소하기") /// "삭제하기" - static let delete: String = "삭제하기" + static let delete: String = Localization.localized("common.delete", fallback: "삭제하기") /// "수정하기" - static let fix: String = "수정하기" + static let fix: String = Localization.localized("common.fix", fallback: "수정하기") /// "로그인이 필요한 서비스입니다" - static let needLogin: String = "로그인이 필요한 서비스입니다" + static let needLogin: String = Localization.localized("common.needLogin", fallback: "로그인이 필요한 서비스입니다") /// "로그인 하시겠습니까?" - static let askLogin: String = "로그인 하시겠습니까?" + static let askLogin: String = Localization.localized("common.askLogin", fallback: "로그인 하시겠습니까?") /// "설정으로 이동" - static let moveToSetting: String = "설정으로 이동" + static let moveToSetting: String = Localization.localized("common.moveToSetting", fallback: "설정으로 이동") /// "탈퇴 처리가 완료되었습니다." - static let withdrawComplete: String = "탈퇴 처리가 완료되었습니다." + static let withdrawComplete: String = Localization.localized("common.withdrawComplete", fallback: "탈퇴 처리가 완료되었습니다.") /// "잠시 후 다시 시도해주세요." - static let tryAgain: String = "잠시 후 다시 시도해주세요." + static let tryAgain: String = Localization.localized("common.tryAgain", fallback: "잠시 후 다시 시도해주세요.") /// "세션이 만료되었습니다. 다시 로그인해주세요." - static let sessionExpired: String = "세션이 만료되었습니다. 다시 로그인해주세요." + static let sessionExpired: String = Localization.localized("common.sessionExpired", fallback: "세션이 만료되었습니다. 다시 로그인해주세요.") /// "에러가 발생했습니다" - static let errorOccured: String = "에러가 발생했습니다" + static let errorOccured: String = Localization.localized("common.errorOccured", fallback: "에러가 발생했습니다") /// "다시 시도하세요" - static let retry: String = "다시 시도하세요" + static let retry: String = Localization.localized("common.retry", fallback: "다시 시도하세요") } // MARK: - TabBar enum TabBar { /// "학식" - static let meal: String = "학식" + static let meal: String = Localization.localized("tabBar.meal", fallback: "학식") /// "지도" - static let map: String = "지도" + static let map: String = Localization.localized("tabBar.map", fallback: "지도") /// "나만아니면돼~" - static let coffee: String = "나만아니면돼~" + static let coffee: String = Localization.localized("tabBar.coffee", fallback: "나만아니면돼~") /// "마이" - static let my: String = "마이" + static let my: String = Localization.localized("tabBar.my", fallback: "마이") } // MARK: - Auth enum Auth { /// "닉네임을 입력해주세요" - static let inputNickName: String = "닉네임을 입력해주세요" + static let inputNickName: String = Localization.localized("auth.inputNickName", fallback: "닉네임을 입력해주세요") /// "Apple로 로그인" - static let signInWithApple: String = "Apple로 로그인" + static let signInWithApple: String = Localization.localized("auth.signInWithApple", fallback: "Apple로 로그인") /// "카카오 로그인" - static let signInWithKakao: String = "카카오 로그인" + static let signInWithKakao: String = Localization.localized("auth.signInWithKakao", fallback: "카카오 로그인") /// "둘러보기" - static let lookingWithNoSignIn: String = "둘러보기" + static let lookingWithNoSignIn: String = Localization.localized("auth.lookingWithNoSignIn", fallback: "둘러보기") /// UserDefaults key for last login provider static let lastLoginProviderKey: String = "lastLoginProvider" /// "최근에 로그인했어요" - static let lastLoginTooltip: String = "최근에 로그인했어요" + static let lastLoginTooltip: String = Localization.localized("auth.lastLoginTooltip", fallback: "최근에 로그인했어요") /// LoginVC - "카카오톡으로 생성된 계정입니다." - static let kakaoAccount: String = "카카오톡으로 생성된 계정입니다." + static let kakaoAccount: String = Localization.localized("auth.kakaoAccount", fallback: "카카오톡으로 생성된 계정입니다.") /// LoginVC - "Apple로 생성된 계정입니다." - static let appleAccount: String = "Apple로 생성된 계정입니다." + static let appleAccount: String = Localization.localized("auth.appleAccount", fallback: "Apple로 생성된 계정입니다.") /// SetNickNameView - "닉네임 설정" - static let setNickname: String = "닉네임 설정" + static let setNickname: String = Localization.localized("auth.setNickname", fallback: "닉네임 설정") /// SetNickNameView - "중복 확인" - static let checkDuplicate: String = "중복 확인" + static let checkDuplicate: String = Localization.localized("auth.checkDuplicate", fallback: "중복 확인") /// SetNickNameView - "소속 설정" - static let setCollege: String = "소속 설정" + static let setCollege: String = Localization.localized("auth.setCollege", fallback: "소속 설정") /// SetNickNameView - "단과대" - static let college: String = "단과대" + static let college: String = Localization.localized("auth.college", fallback: "단과대") /// SetNickNameView - "학과" - static let department: String = "학과" + static let department: String = Localization.localized("auth.department", fallback: "학과") /// SetNickNameView - "연결된 계정" - static let linkedAccount: String = "연결된 계정" + static let linkedAccount: String = Localization.localized("auth.linkedAccount", fallback: "연결된 계정") /// SetNickNameView - "없음" - static let empty: String = "없음" + static let empty: String = Localization.localized("auth.empty", fallback: "없음") /// SetNickNameView - "저장하기" - static let save: String = "저장하기" + static let save: String = Localization.localized("auth.save", fallback: "저장하기") /// SetNickNameView - "카카오" - static let kakao: String = "카카오" + static let kakao: String = Localization.localized("auth.kakao", fallback: "카카오") /// SetNickNameView - "APPLE" - static let apple: String = "APPLE" + static let apple: String = Localization.localized("auth.apple", fallback: "APPLE") /// SetNickNameVC - "변경된 정보가 없습니다." - static let noChanges: String = "변경된 정보가 없습니다." + static let noChanges: String = Localization.localized("auth.noChanges", fallback: "변경된 정보가 없습니다.") /// SetNickNameVC - "유효하지 않은 학과 정보입니다." - static let invalidDepartment: String = "유효하지 않은 학과 정보입니다." + static let invalidDepartment: String = Localization.localized("auth.invalidDepartment", fallback: "유효하지 않은 학과 정보입니다.") /// SetNickNameVC - "정보 업데이트 중 오류가 발생했습니다." - static let updateError: String = "정보 업데이트 중 오류가 발생했습니다." + static let updateError: String = Localization.localized("auth.updateError", fallback: "정보 업데이트 중 오류가 발생했습니다.") /// SetNickNameVC - "내 정보가 수정되었어요." - static let updateSuccess: String = "내 정보가 수정되었어요." + static let updateSuccess: String = Localization.localized("auth.updateSuccess", fallback: "내 정보가 수정되었어요.") /// NIcknameTextFieldResultType - "필수 입력 사항입니다" - static let requiredInput: String = "필수 입력 사항입니다" + static let requiredInput: String = Localization.localized("auth.requiredInput", fallback: "필수 입력 사항입니다") /// NIcknameTextFieldResultType - "중복 확인을 진행해주세요." - static let needCheckDuplicate: String = "중복 확인을 진행해주세요." + static let needCheckDuplicate: String = Localization.localized("auth.needCheckDuplicate", fallback: "중복 확인을 진행해주세요.") /// NIcknameTextFieldResultType - "이미 사용 중인 닉네임이에요." - static let duplicatedNickname: String = "이미 사용 중인 닉네임이에요." + static let duplicatedNickname: String = Localization.localized("auth.duplicatedNickname", fallback: "이미 사용 중인 닉네임이에요.") /// NIcknameTextFieldResultType - "사용가능한 닉네임이에요" - static let availableNickname: String = "사용가능한 닉네임이에요" + static let availableNickname: String = Localization.localized("auth.availableNickname", fallback: "사용가능한 닉네임이에요") /// NIcknameTextFieldResultType - "2~16글자를 입력해 주세요." - static let nicknameLength: String = "2~16글자를 입력해 주세요." + static let nicknameLength: String = Localization.localized("auth.nicknameLength", fallback: "2~16글자를 입력해 주세요.") /// NIcknameTextFieldResultType - "특수문자로 시작/끝나는 닉네임은 사용할 수 없어요." - static let specialCharNickname: String = "특수문자로 시작/끝나는 닉네임은 사용할 수 없어요." + static let specialCharNickname: String = Localization.localized("auth.specialCharNickname", fallback: "특수문자로 시작/끝나는 닉네임은 사용할 수 없어요.") /// NIcknameTextFieldResultType - "연속된 특수문자(--, __)는 사용할 수 없어요." - static let continuousSpecialChar: String = "연속된 특수문자(--, __)는 사용할 수 없어요." + static let continuousSpecialChar: String = Localization.localized("auth.continuousSpecialChar", fallback: "연속된 특수문자(--, __)는 사용할 수 없어요.") /// NIcknameTextFieldResultType - "숫자만으로 된 닉네임은 사용할 수 없어요." - static let numberOnlyNickname: String = "숫자만으로 된 닉네임은 사용할 수 없어요." + static let numberOnlyNickname: String = Localization.localized("auth.numberOnlyNickname", fallback: "숫자만으로 된 닉네임은 사용할 수 없어요.") /// NIcknameTextFieldResultType - "허용 문자(한글/영문/숫자)만 사용할 수 있어요." - static let allowedChar: String = "허용 문자(한글/영문/숫자)만 사용할 수 있어요." + static let allowedChar: String = Localization.localized("auth.allowedChar", fallback: "허용 문자(한글/영문/숫자)만 사용할 수 있어요.") /// NIcknameTextFieldResultType - "사용할 수 없는 단어가 포함되어 있어요." - static let bannedWord: String = "사용할 수 없는 단어가 포함되어 있어요." + static let bannedWord: String = Localization.localized("auth.bannedWord", fallback: "사용할 수 없는 단어가 포함되어 있어요.") /// NIcknameTextFieldResultType - "띄어쓰기로 시작/끝나는 닉네임은 사용할 수 없어요." - static let spaceNickname: String = "띄어쓰기로 시작/끝나는 닉네임은 사용할 수 없어요." + static let spaceNickname: String = Localization.localized("auth.spaceNickname", fallback: "띄어쓰기로 시작/끝나는 닉네임은 사용할 수 없어요.") /// NIcknameTextFieldResultType - "연속된 띄어쓰기는 사용할 수 없어요." - static let continuousSpace: String = "연속된 띄어쓰기는 사용할 수 없어요." + static let continuousSpace: String = Localization.localized("auth.continuousSpace", fallback: "연속된 띄어쓰기는 사용할 수 없어요.") /// NIcknameTextFieldResultType - "이모지, 특수문자는 사용할 수 없어요." - static let emojiSpecialChar: String = "이모지, 특수문자는 사용할 수 없어요." + static let emojiSpecialChar: String = Localization.localized("auth.emojiSpecialChar", fallback: "이모지, 특수문자는 사용할 수 없어요.") /// NIcknameTextFieldResultType - "관리자로 혼동될 수 있는 닉네임은 사용할 수 없어요." - static let adminNickname: String = "관리자로 혼동될 수 있는 닉네임은 사용할 수 없어요." + static let adminNickname: String = Localization.localized("auth.adminNickname", fallback: "관리자로 혼동될 수 있는 닉네임은 사용할 수 없어요.") /// NIcknameTextFieldResultType - "서비스명 단독 닉네임은 사용할 수 없어요." - static let serviceNameNickname: String = "서비스명 단독 닉네임은 사용할 수 없어요." + static let serviceNameNickname: String = Localization.localized("auth.serviceNameNickname", fallback: "서비스명 단독 닉네임은 사용할 수 없어요.") /// NIcknameTextFieldResultType - "욕설, 비속어 등의 표현이 포함된 닉네임은 사용할 수 없어요." - static let slangNickname: String = "욕설, 비속어 등의 표현이 포함된 닉네임은 사용할 수 없어요." + static let slangNickname: String = Localization.localized("auth.slangNickname", fallback: "욕설, 비속어 등의 표현이 포함된 닉네임은 사용할 수 없어요.") } // MARK: - Home enum Home { /// Home - "오늘의 메뉴" - static let todayMenu: String = "오늘의 메뉴" + static let todayMenu: String = Localization.localized("home.todayMenu", fallback: "오늘의 메뉴") /// Home - "가격" - static let price: String = "가격" + static let price: String = Localization.localized("home.price", fallback: "가격") /// Home - "평점" - static let rating: String = "평점" + static let rating: String = Localization.localized("home.rating", fallback: "평점") /// Home - " -" - static let emptyRating: String = " -" + static let emptyRating: String = Localization.localized("home.emptyRating", fallback: " -") /// Home - "제공되는 메뉴가 없습니다" - static let noMenuProvidedMessage: String = "제공되는 메뉴가 없습니다" + static let noMenuProvidedMessage: String = Localization.localized("home.noMenuProvidedMessage", fallback: "제공되는 메뉴가 없습니다") /// CustomTimeTabController - "아침" - static let morning: String = "아침" + static let morning: String = Localization.localized("home.morning", fallback: "아침") /// CustomTimeTabController - "점심" - static let lunch: String = "점심" + static let lunch: String = Localization.localized("home.lunch", fallback: "점심") /// CustomTimeTabController - "저녁" - static let dinner: String = "저녁" + static let dinner: String = Localization.localized("home.dinner", fallback: "저녁") /// RestaurantInfoView - "학생 식당" - static let studentRestaurant: String = "학생 식당" + static let studentRestaurant: String = Localization.localized("home.studentRestaurant", fallback: "학생 식당") /// RestaurantInfoView - "식당 위치" - static let restaurantLocation: String = "식당 위치" + static let restaurantLocation: String = Localization.localized("home.restaurantLocation", fallback: "식당 위치") /// RestaurantInfoView - "식당 사진" - static let restaurantPicture: String = "식당 사진" + static let restaurantPicture: String = Localization.localized("home.restaurantPicture", fallback: "식당 사진") /// RestaurantInfoView - "숭실대학교" - static let soongsilUniversity: String = "숭실대학교" + static let soongsilUniversity: String = Localization.localized("home.soongsilUniversity", fallback: "숭실대학교") /// RestaurantInfoView - "영업 시간" - static let businessHour: String = "영업 시간" + static let businessHour: String = Localization.localized("home.businessHour", fallback: "영업 시간") /// RestaurantInfoView - "비고" - static let note: String = "비고" + static let note: String = Localization.localized("home.note", fallback: "비고") /// RestaurantInfoView - "아시안푸드, 돈까스, 샐러드, 국밥 등\n카페" - static let dodamEtc: String = "아시안푸드, 돈까스, 샐러드, 국밥 등\n카페" + static let dodamEtc: String = Localization.localized("home.dodamEtc", fallback: "아시안푸드, 돈까스, 샐러드, 국밥 등\n카페") /// RestaurantMenuGroupCell - "영업 시간이 아니에요." - static let notBusinessHour: String = "영업 시간이 아니에요." + static let notBusinessHour: String = Localization.localized("home.notBusinessHour", fallback: "영업 시간이 아니에요.") /// RestaurantTableViewHeader - "기숙사 식당" - static let dormitoryRestaurant: String = "기숙사 식당" + static let dormitoryRestaurant: String = Localization.localized("home.dormitoryRestaurant", fallback: "기숙사 식당") } // MARK: - Map enum Map { /// MainMapVC - "제휴 지도" - static let map: String = "제휴 지도" + static let map: String = Localization.localized("map.map", fallback: "제휴 지도") /// MainMapView - "전체" - static let all: String = "전체" + static let all: String = Localization.localized("map.all", fallback: "전체") /// MainMapView - "내 제휴" - static let myPartner: String = "내 제휴" + static let myPartner: String = Localization.localized("map.myPartner", fallback: "내 제휴") /// NoDepartmentSheetVC - "학과를 입력하고\n나만의 제휴를 확인해보세요!" - static let inputDepartment: String = "학과를 입력하고\n나만의 제휴를 확인해보세요!" + static let inputDepartment: String = Localization.localized("map.inputDepartment", fallback: "학과를 입력하고\n나만의 제휴를 확인해보세요!") /// NoDepartmentSheetVC - "학과 입력하기" - static let inputDepartmentButton: String = "학과 입력하기" + static let inputDepartmentButton: String = Localization.localized("map.inputDepartmentButton", fallback: "학과 입력하기") /// PartnershipDetailSheetVC - "음식점" - static let restaurant: String = "음식점" + static let restaurant: String = Localization.localized("map.restaurant", fallback: "음식점") /// PartnershipDetailSheetVC - "카페" - static let cafe: String = "카페" + static let cafe: String = Localization.localized("map.cafe", fallback: "카페") /// PartnershipDetailSheetVC - "주점" - static let pub: String = "주점" + static let pub: String = Localization.localized("map.pub", fallback: "주점") /// PartnershipDetailSheetVC - "학과 정보 없음" - static let noDepartmentInfo: String = "학과 정보 없음" + static let noDepartmentInfo: String = Localization.localized("map.noDepartmentInfo", fallback: "학과 정보 없음") /// MainMapVC+Location - "위치 권한 필요" - static let needLocationAuth: String = "위치 권한 필요" + static let needLocationAuth: String = Localization.localized("map.needLocationAuth", fallback: "위치 권한 필요") /// MainMapVC+Location - "지도에서 내 위치를 바로 확인하고, 현재 위치 주변의 제휴점들을 손쉽게 찾아볼 수 있도록 위치 권한을 허용해 주세요." - static let locationAuthDescription: String = "지도에서 내 위치를 바로 확인하고, 현재 위치 주변의 제휴점들을 손쉽게 찾아볼 수 있도록 위치 권한을 허용해 주세요." + static let locationAuthDescription: String = Localization.localized("map.locationAuthDescription", fallback: "지도에서 내 위치를 바로 확인하고, 현재 위치 주변의 제휴점들을 손쉽게 찾아볼 수 있도록 위치 권한을 허용해 주세요.") } // MARK: - MyPage enum MyPage { /// "마이페이지" - static let myPage: String = "마이페이지" + static let myPage: String = Localization.localized("myPage.myPage", fallback: "마이페이지") /// "내 정보" - static let myInfo: String = "내 정보" + static let myInfo: String = Localization.localized("myPage.myInfo", fallback: "내 정보") /// "내 리뷰" - static let myReview: String = "내 리뷰" + static let myReview: String = Localization.localized("myPage.myReview", fallback: "내 리뷰") /// UserWithdrawVC - "회원탈퇴" - static let withdraw: String = "회원탈퇴" + static let withdraw: String = Localization.localized("myPage.withdraw", fallback: "회원탈퇴") /// MyPageVC - "로그아웃" - static let logout: String = "로그아웃" + static let logout: String = Localization.localized("myPage.logout", fallback: "로그아웃") /// MyPageVC - "정말 로그아웃 하시겠습니까?" - static let askLogout: String = "정말 로그아웃 하시겠습니까?" + static let askLogout: String = Localization.localized("myPage.askLogout", 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) } /// MyPageVC - "알림 설정 중 오류가 발생했습니다." - static let notiSettingError: String = "알림 설정 중 오류가 발생했습니다." + static let notiSettingError: String = Localization.localized("myPage.notiSettingError", fallback: "알림 설정 중 오류가 발생했습니다.") /// CreatorVC - "만든 사람들" - static let creators: String = "만든 사람들" + static let creators: String = Localization.localized("myPage.creators", fallback: "만든 사람들") /// MyReviewVC - "리뷰 수정 혹은 삭제" - static let fixOrDeleteReview: String = "리뷰 수정 혹은 삭제" + static let fixOrDeleteReview: String = Localization.localized("myPage.fixOrDeleteReview", fallback: "리뷰 수정 혹은 삭제") /// MyReviewVC - "작성하신 리뷰를 수정 또는 삭제하시겠습니까?" - static let askFixOrDeleteReview: String = "작성하신 리뷰를 수정 또는 삭제하시겠습니까?" + static let askFixOrDeleteReview: String = Localization.localized("myPage.askFixOrDeleteReview", fallback: "작성하신 리뷰를 수정 또는 삭제하시겠습니까?") /// MyReviewVC - "리뷰 삭제하기" - static let deleteMyReview: String = "리뷰 삭제하기" + static let deleteMyReview: String = Localization.localized("myPage.deleteMyReview", fallback: "리뷰 삭제하기") /// MyReviewVC - "해당 리뷰를 삭제할까요?" - static let askDeleteMyReview: String = "해당 리뷰를 삭제할까요?" + static let askDeleteMyReview: String = Localization.localized("myPage.askDeleteMyReview", fallback: "해당 리뷰를 삭제할까요?") /// MyReviewVC - "리뷰가 성공적으로 삭제되었습니다." - static let deleteMyReviewSuccess: String = "리뷰가 성공적으로 삭제되었습니다." + static let deleteMyReviewSuccess: String = Localization.localized("myPage.deleteMyReviewSuccess", fallback: "리뷰가 성공적으로 삭제되었습니다.") /// MyPageView - "다시 시도해주세요" - static let retry: String = "다시 시도해주세요" + static let retry: String = Localization.localized("myPage.retry", fallback: "다시 시도해주세요") /// MyPageView - "앱 버전" - static let appVersion: String = "앱 버전" + static let appVersion: String = Localization.localized("myPage.appVersion", fallback: "앱 버전") /// MyPageView - "탈퇴하기" - static let withdrawButton: String = "탈퇴하기" + static let withdrawButton: String = Localization.localized("myPage.withdrawButton", fallback: "탈퇴하기") /// MyPageView - "알 수 없음" - static let unknownUser: String = "알 수 없음" + static let unknownUser: String = Localization.localized("myPage.unknownUser", fallback: "알 수 없음") /// NotificationSettingTableViewCell - "푸시 알림 설정" - static let pushNotificationSetting: String = "푸시 알림 설정" + static let pushNotificationSetting: String = Localization.localized("myPage.pushNotificationSetting", fallback: "푸시 알림 설정") /// Push Notification key for UserDefaults static let pushNotificationUserSettingKey: String = "pushNotificationUserSettingKey" /// NotificationSettingTableViewCell - "매일 오전 11시에 알림을 보내드려요" - static let pushNotificationDescription: String = "매일 오전 11시에 알림을 보내드려요" + static let pushNotificationDescription: String = Localization.localized("myPage.pushNotificationDescription", fallback: "매일 오전 11시에 알림을 보내드려요") /// MyPageVC - "문의하기" - static let inquiry: String = "문의하기" + static let inquiry: String = Localization.localized("myPage.inquiry", fallback: "문의하기") /// MyPageVC - "서비스 이용약관" - static let termsOfUse: String = "서비스 이용약관" + static let termsOfUse: String = Localization.localized("myPage.termsOfUse", fallback: "서비스 이용약관") /// MyPageVC - "개인정보 이용약관" - static let privacyTermsOfUse: String = "개인정보 이용약관" + static let privacyTermsOfUse: String = Localization.localized("myPage.privacyTermsOfUse", fallback: "개인정보 이용약관") /// ProvisionVC - "이용약관" - static let defaultTerms: String = "이용약관" + static let defaultTerms: String = Localization.localized("myPage.defaultTerms", fallback: "이용약관") /// UserWithdrawView - "정말 탈퇴하시겠습니까?" - static let confirmWithdrawal: String = "정말 탈퇴하시겠습니까?" + static let confirmWithdrawal: String = Localization.localized("myPage.confirmWithdrawal", fallback: "정말 탈퇴하시겠습니까?") /// UserWithdrawView - "작성한 리뷰 게시글은 삭제되지 않으며, (알수없음)으로 표시됩니다.\n자세한 내용은 서비스이용약관 및 개인정보처리방침을 확인해 주세요." - static let withdrawalNotice: String = "작성한 리뷰 게시글은 삭제되지 않으며, (알수없음)으로 표시됩니다.\n자세한 내용은 서비스이용약관 및 개인정보처리방침을 확인해 주세요." + static let withdrawalNotice: String = Localization.localized("myPage.withdrawalNotice", fallback: "작성한 리뷰 게시글은 삭제되지 않으며, (알수없음)으로 표시됩니다.\n자세한 내용은 서비스이용약관 및 개인정보처리방침을 확인해 주세요.") /// UserWithdrawView - "올바른 입력입니다." - static let validInputMessage: String = "올바른 입력입니다" + static let validInputMessage: String = Localization.localized("myPage.validInputMessage", fallback: "올바른 입력입니다") /// UserWithdrawView - "올바르지 않은 닉네임입니다" - static let invalidNicknameMessage: String = "올바르지 않은 닉네임입니다" + static let invalidNicknameMessage: String = Localization.localized("myPage.invalidNicknameMessage", fallback: "올바르지 않은 닉네임입니다") } // MARK: - Review enum Review { /// ReportVC - "EAT SSU 팀에게 보내기" - static let sendToTeam: String = "EAT SSU 팀에게 보내기" + static let sendToTeam: String = Localization.localized("review.sendToTeam", fallback: "EAT SSU 팀에게 보내기") /// ReportVC - "신고하기" - static let report: String = "신고하기" + static let report: String = Localization.localized("review.report", fallback: "신고하기") /// ReportVC - "사유를 선택해주세요!" - static let selectReason: String = "사유를 선택해주세요!" + static let selectReason: String = Localization.localized("review.selectReason", fallback: "사유를 선택해주세요!") /// ReportVC - "신고가 성공적으로 접수되었어요!" - static let reportSuccess: String = "신고가 성공적으로 접수되었어요!" + static let reportSuccess: String = Localization.localized("review.reportSuccess", fallback: "신고가 성공적으로 접수되었어요!") /// ReportVC, ReportView - "메뉴와 관련없는 내용" - static let unrelatedMenu: String = "메뉴와 관련없는 내용" + static let unrelatedMenu: String = Localization.localized("review.unrelatedMenu", fallback: "메뉴와 관련없는 내용") /// ReportVC, ReportView - "음란성, 욕설 등 부적절한 내용" - static let inappropriateContent: String = "음란성, 욕설 등 부적절한 내용" + static let inappropriateContent: String = Localization.localized("review.inappropriateContent", fallback: "음란성, 욕설 등 부적절한 내용") /// ReportVC, ReportView - "부적절한 홍보 또는 광고" - static let inappropriateAd: String = "부적절한 홍보 또는 광고" + static let inappropriateAd: String = Localization.localized("review.inappropriateAd", fallback: "부적절한 홍보 또는 광고") /// ReportVC, ReportView - "리뷰 작성 취지에 맞지 않는 내용 (복사글 등)" - static let notReviewFormat: String = "리뷰 작성 취지에 맞지 않는 내용 (복사글 등)" + static let notReviewFormat: String = Localization.localized("review.notReviewFormat", fallback: "리뷰 작성 취지에 맞지 않는 내용 (복사글 등)") /// ReportVC, ReportView - "저작권 도용 의심 (사진 등)" - static let copyright: String = "저작권 도용 의심 (사진 등)" + static let copyright: String = Localization.localized("review.copyright", fallback: "저작권 도용 의심 (사진 등)") /// ReportVC, ReportView - "기타 (하단 내용 작성)" - static let etc: String = "기타 (하단 내용 작성)" + static let etc: String = Localization.localized("review.etc", fallback: "기타 (하단 내용 작성)") /// ReportView - "리뷰 신고 사유를 알려주세요" - static let reportReason: String = "리뷰 신고 사유를 알려주세요" + static let reportReason: String = Localization.localized("review.reportReason", fallback: "리뷰 신고 사유를 알려주세요") /// ReportView - "하나의 리뷰에 대해 24시간 내 한 번만 신고 가능합니다." - static let reportGuide: String = "하나의 리뷰에 대해 24시간 내 한 번만 신고 가능합니다." + static let reportGuide: String = Localization.localized("review.reportGuide", fallback: "하나의 리뷰에 대해 24시간 내 한 번만 신고 가능합니다.") /// ReportView - "리뷰 신고 사유를 작성해 주세요" - static let inputReportReason: String = "리뷰 신고 사유를 작성해 주세요" + static let inputReportReason: String = Localization.localized("review.inputReportReason", fallback: "리뷰 신고 사유를 작성해 주세요") /// ReviewVC - "리뷰 작성하기" - static let writeReview: String = "리뷰 작성하기" + static let writeReview: String = Localization.localized("review.writeReview", fallback: "리뷰 작성하기") /// ReviewVC - "리뷰가 성공적으로 등록되었습니다." - static let registerReviewSuccess: String = "리뷰가 성공적으로 등록되었습니다." + static let registerReviewSuccess: String = Localization.localized("review.registerReviewSuccess", fallback: "리뷰가 성공적으로 등록되었습니다.") /// ReviewVC - "리뷰" - static let review: String = "리뷰" + static let review: String = Localization.localized("review.review", fallback: "리뷰") /// ReviewVC - "리뷰 삭제" - static let deleteReview: String = "리뷰 삭제" + static let deleteReview: String = Localization.localized("review.deleteReview", fallback: "리뷰 삭제") /// ReviewVC - "해당 리뷰를 삭제할까요?" - static let askDeleteReview: String = "해당 리뷰를 삭제할까요?" + static let askDeleteReview: String = Localization.localized("review.askDeleteReview", fallback: "해당 리뷰를 삭제할까요?") /// ReviewVC - "리뷰 신고하기" - static let reportReview: String = "리뷰 신고하기" + static let reportReview: String = Localization.localized("review.reportReview", fallback: "리뷰 신고하기") /// ReviewVC - "해당 리뷰를 신고하시겠습니까?" - static let askReportReview: String = "해당 리뷰를 신고하시겠습니까?" + static let askReportReview: String = Localization.localized("review.askReportReview", fallback: "해당 리뷰를 신고하시겠습니까?") /// ReviewVC - "리뷰가 성공적으로 삭제되었습니다." - static let deleteReviewSuccess: String = "리뷰가 성공적으로 삭제되었습니다." + static let deleteReviewSuccess: String = Localization.localized("review.deleteReviewSuccess", fallback: "리뷰가 성공적으로 삭제되었습니다.") /// ReviewVC - "리뷰 삭제에 실패했습니다." - static let deleteReviewFail: String = "리뷰 삭제에 실패했습니다." + static let deleteReviewFail: String = Localization.localized("review.deleteReviewFail", fallback: "리뷰 삭제에 실패했습니다.") /// SetRateVC - "리뷰 수정하기" - static let fixReview: String = "리뷰 수정하기" + static let fixReview: String = Localization.localized("review.fixReview", fallback: "리뷰 수정하기") /// SetRateVC - "리뷰 남기기" - static let leaveReview: String = "리뷰 남기기" + static let 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) // 예외 처리 } // '가' ~ '힣' 사이의 한글 유니코드 범위 @@ -472,116 +483,116 @@ enum TextLiteral { 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 let recommendMenuTitle: String = Localization.localized("review.recommendMenuTitle", fallback: "메뉴를 추천하시겠어요?") /// SetRateVC - "리뷰 수정 완료하기" - static let fixReviewComplete: String = "리뷰 수정 완료하기" + static let fixReviewComplete: String = Localization.localized("review.fixReviewComplete", fallback: "리뷰 수정 완료하기") /// SetRateVC - "완료하기" - static let complete: String = "완료하기" + static let complete: String = Localization.localized("review.complete", fallback: "완료하기") /// SetRateVC - "별점을 입력해주세요!" - static let inputRating: String = "별점을 입력해주세요!" + static let inputRating: String = Localization.localized("review.inputRating", fallback: "별점을 입력해주세요!") /// SetRateVC - "메뉴 목록 조회에 실패했습니다." - static let loadMenuListFail: String = "메뉴 목록 조회에 실패했습니다." + static let loadMenuListFail: String = Localization.localized("review.loadMenuListFail", fallback: "메뉴 목록 조회에 실패했습니다.") /// SetRateVC - "수정할 리뷰 정보가 없습니다." - static let noReviewInfoForFix: String = "수정할 리뷰 정보가 없습니다." + static let noReviewInfoForFix: String = Localization.localized("review.noReviewInfoForFix", fallback: "수정할 리뷰 정보가 없습니다.") /// SetRateVC - "리뷰가 성공적으로 수정되었습니다." - static let fixReviewSuccess: String = "리뷰가 성공적으로 수정되었습니다." + static let fixReviewSuccess: String = Localization.localized("review.fixReviewSuccess", fallback: "리뷰가 성공적으로 수정되었습니다.") /// SetRateVC - "리뷰 수정에 실패했습니다." - static let fixReviewFail: String = "리뷰 수정에 실패했습니다." + static let fixReviewFail: String = Localization.localized("review.fixReviewFail", fallback: "리뷰 수정에 실패했습니다.") /// SetRateVC - "식단 정보가 없습니다." - static let noMealInfo: String = "식단 정보가 없습니다." + static let noMealInfo: String = Localization.localized("review.noMealInfo", fallback: "식단 정보가 없습니다.") /// SetRateVC - "리뷰 업로드에 실패했습니다." - static let uploadReviewFail: String = "리뷰 업로드에 실패했습니다." + static let uploadReviewFail: String = Localization.localized("review.uploadReviewFail", fallback: "리뷰 업로드에 실패했습니다.") /// SetRateVC - "메뉴 정보가 없습니다." - static let noMenuInfo: String = "메뉴 정보가 없습니다." + static let noMenuInfo: String = Localization.localized("review.noMenuInfo", fallback: "메뉴 정보가 없습니다.") /// SetRateVC - "메뉴에 대한 상세한 리뷰를 작성해주세요" - static let inputDetailReview: String = "메뉴에 대한 상세한 리뷰를 작성해주세요" + static let inputDetailReview: String = Localization.localized("review.inputDetailReview", fallback: "메뉴에 대한 상세한 리뷰를 작성해주세요") /// SetRateVC - "나가시겠어요?" - static let askLeave: String = "나가시겠어요?" + static let askLeave: String = Localization.localized("review.askLeave", fallback: "나가시겠어요?") /// SetRateVC - "지금 나가면 작성한 내용이 저장되지 않습니다." - static let leaveWarning: String = "지금 나가면 작성한 내용이 저장되지 않습니다." + static let leaveWarning: String = Localization.localized("review.leaveWarning", fallback: "지금 나가면 작성한 내용이 저장되지 않습니다.") /// SetRateVC - "나가기" - static let leave: String = "나가기" + static let leave: String = Localization.localized("review.leave", fallback: "나가기") /// SetRateVC - "계속 작성" - static let continueWriting: String = "계속 작성" + static let continueWriting: String = Localization.localized("review.continueWriting", fallback: "계속 작성") /// ReviewEmptyViewCell - "아직 작성된 리뷰가 없어요!" - static let noReview: String = "아직 작성된 리뷰가 없어요!" + static let noReview: String = Localization.localized("review.noReview", fallback: "아직 작성된 리뷰가 없어요!") /// ReviewEmptyViewCell - "메뉴에 가장 먼저 리뷰를 남겨주세요!" - static let beFirstReviewer: String = "메뉴에 가장 먼저 리뷰를 남겨주세요!" + static let beFirstReviewer: String = Localization.localized("review.beFirstReviewer", fallback: "메뉴에 가장 먼저 리뷰를 남겨주세요!") /// ReviewEmptyViewCell - "로그인이 필요합니다" - static let needLogin: String = "로그인이 필요합니다" + static let needLogin: String = Localization.localized("review.needLogin", fallback: "로그인이 필요합니다") /// ReviewEmptyViewCell - "로그인 후 리뷰를 확인하세요" - static let checkReviewAfterLogin: String = "로그인 후 리뷰를 확인하세요" + static let checkReviewAfterLogin: String = Localization.localized("review.checkReviewAfterLogin", fallback: "로그인 후 리뷰를 확인하세요") /// ReviewEmptyViewCell - "아직 작성한 리뷰가 없어요" - static let noWrittenReview: String = "아직 작성한 리뷰가 없어요" + static let noWrittenReview: String = Localization.localized("review.noWrittenReview", fallback: "아직 작성한 리뷰가 없어요") /// ReviewEmptyViewCell - "첫 리뷰를 남겨 주세요!" - static let writeFirstReview: String = "첫 리뷰를 남겨 주세요!" + static let 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 let todayMenu: String = Localization.localized("review.todayMenu", fallback: "오늘의 메뉴") /// ReviewRateViewCell - "5점" - static let fiveStars: String = "5점" + static let fiveStars: String = Localization.localized("review.fiveStars", fallback: "5점") /// ReviewRateViewCell - "4점" - static let fourStars: String = "4점" + static let fourStars: String = Localization.localized("review.fourStars", fallback: "4점") /// ReviewRateViewCell - "3점" - static let threeStars: String = "3점" + static let threeStars: String = Localization.localized("review.threeStars", fallback: "3점") /// ReviewRateViewCell - "2점" - static let twoStars: String = "2점" + static let twoStars: String = Localization.localized("review.twoStars", fallback: "2점") /// ReviewRateViewCell - "1점" - static let oneStar: String = "1점" + static let oneStar: String = Localization.localized("review.oneStar", fallback: "1점") /// SetRateView - "오늘의 식사는 어떠셨나요?" - static let rateTodayMeal: String = "오늘의 식사는 어떠셨나요?" + static let rateTodayMeal: String = Localization.localized("review.rateTodayMeal", fallback: "오늘의 식사는 어떠셨나요?") /// SetRateView - "추천하고 싶은 메뉴가 있나요?" - static let recommendMenu: String = "추천하고 싶은 메뉴가 있나요?" + static let 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 +600,66 @@ enum TextLiteral { enum Coffee { /// "나가시겠어요?" - static let askLeave: String = "나가시겠어요?" + static let askLeave: String = Localization.localized("coffee.askLeave", fallback: "나가시겠어요?") /// "지금 나가면 진행 상황이\n저장되지 않습니다." - static let leaveWarning: String = "지금 나가면 진행 상황이\n저장되지 않습니다." + static let leaveWarning: String = Localization.localized("coffee.leaveWarning", fallback: "지금 나가면 진행 상황이\n저장되지 않습니다.") /// "나가기" - static let leave: String = "나가기" + static let leave: String = Localization.localized("coffee.leave", fallback: "나가기") /// "계속하기" - static let continueEvent: String = "계속하기" + static let continueEvent: String = Localization.localized("coffee.continueEvent", fallback: "계속하기") } // MARK: - Splash enum Splash { /// NoticeSplashVC - "긴급 서버 점검 안내" - static let serverInspection: String = "긴급 서버 점검 안내" + static let serverInspection: String = Localization.localized("splash.serverInspection", fallback: "긴급 서버 점검 안내") } // MARK: - PromotionPopup enum PromotionPopup { /// 03. 16(월)~03. 27(금) - static let period: String = "03. 16(월)~03. 27(금)" + static let period: String = Localization.localized("promotionPopup.period", fallback: "03. 16(월)~03. 27(금)") /// EAT-SSU 인스타그램 바로가기 - static let instagramButtonTitle: String = "EAT-SSU 인스타그램 바로가기" + static let instagramButtonTitle: String = Localization.localized("promotionPopup.instagramButtonTitle", fallback: "EAT-SSU 인스타그램 바로가기") /// 자세한 내용은 EAT-SSU 인스타그램을 확인해 주세요 - static let guideMessage: String = "자세한 내용은 EAT-SSU 인스타그램을 확인해 주세요" + static let guideMessage: String = Localization.localized("promotionPopup.guideMessage", fallback: "자세한 내용은 EAT-SSU 인스타그램을 확인해 주세요") /// 다시 보지 않기 - static let neverShowAgain: String = "다시 보지 않기" + static let neverShowAgain: String = Localization.localized("promotionPopup.neverShowAgain", fallback: "다시 보지 않기") /// 닫기 - static let close: String = "닫기" + static let close: String = Localization.localized("promotionPopup.close", fallback: "닫기") } // MARK: - Notification enum Notification { /// 🤔 오늘 밥 뭐 먹지… - static let dailyWeekdayNotificationTitle: String = "🤔 오늘 밥 뭐 먹지…" + static let dailyWeekdayNotificationTitle: String = Localization.localized("notification.dailyWeekdayNotificationTitle", fallback: "🤔 오늘 밥 뭐 먹지…") /// 오늘의 학식을 확인해보세요! - static let dailyWeekdayNotificationBody: String = "오늘의 학식을 확인해보세요!" + static let dailyWeekdayNotificationBody: String = Localization.localized("notification.dailyWeekdayNotificationBody", fallback: "오늘의 학식을 확인해보세요!") } // MARK: - Restaurant enum Restaurant { /// "기숙사 식당" - static let dormitoryRestaurant: String = "기숙사 식당" + static let dormitoryRestaurant: String = Localization.localized("restaurant.dormitoryRestaurant", fallback: "기숙사 식당") /// "도담 식당" - static let dodamRestaurant: String = "도담 식당" + static let dodamRestaurant: String = Localization.localized("restaurant.dodamRestaurant", fallback: "도담 식당") /// "학생 식당" - static let studentRestaurant: String = "학생 식당" + static let studentRestaurant: String = Localization.localized("restaurant.studentRestaurant", fallback: "학생 식당") /// "스낵 코너" - static let snackCorner: String = "스낵 코너" + static let snackCorner: String = Localization.localized("restaurant.snackCorner", fallback: "스낵 코너") /// "FACULTY (교직원 전용)" - static let facultyRestaurant: String = "FACULTY (교직원 전용)" + static let facultyRestaurant: String = Localization.localized("restaurant.facultyRestaurant", fallback: "FACULTY (교직원 전용)") } } diff --git a/EATSSU/App/Sources/Utility/Literal/en.lproj/Localizable.strings b/EATSSU/App/Sources/Utility/Literal/en.lproj/Localizable.strings new file mode 100644 index 00000000..e33fc361 --- /dev/null +++ b/EATSSU/App/Sources/Utility/Literal/en.lproj/Localizable.strings @@ -0,0 +1,428 @@ +// +// Localizable.strings +// EATSSU +// +// Created by jeongminji on 5/3/26. +// + +// MARK: - Common + +/// "확인" +"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" = "Continue with Apple"; +/// "카카오 로그인" +"auth.signInWithKakao" = "Continue with Kakao"; +/// "둘러보기" +"auth.lookingWithNoSignIn" = "Browse"; +/// "최근에 로그인했어요" +"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"; +/// "내 정보" +"myPage.myInfo" = "My Info"; +/// "내 리뷰" +"myPage.myReview" = "My Reviews"; +/// UserWithdrawVC - "회원탈퇴" +"myPage.withdraw" = "Delete Account"; +/// MyPageVC - "로그아웃" +"myPage.logout" = "Log Out"; +/// MyPageVC - "정말 로그아웃 하시겠습니까?" +"myPage.askLogout" = "Are you sure you want to log out?"; +/// MyPageVC - "EAT-SSU 수신 동의" +"myPage.agreeNoti" = "EAT-SSU notifications enabled (%@)"; +/// MyPageVC - "EAT-SSU 수신 거절" +"myPage.disagreeNoti" = "EAT-SSU notifications disabled (%@)"; +/// MyPageVC - "알림 설정 중 오류가 발생했습니다." +"myPage.notiSettingError" = "An error occurred while updating notification settings."; +/// CreatorVC - "만든 사람들" +"myPage.creators" = "Creators"; +/// 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"; +/// NotificationSettingTableViewCell - "푸시 알림 설정" +"myPage.pushNotificationSetting" = "Push Notification Settings"; +/// NotificationSettingTableViewCell - "매일 오전 11시에 알림을 보내드려요" +"myPage.pushNotificationDescription" = "We'll send you a notification every day at 11 AM."; +/// MyPageVC - "문의하기" +"myPage.inquiry" = "Contact Us"; +/// MyPageVC - "서비스 이용약관" +"myPage.termsOfUse" = "Terms of Service"; +/// MyPageVC - "개인정보 이용약관" +"myPage.privacyTermsOfUse" = "Privacy Policy"; +/// 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!"; + + +// 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/Sources/Utility/Literal/ko.lproj/Localizable.strings b/EATSSU/App/Sources/Utility/Literal/ko.lproj/Localizable.strings new file mode 100644 index 00000000..3da405e1 --- /dev/null +++ b/EATSSU/App/Sources/Utility/Literal/ko.lproj/Localizable.strings @@ -0,0 +1,429 @@ +// +// Localizable.strings +// EATSSU +// +// Created by jeongminji on 5/3/26. +// + +// MARK: - Common + +/// "확인" +"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" = "마이페이지"; +/// "내 정보" +"myPage.myInfo" = "내 정보"; +/// "내 리뷰" +"myPage.myReview" = "내 리뷰"; +/// UserWithdrawVC - "회원탈퇴" +"myPage.withdraw" = "회원탈퇴"; +/// MyPageVC - "로그아웃" +"myPage.logout" = "로그아웃"; +/// MyPageVC - "정말 로그아웃 하시겠습니까?" +"myPage.askLogout" = "정말 로그아웃 하시겠습니까?"; +/// MyPageVC - "EAT-SSU 수신 동의" +"myPage.agreeNoti" = "EAT-SSU 수신 동의 (%@)"; +/// MyPageVC - "EAT-SSU 수신 거절" +"myPage.disagreeNoti" = "EAT-SSU 수신 거절 (%@)"; +/// MyPageVC - "알림 설정 중 오류가 발생했습니다." +"myPage.notiSettingError" = "알림 설정 중 오류가 발생했습니다."; +/// CreatorVC - "만든 사람들" +"myPage.creators" = "만든 사람들"; +/// 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" = "알 수 없음"; +/// NotificationSettingTableViewCell - "푸시 알림 설정" +"myPage.pushNotificationSetting" = "푸시 알림 설정"; +/// NotificationSettingTableViewCell - "매일 오전 11시에 알림을 보내드려요" +"myPage.pushNotificationDescription" = "매일 오전 11시에 알림을 보내드려요"; +/// MyPageVC - "문의하기" +"myPage.inquiry" = "문의하기"; +/// MyPageVC - "서비스 이용약관" +"myPage.termsOfUse" = "서비스 이용약관"; +/// MyPageVC - "개인정보 이용약관" +"myPage.privacyTermsOfUse" = "개인정보 이용약관"; +/// 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" = "오늘의 학식을 확인해보세요!"; + + +// MARK: - Restaurant + +/// "기숙사 식당" +"restaurant.dormitoryRestaurant" = "기숙사 식당"; +/// "도담 식당" +"restaurant.dodamRestaurant" = "도담 식당"; +/// "학생 식당" +"restaurant.studentRestaurant" = "학생 식당"; +/// "스낵 코너" +"restaurant.snackCorner" = "스낵 코너"; +/// "FACULTY (교직원 전용)" +"restaurant.facultyRestaurant" = "FACULTY (교직원 전용)"; From bd3471d5a2db30625c9d127511abf7bc8fd381e2 Mon Sep 17 00:00:00 2001 From: jeongminji Date: Sun, 3 May 2026 22:26:01 +0900 Subject: [PATCH 02/16] =?UTF-8?q?[#275]=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=85=8C=EC=9D=B4=EB=B8=94=20cell=20UI=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20&=20configure=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Cell/MyPageTableDefaultCell.swift | 77 ++++++++++++++++--- .../NotificationSettingTableViewCell.swift | 7 +- 2 files changed, 71 insertions(+), 13 deletions(-) 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 + } } From 9c474aadaa266de30a351b7099293a0d422f50a1 Mon Sep 17 00:00:00 2001 From: jeongminji Date: Sun, 3 May 2026 22:26:57 +0900 Subject: [PATCH 03/16] =?UTF-8?q?[#275]=20=EA=B3=B5=ED=86=B5=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20CustomRadioButton=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20&=20=EB=A7=88=EC=9D=B4=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=20RadioSelectionTableViewCell=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Cell/RadioSelectionTableViewCell.swift | 77 +++++++++++++++++++ .../UIComponent/CustomRadioButton.swift | 53 +++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 EATSSU/App/Sources/Presentation/MyPage/View/MyPageView/Cell/RadioSelectionTableViewCell.swift create mode 100644 EATSSU/App/Sources/Utility/UIComponent/CustomRadioButton.swift 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/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() + } +} From d78dccc8c740b2623d4079adb0ce483c036ee590 Mon Sep 17 00:00:00 2001 From: jeongminji Date: Sun, 3 May 2026 22:28:53 +0900 Subject: [PATCH 04/16] =?UTF-8?q?[#275]=20AppLanguage=20enum=EA=B3=BC=20Ma?= =?UTF-8?q?nager=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20TextLiteral=20?= =?UTF-8?q?=EB=8F=99=EC=A0=81=20=EB=A1=9C=EC=BB=AC=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EC=A7=95=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Utility/Literal/AppLanguage.swift | 22 + .../Utility/Literal/AppLanguageManager.swift | 73 ++ .../Sources/Utility/Literal/TextLiteral.swift | 855 +++++++++++++----- .../Literal/en.lproj/Localizable.strings | 59 +- .../Literal/ko.lproj/Localizable.strings | 59 +- 5 files changed, 818 insertions(+), 250 deletions(-) create mode 100644 EATSSU/App/Sources/Utility/Literal/AppLanguage.swift create mode 100644 EATSSU/App/Sources/Utility/Literal/AppLanguageManager.swift 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..1a9d3699 --- /dev/null +++ b/EATSSU/App/Sources/Utility/Literal/AppLanguageManager.swift @@ -0,0 +1,73 @@ +// +// 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) + + NotificationCenter.default.post(name: .appLanguageDidChange, object: nil) + } +} diff --git a/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift b/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift index 394e4c76..6c2e2b0f 100644 --- a/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift +++ b/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift @@ -8,19 +8,37 @@ import Foundation private enum Localization { - static func localized(_ key: String, fallback: String) -> String { - NSLocalizedString(key, tableName: "Localizable", bundle: .main, value: fallback, comment: "") + 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 { + + static func formatted( + _ key: String, + fallback: String, + _ arguments: CVarArg... + ) -> String { let format = localized(key, fallback: fallback) - return String(format: format, locale: Locale.current, arguments: arguments) + + 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" @@ -30,292 +48,451 @@ enum TextLiteral { enum Common { /// "확인" - static let confirm: String = Localization.localized("common.confirm", fallback: "확인") + static var confirm: String { + Localization.localized("common.confirm", fallback: "확인") + } /// "취소" - static let cancel: String = Localization.localized("common.cancel", fallback: "취소") + static var cancel: String { + Localization.localized("common.cancel", fallback: "취소") + } /// "취소하기" - static let cancelDark: String = Localization.localized("common.cancelDark", fallback: "취소하기") + static var cancelDark: String { + Localization.localized("common.cancelDark", fallback: "취소하기") + } /// "삭제하기" - static let delete: String = Localization.localized("common.delete", fallback: "삭제하기") + static var delete: String { + Localization.localized("common.delete", fallback: "삭제하기") + } /// "수정하기" - static let fix: String = Localization.localized("common.fix", fallback: "수정하기") + static var fix: String { + Localization.localized("common.fix", fallback: "수정하기") + } /// "로그인이 필요한 서비스입니다" - static let needLogin: String = Localization.localized("common.needLogin", fallback: "로그인이 필요한 서비스입니다") + static var needLogin: String { + Localization.localized("common.needLogin", fallback: "로그인이 필요한 서비스입니다") + } /// "로그인 하시겠습니까?" - static let askLogin: String = Localization.localized("common.askLogin", fallback: "로그인 하시겠습니까?") + static var askLogin: String { + Localization.localized("common.askLogin", fallback: "로그인 하시겠습니까?") + } /// "설정으로 이동" - static let moveToSetting: String = Localization.localized("common.moveToSetting", fallback: "설정으로 이동") + static var moveToSetting: String { + Localization.localized("common.moveToSetting", fallback: "설정으로 이동") + } /// "탈퇴 처리가 완료되었습니다." - static let withdrawComplete: String = Localization.localized("common.withdrawComplete", fallback: "탈퇴 처리가 완료되었습니다.") + static var withdrawComplete: String { + Localization.localized("common.withdrawComplete", fallback: "탈퇴 처리가 완료되었습니다.") + } /// "잠시 후 다시 시도해주세요." - static let tryAgain: String = Localization.localized("common.tryAgain", fallback: "잠시 후 다시 시도해주세요.") + static var tryAgain: String { + Localization.localized("common.tryAgain", fallback: "잠시 후 다시 시도해주세요.") + } /// "세션이 만료되었습니다. 다시 로그인해주세요." - static let sessionExpired: String = Localization.localized("common.sessionExpired", fallback: "세션이 만료되었습니다. 다시 로그인해주세요.") + static var sessionExpired: String { + Localization.localized("common.sessionExpired", fallback: "세션이 만료되었습니다. 다시 로그인해주세요.") + } /// "에러가 발생했습니다" - static let errorOccured: String = Localization.localized("common.errorOccured", fallback: "에러가 발생했습니다") + static var errorOccured: String { + Localization.localized("common.errorOccured", fallback: "에러가 발생했습니다") + } /// "다시 시도하세요" - static let retry: String = Localization.localized("common.retry", fallback: "다시 시도하세요") + static var retry: String { + Localization.localized("common.retry", fallback: "다시 시도하세요") + } } // MARK: - TabBar enum TabBar { /// "학식" - static let meal: String = Localization.localized("tabBar.meal", fallback: "학식") + static var meal: String { + Localization.localized("tabBar.meal", fallback: "학식") + } /// "지도" - static let map: String = Localization.localized("tabBar.map", fallback: "지도") + static var map: String { + Localization.localized("tabBar.map", fallback: "지도") + } /// "나만아니면돼~" - static let coffee: String = Localization.localized("tabBar.coffee", fallback: "나만아니면돼~") + static var coffee: String { + Localization.localized("tabBar.coffee", fallback: "나만아니면돼~") + } /// "마이" - static let my: String = Localization.localized("tabBar.my", fallback: "마이") + static var my: String { + Localization.localized("tabBar.my", fallback: "마이") + } } // MARK: - Auth enum Auth { /// "닉네임을 입력해주세요" - static let inputNickName: String = Localization.localized("auth.inputNickName", fallback: "닉네임을 입력해주세요") + static var inputNickName: String { + Localization.localized("auth.inputNickName", fallback: "닉네임을 입력해주세요") + } /// "Apple로 로그인" - static let signInWithApple: String = Localization.localized("auth.signInWithApple", fallback: "Apple로 로그인") + static var signInWithApple: String { + Localization.localized("auth.signInWithApple", fallback: "Apple로 로그인") + } /// "카카오 로그인" - static let signInWithKakao: String = Localization.localized("auth.signInWithKakao", fallback: "카카오 로그인") + static var signInWithKakao: String { + Localization.localized("auth.signInWithKakao", fallback: "카카오 로그인") + } /// "둘러보기" - static let lookingWithNoSignIn: String = Localization.localized("auth.lookingWithNoSignIn", fallback: "둘러보기") + static var lookingWithNoSignIn: String { + Localization.localized("auth.lookingWithNoSignIn", fallback: "둘러보기") + } /// UserDefaults key for last login provider static let lastLoginProviderKey: String = "lastLoginProvider" /// "최근에 로그인했어요" - static let lastLoginTooltip: String = Localization.localized("auth.lastLoginTooltip", fallback: "최근에 로그인했어요") + static var lastLoginTooltip: String { + Localization.localized("auth.lastLoginTooltip", fallback: "최근에 로그인했어요") + } /// LoginVC - "카카오톡으로 생성된 계정입니다." - static let kakaoAccount: String = Localization.localized("auth.kakaoAccount", fallback: "카카오톡으로 생성된 계정입니다.") + static var kakaoAccount: String { + Localization.localized("auth.kakaoAccount", fallback: "카카오톡으로 생성된 계정입니다.") + } /// LoginVC - "Apple로 생성된 계정입니다." - static let appleAccount: String = Localization.localized("auth.appleAccount", fallback: "Apple로 생성된 계정입니다.") + static var appleAccount: String { + Localization.localized("auth.appleAccount", fallback: "Apple로 생성된 계정입니다.") + } /// SetNickNameView - "닉네임 설정" - static let setNickname: String = Localization.localized("auth.setNickname", fallback: "닉네임 설정") + static var setNickname: String { + Localization.localized("auth.setNickname", fallback: "닉네임 설정") + } /// SetNickNameView - "중복 확인" - static let checkDuplicate: String = Localization.localized("auth.checkDuplicate", fallback: "중복 확인") + static var checkDuplicate: String { + Localization.localized("auth.checkDuplicate", fallback: "중복 확인") + } /// SetNickNameView - "소속 설정" - static let setCollege: String = Localization.localized("auth.setCollege", fallback: "소속 설정") + static var setCollege: String { + Localization.localized("auth.setCollege", fallback: "소속 설정") + } /// SetNickNameView - "단과대" - static let college: String = Localization.localized("auth.college", fallback: "단과대") + static var college: String { + Localization.localized("auth.college", fallback: "단과대") + } /// SetNickNameView - "학과" - static let department: String = Localization.localized("auth.department", fallback: "학과") + static var department: String { + Localization.localized("auth.department", fallback: "학과") + } /// SetNickNameView - "연결된 계정" - static let linkedAccount: String = Localization.localized("auth.linkedAccount", fallback: "연결된 계정") + static var linkedAccount: String { + Localization.localized("auth.linkedAccount", fallback: "연결된 계정") + } /// SetNickNameView - "없음" - static let empty: String = Localization.localized("auth.empty", fallback: "없음") + static var empty: String { + Localization.localized("auth.empty", fallback: "없음") + } /// SetNickNameView - "저장하기" - static let save: String = Localization.localized("auth.save", fallback: "저장하기") + static var save: String { + Localization.localized("auth.save", fallback: "저장하기") + } /// SetNickNameView - "카카오" - static let kakao: String = Localization.localized("auth.kakao", fallback: "카카오") + static var kakao: String { + Localization.localized("auth.kakao", fallback: "카카오") + } /// SetNickNameView - "APPLE" - static let apple: String = Localization.localized("auth.apple", fallback: "APPLE") + static var apple: String { + Localization.localized("auth.apple", fallback: "APPLE") + } /// SetNickNameVC - "변경된 정보가 없습니다." - static let noChanges: String = Localization.localized("auth.noChanges", fallback: "변경된 정보가 없습니다.") + static var noChanges: String { + Localization.localized("auth.noChanges", fallback: "변경된 정보가 없습니다.") + } /// SetNickNameVC - "유효하지 않은 학과 정보입니다." - static let invalidDepartment: String = Localization.localized("auth.invalidDepartment", fallback: "유효하지 않은 학과 정보입니다.") + static var invalidDepartment: String { + Localization.localized("auth.invalidDepartment", fallback: "유효하지 않은 학과 정보입니다.") + } /// SetNickNameVC - "정보 업데이트 중 오류가 발생했습니다." - static let updateError: String = Localization.localized("auth.updateError", fallback: "정보 업데이트 중 오류가 발생했습니다.") + static var updateError: String { + Localization.localized("auth.updateError", fallback: "정보 업데이트 중 오류가 발생했습니다.") + } /// SetNickNameVC - "내 정보가 수정되었어요." - static let updateSuccess: String = Localization.localized("auth.updateSuccess", fallback: "내 정보가 수정되었어요.") + static var updateSuccess: String { + Localization.localized("auth.updateSuccess", fallback: "내 정보가 수정되었어요.") + } /// NIcknameTextFieldResultType - "필수 입력 사항입니다" - static let requiredInput: String = Localization.localized("auth.requiredInput", fallback: "필수 입력 사항입니다") + static var requiredInput: String { + Localization.localized("auth.requiredInput", fallback: "필수 입력 사항입니다") + } /// NIcknameTextFieldResultType - "중복 확인을 진행해주세요." - static let needCheckDuplicate: String = Localization.localized("auth.needCheckDuplicate", fallback: "중복 확인을 진행해주세요.") + static var needCheckDuplicate: String { + Localization.localized("auth.needCheckDuplicate", fallback: "중복 확인을 진행해주세요.") + } /// NIcknameTextFieldResultType - "이미 사용 중인 닉네임이에요." - static let duplicatedNickname: String = Localization.localized("auth.duplicatedNickname", fallback: "이미 사용 중인 닉네임이에요.") + static var duplicatedNickname: String { + Localization.localized("auth.duplicatedNickname", fallback: "이미 사용 중인 닉네임이에요.") + } /// NIcknameTextFieldResultType - "사용가능한 닉네임이에요" - static let availableNickname: String = Localization.localized("auth.availableNickname", fallback: "사용가능한 닉네임이에요") + static var availableNickname: String { + Localization.localized("auth.availableNickname", fallback: "사용가능한 닉네임이에요") + } /// NIcknameTextFieldResultType - "2~16글자를 입력해 주세요." - static let nicknameLength: String = Localization.localized("auth.nicknameLength", fallback: "2~16글자를 입력해 주세요.") + static var nicknameLength: String { + Localization.localized("auth.nicknameLength", fallback: "2~16글자를 입력해 주세요.") + } /// NIcknameTextFieldResultType - "특수문자로 시작/끝나는 닉네임은 사용할 수 없어요." - static let specialCharNickname: String = Localization.localized("auth.specialCharNickname", fallback: "특수문자로 시작/끝나는 닉네임은 사용할 수 없어요.") + static var specialCharNickname: String { + Localization.localized("auth.specialCharNickname", fallback: "특수문자로 시작/끝나는 닉네임은 사용할 수 없어요.") + } /// NIcknameTextFieldResultType - "연속된 특수문자(--, __)는 사용할 수 없어요." - static let continuousSpecialChar: String = Localization.localized("auth.continuousSpecialChar", fallback: "연속된 특수문자(--, __)는 사용할 수 없어요.") + static var continuousSpecialChar: String { + Localization.localized("auth.continuousSpecialChar", fallback: "연속된 특수문자(--, __)는 사용할 수 없어요.") + } /// NIcknameTextFieldResultType - "숫자만으로 된 닉네임은 사용할 수 없어요." - static let numberOnlyNickname: String = Localization.localized("auth.numberOnlyNickname", fallback: "숫자만으로 된 닉네임은 사용할 수 없어요.") + static var numberOnlyNickname: String { + Localization.localized("auth.numberOnlyNickname", fallback: "숫자만으로 된 닉네임은 사용할 수 없어요.") + } /// NIcknameTextFieldResultType - "허용 문자(한글/영문/숫자)만 사용할 수 있어요." - static let allowedChar: String = Localization.localized("auth.allowedChar", fallback: "허용 문자(한글/영문/숫자)만 사용할 수 있어요.") + static var allowedChar: String { + Localization.localized("auth.allowedChar", fallback: "허용 문자(한글/영문/숫자)만 사용할 수 있어요.") + } /// NIcknameTextFieldResultType - "사용할 수 없는 단어가 포함되어 있어요." - static let bannedWord: String = Localization.localized("auth.bannedWord", fallback: "사용할 수 없는 단어가 포함되어 있어요.") + static var bannedWord: String { + Localization.localized("auth.bannedWord", fallback: "사용할 수 없는 단어가 포함되어 있어요.") + } /// NIcknameTextFieldResultType - "띄어쓰기로 시작/끝나는 닉네임은 사용할 수 없어요." - static let spaceNickname: String = Localization.localized("auth.spaceNickname", fallback: "띄어쓰기로 시작/끝나는 닉네임은 사용할 수 없어요.") + static var spaceNickname: String { + Localization.localized("auth.spaceNickname", fallback: "띄어쓰기로 시작/끝나는 닉네임은 사용할 수 없어요.") + } /// NIcknameTextFieldResultType - "연속된 띄어쓰기는 사용할 수 없어요." - static let continuousSpace: String = Localization.localized("auth.continuousSpace", fallback: "연속된 띄어쓰기는 사용할 수 없어요.") + static var continuousSpace: String { + Localization.localized("auth.continuousSpace", fallback: "연속된 띄어쓰기는 사용할 수 없어요.") + } /// NIcknameTextFieldResultType - "이모지, 특수문자는 사용할 수 없어요." - static let emojiSpecialChar: String = Localization.localized("auth.emojiSpecialChar", fallback: "이모지, 특수문자는 사용할 수 없어요.") + static var emojiSpecialChar: String { + Localization.localized("auth.emojiSpecialChar", fallback: "이모지, 특수문자는 사용할 수 없어요.") + } /// NIcknameTextFieldResultType - "관리자로 혼동될 수 있는 닉네임은 사용할 수 없어요." - static let adminNickname: String = Localization.localized("auth.adminNickname", fallback: "관리자로 혼동될 수 있는 닉네임은 사용할 수 없어요.") + static var adminNickname: String { + Localization.localized("auth.adminNickname", fallback: "관리자로 혼동될 수 있는 닉네임은 사용할 수 없어요.") + } /// NIcknameTextFieldResultType - "서비스명 단독 닉네임은 사용할 수 없어요." - static let serviceNameNickname: String = Localization.localized("auth.serviceNameNickname", fallback: "서비스명 단독 닉네임은 사용할 수 없어요.") + static var serviceNameNickname: String { + Localization.localized("auth.serviceNameNickname", fallback: "서비스명 단독 닉네임은 사용할 수 없어요.") + } /// NIcknameTextFieldResultType - "욕설, 비속어 등의 표현이 포함된 닉네임은 사용할 수 없어요." - static let slangNickname: String = Localization.localized("auth.slangNickname", fallback: "욕설, 비속어 등의 표현이 포함된 닉네임은 사용할 수 없어요.") + static var slangNickname: String { + Localization.localized("auth.slangNickname", fallback: "욕설, 비속어 등의 표현이 포함된 닉네임은 사용할 수 없어요.") + } } // MARK: - Home enum Home { /// Home - "오늘의 메뉴" - static let todayMenu: String = Localization.localized("home.todayMenu", fallback: "오늘의 메뉴") + static var todayMenu: String { + Localization.localized("home.todayMenu", fallback: "오늘의 메뉴") + } /// Home - "가격" - static let price: String = Localization.localized("home.price", fallback: "가격") + static var price: String { + Localization.localized("home.price", fallback: "가격") + } /// Home - "평점" - static let rating: String = Localization.localized("home.rating", fallback: "평점") + static var rating: String { + Localization.localized("home.rating", fallback: "평점") + } /// Home - " -" - static let emptyRating: String = Localization.localized("home.emptyRating", fallback: " -") + static var emptyRating: String { + Localization.localized("home.emptyRating", fallback: " -") + } /// Home - "제공되는 메뉴가 없습니다" - static let noMenuProvidedMessage: String = Localization.localized("home.noMenuProvidedMessage", fallback: "제공되는 메뉴가 없습니다") + static var noMenuProvidedMessage: String { + Localization.localized("home.noMenuProvidedMessage", fallback: "제공되는 메뉴가 없습니다") + } /// CustomTimeTabController - "아침" - static let morning: String = Localization.localized("home.morning", fallback: "아침") + static var morning: String { + Localization.localized("home.morning", fallback: "아침") + } /// CustomTimeTabController - "점심" - static let lunch: String = Localization.localized("home.lunch", fallback: "점심") + static var lunch: String { + Localization.localized("home.lunch", fallback: "점심") + } /// CustomTimeTabController - "저녁" - static let dinner: String = Localization.localized("home.dinner", fallback: "저녁") + static var dinner: String { + Localization.localized("home.dinner", fallback: "저녁") + } /// RestaurantInfoView - "학생 식당" - static let studentRestaurant: String = Localization.localized("home.studentRestaurant", fallback: "학생 식당") + static var studentRestaurant: String { + Localization.localized("home.studentRestaurant", fallback: "학생 식당") + } /// RestaurantInfoView - "식당 위치" - static let restaurantLocation: String = Localization.localized("home.restaurantLocation", fallback: "식당 위치") + static var restaurantLocation: String { + Localization.localized("home.restaurantLocation", fallback: "식당 위치") + } /// RestaurantInfoView - "식당 사진" - static let restaurantPicture: String = Localization.localized("home.restaurantPicture", fallback: "식당 사진") + static var restaurantPicture: String { + Localization.localized("home.restaurantPicture", fallback: "식당 사진") + } /// RestaurantInfoView - "숭실대학교" - static let soongsilUniversity: String = Localization.localized("home.soongsilUniversity", fallback: "숭실대학교") + static var soongsilUniversity: String { + Localization.localized("home.soongsilUniversity", fallback: "숭실대학교") + } /// RestaurantInfoView - "영업 시간" - static let businessHour: String = Localization.localized("home.businessHour", fallback: "영업 시간") + static var businessHour: String { + Localization.localized("home.businessHour", fallback: "영업 시간") + } /// RestaurantInfoView - "비고" - static let note: String = Localization.localized("home.note", fallback: "비고") + static var note: String { + Localization.localized("home.note", fallback: "비고") + } /// RestaurantInfoView - "아시안푸드, 돈까스, 샐러드, 국밥 등\n카페" - static let dodamEtc: String = Localization.localized("home.dodamEtc", fallback: "아시안푸드, 돈까스, 샐러드, 국밥 등\n카페") + static var dodamEtc: String { + Localization.localized("home.dodamEtc", fallback: "아시안푸드, 돈까스, 샐러드, 국밥 등\n카페") + } /// RestaurantMenuGroupCell - "영업 시간이 아니에요." - static let notBusinessHour: String = Localization.localized("home.notBusinessHour", fallback: "영업 시간이 아니에요.") + static var notBusinessHour: String { + Localization.localized("home.notBusinessHour", fallback: "영업 시간이 아니에요.") + } /// RestaurantTableViewHeader - "기숙사 식당" - static let dormitoryRestaurant: String = Localization.localized("home.dormitoryRestaurant", fallback: "기숙사 식당") + static var dormitoryRestaurant: String { + Localization.localized("home.dormitoryRestaurant", fallback: "기숙사 식당") + } } // MARK: - Map enum Map { /// MainMapVC - "제휴 지도" - static let map: String = Localization.localized("map.map", fallback: "제휴 지도") + static var map: String { + Localization.localized("map.map", fallback: "제휴 지도") + } /// MainMapView - "전체" - static let all: String = Localization.localized("map.all", fallback: "전체") + static var all: String { + Localization.localized("map.all", fallback: "전체") + } /// MainMapView - "내 제휴" - static let myPartner: String = Localization.localized("map.myPartner", fallback: "내 제휴") + static var myPartner: String { + Localization.localized("map.myPartner", fallback: "내 제휴") + } /// NoDepartmentSheetVC - "학과를 입력하고\n나만의 제휴를 확인해보세요!" - static let inputDepartment: String = Localization.localized("map.inputDepartment", fallback: "학과를 입력하고\n나만의 제휴를 확인해보세요!") + static var inputDepartment: String { + Localization.localized("map.inputDepartment", fallback: "학과를 입력하고\n나만의 제휴를 확인해보세요!") + } /// NoDepartmentSheetVC - "학과 입력하기" - static let inputDepartmentButton: String = Localization.localized("map.inputDepartmentButton", fallback: "학과 입력하기") + static var inputDepartmentButton: String { + Localization.localized("map.inputDepartmentButton", fallback: "학과 입력하기") + } /// PartnershipDetailSheetVC - "음식점" - static let restaurant: String = Localization.localized("map.restaurant", fallback: "음식점") + static var restaurant: String { + Localization.localized("map.restaurant", fallback: "음식점") + } /// PartnershipDetailSheetVC - "카페" - static let cafe: String = Localization.localized("map.cafe", fallback: "카페") + static var cafe: String { + Localization.localized("map.cafe", fallback: "카페") + } /// PartnershipDetailSheetVC - "주점" - static let pub: String = Localization.localized("map.pub", fallback: "주점") + static var pub: String { + Localization.localized("map.pub", fallback: "주점") + } /// PartnershipDetailSheetVC - "학과 정보 없음" - static let noDepartmentInfo: String = Localization.localized("map.noDepartmentInfo", fallback: "학과 정보 없음") + static var noDepartmentInfo: String { + Localization.localized("map.noDepartmentInfo", fallback: "학과 정보 없음") + } /// MainMapVC+Location - "위치 권한 필요" - static let needLocationAuth: String = Localization.localized("map.needLocationAuth", fallback: "위치 권한 필요") + static var needLocationAuth: String { + Localization.localized("map.needLocationAuth", fallback: "위치 권한 필요") + } /// MainMapVC+Location - "지도에서 내 위치를 바로 확인하고, 현재 위치 주변의 제휴점들을 손쉽게 찾아볼 수 있도록 위치 권한을 허용해 주세요." - static let locationAuthDescription: String = Localization.localized("map.locationAuthDescription", fallback: "지도에서 내 위치를 바로 확인하고, 현재 위치 주변의 제휴점들을 손쉽게 찾아볼 수 있도록 위치 권한을 허용해 주세요.") + static var locationAuthDescription: String { + Localization.localized( + "map.locationAuthDescription", + fallback: "지도에서 내 위치를 바로 확인하고, 현재 위치 주변의 제휴점들을 손쉽게 찾아볼 수 있도록 위치 권한을 허용해 주세요." + ) + } } // MARK: - MyPage enum MyPage { /// "마이페이지" - static let myPage: String = Localization.localized("myPage.myPage", fallback: "마이페이지") - - /// "내 정보" - static let myInfo: String = Localization.localized("myPage.myInfo", fallback: "내 정보") + static var myPage: String { + Localization.localized("myPage.myPage", fallback: "마이페이지") + } - /// "내 리뷰" - static let myReview: String = Localization.localized("myPage.myReview", fallback: "내 리뷰") - /// UserWithdrawVC - "회원탈퇴" - static let withdraw: String = Localization.localized("myPage.withdraw", fallback: "회원탈퇴") - - /// MyPageVC - "로그아웃" - static let logout: String = Localization.localized("myPage.logout", fallback: "로그아웃") - - /// MyPageVC - "정말 로그아웃 하시겠습니까?" - static let askLogout: String = Localization.localized("myPage.askLogout", fallback: "정말 로그아웃 하시겠습니까?") - + static var withdraw: String { + Localization.localized("myPage.withdraw", fallback: "회원탈퇴") + } + /// MyPageVC - "EAT-SSU 수신 동의" static func agreeNoti(date: String) -> String { return Localization.formatted("myPage.agreeNoti", fallback: "EAT-SSU 수신 동의 (%@)", date) @@ -325,236 +502,432 @@ enum TextLiteral { static func disagreeNoti(date: String) -> String { 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 = Localization.localized("myPage.notiSettingError", fallback: "알림 설정 중 오류가 발생했습니다.") + 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 = Localization.localized("myPage.creators", fallback: "만든 사람들") + 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 = Localization.localized("myPage.fixOrDeleteReview", fallback: "리뷰 수정 혹은 삭제") + static var fixOrDeleteReview: String { + Localization.localized("myPage.fixOrDeleteReview", fallback: "리뷰 수정 혹은 삭제") + } /// MyReviewVC - "작성하신 리뷰를 수정 또는 삭제하시겠습니까?" - static let askFixOrDeleteReview: String = Localization.localized("myPage.askFixOrDeleteReview", fallback: "작성하신 리뷰를 수정 또는 삭제하시겠습니까?") + static var askFixOrDeleteReview: String { + Localization.localized("myPage.askFixOrDeleteReview", fallback: "작성하신 리뷰를 수정 또는 삭제하시겠습니까?") + } /// MyReviewVC - "리뷰 삭제하기" - static let deleteMyReview: String = Localization.localized("myPage.deleteMyReview", fallback: "리뷰 삭제하기") + static var deleteMyReview: String { + Localization.localized("myPage.deleteMyReview", fallback: "리뷰 삭제하기") + } /// MyReviewVC - "해당 리뷰를 삭제할까요?" - static let askDeleteMyReview: String = Localization.localized("myPage.askDeleteMyReview", fallback: "해당 리뷰를 삭제할까요?") + static var askDeleteMyReview: String { + Localization.localized("myPage.askDeleteMyReview", fallback: "해당 리뷰를 삭제할까요?") + } /// MyReviewVC - "리뷰가 성공적으로 삭제되었습니다." - static let deleteMyReviewSuccess: String = Localization.localized("myPage.deleteMyReviewSuccess", fallback: "리뷰가 성공적으로 삭제되었습니다.") + static var deleteMyReviewSuccess: String { + Localization.localized("myPage.deleteMyReviewSuccess", fallback: "리뷰가 성공적으로 삭제되었습니다.") + } /// MyPageView - "다시 시도해주세요" - static let retry: String = Localization.localized("myPage.retry", fallback: "다시 시도해주세요") + static var retry: String { + Localization.localized("myPage.retry", fallback: "다시 시도해주세요") + } /// MyPageView - "앱 버전" - static let appVersion: String = Localization.localized("myPage.appVersion", fallback: "앱 버전") + static var appVersion: String { + Localization.localized("myPage.appVersion", fallback: "앱 버전") + } /// MyPageView - "탈퇴하기" - static let withdrawButton: String = Localization.localized("myPage.withdrawButton", fallback: "탈퇴하기") + static var withdrawButton: String { + Localization.localized("myPage.withdrawButton", fallback: "탈퇴하기") + } /// MyPageView - "알 수 없음" - static let unknownUser: String = Localization.localized("myPage.unknownUser", fallback: "알 수 없음") - - /// NotificationSettingTableViewCell - "푸시 알림 설정" - static let pushNotificationSetting: String = Localization.localized("myPage.pushNotificationSetting", fallback: "푸시 알림 설정") - - /// Push Notification key for UserDefaults - static let pushNotificationUserSettingKey: String = "pushNotificationUserSettingKey" - - /// NotificationSettingTableViewCell - "매일 오전 11시에 알림을 보내드려요" - static let pushNotificationDescription: String = Localization.localized("myPage.pushNotificationDescription", fallback: "매일 오전 11시에 알림을 보내드려요") - - /// MyPageVC - "문의하기" - static let inquiry: String = Localization.localized("myPage.inquiry", fallback: "문의하기") - - /// MyPageVC - "서비스 이용약관" - static let termsOfUse: String = Localization.localized("myPage.termsOfUse", fallback: "서비스 이용약관") - - /// MyPageVC - "개인정보 이용약관" - static let privacyTermsOfUse: String = Localization.localized("myPage.privacyTermsOfUse", fallback: "개인정보 이용약관") + static var unknownUser: String { + Localization.localized("myPage.unknownUser", fallback: "알 수 없음") + } /// ProvisionVC - "이용약관" - static let defaultTerms: String = Localization.localized("myPage.defaultTerms", fallback: "이용약관") - + static var defaultTerms: String { + Localization.localized("myPage.defaultTerms", fallback: "이용약관") + } + /// UserWithdrawView - "정말 탈퇴하시겠습니까?" - static let confirmWithdrawal: String = Localization.localized("myPage.confirmWithdrawal", fallback: "정말 탈퇴하시겠습니까?") + static var confirmWithdrawal: String { + Localization.localized("myPage.confirmWithdrawal", fallback: "정말 탈퇴하시겠습니까?") + } /// UserWithdrawView - "작성한 리뷰 게시글은 삭제되지 않으며, (알수없음)으로 표시됩니다.\n자세한 내용은 서비스이용약관 및 개인정보처리방침을 확인해 주세요." - static let withdrawalNotice: String = Localization.localized("myPage.withdrawalNotice", fallback: "작성한 리뷰 게시글은 삭제되지 않으며, (알수없음)으로 표시됩니다.\n자세한 내용은 서비스이용약관 및 개인정보처리방침을 확인해 주세요.") + static var withdrawalNotice: String { + Localization.localized( + "myPage.withdrawalNotice", + fallback: "작성한 리뷰 게시글은 삭제되지 않으며, (알수없음)으로 표시됩니다.\n자세한 내용은 서비스이용약관 및 개인정보처리방침을 확인해 주세요." + ) + } /// UserWithdrawView - "올바른 입력입니다." - static let validInputMessage: String = Localization.localized("myPage.validInputMessage", fallback: "올바른 입력입니다") + static var validInputMessage: String { + Localization.localized("myPage.validInputMessage", fallback: "올바른 입력입니다") + } /// UserWithdrawView - "올바르지 않은 닉네임입니다" - static let invalidNicknameMessage: String = Localization.localized("myPage.invalidNicknameMessage", fallback: "올바르지 않은 닉네임입니다") + static var invalidNicknameMessage: String { + Localization.localized("myPage.invalidNicknameMessage", fallback: "올바르지 않은 닉네임입니다") + } } // MARK: - Review enum Review { /// ReportVC - "EAT SSU 팀에게 보내기" - static let sendToTeam: String = Localization.localized("review.sendToTeam", fallback: "EAT SSU 팀에게 보내기") + static var sendToTeam: String { + Localization.localized("review.sendToTeam", fallback: "EAT SSU 팀에게 보내기") + } /// ReportVC - "신고하기" - static let report: String = Localization.localized("review.report", fallback: "신고하기") + static var report: String { + Localization.localized("review.report", fallback: "신고하기") + } /// ReportVC - "사유를 선택해주세요!" - static let selectReason: String = Localization.localized("review.selectReason", fallback: "사유를 선택해주세요!") + static var selectReason: String { + Localization.localized("review.selectReason", fallback: "사유를 선택해주세요!") + } /// ReportVC - "신고가 성공적으로 접수되었어요!" - static let reportSuccess: String = Localization.localized("review.reportSuccess", fallback: "신고가 성공적으로 접수되었어요!") + static var reportSuccess: String { + Localization.localized("review.reportSuccess", fallback: "신고가 성공적으로 접수되었어요!") + } /// ReportVC, ReportView - "메뉴와 관련없는 내용" - static let unrelatedMenu: String = Localization.localized("review.unrelatedMenu", fallback: "메뉴와 관련없는 내용") + static var unrelatedMenu: String { + Localization.localized("review.unrelatedMenu", fallback: "메뉴와 관련없는 내용") + } /// ReportVC, ReportView - "음란성, 욕설 등 부적절한 내용" - static let inappropriateContent: String = Localization.localized("review.inappropriateContent", fallback: "음란성, 욕설 등 부적절한 내용") + static var inappropriateContent: String { + Localization.localized("review.inappropriateContent", fallback: "음란성, 욕설 등 부적절한 내용") + } /// ReportVC, ReportView - "부적절한 홍보 또는 광고" - static let inappropriateAd: String = Localization.localized("review.inappropriateAd", fallback: "부적절한 홍보 또는 광고") + static var inappropriateAd: String { + Localization.localized("review.inappropriateAd", fallback: "부적절한 홍보 또는 광고") + } /// ReportVC, ReportView - "리뷰 작성 취지에 맞지 않는 내용 (복사글 등)" - static let notReviewFormat: String = Localization.localized("review.notReviewFormat", fallback: "리뷰 작성 취지에 맞지 않는 내용 (복사글 등)") + static var notReviewFormat: String { + Localization.localized("review.notReviewFormat", fallback: "리뷰 작성 취지에 맞지 않는 내용 (복사글 등)") + } /// ReportVC, ReportView - "저작권 도용 의심 (사진 등)" - static let copyright: String = Localization.localized("review.copyright", fallback: "저작권 도용 의심 (사진 등)") + static var copyright: String { + Localization.localized("review.copyright", fallback: "저작권 도용 의심 (사진 등)") + } /// ReportVC, ReportView - "기타 (하단 내용 작성)" - static let etc: String = Localization.localized("review.etc", fallback: "기타 (하단 내용 작성)") + static var etc: String { + Localization.localized("review.etc", fallback: "기타 (하단 내용 작성)") + } /// ReportView - "리뷰 신고 사유를 알려주세요" - static let reportReason: String = Localization.localized("review.reportReason", fallback: "리뷰 신고 사유를 알려주세요") + static var reportReason: String { + Localization.localized("review.reportReason", fallback: "리뷰 신고 사유를 알려주세요") + } /// ReportView - "하나의 리뷰에 대해 24시간 내 한 번만 신고 가능합니다." - static let reportGuide: String = Localization.localized("review.reportGuide", fallback: "하나의 리뷰에 대해 24시간 내 한 번만 신고 가능합니다.") + static var reportGuide: String { + Localization.localized("review.reportGuide", fallback: "하나의 리뷰에 대해 24시간 내 한 번만 신고 가능합니다.") + } /// ReportView - "리뷰 신고 사유를 작성해 주세요" - static let inputReportReason: String = Localization.localized("review.inputReportReason", fallback: "리뷰 신고 사유를 작성해 주세요") + static var inputReportReason: String { + Localization.localized("review.inputReportReason", fallback: "리뷰 신고 사유를 작성해 주세요") + } /// ReviewVC - "리뷰 작성하기" - static let writeReview: String = Localization.localized("review.writeReview", fallback: "리뷰 작성하기") + static var writeReview: String { + Localization.localized("review.writeReview", fallback: "리뷰 작성하기") + } /// ReviewVC - "리뷰가 성공적으로 등록되었습니다." - static let registerReviewSuccess: String = Localization.localized("review.registerReviewSuccess", fallback: "리뷰가 성공적으로 등록되었습니다.") + static var registerReviewSuccess: String { + Localization.localized("review.registerReviewSuccess", fallback: "리뷰가 성공적으로 등록되었습니다.") + } /// ReviewVC - "리뷰" - static let review: String = Localization.localized("review.review", fallback: "리뷰") + static var review: String { + Localization.localized("review.review", fallback: "리뷰") + } /// ReviewVC - "리뷰 삭제" - static let deleteReview: String = Localization.localized("review.deleteReview", fallback: "리뷰 삭제") + static var deleteReview: String { + Localization.localized("review.deleteReview", fallback: "리뷰 삭제") + } /// ReviewVC - "해당 리뷰를 삭제할까요?" - static let askDeleteReview: String = Localization.localized("review.askDeleteReview", fallback: "해당 리뷰를 삭제할까요?") + static var askDeleteReview: String { + Localization.localized("review.askDeleteReview", fallback: "해당 리뷰를 삭제할까요?") + } /// ReviewVC - "리뷰 신고하기" - static let reportReview: String = Localization.localized("review.reportReview", fallback: "리뷰 신고하기") + static var reportReview: String { + Localization.localized("review.reportReview", fallback: "리뷰 신고하기") + } /// ReviewVC - "해당 리뷰를 신고하시겠습니까?" - static let askReportReview: String = Localization.localized("review.askReportReview", fallback: "해당 리뷰를 신고하시겠습니까?") + static var askReportReview: String { + Localization.localized("review.askReportReview", fallback: "해당 리뷰를 신고하시겠습니까?") + } /// ReviewVC - "리뷰가 성공적으로 삭제되었습니다." - static let deleteReviewSuccess: String = Localization.localized("review.deleteReviewSuccess", fallback: "리뷰가 성공적으로 삭제되었습니다.") + static var deleteReviewSuccess: String { + Localization.localized("review.deleteReviewSuccess", fallback: "리뷰가 성공적으로 삭제되었습니다.") + } /// ReviewVC - "리뷰 삭제에 실패했습니다." - static let deleteReviewFail: String = Localization.localized("review.deleteReviewFail", fallback: "리뷰 삭제에 실패했습니다.") + static var deleteReviewFail: String { + Localization.localized("review.deleteReviewFail", fallback: "리뷰 삭제에 실패했습니다.") + } /// SetRateVC - "리뷰 수정하기" - static let fixReview: String = Localization.localized("review.fixReview", fallback: "리뷰 수정하기") + static var fixReview: String { + Localization.localized("review.fixReview", fallback: "리뷰 수정하기") + } /// SetRateVC - "리뷰 남기기" - static let leaveReview: String = Localization.localized("review.leaveReview", fallback: "리뷰 남기기") + 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 Localization.formatted("review.recommendMenu.default", fallback: "%@을(를) 추천하시겠어요?", 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 Localization.formatted("review.recommendMenu.withJongseong", fallback: "%@을 추천하시겠어요?", name) // 받침 있음 + return Localization.formatted("review.recommendMenu.withJongseong", fallback: "%@을 추천하시겠어요?", name) } } - return Localization.formatted("review.recommendMenu.withoutJongseong", fallback: "%@를 추천하시겠어요?", name) // 받침 없음 + return Localization.formatted("review.recommendMenu.withoutJongseong", fallback: "%@를 추천하시겠어요?", name) } /// SetRateVC - "메뉴를 추천하시겠어요?" - static let recommendMenuTitle: String = Localization.localized("review.recommendMenuTitle", fallback: "메뉴를 추천하시겠어요?") + static var recommendMenuTitle: String { + Localization.localized("review.recommendMenuTitle", fallback: "메뉴를 추천하시겠어요?") + } /// SetRateVC - "리뷰 수정 완료하기" - static let fixReviewComplete: String = Localization.localized("review.fixReviewComplete", fallback: "리뷰 수정 완료하기") + static var fixReviewComplete: String { + Localization.localized("review.fixReviewComplete", fallback: "리뷰 수정 완료하기") + } /// SetRateVC - "완료하기" - static let complete: String = Localization.localized("review.complete", fallback: "완료하기") + static var complete: String { + Localization.localized("review.complete", fallback: "완료하기") + } /// SetRateVC - "별점을 입력해주세요!" - static let inputRating: String = Localization.localized("review.inputRating", fallback: "별점을 입력해주세요!") + static var inputRating: String { + Localization.localized("review.inputRating", fallback: "별점을 입력해주세요!") + } /// SetRateVC - "메뉴 목록 조회에 실패했습니다." - static let loadMenuListFail: String = Localization.localized("review.loadMenuListFail", fallback: "메뉴 목록 조회에 실패했습니다.") + static var loadMenuListFail: String { + Localization.localized("review.loadMenuListFail", fallback: "메뉴 목록 조회에 실패했습니다.") + } /// SetRateVC - "수정할 리뷰 정보가 없습니다." - static let noReviewInfoForFix: String = Localization.localized("review.noReviewInfoForFix", fallback: "수정할 리뷰 정보가 없습니다.") + static var noReviewInfoForFix: String { + Localization.localized("review.noReviewInfoForFix", fallback: "수정할 리뷰 정보가 없습니다.") + } /// SetRateVC - "리뷰가 성공적으로 수정되었습니다." - static let fixReviewSuccess: String = Localization.localized("review.fixReviewSuccess", fallback: "리뷰가 성공적으로 수정되었습니다.") + static var fixReviewSuccess: String { + Localization.localized("review.fixReviewSuccess", fallback: "리뷰가 성공적으로 수정되었습니다.") + } /// SetRateVC - "리뷰 수정에 실패했습니다." - static let fixReviewFail: String = Localization.localized("review.fixReviewFail", fallback: "리뷰 수정에 실패했습니다.") + static var fixReviewFail: String { + Localization.localized("review.fixReviewFail", fallback: "리뷰 수정에 실패했습니다.") + } /// SetRateVC - "식단 정보가 없습니다." - static let noMealInfo: String = Localization.localized("review.noMealInfo", fallback: "식단 정보가 없습니다.") + static var noMealInfo: String { + Localization.localized("review.noMealInfo", fallback: "식단 정보가 없습니다.") + } /// SetRateVC - "리뷰 업로드에 실패했습니다." - static let uploadReviewFail: String = Localization.localized("review.uploadReviewFail", fallback: "리뷰 업로드에 실패했습니다.") + static var uploadReviewFail: String { + Localization.localized("review.uploadReviewFail", fallback: "리뷰 업로드에 실패했습니다.") + } /// SetRateVC - "메뉴 정보가 없습니다." - static let noMenuInfo: String = Localization.localized("review.noMenuInfo", fallback: "메뉴 정보가 없습니다.") + static var noMenuInfo: String { + Localization.localized("review.noMenuInfo", fallback: "메뉴 정보가 없습니다.") + } /// SetRateVC - "메뉴에 대한 상세한 리뷰를 작성해주세요" - static let inputDetailReview: String = Localization.localized("review.inputDetailReview", fallback: "메뉴에 대한 상세한 리뷰를 작성해주세요") + static var inputDetailReview: String { + Localization.localized("review.inputDetailReview", fallback: "메뉴에 대한 상세한 리뷰를 작성해주세요") + } /// SetRateVC - "나가시겠어요?" - static let askLeave: String = Localization.localized("review.askLeave", fallback: "나가시겠어요?") + static var askLeave: String { + Localization.localized("review.askLeave", fallback: "나가시겠어요?") + } /// SetRateVC - "지금 나가면 작성한 내용이 저장되지 않습니다." - static let leaveWarning: String = Localization.localized("review.leaveWarning", fallback: "지금 나가면 작성한 내용이 저장되지 않습니다.") + static var leaveWarning: String { + Localization.localized("review.leaveWarning", fallback: "지금 나가면 작성한 내용이 저장되지 않습니다.") + } /// SetRateVC - "나가기" - static let leave: String = Localization.localized("review.leave", fallback: "나가기") + static var leave: String { + Localization.localized("review.leave", fallback: "나가기") + } /// SetRateVC - "계속 작성" - static let continueWriting: String = Localization.localized("review.continueWriting", fallback: "계속 작성") + static var continueWriting: String { + Localization.localized("review.continueWriting", fallback: "계속 작성") + } /// ReviewEmptyViewCell - "아직 작성된 리뷰가 없어요!" - static let noReview: String = Localization.localized("review.noReview", fallback: "아직 작성된 리뷰가 없어요!") + static var noReview: String { + Localization.localized("review.noReview", fallback: "아직 작성된 리뷰가 없어요!") + } /// ReviewEmptyViewCell - "메뉴에 가장 먼저 리뷰를 남겨주세요!" - static let beFirstReviewer: String = Localization.localized("review.beFirstReviewer", fallback: "메뉴에 가장 먼저 리뷰를 남겨주세요!") + static var beFirstReviewer: String { + Localization.localized("review.beFirstReviewer", fallback: "메뉴에 가장 먼저 리뷰를 남겨주세요!") + } /// ReviewEmptyViewCell - "로그인이 필요합니다" - static let needLogin: String = Localization.localized("review.needLogin", fallback: "로그인이 필요합니다") + static var needLogin: String { + Localization.localized("review.needLogin", fallback: "로그인이 필요합니다") + } /// ReviewEmptyViewCell - "로그인 후 리뷰를 확인하세요" - static let checkReviewAfterLogin: String = Localization.localized("review.checkReviewAfterLogin", fallback: "로그인 후 리뷰를 확인하세요") + static var checkReviewAfterLogin: String { + Localization.localized("review.checkReviewAfterLogin", fallback: "로그인 후 리뷰를 확인하세요") + } /// ReviewEmptyViewCell - "아직 작성한 리뷰가 없어요" - static let noWrittenReview: String = Localization.localized("review.noWrittenReview", fallback: "아직 작성한 리뷰가 없어요") + static var noWrittenReview: String { + Localization.localized("review.noWrittenReview", fallback: "아직 작성한 리뷰가 없어요") + } /// ReviewEmptyViewCell - "첫 리뷰를 남겨 주세요!" - static let writeFirstReview: String = Localization.localized("review.writeFirstReview", fallback: "첫 리뷰를 남겨 주세요!") + static var writeFirstReview: String { + Localization.localized("review.writeFirstReview", fallback: "첫 리뷰를 남겨 주세요!") + } /// ReviewDividerCell - "리뷰" static func reviewCount(_ count: Int) -> String { @@ -562,28 +935,44 @@ enum TextLiteral { } /// ReviewRateViewCell - "오늘의 메뉴" - static let todayMenu: String = Localization.localized("review.todayMenu", fallback: "오늘의 메뉴") + static var todayMenu: String { + Localization.localized("review.todayMenu", fallback: "오늘의 메뉴") + } /// ReviewRateViewCell - "5점" - static let fiveStars: String = Localization.localized("review.fiveStars", fallback: "5점") + static var fiveStars: String { + Localization.localized("review.fiveStars", fallback: "5점") + } /// ReviewRateViewCell - "4점" - static let fourStars: String = Localization.localized("review.fourStars", fallback: "4점") + static var fourStars: String { + Localization.localized("review.fourStars", fallback: "4점") + } /// ReviewRateViewCell - "3점" - static let threeStars: String = Localization.localized("review.threeStars", fallback: "3점") + static var threeStars: String { + Localization.localized("review.threeStars", fallback: "3점") + } /// ReviewRateViewCell - "2점" - static let twoStars: String = Localization.localized("review.twoStars", fallback: "2점") + static var twoStars: String { + Localization.localized("review.twoStars", fallback: "2점") + } /// ReviewRateViewCell - "1점" - static let oneStar: String = Localization.localized("review.oneStar", fallback: "1점") + static var oneStar: String { + Localization.localized("review.oneStar", fallback: "1점") + } /// SetRateView - "오늘의 식사는 어떠셨나요?" - static let rateTodayMeal: String = Localization.localized("review.rateTodayMeal", fallback: "오늘의 식사는 어떠셨나요?") + static var rateTodayMeal: String { + Localization.localized("review.rateTodayMeal", fallback: "오늘의 식사는 어떠셨나요?") + } /// SetRateView - "추천하고 싶은 메뉴가 있나요?" - static let recommendMenu: String = Localization.localized("review.recommendMenu", fallback: "추천하고 싶은 메뉴가 있나요?") + static var recommendMenu: String { + Localization.localized("review.recommendMenu", fallback: "추천하고 싶은 메뉴가 있나요?") + } /// SetRateView - "사진 추가 (0/1)" static func addPhoto(count: Int) -> String { @@ -600,66 +989,104 @@ enum TextLiteral { enum Coffee { /// "나가시겠어요?" - static let askLeave: String = Localization.localized("coffee.askLeave", fallback: "나가시겠어요?") + static var askLeave: String { + Localization.localized("coffee.askLeave", fallback: "나가시겠어요?") + } /// "지금 나가면 진행 상황이\n저장되지 않습니다." - static let leaveWarning: String = Localization.localized("coffee.leaveWarning", fallback: "지금 나가면 진행 상황이\n저장되지 않습니다.") + static var leaveWarning: String { + Localization.localized("coffee.leaveWarning", fallback: "지금 나가면 진행 상황이\n저장되지 않습니다.") + } /// "나가기" - static let leave: String = Localization.localized("coffee.leave", fallback: "나가기") + static var leave: String { + Localization.localized("coffee.leave", fallback: "나가기") + } /// "계속하기" - static let continueEvent: String = Localization.localized("coffee.continueEvent", fallback: "계속하기") + static var continueEvent: String { + Localization.localized("coffee.continueEvent", fallback: "계속하기") + } } // MARK: - Splash enum Splash { /// NoticeSplashVC - "긴급 서버 점검 안내" - static let serverInspection: String = Localization.localized("splash.serverInspection", fallback: "긴급 서버 점검 안내") + static var serverInspection: String { + Localization.localized("splash.serverInspection", fallback: "긴급 서버 점검 안내") + } } // MARK: - PromotionPopup enum PromotionPopup { /// 03. 16(월)~03. 27(금) - static let period: String = Localization.localized("promotionPopup.period", fallback: "03. 16(월)~03. 27(금)") + static var period: String { + Localization.localized("promotionPopup.period", fallback: "03. 16(월)~03. 27(금)") + } /// EAT-SSU 인스타그램 바로가기 - static let instagramButtonTitle: String = Localization.localized("promotionPopup.instagramButtonTitle", fallback: "EAT-SSU 인스타그램 바로가기") + static var instagramButtonTitle: String { + Localization.localized("promotionPopup.instagramButtonTitle", fallback: "EAT-SSU 인스타그램 바로가기") + } /// 자세한 내용은 EAT-SSU 인스타그램을 확인해 주세요 - static let guideMessage: String = Localization.localized("promotionPopup.guideMessage", fallback: "자세한 내용은 EAT-SSU 인스타그램을 확인해 주세요") + static var guideMessage: String { + Localization.localized("promotionPopup.guideMessage", fallback: "자세한 내용은 EAT-SSU 인스타그램을 확인해 주세요") + } /// 다시 보지 않기 - static let neverShowAgain: String = Localization.localized("promotionPopup.neverShowAgain", fallback: "다시 보지 않기") + static var neverShowAgain: String { + Localization.localized("promotionPopup.neverShowAgain", fallback: "다시 보지 않기") + } /// 닫기 - static let close: String = Localization.localized("promotionPopup.close", fallback: "닫기") + static var close: String { + Localization.localized("promotionPopup.close", fallback: "닫기") + } } // MARK: - Notification enum Notification { /// 🤔 오늘 밥 뭐 먹지… - static let dailyWeekdayNotificationTitle: String = Localization.localized("notification.dailyWeekdayNotificationTitle", fallback: "🤔 오늘 밥 뭐 먹지…") + static var dailyWeekdayNotificationTitle: String { + Localization.localized("notification.dailyWeekdayNotificationTitle", fallback: "🤔 오늘 밥 뭐 먹지…") + } /// 오늘의 학식을 확인해보세요! - static let dailyWeekdayNotificationBody: String = Localization.localized("notification.dailyWeekdayNotificationBody", fallback: "오늘의 학식을 확인해보세요!") + static var dailyWeekdayNotificationBody: String { + Localization.localized("notification.dailyWeekdayNotificationBody", fallback: "오늘의 학식을 확인해보세요!") + } } // MARK: - Restaurant enum Restaurant { /// "기숙사 식당" - static let dormitoryRestaurant: String = Localization.localized("restaurant.dormitoryRestaurant", fallback: "기숙사 식당") + static var dormitoryRestaurant: String { + Localization.localized("restaurant.dormitoryRestaurant", fallback: "기숙사 식당") + } + /// "도담 식당" - static let dodamRestaurant: String = Localization.localized("restaurant.dodamRestaurant", fallback: "도담 식당") + static var dodamRestaurant: String { + Localization.localized("restaurant.dodamRestaurant", fallback: "도담 식당") + } + /// "학생 식당" - static let studentRestaurant: String = Localization.localized("restaurant.studentRestaurant", fallback: "학생 식당") + static var studentRestaurant: String { + Localization.localized("restaurant.studentRestaurant", fallback: "학생 식당") + } + /// "스낵 코너" - static let snackCorner: String = Localization.localized("restaurant.snackCorner", fallback: "스낵 코너") + static var snackCorner: String { + Localization.localized("restaurant.snackCorner", fallback: "스낵 코너") + } + /// "FACULTY (교직원 전용)" - static let facultyRestaurant: String = Localization.localized("restaurant.facultyRestaurant", fallback: "FACULTY (교직원 전용)") + static var facultyRestaurant: String { + Localization.localized("restaurant.facultyRestaurant", fallback: "FACULTY (교직원 전용)") + } } } diff --git a/EATSSU/App/Sources/Utility/Literal/en.lproj/Localizable.strings b/EATSSU/App/Sources/Utility/Literal/en.lproj/Localizable.strings index e33fc361..f061709a 100644 --- a/EATSSU/App/Sources/Utility/Literal/en.lproj/Localizable.strings +++ b/EATSSU/App/Sources/Utility/Literal/en.lproj/Localizable.strings @@ -192,24 +192,57 @@ /// "마이페이지" "myPage.myPage" = "My Page"; -/// "내 정보" -"myPage.myInfo" = "My Info"; -/// "내 리뷰" -"myPage.myReview" = "My Reviews"; /// UserWithdrawVC - "회원탈퇴" "myPage.withdraw" = "Delete Account"; -/// MyPageVC - "로그아웃" -"myPage.logout" = "Log Out"; -/// MyPageVC - "정말 로그아웃 하시겠습니까?" -"myPage.askLogout" = "Are you sure you want to log out?"; /// 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 - "작성하신 리뷰를 수정 또는 삭제하시겠습니까?" @@ -228,16 +261,6 @@ "myPage.withdrawButton" = "Delete Account"; /// MyPageView - "알 수 없음" "myPage.unknownUser" = "Unknown"; -/// NotificationSettingTableViewCell - "푸시 알림 설정" -"myPage.pushNotificationSetting" = "Push Notification Settings"; -/// NotificationSettingTableViewCell - "매일 오전 11시에 알림을 보내드려요" -"myPage.pushNotificationDescription" = "We'll send you a notification every day at 11 AM."; -/// MyPageVC - "문의하기" -"myPage.inquiry" = "Contact Us"; -/// MyPageVC - "서비스 이용약관" -"myPage.termsOfUse" = "Terms of Service"; -/// MyPageVC - "개인정보 이용약관" -"myPage.privacyTermsOfUse" = "Privacy Policy"; /// ProvisionVC - "이용약관" "myPage.defaultTerms" = "Terms of Service"; /// UserWithdrawView - "정말 탈퇴하시겠습니까?" diff --git a/EATSSU/App/Sources/Utility/Literal/ko.lproj/Localizable.strings b/EATSSU/App/Sources/Utility/Literal/ko.lproj/Localizable.strings index 3da405e1..4200f58e 100644 --- a/EATSSU/App/Sources/Utility/Literal/ko.lproj/Localizable.strings +++ b/EATSSU/App/Sources/Utility/Literal/ko.lproj/Localizable.strings @@ -193,24 +193,57 @@ /// "마이페이지" "myPage.myPage" = "마이페이지"; -/// "내 정보" -"myPage.myInfo" = "내 정보"; -/// "내 리뷰" -"myPage.myReview" = "내 리뷰"; /// UserWithdrawVC - "회원탈퇴" "myPage.withdraw" = "회원탈퇴"; -/// MyPageVC - "로그아웃" -"myPage.logout" = "로그아웃"; -/// MyPageVC - "정말 로그아웃 하시겠습니까?" -"myPage.askLogout" = "정말 로그아웃 하시겠습니까?"; /// 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 - "작성하신 리뷰를 수정 또는 삭제하시겠습니까?" @@ -229,16 +262,6 @@ "myPage.withdrawButton" = "탈퇴하기"; /// MyPageView - "알 수 없음" "myPage.unknownUser" = "알 수 없음"; -/// NotificationSettingTableViewCell - "푸시 알림 설정" -"myPage.pushNotificationSetting" = "푸시 알림 설정"; -/// NotificationSettingTableViewCell - "매일 오전 11시에 알림을 보내드려요" -"myPage.pushNotificationDescription" = "매일 오전 11시에 알림을 보내드려요"; -/// MyPageVC - "문의하기" -"myPage.inquiry" = "문의하기"; -/// MyPageVC - "서비스 이용약관" -"myPage.termsOfUse" = "서비스 이용약관"; -/// MyPageVC - "개인정보 이용약관" -"myPage.privacyTermsOfUse" = "개인정보 이용약관"; /// ProvisionVC - "이용약관" "myPage.defaultTerms" = "이용약관"; /// UserWithdrawView - "정말 탈퇴하시겠습니까?" From 111a30270ac37b2498967a89945dcdaed426f19f Mon Sep 17 00:00:00 2001 From: jeongminji Date: Sun, 3 May 2026 22:30:16 +0900 Subject: [PATCH 05/16] =?UTF-8?q?[#275]=20MyPage=20cell=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=AA=A8=EB=8D=B8=20MyPageSectionData=EB=A1=9C=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MyPage/Enum/MyPageLabels.swift | 84 +++++++++++++------ .../MyPage/Model/MyPageLocalData.swift | 42 ---------- .../MyPage/Model/MyPageRightItemData.swift | 20 ----- .../MyPage/Model/MyPageSectionData.swift | 44 ++++++++++ 4 files changed, 104 insertions(+), 86 deletions(-) delete mode 100644 EATSSU/App/Sources/Presentation/MyPage/Model/MyPageLocalData.swift delete mode 100644 EATSSU/App/Sources/Presentation/MyPage/Model/MyPageRightItemData.swift create mode 100644 EATSSU/App/Sources/Presentation/MyPage/Model/MyPageSectionData.swift 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/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 + ] + ) + ] + } +} From 18a893cca77b3c025478841514ff6d838cfdf526 Mon Sep 17 00:00:00 2001 From: jeongminji Date: Sun, 3 May 2026 22:31:06 +0900 Subject: [PATCH 06/16] =?UTF-8?q?[#275]=20=EC=96=B8=EC=96=B4=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B7=B0=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20?= =?UTF-8?q?&=20=EC=9D=B4=EC=9A=A9=EC=95=BD=EA=B0=84=20=EB=B7=B0=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LanguageSettingViewController.swift | 173 ++++++++++++++++++ .../TermsAndPolicyViewController.swift | 144 +++++++++++++++ 2 files changed, 317 insertions(+) create mode 100644 EATSSU/App/Sources/Presentation/MyPage/ViewController/LanguageSettingViewController.swift create mode 100644 EATSSU/App/Sources/Presentation/MyPage/ViewController/TermsAndPolicyViewController.swift 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..75f1e24c --- /dev/null +++ b/EATSSU/App/Sources/Presentation/MyPage/ViewController/LanguageSettingViewController.swift @@ -0,0 +1,173 @@ +// +// 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 + + 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() + } + + // 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 + ) + } + + @objc + private func backButtonDidTap() { + if didChangeLanguage { + resetRootViewController() + print("루트 새로 변경됨") + } else { + navigationController?.popViewController(animated: true) + print("루트 새로 변경안됨") + } + } + + 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 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) + } +} 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) + } +} From 252ec8f330a11f6ee0224665514d67f6ed7d6348 Mon Sep 17 00:00:00 2001 From: jeongminji Date: Sun, 3 May 2026 22:31:45 +0900 Subject: [PATCH 07/16] =?UTF-8?q?[#275]=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20UI=20=EB=A6=AC=EB=89=B4=EC=96=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MyPage/Enum/MyPageTableMetric.swift | 25 ++ .../MyPage/View/MyPageView/MyPageView.swift | 132 +++++--- .../ViewController/MyPageViewController.swift | 290 ++++++++++++------ 3 files changed, 316 insertions(+), 131 deletions(-) create mode 100644 EATSSU/App/Sources/Presentation/MyPage/Enum/MyPageTableMetric.swift 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/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/MyPageViewController.swift b/EATSSU/App/Sources/Presentation/MyPage/ViewController/MyPageViewController.swift index d93632d2..f6e81786 100644 --- a/EATSSU/App/Sources/Presentation/MyPage/ViewController/MyPageViewController.swift +++ b/EATSSU/App/Sources/Presentation/MyPage/ViewController/MyPageViewController.swift @@ -16,20 +16,20 @@ import SnapKit final class MyPageViewController: BaseViewController { // MARK: - Properties - + 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 +39,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 +86,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 +189,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 +210,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 +292,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: "https://www.instagram.com/eatssu.official/") { + 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 +380,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 +410,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) From 100ba784f9612baf0a08e89fd7766902e5945f13 Mon Sep 17 00:00:00 2001 From: jeongminji Date: Sun, 3 May 2026 22:33:00 +0900 Subject: [PATCH 08/16] =?UTF-8?q?[#275]=20=EC=9E=98=EB=AA=BB=EB=93=A4?= =?UTF-8?q?=EC=96=B4=EA=B0=84=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- EATSSU/App/Sources/Utility/Literal/AppLanguageManager.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/EATSSU/App/Sources/Utility/Literal/AppLanguageManager.swift b/EATSSU/App/Sources/Utility/Literal/AppLanguageManager.swift index 1a9d3699..77d5f1f8 100644 --- a/EATSSU/App/Sources/Utility/Literal/AppLanguageManager.swift +++ b/EATSSU/App/Sources/Utility/Literal/AppLanguageManager.swift @@ -67,7 +67,5 @@ final class AppLanguageManager { func changeLanguage(to language: AppLanguage) { UserDefaults.standard.set(language.rawValue, forKey: Constant.selectedLanguageKey) UserDefaults.standard.set(true, forKey: Constant.didSelectLanguageManuallyKey) - - NotificationCenter.default.post(name: .appLanguageDidChange, object: nil) } } From 5fe5f24380f04950d9eb74f00a9026c79bba63f0 Mon Sep 17 00:00:00 2001 From: jeongminji Date: Sun, 3 May 2026 22:55:10 +0900 Subject: [PATCH 09/16] =?UTF-8?q?[#275]=20=EC=BD=94=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewController/LanguageSettingViewController.swift | 2 -- .../MyPage/ViewController/MyPageViewController.swift | 5 ++++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/EATSSU/App/Sources/Presentation/MyPage/ViewController/LanguageSettingViewController.swift b/EATSSU/App/Sources/Presentation/MyPage/ViewController/LanguageSettingViewController.swift index 75f1e24c..a10d237a 100644 --- a/EATSSU/App/Sources/Presentation/MyPage/ViewController/LanguageSettingViewController.swift +++ b/EATSSU/App/Sources/Presentation/MyPage/ViewController/LanguageSettingViewController.swift @@ -87,10 +87,8 @@ final class LanguageSettingViewController: BaseViewController { private func backButtonDidTap() { if didChangeLanguage { resetRootViewController() - print("루트 새로 변경됨") } else { navigationController?.popViewController(animated: true) - print("루트 새로 변경안됨") } } diff --git a/EATSSU/App/Sources/Presentation/MyPage/ViewController/MyPageViewController.swift b/EATSSU/App/Sources/Presentation/MyPage/ViewController/MyPageViewController.swift index f6e81786..ffe96a55 100644 --- a/EATSSU/App/Sources/Presentation/MyPage/ViewController/MyPageViewController.swift +++ b/EATSSU/App/Sources/Presentation/MyPage/ViewController/MyPageViewController.swift @@ -16,6 +16,9 @@ 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 @@ -304,7 +307,7 @@ extension MyPageViewController: UITableViewDelegate { // TODO: 실제 로그 이름 통일 //AnalyticsService.logEvent("click_mypage_menu", parameters: ["menu": "instagram"]) - if let instagramURL = URL(string: "https://www.instagram.com/eatssu.official/") { + if let instagramURL = URL(string: URLConstants.instagram) { UIApplication.shared.open(instagramURL) } From 9c3dd440bc7b2917e44bc0f267fe4df7989f7043 Mon Sep 17 00:00:00 2001 From: jeongminji Date: Sun, 3 May 2026 23:25:09 +0900 Subject: [PATCH 10/16] =?UTF-8?q?[#275]=20Project.swift=EA=B0=80=20?= =?UTF-8?q?=EB=A6=AC=EC=86=8C=EC=8A=A4=EB=A1=9C=20=ED=8F=AC=ED=95=A8?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=84=A4=EC=A0=95=ED=95=9C=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=EB=A1=9C=20Localizable.strings=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Utility/Literal => Resources}/en.lproj/Localizable.strings | 0 .../Utility/Literal => Resources}/ko.lproj/Localizable.strings | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename EATSSU/App/{Sources/Utility/Literal => Resources}/en.lproj/Localizable.strings (100%) rename EATSSU/App/{Sources/Utility/Literal => Resources}/ko.lproj/Localizable.strings (100%) diff --git a/EATSSU/App/Sources/Utility/Literal/en.lproj/Localizable.strings b/EATSSU/App/Resources/en.lproj/Localizable.strings similarity index 100% rename from EATSSU/App/Sources/Utility/Literal/en.lproj/Localizable.strings rename to EATSSU/App/Resources/en.lproj/Localizable.strings diff --git a/EATSSU/App/Sources/Utility/Literal/ko.lproj/Localizable.strings b/EATSSU/App/Resources/ko.lproj/Localizable.strings similarity index 100% rename from EATSSU/App/Sources/Utility/Literal/ko.lproj/Localizable.strings rename to EATSSU/App/Resources/ko.lproj/Localizable.strings From cd04509cd903bca421007237f4d3bfdf5fbc8d18 Mon Sep 17 00:00:00 2001 From: jeongminji Date: Sun, 3 May 2026 23:28:18 +0900 Subject: [PATCH 11/16] =?UTF-8?q?[#275]=20Project.swift=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20known=20regi?= =?UTF-8?q?on=EC=97=90=20=EC=98=81=EC=96=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- EATSSU/Project.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, From bafb18a68f94c039bec4d3e5e31b67f582cbb99b Mon Sep 17 00:00:00 2001 From: jeongminji Date: Sun, 3 May 2026 23:46:17 +0900 Subject: [PATCH 12/16] =?UTF-8?q?[#275]=20=EC=96=B8=EC=96=B4=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B7=B0=20=EC=8A=A4=EC=99=80=EC=9D=B4=ED=94=84?= =?UTF-8?q?=EB=A1=9C=20=EB=92=A4=EB=A1=9C=20=EA=B0=80=EA=B8=B0=20=ED=99=9C?= =?UTF-8?q?=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LanguageSettingViewController.swift | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/EATSSU/App/Sources/Presentation/MyPage/ViewController/LanguageSettingViewController.swift b/EATSSU/App/Sources/Presentation/MyPage/ViewController/LanguageSettingViewController.swift index a10d237a..3c54718d 100644 --- a/EATSSU/App/Sources/Presentation/MyPage/ViewController/LanguageSettingViewController.swift +++ b/EATSSU/App/Sources/Presentation/MyPage/ViewController/LanguageSettingViewController.swift @@ -12,12 +12,16 @@ 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 } @@ -39,6 +43,19 @@ final class LanguageSettingViewController: BaseViewController { setTableViewDelegate() registerTableViewCells() + setInteractivePopGesture() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + guard didChangeLanguage, + isMovingFromParent, + !isResettingRootViewController else { + return + } + + resetRootAfterLanguageChangeIfNeeded() } // MARK: - Functions @@ -83,10 +100,15 @@ final class LanguageSettingViewController: BaseViewController { ) } + private func setInteractivePopGesture() { + navigationController?.interactivePopGestureRecognizer?.delegate = self + navigationController?.interactivePopGestureRecognizer?.isEnabled = true + } + @objc private func backButtonDidTap() { if didChangeLanguage { - resetRootViewController() + resetRootAfterLanguageChangeIfNeeded() } else { navigationController?.popViewController(animated: true) } @@ -108,6 +130,13 @@ final class LanguageSettingViewController: BaseViewController { 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 { @@ -169,3 +198,11 @@ extension LanguageSettingViewController: UITableViewDelegate { changeLanguage(to: language) } } + +// MARK: - UIGestureRecognizerDelegate + +extension LanguageSettingViewController: UIGestureRecognizerDelegate { + func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + return navigationController?.viewControllers.count ?? 0 > 1 + } +} From 8e51d1f941d1b1c69525e191b8f4b5e8efd4a238 Mon Sep 17 00:00:00 2001 From: jeongminji Date: Mon, 4 May 2026 00:01:41 +0900 Subject: [PATCH 13/16] =?UTF-8?q?[#275]=20=EC=95=8C=EB=A6=BC=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EC=BB=AC=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EC=A7=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Resources/en.lproj/Localizable.strings | 8 ++++++ .../Resources/ko.lproj/Localizable.strings | 8 ++++++ .../Notification/NotificationManager.swift | 13 +++++---- .../Sources/Utility/Literal/TextLiteral.swift | 28 +++++++++++++++++++ 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/EATSSU/App/Resources/en.lproj/Localizable.strings b/EATSSU/App/Resources/en.lproj/Localizable.strings index f061709a..5654ca4b 100644 --- a/EATSSU/App/Resources/en.lproj/Localizable.strings +++ b/EATSSU/App/Resources/en.lproj/Localizable.strings @@ -435,6 +435,14 @@ "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 diff --git a/EATSSU/App/Resources/ko.lproj/Localizable.strings b/EATSSU/App/Resources/ko.lproj/Localizable.strings index 4200f58e..7be7489c 100644 --- a/EATSSU/App/Resources/ko.lproj/Localizable.strings +++ b/EATSSU/App/Resources/ko.lproj/Localizable.strings @@ -436,6 +436,14 @@ "notification.dailyWeekdayNotificationTitle" = "🤔 오늘 밥 뭐 먹지…"; /// 오늘의 학식을 확인해보세요! "notification.dailyWeekdayNotificationBody" = "오늘의 학식을 확인해보세요!"; +/// 알림 권한 필요 +"notification_error_permission_denied_message" = "알림 권한 필요"; +/// 알림을 받으려면 설정에서 알림 권한을 허용해주세요. +"notification_error_permission_denied_description" = "알림을 받으려면 설정에서 알림 권한을 허용해주세요."; +/// 알 수 없는 오류 +"notification_error_unknown_message" = "알 수 없는 오류"; +/// 다시 시도해주세요. +"notification_error_unknown_description" = "다시 시도해주세요."; // MARK: - Restaurant 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/Utility/Literal/TextLiteral.swift b/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift index 6c2e2b0f..d2bb3ca8 100644 --- a/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift +++ b/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift @@ -1059,6 +1059,34 @@ enum TextLiteral { 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 From 1ac3467b2fc3f9d88e5b815523e50d7c5349a339 Mon Sep 17 00:00:00 2001 From: jeongminji Date: Mon, 4 May 2026 01:29:34 +0900 Subject: [PATCH 14/16] =?UTF-8?q?[#275]=20=EC=86=8C=EC=85=9C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EB=B2=84=ED=8A=BC=20=EB=A1=9C=EC=BB=AC?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EC=A7=95=20=EC=9C=84=ED=95=B4=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=EC=97=90=EC=84=9C=20=EC=A7=81=EC=A0=91=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UIComponent/SocialLoginButton.swift | 154 ++++++++++++++++++ .../AppleLoginButton.svg | 5 - .../AppleLoginButton.imageset/Contents.json | 21 --- .../AppleLoginLogo.svg | 3 + .../Contents.json | 2 +- .../KakaoLoginButton.svg | 12 -- .../KakaoLoginLogo.imageset/Contents.json | 12 ++ .../KakaoLoginLogo.svg | 3 + 8 files changed, 173 insertions(+), 39 deletions(-) create mode 100644 EATSSU/App/Sources/Utility/UIComponent/SocialLoginButton.swift delete mode 100644 EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/AppleLoginButton.imageset/AppleLoginButton.svg delete mode 100644 EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/AppleLoginButton.imageset/Contents.json create mode 100644 EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/AppleLoginLogo.imageset/AppleLoginLogo.svg rename EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/{KakaoLoginButton.imageset => AppleLoginLogo.imageset}/Contents.json (74%) delete mode 100644 EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoLoginButton.imageset/KakaoLoginButton.svg create mode 100644 EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoLoginLogo.imageset/Contents.json create mode 100644 EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoLoginLogo.imageset/KakaoLoginLogo.svg 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/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/KakaoLoginLogo.imageset/Contents.json b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoLoginLogo.imageset/Contents.json new file mode 100644 index 00000000..4823ae8d --- /dev/null +++ b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoLoginLogo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "KakaoLoginLogo.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} 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 @@ + + + From 96ab6097444a3724f591cb816cd98e1b3fac372a Mon Sep 17 00:00:00 2001 From: jeongminji Date: Mon, 4 May 2026 01:29:53 +0900 Subject: [PATCH 15/16] =?UTF-8?q?[#275]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EB=B7=B0=20=EB=A1=9C=EC=BB=AC=EB=9D=BC=EC=9D=B4=EC=A7=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Resources/en.lproj/Localizable.strings | 8 ++-- .../Resources/ko.lproj/Localizable.strings | 2 + .../Presentation/Auth/View/LoginView.swift | 43 +++++++++--------- .../Sources/Utility/Extension/String+.swift | 44 +++++++++++++++++++ .../Sources/Utility/Literal/TextLiteral.swift | 4 ++ 5 files changed, 77 insertions(+), 24 deletions(-) create mode 100644 EATSSU/App/Sources/Utility/Extension/String+.swift diff --git a/EATSSU/App/Resources/en.lproj/Localizable.strings b/EATSSU/App/Resources/en.lproj/Localizable.strings index 5654ca4b..6b8944e4 100644 --- a/EATSSU/App/Resources/en.lproj/Localizable.strings +++ b/EATSSU/App/Resources/en.lproj/Localizable.strings @@ -7,6 +7,8 @@ // MARK: - Common +/// "숭실대에서 먹자" +"common.logoSubTitle" = "Eat at Soongsil"; /// "확인" "common.confirm" = "Confirm"; /// "취소" @@ -52,11 +54,11 @@ /// "닉네임을 입력해주세요" "auth.inputNickName" = "Please enter a nickname"; /// "Apple로 로그인" -"auth.signInWithApple" = "Continue with Apple"; +"auth.signInWithApple" = "Sign in with Apple"; /// "카카오 로그인" -"auth.signInWithKakao" = "Continue with Kakao"; +"auth.signInWithKakao" = "Login with Kakao"; /// "둘러보기" -"auth.lookingWithNoSignIn" = "Browse"; +"auth.lookingWithNoSignIn" = "Browse Without Signing In"; /// "최근에 로그인했어요" "auth.lastLoginTooltip" = "Recently used"; /// LoginVC - "카카오톡으로 생성된 계정입니다." diff --git a/EATSSU/App/Resources/ko.lproj/Localizable.strings b/EATSSU/App/Resources/ko.lproj/Localizable.strings index 7be7489c..775190d3 100644 --- a/EATSSU/App/Resources/ko.lproj/Localizable.strings +++ b/EATSSU/App/Resources/ko.lproj/Localizable.strings @@ -7,6 +7,8 @@ // MARK: - Common +/// "숭실대에서 먹자" +"common.logoSubTitle" = "숭실대에서 먹자"; /// "확인" "common.confirm" = "확인"; /// "취소" 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/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/TextLiteral.swift b/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift index d2bb3ca8..767382d0 100644 --- a/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift +++ b/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift @@ -47,6 +47,10 @@ enum TextLiteral { // MARK: - Common enum Common { + /// "확인" + static var logoSubTitle: String { + Localization.localized("common.logoSubTitle", fallback: "숭실대에서 먹자") + } /// "확인" static var confirm: String { Localization.localized("common.confirm", fallback: "확인") From c5852ed3f5aa6f57d92af92f3ba1133df7c67306 Mon Sep 17 00:00:00 2001 From: jeongminji Date: Mon, 4 May 2026 01:31:59 +0900 Subject: [PATCH 16/16] =?UTF-8?q?[#275]=20LookAroundButton=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LookAroundButton.imageset/Contents.json | 12 ------------ .../LookAroundButton.imageset/LookAroundButton.svg | 3 --- 2 files changed, 15 deletions(-) delete mode 100644 EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/LookAroundButton.imageset/Contents.json delete mode 100644 EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/LookAroundButton.imageset/LookAroundButton.svg diff --git a/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/LookAroundButton.imageset/Contents.json b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/LookAroundButton.imageset/Contents.json deleted file mode 100644 index 0e0074c0..00000000 --- a/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/LookAroundButton.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "LookAroundButton.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} 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 @@ - - -