Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f6611ec
[#431] 디자인 시스템에 AI 번역 포인트 컬러 aiAccent 추가
Hrepay Jul 18, 2026
c7cf29d
[#431] 리뷰 번역 API 라우터 케이스 및 응답 DTO 추가
Hrepay Jul 18, 2026
fd709e2
[#431] 리뷰 셀에 AI 번역 UI(5개 상태) 및 유의사항 툴팁 구현
Hrepay Jul 18, 2026
86f0a93
[#431] 리뷰 번역 UI 5개 상태 프리뷰 추가
Hrepay Jul 18, 2026
da2d34a
[#431] 번역 툴팁 바깥 탭/스크롤 시 자동 닫힘 처리
Hrepay Jul 18, 2026
2df307c
[#431] 로딩 인디케이터를 아크 회전 커스텀 스피너(ESSpinner)로 교체
Hrepay Jul 18, 2026
7f07bfe
[#431] QA 반영 - 리뷰 셀 닉네임/날짜 폰트 및 간격 조정
Hrepay Jul 18, 2026
6972c92
[#431] QA 반영 - 리뷰 작성 메뉴 추천 칩 높이 및 행 간격 축소
Hrepay Jul 18, 2026
e169a2d
[#431] QA 반영 - 신고 사유 버튼 간격 12에서 8로 축소
Hrepay Jul 18, 2026
cadcd89
[#431] QA 반영 - 프로필 이미지 32로 확대 및 닉네임-별점 간격 원복
Hrepay Jul 18, 2026
dda0e61
[#431] QA 반영 - 추천 칩 크기 피그마 실측(58x36) 기준으로 수정
Hrepay Jul 18, 2026
9791e04
[#431] QA 반영 - 별점과 추천 타이틀 간격 피그마 기준 26으로 조정
Hrepay Jul 18, 2026
3df5b33
[#431] QA 반영 - 스피너를 연한 트랙 원 + 작은 진한 호 구조로 수정
Hrepay Jul 18, 2026
4ea6a02
[#431] 코드리뷰 반영: 번역 상태 무효화, 실패 데드코드 제거, 셀 갱신 최적화
Hrepay Jul 18, 2026
8dee70d
[#431] 코드리뷰 반영: 스피너 포그라운드 복구 및 기본색 중립화, 프리뷰 셀 보존
Hrepay Jul 18, 2026
db2b94f
[#431] QA 반영 - 리뷰 본문과 번역 버튼 사이 간격 4로 조정
Hrepay Jul 18, 2026
57eb0c1
[#431] QA 반영 - 툴팁을 X 버튼 없는 플랫 박스 디자인으로 변경
Hrepay Jul 18, 2026
00f3148
[#431] 식당 인포메이션 시트 레이아웃을 새 디자인으로 개편
Hrepay Jul 18, 2026
c5f0ffa
[#431] DEV 타깃 Release 빌드가 dev 서버를 바라보도록 xcconfig 변경
Hrepay Jul 18, 2026
1f978d0
[#431] 번역 실패 상태 툴팁에 실패 안내 문구 적용
Hrepay Jul 18, 2026
a065bdb
[#431] 인포메이션 시트를 컨텐츠 높이 맞춤 detent로 변경 (하단 여백 54)
Hrepay Jul 18, 2026
932ecbe
[#431] 번역 UI 포인트 컬러를 기존 Info 토큰(#3D69FF)으로 교체하고 aiAccent 제거
Hrepay Jul 18, 2026
8dc993f
[#431] QA 반영 - 리뷰 사이 여백을 20으로 조정
Hrepay Jul 18, 2026
424b339
[#431] 수출 규정 면제 키 추가로 테플 컴플라이언스 대기 제거
Hrepay Jul 18, 2026
4b884e5
[#431] QA 반영 - 인포 아이콘을 스펙대로 16 크기로 렌더링
Hrepay Jul 18, 2026
bf2f4e9
[#431] QA 반영 - 메뉴 태그 칩 폰트 caption2, 높이 26으로 변경
Hrepay Jul 18, 2026
67083ee
[#431] QA 반영 - 인포 아이콘을 16 크기로 리사이즈해 적용
Hrepay Jul 18, 2026
c6ffb52
[#431] 코드리뷰 반영: 스피너 옵저버 해제, 토큰 체크 통일, 시트 높이 오토레이아웃 계산
Hrepay Jul 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions EATSSU/App/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -473,3 +473,22 @@
"restaurant.snackCorner" = "Snack Corner";
/// "FACULTY (교직원 전용)"
"restaurant.facultyRestaurant" = "FACULTY (Faculty Only)";

// MARK: - Review Translation

/// "번역하기"
"review.translate" = "Translate";
/// "번역 중..."
"review.translating" = "Translating...";
/// "AI가 한국어에서 번역함"
"review.translatedFromKorean" = "Translated from Korean by AI";
/// "원문 보기"
"review.showOriginal" = "Show original";
/// "번역 보기"
"review.showTranslation" = "Show translation";
/// "번역을 사용할 수 없어요"
"review.translationUnavailable" = "Translation unavailable";
/// "AI 번역은 정확하지 않을 수 있어요."
"review.translationDisclaimer" = "AI translations may not always be accurate.";
/// "AI 번역이 일시적으로 불가능해요. 잠시 후 다시 시도해 주세요."
"review.translationFailedTooltip" = "AI translation is temporarily unavailable. Please try again later.";
19 changes: 19 additions & 0 deletions EATSSU/App/Resources/ja.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -474,3 +474,22 @@
"restaurant.snackCorner" = "スナックコーナー";
/// "FACULTY (교직원 전용)"
"restaurant.facultyRestaurant" = "FACULTY(教職員専用)";

// MARK: - Review Translation

/// "번역하기"
"review.translate" = "翻訳";
/// "번역 중..."
"review.translating" = "翻訳中...";
/// "AI가 한국어에서 번역함"
"review.translatedFromKorean" = "AIが韓国語から翻訳しました";
/// "원문 보기"
"review.showOriginal" = "原文を表示";
/// "번역 보기"
"review.showTranslation" = "翻訳を表示";
/// "번역을 사용할 수 없어요"
"review.translationUnavailable" = "翻訳を利用できません";
/// "AI 번역은 정확하지 않을 수 있어요."
"review.translationDisclaimer" = "AI翻訳は正確でない場合があります。";
/// "AI 번역이 일시적으로 불가능해요. 잠시 후 다시 시도해 주세요."
"review.translationFailedTooltip" = "AI翻訳は一時的に利用できません。しばらくしてからもう一度お試しください。";
19 changes: 19 additions & 0 deletions EATSSU/App/Resources/ko.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -474,3 +474,22 @@
"restaurant.snackCorner" = "스낵 코너";
/// "FACULTY (교직원 전용)"
"restaurant.facultyRestaurant" = "FACULTY (교직원 전용)";

// MARK: - Review Translation

/// "번역하기"
"review.translate" = "번역하기";
/// "번역 중..."
"review.translating" = "번역 중...";
/// "AI가 한국어에서 번역함"
"review.translatedFromKorean" = "AI가 한국어에서 번역함";
/// "원문 보기"
"review.showOriginal" = "원문 보기";
/// "번역 보기"
"review.showTranslation" = "번역 보기";
/// "번역을 사용할 수 없어요"
"review.translationUnavailable" = "번역을 사용할 수 없어요";
/// "AI 번역은 정확하지 않을 수 있어요."
"review.translationDisclaimer" = "AI 번역은 정확하지 않을 수 있어요.";
/// "AI 번역이 일시적으로 불가능해요. 잠시 후 다시 시도해 주세요."
"review.translationFailedTooltip" = "AI 번역이 일시적으로 불가능해요. 잠시 후 다시 시도해 주세요.";
19 changes: 19 additions & 0 deletions EATSSU/App/Resources/vi.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -474,3 +474,22 @@
"restaurant.snackCorner" = "Quầy đồ ăn nhẹ";
/// "FACULTY (교직원 전용)"
"restaurant.facultyRestaurant" = "FACULTY (Chỉ dành cho giảng viên/nhân viên)";

// MARK: - Review Translation

/// "번역하기"
"review.translate" = "Dịch";
/// "번역 중..."
"review.translating" = "Đang dịch...";
/// "AI가 한국어에서 번역함"
"review.translatedFromKorean" = "Được AI dịch từ tiếng Hàn";
/// "원문 보기"
"review.showOriginal" = "Xem bản gốc";
/// "번역 보기"
"review.showTranslation" = "Xem bản dịch";
/// "번역을 사용할 수 없어요"
"review.translationUnavailable" = "Không thể dịch";
/// "AI 번역은 정확하지 않을 수 있어요."
"review.translationDisclaimer" = "Bản dịch AI có thể không chính xác.";
/// "AI 번역이 일시적으로 불가능해요. 잠시 후 다시 시도해 주세요."
"review.translationFailedTooltip" = "Dịch AI tạm thời không khả dụng. Vui lòng thử lại sau.";
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// ReviewTranslationResponse.swift
// EATSSU
//
// Created by 황상환 on 7/18/26.
//

/// 리뷰 번역 API 응답
///
/// 실제 사용하는 값은 translatedContent뿐이라, 나머지는 서버가 생략해도 디코딩이 깨지지 않도록 옵셔널로 둔다.
struct ReviewTranslationResponse: Codable {
let reviewId: Int?
let language: String?
let translatedContent: String
/// 서버 캐시 히트 여부
let cached: Bool?
}
10 changes: 9 additions & 1 deletion EATSSU/App/Sources/Data/Network/Router/ReviewRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ enum ReviewRouter {
size: Int? = 20)
case getFixedMenuStatistics(_ menuId: Int)
case getMealStatistics(_ mealId: Int)
case translateReview(_ reviewId: Int, language: String)
}

extension ReviewRouter: TargetType {
Expand Down Expand Up @@ -50,14 +51,16 @@ extension ReviewRouter: TargetType {
"/v2/reviews/statistics/menus/\(menuId)"
case let .getMealStatistics(mealId):
"/v2/reviews/statistics/meals/\(mealId)"
case let .translateReview(reviewId, _):
"/v2/reviews/\(reviewId)/translate"
}
}

var method: Moya.Method {
switch self {
case .getValidMenusForReview, .newReviewList, .getFixedMenuStatistics, .getMealStatistics:
.get
case .report:
case .report, .translateReview:
.post
case .deleteReview:
.delete
Expand Down Expand Up @@ -107,6 +110,11 @@ extension ReviewRouter: TargetType {
.requestPlain
case .getMealStatistics:
.requestPlain
case let .translateReview(_, language):
.requestParameters(
parameters: ["language": language],
encoding: URLEncoding.queryString
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,74 +15,63 @@ import EATSSUDesign
final class RestaurantInfoView: BaseUIView {
// MARK: - UI Components

let restaurantImage = UIImageView(image: EATSSUDesignAsset.Images.restaurantImage.image)
let restaurantImage: UIImageView = {
let imageView = UIImageView(image: EATSSUDesignAsset.Images.restaurantImage.image)
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = 8
imageView.clipsToBounds = true
return imageView
}()

var restaurantNameLabel: UILabel = {
let label = UILabel()
label.text = TextLiteral.Home.studentRestaurant
label.font = EATSSUDesignFontFamily.Pretendard.bold.font(size: 20)
label.font = EATSSUDesignFontFamily.Pretendard.bold.font(size: 18)
return label
}()

private let locationTitleLabel: UILabel = {
let label = UILabel()
label.text = TextLiteral.Home.restaurantLocation
label.font = EATSSUDesignFontFamily.Pretendard.bold.font(size: 18)
return label
}()

private let imageTitleLabel: UILabel = {
let label = UILabel()
label.text = TextLiteral.Home.restaurantPicture
label.font = EATSSUDesignFontFamily.Pretendard.bold.font(size: 18)
label.font = EATSSUDesignFontFamily.Pretendard.regular.font(size: 13)
label.textColor = .gray500
return label
}()

private var locationLabel: UILabel = {
let label = UILabel()
label.text = TextLiteral.Home.soongsilUniversity
label.font = EATSSUDesignFontFamily.Pretendard.medium.font(size: 16)
label.numberOfLines = 0
return label
}()

private let openingTimeTitleLabel: UILabel = {
let label = UILabel()
label.text = TextLiteral.Home.businessHour
label.font = EATSSUDesignFontFamily.Pretendard.bold.font(size: 18)
label.font = EATSSUDesignFontFamily.Pretendard.regular.font(size: 13)
label.textColor = .gray500
return label
}()

private let openingTimeLabel: UILabel = {
let label = UILabel()
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 10
label.attributedText = NSAttributedString(
string: "08:00~09:30\n11:00~14:00\n17:00~18:30",
attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle]
)
label.numberOfLines = 0
label.textAlignment = .right
label.font = EATSSUDesignFontFamily.Pretendard.medium.font(size: 16)
return label
}()

private let ectTitleLabel: UILabel = {
let label = UILabel()
label.text = TextLiteral.Home.note
label.font = EATSSUDesignFontFamily.Pretendard.bold.font(size: 18)
label.font = EATSSUDesignFontFamily.Pretendard.regular.font(size: 13)
label.textColor = .gray500
return label
}()

private let ectLabel: UILabel = {
let label = UILabel()
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 10
label.attributedText = NSAttributedString(
string: TextLiteral.Home.dodamEtc,
attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle]
)
label.numberOfLines = 0
label.textAlignment = .right
label.font = EATSSUDesignFontFamily.Pretendard.medium.font(size: 16)
return label
}()
Expand All @@ -91,10 +80,9 @@ final class RestaurantInfoView: BaseUIView {

override func configureUI() {
addSubviews(restaurantNameLabel,
restaurantImage,
locationTitleLabel,
locationLabel,
imageTitleLabel,
restaurantImage,
openingTimeTitleLabel,
openingTimeLabel,
ectTitleLabel,
Expand All @@ -106,47 +94,44 @@ final class RestaurantInfoView: BaseUIView {
$0.top.equalToSuperview().offset(38)
$0.centerX.equalToSuperview()
}
restaurantImage.snp.makeConstraints {
$0.top.equalTo(restaurantNameLabel.snp.bottom).offset(28)
$0.horizontalEdges.equalToSuperview().inset(20)
$0.height.equalTo(232)
}
locationTitleLabel.snp.makeConstraints {
$0.top.equalTo(restaurantNameLabel.snp.bottom).offset(56)
$0.leading.equalToSuperview().offset(21)
$0.top.equalTo(restaurantImage.snp.bottom).offset(28)
$0.leading.equalToSuperview().offset(20)
}
locationLabel.snp.makeConstraints {
$0.top.equalTo(locationTitleLabel)
$0.trailing.equalToSuperview().inset(21)
}
imageTitleLabel.snp.makeConstraints {
$0.top.equalTo(locationTitleLabel.snp.bottom).offset(24)
$0.leading.equalToSuperview().offset(21)
}
restaurantImage.snp.makeConstraints {
$0.top.equalTo(imageTitleLabel.snp.bottom).offset(8)
$0.horizontalEdges.equalToSuperview().inset(21)
$0.height.equalTo(232)
$0.top.equalTo(locationTitleLabel.snp.bottom).offset(12)
$0.horizontalEdges.equalToSuperview().inset(20)
}
openingTimeTitleLabel.snp.makeConstraints {
$0.top.equalTo(restaurantImage.snp.bottom).offset(24)
$0.leading.equalToSuperview().offset(21)
$0.top.equalTo(locationLabel.snp.bottom).offset(32)
$0.leading.equalToSuperview().offset(20)
}
openingTimeLabel.snp.makeConstraints {
$0.top.equalTo(openingTimeTitleLabel)
$0.trailing.equalToSuperview().inset(21)
$0.width.equalTo(250)
$0.top.equalTo(openingTimeTitleLabel.snp.bottom).offset(12)
$0.horizontalEdges.equalToSuperview().inset(20)
}
ectTitleLabel.snp.makeConstraints {
$0.top.equalTo(openingTimeLabel.snp.bottom).offset(24)
$0.leading.equalToSuperview().offset(21)
$0.top.equalTo(openingTimeLabel.snp.bottom).offset(32)
$0.leading.equalToSuperview().offset(20)
}
ectLabel.snp.makeConstraints {
$0.top.equalTo(ectTitleLabel)
$0.trailing.equalToSuperview().inset(21)
$0.top.equalTo(ectTitleLabel.snp.bottom).offset(12)
$0.horizontalEdges.equalToSuperview().inset(20)
// 시트 높이 계산용 하단 여백. 시트가 컨텐츠보다 클 때(iOS 15 large 폴백)를 위해 required 미만으로 설정
$0.bottom.equalToSuperview().inset(54).priority(.high)
}
Comment thread
Hrepay marked this conversation as resolved.
}

func bind(data: RestaurantInfoData) {
restaurantNameLabel.text = data.name
locationLabel.text = data.location
openingTimeLabel.text = data.time
ectLabel.text = data.etc
setValueText(data.location, on: locationLabel)
setValueText(data.time, on: openingTimeLabel)
setValueText(data.etc, on: ectLabel)
loadImage(with: data.image, into: restaurantImage)
}

Expand All @@ -157,4 +142,17 @@ final class RestaurantInfoView: BaseUIView {
print("Invalid URL string.")
}
}

/// 여러 줄 값 레이블에 줄 간격을 유지한 채 텍스트를 적용
private func setValueText(_ text: String, on label: UILabel) {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 8
label.attributedText = NSAttributedString(
string: text,
attributes: [
.paragraphStyle: paragraphStyle,
.font: EATSSUDesignFontFamily.Pretendard.medium.font(size: 16)
]
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -384,10 +384,21 @@ extension HomeRestaurantViewController: UITableViewDataSource {
HomeAnalyticsManager.shared.logClickRestaurantInfo(restaurantName: restaurantName)
let vc = RestaurantInfoViewController()
vc.modalPresentationStyle = .pageSheet
vc.sheetPresentationController?.prefersGrabberVisible = true

vc.loadViewIfNeeded()
self.infoDelegate = vc
self.infoDelegate?.didTappedRestaurantInfo(restaurantName: restaurantName)

if let sheet = vc.sheetPresentationController {
if #available(iOS 16.0, *) {
let height = vc.calculatePreferredHeight()
sheet.detents = [.custom { _ in height }]
} else {
sheet.detents = [.large()]
}
sheet.prefersGrabberVisible = true
}

self.present(vc, animated: true)
}),
for: .touchUpInside
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
// Created by 최지우 on 2023/08/29.
//

import UIKit

import SnapKit

import Moya
Expand Down Expand Up @@ -32,6 +34,16 @@ final class RestaurantInfoViewController: BaseViewController {
$0.edges.equalToSuperview()
}
}

/// 컨텐츠에 맞는 시트 높이 (마지막 텍스트 아래 여백 54, 세이프에리어 포함)
func calculatePreferredHeight() -> CGFloat {
let targetSize = CGSize(width: view.bounds.width, height: UIView.layoutFittingCompressedSize.height)
return view.systemLayoutSizeFitting(
targetSize,
withHorizontalFittingPriority: .required,
verticalFittingPriority: .fittingSizeLevel
).height
}
Comment thread
Hrepay marked this conversation as resolved.
}

// MARK: - RestaurantInfoDelegate
Expand Down
Loading