diff --git a/app/src/main/java/com/eatssu/android/data/remote/dto/response/ReviewTranslationResponse.kt b/app/src/main/java/com/eatssu/android/data/remote/dto/response/ReviewTranslationResponse.kt new file mode 100644 index 000000000..62a9dbd29 --- /dev/null +++ b/app/src/main/java/com/eatssu/android/data/remote/dto/response/ReviewTranslationResponse.kt @@ -0,0 +1,24 @@ +package com.eatssu.android.data.remote.dto.response + +import com.eatssu.android.domain.model.ReviewTranslation +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ReviewTranslationResponse( + @SerialName("reviewId") val reviewId: Long? = null, + @SerialName("sourceLanguage") val sourceLanguage: String? = null, + @SerialName("targetLanguage") val targetLanguage: String? = null, + @SerialName("translatedContent") val translatedContent: String? = null, + @SerialName("provider") val provider: String? = null, + @SerialName("cached") val cached: Boolean? = null, +) + +fun ReviewTranslationResponse.toDomain() = ReviewTranslation( + reviewId = reviewId ?: -1L, + sourceLanguage = sourceLanguage.orEmpty(), + targetLanguage = targetLanguage.orEmpty(), + translatedContent = translatedContent.orEmpty(), + provider = provider.orEmpty(), + cached = cached ?: false, +) diff --git a/app/src/main/java/com/eatssu/android/data/remote/repository/ReviewRepositoryImpl.kt b/app/src/main/java/com/eatssu/android/data/remote/repository/ReviewRepositoryImpl.kt index 6e3f7d839..414a5b09d 100644 --- a/app/src/main/java/com/eatssu/android/data/remote/repository/ReviewRepositoryImpl.kt +++ b/app/src/main/java/com/eatssu/android/data/remote/repository/ReviewRepositoryImpl.kt @@ -115,6 +115,13 @@ class ReviewRepositoryImpl @Inject constructor(private val reviewService: Review return reviewService.getMyReviews().map { it.toDomain() }.orEmptyList() } + override suspend fun getReviewTranslation( + reviewId: Long, + targetLanguage: String, + ) = reviewService.getReviewTranslation(reviewId, targetLanguage) + .map { it.toDomain() } + .orNull() + override fun getMenuReviewListPaged(menuId: Long?): Flow> { return Pager( config = PagingConfig(pageSize = 20), diff --git a/app/src/main/java/com/eatssu/android/data/remote/service/ReviewService.kt b/app/src/main/java/com/eatssu/android/data/remote/service/ReviewService.kt index 4ebacda98..b9d92b75b 100644 --- a/app/src/main/java/com/eatssu/android/data/remote/service/ReviewService.kt +++ b/app/src/main/java/com/eatssu/android/data/remote/service/ReviewService.kt @@ -12,6 +12,7 @@ import com.eatssu.android.data.remote.dto.response.MenuOfMealResponse import com.eatssu.android.data.remote.dto.response.MenuReviewInfoResponse import com.eatssu.android.data.remote.dto.response.MenuReviewListResponse import com.eatssu.android.data.remote.dto.response.MyReviewListResponse +import com.eatssu.android.data.remote.dto.response.ReviewTranslationResponse import okhttp3.MultipartBody import retrofit2.http.Body import retrofit2.http.DELETE @@ -87,4 +88,10 @@ interface ReviewService { @GET("users/v2/reviews") // 내가 쓴 리뷰 suspend fun getMyReviews(): ApiResult + @GET("/v2/reviews/{reviewId}/translation") + suspend fun getReviewTranslation( + @Path("reviewId") reviewId: Long, + @Query("targetLanguage") targetLanguage: String, + ): ApiResult + } diff --git a/app/src/main/java/com/eatssu/android/domain/model/ReviewTranslation.kt b/app/src/main/java/com/eatssu/android/domain/model/ReviewTranslation.kt new file mode 100644 index 000000000..848e88e9e --- /dev/null +++ b/app/src/main/java/com/eatssu/android/domain/model/ReviewTranslation.kt @@ -0,0 +1,10 @@ +package com.eatssu.android.domain.model + +data class ReviewTranslation( + val reviewId: Long, + val sourceLanguage: String, + val targetLanguage: String, + val translatedContent: String, + val provider: String, + val cached: Boolean, +) diff --git a/app/src/main/java/com/eatssu/android/domain/repository/ReviewRepository.kt b/app/src/main/java/com/eatssu/android/domain/repository/ReviewRepository.kt index 5c19ac016..a324c76e2 100644 --- a/app/src/main/java/com/eatssu/android/domain/repository/ReviewRepository.kt +++ b/app/src/main/java/com/eatssu/android/domain/repository/ReviewRepository.kt @@ -4,6 +4,7 @@ import androidx.paging.PagingData import com.eatssu.android.domain.model.MenuMini import com.eatssu.android.domain.model.Review import com.eatssu.android.domain.model.ReviewInfo +import com.eatssu.android.domain.model.ReviewTranslation import java.io.File import kotlinx.coroutines.flow.Flow @@ -56,6 +57,11 @@ interface ReviewRepository { suspend fun getMyReviews(): List + suspend fun getReviewTranslation( + reviewId: Long, + targetLanguage: String, + ): ReviewTranslation? + fun getMenuReviewListPaged(menuId: Long?): Flow> fun getMealReviewListPaged(mealId: Long?): Flow> diff --git a/app/src/main/java/com/eatssu/android/domain/usecase/review/GetReviewTranslationUseCase.kt b/app/src/main/java/com/eatssu/android/domain/usecase/review/GetReviewTranslationUseCase.kt new file mode 100644 index 000000000..012c523c9 --- /dev/null +++ b/app/src/main/java/com/eatssu/android/domain/usecase/review/GetReviewTranslationUseCase.kt @@ -0,0 +1,14 @@ +package com.eatssu.android.domain.usecase.review + +import com.eatssu.android.domain.model.ReviewTranslation +import com.eatssu.android.domain.repository.ReviewRepository +import javax.inject.Inject + +class GetReviewTranslationUseCase @Inject constructor( + private val reviewRepository: ReviewRepository, +) { + suspend operator fun invoke( + reviewId: Long, + targetLanguage: String, + ): ReviewTranslation? = reviewRepository.getReviewTranslation(reviewId, targetLanguage) +} diff --git a/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListScreen.kt b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListScreen.kt index ae55778a9..6bafb4eef 100644 --- a/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListScreen.kt +++ b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListScreen.kt @@ -52,6 +52,9 @@ import com.eatssu.android.presentation.cafeteria.review.list.component.OthersRev import com.eatssu.android.presentation.cafeteria.review.list.component.ReviewItem import com.eatssu.android.presentation.cafeteria.review.list.component.ReviewProgressBar import com.eatssu.android.presentation.cafeteria.review.report.ReportActivity +import com.eatssu.android.presentation.cafeteria.review.translation.ReviewTranslationUiState +import com.eatssu.android.presentation.cafeteria.review.translation.currentReviewTranslationTargetLanguage +import com.eatssu.android.presentation.cafeteria.review.translation.shouldShowReviewTranslationAction import com.eatssu.android.presentation.util.TrackScreenViewEvent import com.eatssu.android.presentation.util.showToast import com.eatssu.common.UiEvent @@ -95,8 +98,10 @@ fun ReviewListScreen( TrackScreenViewEvent(ScreenId.REVIEW_V2_VIEW) val reviewListState by viewModel.uiState.collectAsStateWithLifecycle() + val translationStates by viewModel.translationStates.collectAsStateWithLifecycle() val reviewPagingItems = viewModel.reviewPagingData.collectAsLazyPagingItems() val uiEvent by viewModel.uiEvent.collectAsStateWithLifecycle(initialValue = null) + val targetLanguage = currentReviewTranslationTargetLanguage() LaunchedEffect(refreshNonce) { if (refreshNonce == 0L) return@LaunchedEffect @@ -124,6 +129,7 @@ fun ReviewListScreen( ReviewListScreen( uiState = reviewListState, + translationStates = translationStates, reviewPagingItems = reviewPagingItems, modifier = modifier, menuName = menuName, @@ -131,13 +137,16 @@ fun ReviewListScreen( onBack = onBack, onReviewWriteButtonClick = onWriteButtonClick, onModifyClick = onModifyClick, - onDeleteClick = { reviewId -> viewModel.deleteReview(reviewId) } + onDeleteClick = { reviewId -> viewModel.deleteReview(reviewId) }, + onTranslationClick = { review -> viewModel.toggleReviewTranslation(review, targetLanguage) }, + targetLanguage = targetLanguage, ) } @Composable internal fun ReviewListScreen( uiState: UiState, + translationStates: Map, reviewPagingItems: LazyPagingItems, modifier: Modifier = Modifier, menuName: String, @@ -146,6 +155,8 @@ internal fun ReviewListScreen( onReviewWriteButtonClick: () -> Unit, onModifyClick: (Review) -> Unit, onDeleteClick: (reviewId: Long) -> Unit, + onTranslationClick: (Review) -> Unit, + targetLanguage: String, ) { val context = LocalContext.current val analyticsTracker = LocalAnalyticsTracker.current @@ -353,6 +364,7 @@ internal fun ReviewListScreen( ) { index -> val item = reviewPagingItems.get(index) item?.let { + val translationState = translationStates[it.reviewId] ReviewItem( modifier = Modifier.padding( horizontal = 24.dp, @@ -364,6 +376,11 @@ internal fun ReviewListScreen( rating = it.rating, menuLikeInfoList = it.menuLikeInfoList, imgUrl = it.imgUrl, + translatedContent = translationState?.translatedContent, + isTranslationVisible = translationState?.isTranslated == true, + isTranslationLoading = translationState?.isLoading == true, + showTranslationAction = shouldShowReviewTranslationAction(targetLanguage), + onTranslationClick = { onTranslationClick(it) }, onMoreClick = { if (it.isWriter) { showMyBottomSheet = true @@ -636,6 +653,9 @@ fun ReviewListPreview() { onReviewWriteButtonClick = {}, onModifyClick = {}, onDeleteClick = {}, + onTranslationClick = {}, + targetLanguage = "EN", + translationStates = emptyMap(), uiState = UiState.Success( ReviewListState( reviewInfo = ReviewInfo( @@ -672,6 +692,9 @@ fun ReviewListLoadingPreview() { onReviewWriteButtonClick = {}, onModifyClick = {}, onDeleteClick = {}, + onTranslationClick = {}, + targetLanguage = "EN", + translationStates = emptyMap(), uiState = UiState.Success( ReviewListState( reviewInfo = ReviewInfo( @@ -708,6 +731,9 @@ fun ReviewListEmptyPreview() { onReviewWriteButtonClick = {}, onModifyClick = {}, onDeleteClick = {}, + onTranslationClick = {}, + targetLanguage = "EN", + translationStates = emptyMap(), uiState = UiState.Success( ReviewListState( reviewInfo = ReviewInfo( @@ -744,6 +770,9 @@ fun ReviewListErrorPreview() { onReviewWriteButtonClick = {}, onModifyClick = {}, onDeleteClick = {}, + onTranslationClick = {}, + targetLanguage = "EN", + translationStates = emptyMap(), uiState = UiState.Error, reviewPagingItems = rememberPreviewPagingItems(pagingData), ) diff --git a/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListViewModel.kt b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListViewModel.kt index 7b2287ba5..615690b5a 100644 --- a/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListViewModel.kt +++ b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListViewModel.kt @@ -10,6 +10,8 @@ import com.eatssu.android.domain.model.ReviewInfo import com.eatssu.android.domain.usecase.review.DeleteReviewUseCase import com.eatssu.android.domain.usecase.review.GetReviewInfoUseCase import com.eatssu.android.domain.usecase.review.GetReviewListPagedUseCase +import com.eatssu.android.domain.usecase.review.GetReviewTranslationUseCase +import com.eatssu.android.presentation.cafeteria.review.translation.ReviewTranslationUiState import com.eatssu.common.UiEvent import com.eatssu.common.UiState import com.eatssu.common.UiText @@ -33,6 +35,7 @@ class ReviewListViewModel @Inject constructor( private val getReviewInfoUseCase: GetReviewInfoUseCase, private val getReviewListPagedUseCase: GetReviewListPagedUseCase, private val deleteReviewUseCase: DeleteReviewUseCase, + private val getReviewTranslationUseCase: GetReviewTranslationUseCase, ) : ViewModel() { private val _uiState = MutableStateFlow>(UiState.Init) @@ -41,6 +44,10 @@ class ReviewListViewModel @Inject constructor( private val _uiEvent: MutableSharedFlow = MutableSharedFlow() val uiEvent = _uiEvent.asSharedFlow() + private val _translationStates = MutableStateFlow>(emptyMap()) + val translationStates: StateFlow> = + _translationStates.asStateFlow() + private val _loadParams = MutableSharedFlow>( replay = 1, extraBufferCapacity = 1, @@ -95,12 +102,53 @@ class ReviewListViewModel @Inject constructor( } _uiEvent.emit(ReviewListEvent.ReviewDeleted) + _translationStates.value = _translationStates.value - reviewId // 정보 갱신 val currentParams = _loadParams.replayCache.lastOrNull() if (currentParams != null) loadReviewInfo(currentParams.first, currentParams.second) } } + + fun toggleReviewTranslation( + review: Review, + targetLanguage: String, + ) { + val currentState = _translationStates.value[review.reviewId] + if (currentState?.isLoading == true) return + + if (currentState?.isTranslated == true) { + _translationStates.value = _translationStates.value + ( + review.reviewId to currentState.copy(isTranslated = false) + ) + return + } + + viewModelScope.launch { + _translationStates.value = _translationStates.value + ( + review.reviewId to ReviewTranslationUiState(isLoading = true) + ) + + val translation = getReviewTranslationUseCase(review.reviewId, targetLanguage) + if (translation == null || translation.translatedContent.isBlank()) { + _translationStates.value = _translationStates.value - review.reviewId + _uiEvent.emit( + UiEvent.ShowToast( + UiText.StringResource(R.string.toast_review_translation_failed), + ToastType.ERROR + ) + ) + return@launch + } + + _translationStates.value = _translationStates.value + ( + review.reviewId to ReviewTranslationUiState( + translatedContent = translation.translatedContent, + isTranslated = true, + ) + ) + } + } } data class ReviewListState( diff --git a/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/component/ReviewItem.kt b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/component/ReviewItem.kt index d41ad6e00..e932f74a7 100644 --- a/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/component/ReviewItem.kt +++ b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/component/ReviewItem.kt @@ -10,13 +10,14 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -25,6 +26,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import coil.compose.AsyncImage @@ -45,6 +47,11 @@ fun ReviewItem( modifier: Modifier = Modifier, menuLikeInfoList: List? = null, imgUrl: String? = null, + translatedContent: String? = null, + isTranslationVisible: Boolean = false, + isTranslationLoading: Boolean = false, + showTranslationAction: Boolean = false, + onTranslationClick: () -> Unit = {}, onMoreClick: () -> Unit = {}, // 바텀시트 열기 콜백 ) { Column(modifier = modifier.padding(vertical = 24.dp)) { @@ -64,7 +71,7 @@ fun ReviewItem( Column { Text( writeName, - style = EatssuTheme.typography.caption1 + style = EatssuTheme.typography.caption2 ) Spacer(modifier = Modifier.height(2.dp)) RatingBarSmall(rating = rating) @@ -80,29 +87,32 @@ fun ReviewItem( ) { Box( modifier = Modifier - .padding(18.dp) // 터치 영역 확장 ( (48 - 12) / 2 ) - .offset(x = 18.dp, y = 18.dp) // 시각 위치 되돌리기 + .minimumInteractiveComponentSize() .clickable( onClick = onMoreClick, indication = null, interactionSource = remember { MutableInteractionSource() } ) ) { - Icon( - painter = painterResource(id = R.drawable.ic_menu_12), - contentDescription = "etc", - modifier = Modifier.size(12.dp), - tint = Color.Unspecified - ) + Column( + horizontalAlignment = Alignment.End + ) { + Icon( + painter = painterResource(id = R.drawable.ic_menu_12), + contentDescription = "etc", + modifier = Modifier.size(12.dp), + tint = Color.Unspecified + ) + + Text( + text = writeDate, + style = EatssuTheme.typography.caption2, + color = Gray400 + ) + } } - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = writeDate, - style = EatssuTheme.typography.caption3, - color = Gray400 - ) } } @@ -128,7 +138,32 @@ fun ReviewItem( Spacer(modifier = Modifier.height(8.dp)) - Text(content, style = EatssuTheme.typography.body3) + Text( + text = if (isTranslationVisible && translatedContent != null) { + translatedContent + } else { + content + }, + style = EatssuTheme.typography.body3 + ) + + if (showTranslationAction && content.isNotBlank()) { + Spacer(modifier = Modifier.height(4.dp)) + TextButton( + onClick = onTranslationClick, + enabled = !isTranslationLoading, + modifier = Modifier.align(Alignment.Start) + ) { + Text( + text = when { + isTranslationLoading -> stringResource(R.string.review_translating) + isTranslationVisible -> stringResource(R.string.review_show_original) + else -> stringResource(R.string.review_show_translation) + }, + style = EatssuTheme.typography.caption2 + ) + } + } // 이미지가 있는 경우에만 표시 if (!imgUrl.isNullOrBlank() && imgUrl != "null") { @@ -197,4 +232,4 @@ fun ReviewItemWithoutImagePreview() { imgUrl = null ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/translation/ReviewTranslationLanguage.kt b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/translation/ReviewTranslationLanguage.kt new file mode 100644 index 000000000..d6f8c24e8 --- /dev/null +++ b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/translation/ReviewTranslationLanguage.kt @@ -0,0 +1,20 @@ +package com.eatssu.android.presentation.cafeteria.review.translation + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalConfiguration +import androidx.core.os.ConfigurationCompat +import java.util.Locale + +@Composable +fun currentReviewTranslationTargetLanguage(): String { + val configuration = LocalConfiguration.current + val locale = ConfigurationCompat.getLocales(configuration)[0] ?: Locale.getDefault() + return when (locale.language.lowercase()) { + "en" -> "EN" + "ja" -> "JA" + "vi" -> "VI" + else -> "KO" + } +} + +fun shouldShowReviewTranslationAction(targetLanguage: String): Boolean = targetLanguage in setOf("KO", "EN", "JA", "VI") diff --git a/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/translation/ReviewTranslationUiState.kt b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/translation/ReviewTranslationUiState.kt new file mode 100644 index 000000000..6b8c4447f --- /dev/null +++ b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/translation/ReviewTranslationUiState.kt @@ -0,0 +1,7 @@ +package com.eatssu.android.presentation.cafeteria.review.translation + +data class ReviewTranslationUiState( + val translatedContent: String? = null, + val isLoading: Boolean = false, + val isTranslated: Boolean = false, +) diff --git a/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/write/component/MenuLikeButtonItem.kt b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/write/component/MenuLikeButtonItem.kt index 45446b45d..33bb2deea 100644 --- a/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/write/component/MenuLikeButtonItem.kt +++ b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/write/component/MenuLikeButtonItem.kt @@ -18,7 +18,7 @@ fun MenuLikeButtonItem( onLikeChanged: (Boolean) -> Unit, ) { - Row(modifier.padding(vertical = 6.dp)) { + Row(modifier.padding(vertical = 3.dp)) { Text( mealName, style = EatssuTheme.typography.body3 diff --git a/app/src/main/java/com/eatssu/android/presentation/map/MapViewModel.kt b/app/src/main/java/com/eatssu/android/presentation/map/MapViewModel.kt index d60bdf0ba..7061b3aaf 100644 --- a/app/src/main/java/com/eatssu/android/presentation/map/MapViewModel.kt +++ b/app/src/main/java/com/eatssu/android/presentation/map/MapViewModel.kt @@ -88,7 +88,7 @@ class MapViewModel @Inject constructor( val availableFilters = if (hasFestival) { FilterType.entries } else { - listOf(FilterType.All, FilterType.Mine) + listOf(FilterType.Mine, FilterType.All) } // Festival 제휴가 하나라도 있으면 Festival을 우선하고, 없으면 기존 기본 필터 규칙을 따른다. diff --git a/app/src/main/java/com/eatssu/android/presentation/map/component/PartnershipToggleItem.kt b/app/src/main/java/com/eatssu/android/presentation/map/component/PartnershipToggleItem.kt index d9bde5e6f..18f6e4606 100644 --- a/app/src/main/java/com/eatssu/android/presentation/map/component/PartnershipToggleItem.kt +++ b/app/src/main/java/com/eatssu/android/presentation/map/component/PartnershipToggleItem.kt @@ -29,8 +29,8 @@ import timber.log.Timber enum class FilterType(@StringRes val labelResId: Int) { Festival(R.string.partnership_filter_festival), - All(R.string.partnership_filter_all), Mine(R.string.partnership_filter_mine), + All(R.string.partnership_filter_all), } @Composable diff --git a/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageFragment.kt b/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageFragment.kt index 66e5d9d41..d58f60cea 100644 --- a/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageFragment.kt +++ b/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageFragment.kt @@ -62,7 +62,7 @@ import javax.inject.Inject class MyPageFragment : Fragment() { companion object { - private const val MENU_NOTIFICATION_SETTINGS = "notification_settings" + private const val MENU_NOTIFICATION_SETTINGS = "notification_setting" private const val MENU_MY_INFO = "my_info" private const val MENU_MY_REVIEW = "my_review" private const val MENU_INQUIRY = "inquiry" @@ -70,6 +70,7 @@ class MyPageFragment : Fragment() { private const val MENU_CREATOR = "creator" private const val MENU_LOGOUT = "logout" private const val MENU_WITHDRAW = "withdraw" + private const val MENU_INSTAGRAM = "insta" } @Inject @@ -137,6 +138,7 @@ class MyPageFragment : Fragment() { }, onInquireClick = ::openInquire, onInstagramClick = { + mainViewModel.trackMyPageMenu(MENU_INSTAGRAM) startWebView( getString(R.string.eatssu_instagram_url), getString(R.string.eatssu_instagram), diff --git a/app/src/main/java/com/eatssu/android/presentation/mypage/myreview/MyReviewListScreen.kt b/app/src/main/java/com/eatssu/android/presentation/mypage/myreview/MyReviewListScreen.kt index 41d2349a0..075ed3e0e 100644 --- a/app/src/main/java/com/eatssu/android/presentation/mypage/myreview/MyReviewListScreen.kt +++ b/app/src/main/java/com/eatssu/android/presentation/mypage/myreview/MyReviewListScreen.kt @@ -35,6 +35,9 @@ import com.eatssu.android.R import com.eatssu.android.domain.model.Review import com.eatssu.android.presentation.cafeteria.review.list.component.MyReviewBottomSheet import com.eatssu.android.presentation.cafeteria.review.list.component.ReviewItem +import com.eatssu.android.presentation.cafeteria.review.translation.ReviewTranslationUiState +import com.eatssu.android.presentation.cafeteria.review.translation.currentReviewTranslationTargetLanguage +import com.eatssu.android.presentation.cafeteria.review.translation.shouldShowReviewTranslationAction import com.eatssu.android.presentation.util.showToast import com.eatssu.common.UiEvent import com.eatssu.common.UiState @@ -60,7 +63,9 @@ fun MyReviewListScreen( val reviewListState by viewModel.uiState.collectAsStateWithLifecycle() val userNickname by viewModel.nickname.collectAsStateWithLifecycle() + val translationStates by viewModel.translationStates.collectAsStateWithLifecycle() val uiEvent by viewModel.uiEvent.collectAsStateWithLifecycle(initialValue = null) + val targetLanguage = currentReviewTranslationTargetLanguage() when (uiEvent) { is UiEvent.ShowToast -> { @@ -71,10 +76,13 @@ fun MyReviewListScreen( MyReviewListScreen( uiState = reviewListState, userNickname = userNickname, + translationStates = translationStates, modifier = modifier, onBack = onBack, onDeleteClick = { reviewId -> viewModel.deleteReview(reviewId) }, onModifyClick = onModifyClick, + onTranslationClick = { review -> viewModel.toggleReviewTranslation(review, targetLanguage) }, + targetLanguage = targetLanguage, ) } @@ -82,10 +90,13 @@ fun MyReviewListScreen( internal fun MyReviewListScreen( uiState: UiState, userNickname: String, + translationStates: Map, modifier: Modifier = Modifier, onBack: () -> Unit = {}, onModifyClick: (Review) -> Unit, onDeleteClick: (reviewId: Long) -> Unit, + onTranslationClick: (Review) -> Unit, + targetLanguage: String, ) { var showBottomSheet by remember { mutableStateOf(false) } var selectedReview by remember { mutableStateOf(null) } @@ -134,6 +145,7 @@ internal fun MyReviewListScreen( .padding(horizontal = 24.dp), ) { items(reviewList) { item -> + val translationState = translationStates[item.reviewId] ReviewItem( modifier = Modifier, writeName = userNickname, @@ -142,6 +154,11 @@ internal fun MyReviewListScreen( rating = item.rating, menuLikeInfoList = item.menuLikeInfoList, imgUrl = item.imgUrl, + translatedContent = translationState?.translatedContent, + isTranslationVisible = translationState?.isTranslated == true, + isTranslationLoading = translationState?.isLoading == true, + showTranslationAction = shouldShowReviewTranslationAction(targetLanguage), + onTranslationClick = { onTranslationClick(item) }, onMoreClick = { selectedReview = item showBottomSheet = true @@ -220,6 +237,9 @@ fun ReviewListPreview() { userNickname = "숭실푸드파이터", onDeleteClick = {}, onModifyClick = {}, + onTranslationClick = {}, + targetLanguage = "EN", + translationStates = emptyMap(), uiState = UiState.Success( MyReviewState.ReviewExists( myReviews = listOf( @@ -318,6 +338,9 @@ fun ReviewListEmptyPreview() { userNickname = "숭실푸드파이터", onDeleteClick = {}, onModifyClick = {}, + onTranslationClick = {}, + targetLanguage = "EN", + translationStates = emptyMap(), uiState = UiState.Success( MyReviewState.NoReview ) diff --git a/app/src/main/java/com/eatssu/android/presentation/mypage/myreview/MyReviewViewModel.kt b/app/src/main/java/com/eatssu/android/presentation/mypage/myreview/MyReviewViewModel.kt index 7a2499602..340cdcd1a 100644 --- a/app/src/main/java/com/eatssu/android/presentation/mypage/myreview/MyReviewViewModel.kt +++ b/app/src/main/java/com/eatssu/android/presentation/mypage/myreview/MyReviewViewModel.kt @@ -6,7 +6,9 @@ import com.eatssu.android.R import com.eatssu.android.domain.model.Review import com.eatssu.android.domain.usecase.review.DeleteReviewUseCase import com.eatssu.android.domain.usecase.review.GetMyReviewsUseCase +import com.eatssu.android.domain.usecase.review.GetReviewTranslationUseCase import com.eatssu.android.domain.usecase.user.GetUserNickNameUseCase +import com.eatssu.android.presentation.cafeteria.review.translation.ReviewTranslationUiState import com.eatssu.common.UiEvent import com.eatssu.common.UiState import com.eatssu.common.UiText @@ -25,6 +27,7 @@ class MyReviewViewModel @Inject constructor( private val getMyReviewsUseCase: GetMyReviewsUseCase, private val getUserNickNameUseCase: GetUserNickNameUseCase, private val deleteReviewUseCase: DeleteReviewUseCase, + private val getReviewTranslationUseCase: GetReviewTranslationUseCase, ) : ViewModel() { private val _uiState: MutableStateFlow> = MutableStateFlow(UiState.Init) @@ -36,6 +39,10 @@ class MyReviewViewModel @Inject constructor( private val _nickname = MutableStateFlow("") val nickname: StateFlow = _nickname + private val _translationStates = MutableStateFlow>(emptyMap()) + val translationStates: StateFlow> = + _translationStates.asStateFlow() + init { getMyReviewList() } @@ -81,9 +88,50 @@ class MyReviewViewModel @Inject constructor( ) ) // 삭제 성공 시 내 리뷰 목록 재조회 + _translationStates.value = _translationStates.value - reviewId getMyReviewList() } } + + fun toggleReviewTranslation( + review: Review, + targetLanguage: String, + ) { + val currentState = _translationStates.value[review.reviewId] + if (currentState?.isLoading == true) return + + if (currentState?.isTranslated == true) { + _translationStates.value = _translationStates.value + ( + review.reviewId to currentState.copy(isTranslated = false) + ) + return + } + + viewModelScope.launch { + _translationStates.value = _translationStates.value + ( + review.reviewId to ReviewTranslationUiState(isLoading = true) + ) + + val translation = getReviewTranslationUseCase(review.reviewId, targetLanguage) + if (translation == null || translation.translatedContent.isBlank()) { + _translationStates.value = _translationStates.value - review.reviewId + _uiEvent.emit( + UiEvent.ShowToast( + UiText.StringResource(R.string.toast_review_translation_failed), + ToastType.ERROR + ) + ) + return@launch + } + + _translationStates.value = _translationStates.value + ( + review.reviewId to ReviewTranslationUiState( + translatedContent = translation.translatedContent, + isTranslated = true, + ) + ) + } + } } @@ -93,4 +141,4 @@ sealed class MyReviewState { ) : MyReviewState() data object NoReview : MyReviewState() -} \ No newline at end of file +} diff --git a/app/src/main/res/layout/activity_report.xml b/app/src/main/res/layout/activity_report.xml index a653ef402..c7f201251 100644 --- a/app/src/main/res/layout/activity_report.xml +++ b/app/src/main/res/layout/activity_report.xml @@ -67,7 +67,7 @@ style="@style/Body2" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="12dp" + android:layout_marginTop="8dp" android:background="@drawable/selector_report" android:button="@null" android:text="@string/report_type_improper_content" @@ -78,7 +78,7 @@ style="@style/Body2" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="12dp" + android:layout_marginTop="8dp" android:background="@drawable/selector_report" android:button="@null" android:text="@string/report_type_improper_advertisement" @@ -89,7 +89,7 @@ style="@style/Body2" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="12dp" + android:layout_marginTop="8dp" android:background="@drawable/selector_report" android:button="@null" android:text="@string/report_type_copy" @@ -100,7 +100,7 @@ style="@style/Body2" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="12dp" + android:layout_marginTop="8dp" android:background="@drawable/selector_report" android:button="@null" android:text="@string/report_type_copyright" @@ -111,7 +111,7 @@ style="@style/Body2" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="12dp" + android:layout_marginTop="8dp" android:background="@drawable/selector_report" android:button="@null" android:text="@string/report_type_extra" diff --git a/app/src/main/res/layout/activity_sign_out.xml b/app/src/main/res/layout/activity_sign_out.xml index ab846631d..31c91c265 100644 --- a/app/src/main/res/layout/activity_sign_out.xml +++ b/app/src/main/res/layout/activity_sign_out.xml @@ -20,9 +20,10 @@ + - + + + - + android:layout_height="match_parent" + android:scaleType="centerCrop" + android:src="@drawable/img_dodam" /> + + android:layout_marginTop="24dp" + android:orientation="vertical"> + android:textColor="@color/gray500" /> - - - - - - + android:layout_marginTop="24dp" + android:orientation="vertical"> + android:textColor="@color/gray500" /> @@ -130,30 +117,27 @@ + android:layout_marginTop="24dp" + android:orientation="vertical"> + android:textColor="@color/gray500" /> - \ No newline at end of file + diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index a1296bd5e..a52dbc4b1 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -35,9 +35,9 @@ Cafeteria - Map + Partner Map Lucky Game - My + My Page Back @@ -51,7 +51,7 @@ Cancel Confirm Update - Check Availability -> Check + Check @@ -61,7 +61,7 @@ You need to install the latest version of the app. Please allow location access so you can quickly check your location and find partner restaurants nearby. Notification Permission Required - To receive notifications, you need to enable notification permission.\n Please allow notification access in Settings. + To receive notifications, you need to enable notification permission.\nPlease allow notification access in Settings. Go to Settings Please enter a title Please enter the content @@ -148,6 +148,10 @@ Add Photo (%1$d/%2$d) Review Settings An error occurred. + Show translation + Show original + Translating... + Failed to translate the review. Write a Review @@ -169,7 +173,7 @@ Today\'s Menu Price - Rating → ★ + Student Cafeteria @@ -194,9 +198,9 @@ Log Out Delete Account App Version - 알림 및 활동 - 서비스 정보 - 기타 + Notifications and Activity + Service Info + etc. Push Notification Settings We\'ll send you a notification every day at 11:00 AM. @@ -223,14 +227,14 @@ Edit Nickname - Set Affiliation -> College & Department + College & Department Could not load the information. Connection Error - Unable to communicate with the server.\n Please try again later. + Unable to communicate with the server.\nPlease try again later. @@ -261,15 +265,15 @@ Privacy Policy Privacy Policy Terms of Service - 약관 및 정책 - Credits + Terms and Policies + Creators Open-Source Libraries Are you sure you want to delete your account? - Your reviews will not be deleted and will be displayed as (Unknown).\n For details, please check the Terms of Service and Privacy Policy. + Your reviews will not be deleted and will appear as (Unknown).\nFor details, please check the Terms of Service and Privacy Policy. Please enter your nickname @@ -286,7 +290,7 @@ Enter your department - Enter your department\n and check your personalized partnerships! + Enter your department\nand check your personalized partnerships! College/Department information unavailable @@ -297,9 +301,10 @@ Hours Favorite Partners My Partners - 축제 + Festival All Department + EAT-SSU Instagram diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index b897ca8fd..99bfd5e89 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -35,7 +35,7 @@ 学食 - 地図 + マップ ラッキーゲーム マイページ 戻る @@ -51,22 +51,22 @@ キャンセル 確認 アップデート - 重複確認 + 確認 ログアウト ログアウトしますか? - アプリの最新バージョンをインストールする必要があります。 - 現在地をすばやく確認し、近くの提携店舗を探すために位置情報の利用を許可してください。 + 最新バージョンのアプリをインストールする必要があります。 + 現在地をすぐに確認し、近くの提携レストランを探せるように、位置情報へのアクセスを許可してください。 通知権限が必要です - 通知を受け取るには、通知権限を有効にする必要があります。\n 設定で通知を許可してください。 + 通知を受け取るには、通知権限を有効にする必要があります。\n設定で通知を許可してください。 設定へ移動 タイトルを入力してください - 内容を入力してください - ネットワーク接続なし - Wi-Fiまたはモバイルデータ接続を確認してください。 + 本文を入力してください + ネットワークに接続されていません + Wi-Fiまたはモバイルデータ通信を確認してください。 お知らせ @@ -86,7 +86,7 @@ 画像をアップロードしました。 画像のアップロードに失敗しました。 画像の圧縮に失敗しました。 - 画像ファイルが見つかりませんでした。 + 画像ファイルが見つかりません。 @@ -100,10 +100,10 @@ ニックネームの変更に失敗しました。 先に学部を選択してください。 学科を選択してください。 - 情報が更新されました。 - ニックネームが変更されました。 - 学科情報が更新されました。 - 変更内容はありません。 + 情報を更新しました。 + ニックネームを変更しました。 + 学科情報を更新しました。 + 変更はありません。 @@ -114,8 +114,8 @@ 韓国語、英字、数字のみ使用できます。 連続した特殊文字(--、__)は使用できません。 数字だけのニックネームは使用できません。 - ニックネームの先頭または末尾に特殊文字は使用できません。 - 悪口や不適切な表現を含むニックネームは使用できません。 + 特殊文字で始まる、または終わるニックネームは使用できません。 + 暴言や不適切な表現を含むニックネームは使用できません。 このニックネームは使用できません。 @@ -123,14 +123,14 @@ アプリの初期化中にエラーが発生しました。 アプリをアップデートしてください。 - 提携情報が見つかりません。 + 提携情報がありません。 ログインに失敗しました。 ログアウトしました。 もう一度ログインしてください。 セッションの有効期限が切れました。もう一度ログインしてください。 ニックネームを設定してください。 - EAT-SSU通知が有効になりました(%1$s) - EAT-SSU通知が無効になりました(%1$s) + EAT-SSUの通知をオンにしました(%1$s) + EAT-SSUの通知をオフにしました(%1$s) オープンソースライブラリを読み込めませんでした。 アカウントを削除しました。 アカウントの削除に失敗しました。 @@ -140,14 +140,18 @@ 今日の食事はいかがでしたか? おすすめしたいメニューはありますか? - メニューについて詳しくレビューを書いてください + メニューについて詳しいレビューを書いてください 画面を準備しています。 投稿中... 更新中... - 写真をタップすると削除できます。 + 写真をタップすると削除されます。 写真を追加(%1$d/%2$d) レビュー設定 エラーが発生しました。 + 翻訳を見る + 原文を見る + 翻訳中... + レビューの翻訳に失敗しました。 レビューを書く @@ -156,8 +160,8 @@ レビュー 自分のレビュー まだレビューがありません - 最初のレビューを書いてみましょう! - このメニューに最初のレビューを書いてみましょう! + 最初のレビューを投稿しましょう! + このメニューに最初のレビューを残しましょう! @@ -169,7 +173,7 @@ 今日のメニュー 価格 - 評価 + 学生食堂 @@ -180,7 +184,7 @@ 朝食 昼食 夕食 - 本日のメニューはありません。 + 今日のメニューはありません。 読み込み中 ネットワーク接続を確認してください。 設定が必要です @@ -194,9 +198,9 @@ ログアウト アカウント削除 アプリバージョン - 알림 및 활동 - 서비스 정보 - 기타 + Notifications and Activity + Service Info + など プッシュ通知設定 毎日午前11時に通知をお送りします。 @@ -208,7 +212,7 @@ - Kakao + カカオ 連携済みアカウント @@ -217,27 +221,27 @@ ニックネーム ニックネームを設定してください %1$d〜%2$d文字で入力してください。 - このニックネームは使用できます! + 使用できるニックネームです! ニックネーム編集 - 所属設定 + 学部・学科 情報を読み込めませんでした。 - 接続エラー - サーバーと通信できません。\n しばらくしてからもう一度お試しください。 + 通信エラー + サーバーと通信できません。\nしばらくしてからもう一度お試しください。 このレビューを通報する理由を選択してください。 同じレビューは24時間以内に1回のみ通報できます。 - 通報理由を入力してください + レビューを通報する理由を入力してください @@ -250,8 +254,8 @@ - 숭실대에서 - 숭실대에서 먹자 + 崇実大で + 食べよう @@ -261,24 +265,24 @@ プライバシーポリシー プライバシーポリシー 利用規約 - 약관 및 정책 - クレジット + 規約と方針 + 開発者 オープンソースライブラリ 本当にアカウントを削除しますか? - レビューは削除されず、(不明)として表示されます。\n 詳細は利用規約とプライバシーポリシーをご確認ください。 + 投稿したレビューは削除されず、(不明)として表示されます。\n詳細は利用規約およびプライバシーポリシーをご確認ください。 ニックネームを入力してください - 🤔 今日なに食べる? - 今日の学食メニューを確認しよう! + 🤔 今日何食べよう? + 今日の学食メニューをチェックしましょう! 昼食前通知 - 昼食時間前にプッシュ通知を送信します。 + 昼食前にプッシュ通知を送信します。 サーバー通知 EAT-SSUサーバーから送信された通知を表示します。 @@ -286,8 +290,8 @@ 学科を入力 - 学科を入力して\n 自分に合った提携情報を確認しましょう! - 学部/学科情報なし + 学科を入力して\n自分だけの提携特典を確認しましょう! + 学部・学科情報を確認できません @@ -295,11 +299,12 @@ 場所 備考 営業時間 - お気に入り提携先 - 自分の提携先 - 축제 + お気に入りの提携 + 自分の提携 + 祭り すべて 学科 + EAT-SSU Instagram diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 7ea7f3f49..ba1ebe9c9 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -11,238 +11,242 @@ - - - - - - - + T2 + T3 + T4 + T5 + T6 + T7 + CN - 리뷰 작성하기 - 리뷰 수정하기 - 탈퇴하기 - 신고하기 - 위젯 설정 - 제휴 지도 - 강제 업데이트 + Viết đánh giá + Chỉnh sửa đánh giá + Xóa tài khoản + Báo cáo + Cài đặt widget + Bản đồ đối tác + Bắt buộc cập nhật - 학식 - 지도 - 나만아니면돼~ - 마이 - back + Nhà ăn + Bản đồ + Game may mắn + Trang của tôi + Quay lại - 완료하기 - 수정하기 - 삭제하기 - 저장하기 - 신고하기 - 취소 - 확인 - 업데이트 - 중복확인 + Xong + Chỉnh sửa + Xóa + Lưu + Báo cáo + Hủy + Xác nhận + Cập nhật + Kiểm tra - 로그아웃 - 로그아웃 하시겠습니까? - 새 버전의 앱을 설치해야 합니다. - 내 위치를 바로 확인하며 제휴 식당을 찾아볼 수 있도록 위치 권한을 허용해 주세요. - 알림 권한 필요 - 알림을 받으려면 알림 권한을 활성화해야 합니다.\n설정에서 알림 권한을 허용해주세요. - 설정으로 이동 - 제목을 입력해주세요 - 본문을 입력해주세요 - 네트워크 연결 안 됨 - Wi-Fi, 모바일 데이터를 확인해주세요 - 공지 + Đăng xuất + Bạn có muốn đăng xuất không? + Bạn cần cài đặt phiên bản mới nhất của ứng dụng. + Vui lòng cho phép truy cập vị trí để nhanh chóng xem vị trí của bạn và tìm các nhà hàng đối tác gần đó. + Cần quyền thông báo + Để nhận thông báo, bạn cần bật quyền thông báo.\nVui lòng cho phép thông báo trong Cài đặt. + Đi tới Cài đặt + Vui lòng nhập tiêu đề + Vui lòng nhập nội dung + Không có kết nối mạng + Vui lòng kiểm tra kết nối Wi-Fi hoặc dữ liệu di động. + Thông báo - 리뷰가 등록되었어요. - 리뷰 작성에 실패하였습니다. - 리뷰를 수정했습니다. - 리뷰 수정이 실패했습니다. - 리뷰가 삭제되었어요. - 리뷰 삭제에 실패했습니다. - 리뷰를 불러오지 못했습니다. + Đánh giá của bạn đã được đăng. + Không thể đăng đánh giá. + Đánh giá của bạn đã được cập nhật. + Không thể cập nhật đánh giá. + Đánh giá của bạn đã được xóa. + Không thể xóa đánh giá. + Không thể tải đánh giá. - 이미지가 업로드되었습니다. - 이미지 업로드에 실패하였습니다. - 이미지 압축에 실패하였습니다. - 이미지 파일을 찾을 수 없습니다. + Đã tải ảnh lên thành công. + Không thể tải ảnh lên. + Không thể nén ảnh. + Không tìm thấy tệp ảnh. - 신고가 성공적으로 접수되었어요! - 신고가 실패하였습니다. + Báo cáo của bạn đã được gửi thành công! + Không thể gửi báo cáo. - 닉네임 변경에 실패했어요. - 단과대를 먼저 선택해주세요. - 학과를 선택해주세요. - 정보가 업데이트되었습니다. - 닉네임이 변경되었습니다. - 학과 정보가 업데이트되었습니다. - 변경사항이 없습니다. + Không thể đổi biệt danh. + Vui lòng chọn trường/khoa trước. + Vui lòng chọn ngành học. + Thông tin của bạn đã được cập nhật. + Biệt danh của bạn đã được thay đổi. + Thông tin ngành/khoa của bạn đã được cập nhật. + Không có thay đổi nào. - %1$d~%2$d글자를 입력해 주세요. - 띄어쓰기를 제외한 공백 문자는 사용할 수 없어요. - 연속된 띄어쓰기는 사용할 수 없어요. - 허용 문자(한글/영문/숫자)만 사용할 수 있어요. - 연속된 특수문자(--, __)는 사용할 수 없어요. - 숫자만으로 된 닉네임은 사용할 수 없어요. - 특수문자로 시작/끝나는 닉네임은 사용할 수 없어요. - 욕설, 비속어 등의 표현이 포함된 닉네임은 사용할 수 없어요. - 올바르지 않은 닉네임이에요. + Vui lòng nhập từ %1$d đến %2$d ký tự. + Không được sử dụng ký tự khoảng trắng ngoài dấu cách. + Không được nhập nhiều dấu cách liên tiếp. + Chỉ được dùng chữ Hàn, chữ tiếng Anh và số. + Không được dùng ký tự đặc biệt liên tiếp (--, __). + Biệt danh không được chỉ gồm số. + Biệt danh không được bắt đầu hoặc kết thúc bằng ký tự đặc biệt. + Không được dùng biệt danh chứa từ ngữ thô tục hoặc xúc phạm. + Biệt danh này không hợp lệ. - 앱 초기화 중 오류가 발생했습니다 - 앱을 업데이트해주세요 - 제휴 정보가 없습니다. - 로그인에 실패했어요. - 로그아웃했어요. - 다시 로그인해주세요. - 세션이 만료되어 다시 로그인해주세요. - 닉네임을 설정해주세요. - EAT-SSU 수신 동의 (%1$s) - EAT-SSU 수신 거절 (%1$s) - 오픈소스 라이브러리를 불러올 수 없어요. - 회원탈퇴되었어요. - 회원탈퇴에 실패했어요. + Đã xảy ra lỗi khi khởi tạo ứng dụng. + Vui lòng cập nhật ứng dụng. + Không có thông tin đối tác. + Đăng nhập thất bại. + Bạn đã đăng xuất. + Vui lòng đăng nhập lại. + Phiên đăng nhập đã hết hạn. Vui lòng đăng nhập lại. + Vui lòng đặt biệt danh. + Đã bật thông báo EAT-SSU (%1$s) + Đã tắt thông báo EAT-SSU (%1$s) + Không thể tải thư viện mã nguồn mở. + Tài khoản của bạn đã được xóa. + Không thể xóa tài khoản. - 오늘의 식사는 어땠나요? - 추천하고 싶은 메뉴가 있나요? - 메뉴에 대한 상세한 리뷰를 작성해주세요 - 화면을 준비하는 중입니다. - 작성 중... - 수정 중... - 사진 클릭 시, 삭제됩니다. - 사진 추가(%1$d/%2$d) - 리뷰 설정 - 에러가 발생했습니다. - 리뷰 작성하기 + Bữa ăn hôm nay của bạn thế nào? + Bạn muốn giới thiệu món nào không? + Hãy viết đánh giá chi tiết về món ăn + Đang chuẩn bị màn hình. + Đang đăng... + Đang cập nhật... + Nhấn vào ảnh để xóa. + Thêm ảnh (%1$d/%2$d) + Cài đặt đánh giá + Đã xảy ra lỗi. + Xem bản dịch + Xem bản gốc + Đang dịch... + Không thể dịch đánh giá. + Viết đánh giá - 리뷰 - 내 리뷰 - 아직 작성한 리뷰가 없어요 - 첫 리뷰를 남겨 주세요! - 메뉴에 가장 먼저 리뷰를 남겨주세요! + Đánh giá + Đánh giá của tôi + Chưa có đánh giá + Hãy là người đầu tiên để lại đánh giá! + Hãy là người đầu tiên đánh giá món này! - %1$d점 + %1$d điểm - 오늘의 메뉴 - 가격 - 평점 - 학생식당 + Thực đơn hôm nay + Giá + + Nhà ăn sinh viên - 확인하고 싶은 식당을 선택하세요. - 선택하기 - 아침 - 점심 - 저녁 - 오늘의 메뉴가 없습니다. - 로딩 중 - 네트워크 연결 상태를 확인해주세요. - 설정 필요 - 위젯 설정에서 식당을 선택해주세요. + Chọn nhà ăn bạn muốn xem. + Chọn + Bữa sáng + Bữa trưa + Bữa tối + Hôm nay không có thực đơn. + Đang tải + Vui lòng kiểm tra kết nối mạng. + Cần thiết lập + Vui lòng chọn nhà ăn trong Cài đặt widget. - 마이페이지 - 내 정보 - 로그아웃 - 회원탈퇴 - 앱 버전 - 알림 및 활동 - 서비스 정보 - 기타 - 푸시 알림 설정 - 매일 오전 11시에 알림을 보내드려요 + Trang của tôi + Thông tin của tôi + Đăng xuất + Xóa tài khoản + Phiên bản ứng dụng + Notifications and Activity + Service Info + và vân vân + Cài đặt thông báo đẩy + Bạn sẽ nhận được thông báo mỗi ngày lúc 11:00. - 언어 설정 - 사용할 언어를 선택하세요. - 시스템 언어 (기본값) + Cài đặt ngôn ngữ + Chọn ngôn ngữ bạn muốn sử dụng. + Ngôn ngữ hệ thống (mặc định) - 카카오 - 연결된 계정 + Kakao + Tài khoản đã liên kết - 닉네임 - 닉네임을 설정해주세요 - %1$d~%2$d글자를 입력해주세요. - 사용 가능한 닉네임입니다! + Biệt danh + Vui lòng đặt biệt danh + Vui lòng nhập từ %1$d đến %2$d ký tự. + Biệt danh này có thể sử dụng! - 닉네임 설정 - 소속 설정 + Chỉnh sửa biệt danh + Trường & Ngành - 정보를 불러올 수 없어요. - 통신 오류 - 서버와 통신할 수 없습니다.\n잠시 후 다시 시도해주세요. + Không thể tải thông tin. + Lỗi kết nối + Không thể kết nối với máy chủ.\nVui lòng thử lại sau. - 리뷰를 신고하는 이유를 선택해주세요. - 하나의 리뷰에 대해 24시간 내 한 번만 신고 가능합니다. - 리뷰 신고 사유를 작성해 주세요 + Vui lòng chọn lý do bạn báo cáo đánh giá này. + Bạn chỉ có thể báo cáo cùng một đánh giá một lần trong vòng 24 giờ. + Vui lòng nhập lý do báo cáo đánh giá này - 최대 150자 + Tối đa 150 ký tự @@ -250,56 +254,57 @@ - 숭실대에서 - 먹자 + Ăn tại + Soongsil - 문의하기 - 서비스 이용약관 - 개인정보 처리방침 - 개인정보 처리방침 - 서비스 이용약관 - 약관 및 정책 - 만든 사람들 - 오픈소스 라이브러리 + Liên hệ + Điều khoản dịch vụ + Chính sách quyền riêng tư + Chính sách quyền riêng tư + Điều khoản dịch vụ + Điều khoản và Chính sách + Thông tin đội ngũ + Thư viện mã nguồn mở - 정말 탈퇴하시겠습니까? - 작성한 리뷰는 삭제되지 않으며, (알수없음)으로 표시됩니다.\n자세한 내용은 서비스 이용약관 및 개인정보처리방침을 확인해 주세요. - 닉네임을 입력해주세요 + Bạn có chắc muốn xóa tài khoản không? + Đánh giá của bạn sẽ vẫn hiển thị là (Không xác định).\nXem Điều khoản và Chính sách để biết thêm. + Vui lòng nhập biệt danh của bạn - 🤔 오늘 밥 뭐 먹지 - 오늘의 학식을 확인해보세요! - 점심시간 전 알림 - 점심시간 전, 푸시알림을 발송합니다. - 서버가 보낸 알림 - 잇슈 서버가 보낸 알림을 표시합니다. + 🤔 Hôm nay ăn gì đây? + Xem thực đơn nhà ăn hôm nay! + Thông báo trước bữa trưa + Gửi thông báo đẩy trước giờ ăn trưa. + Thông báo từ máy chủ + Hiển thị thông báo được gửi từ máy chủ EAT-SSU. - 학과 입력하기 - 학과를 입력하고\n나만의 제휴를 확인해보세요! - 단과대/학과 정보를 알 수 없음 + Nhập ngành học của bạn + Nhập ngành học của bạn\nvà xem các ưu đãi dành riêng cho bạn! + Không có thông tin khoa/ngành - 식당 위치 - 비고 - 영업 시간 - 찜한 제휴 - 내 제휴 - 축제 - 전체 - 학과 + Vị trí + Ghi chú + Giờ mở cửa + Ưu đãi yêu thích + Ưu đãi của tôi + các hoạt động lễ hội + Tất cả + Ngành/Khoa + EAT-SSU Instagram diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 317a4f716..16750eb98 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -145,6 +145,10 @@ 사진 추가(%1$d/%2$d) 리뷰 설정 에러가 발생했습니다. + 번역 보기 + 원문 보기 + 번역 중... + 리뷰 번역에 실패했습니다. 리뷰 작성하기 @@ -306,7 +310,7 @@ https://github.com/EAT-SSU/Docs/wiki/EAT%E2%80%90SSU-%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC%EB%B0%A9%EC%B9%A8 https://github.com/EAT-SSU/Docs/wiki/EAT%E2%80%90SSU-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%9D%B4%EC%9A%A9%EC%95%BD%EA%B4%80 https://eatssu-coffee.figma.site/ - EAT-SSU 인스타그램 바로가기 + EAT-SSU 인스타그램 바로가기 eatssu.official https://www.instagram.com/eatssu.official/ https://www.instagram.com/p/DVu1n6SEs5b/ diff --git a/app/src/test/java/com/eatssu/android/data/remote/repository/ReviewRepositoryImplBehaviorSpec.kt b/app/src/test/java/com/eatssu/android/data/remote/repository/ReviewRepositoryImplBehaviorSpec.kt index c72a72a25..1720fdd48 100644 --- a/app/src/test/java/com/eatssu/android/data/remote/repository/ReviewRepositoryImplBehaviorSpec.kt +++ b/app/src/test/java/com/eatssu/android/data/remote/repository/ReviewRepositoryImplBehaviorSpec.kt @@ -10,6 +10,7 @@ import com.eatssu.android.data.remote.dto.response.MenuList import com.eatssu.android.data.remote.dto.response.MenuOfMealResponse import com.eatssu.android.data.remote.dto.response.MenuReviewInfoResponse import com.eatssu.android.data.remote.dto.response.MyReviewListResponse +import com.eatssu.android.data.remote.dto.response.ReviewTranslationResponse import com.eatssu.android.data.remote.service.ReviewService import com.eatssu.android.domain.model.Review import com.eatssu.android.test.AppBehaviorSpec @@ -387,5 +388,36 @@ class ReviewRepositoryImplBehaviorSpec : AppBehaviorSpec({ } } } + `when`("getReviewTranslation API succeeds") { + coEvery { service.getReviewTranslation(1L, "EN") } returns ApiResult.Success( + ReviewTranslationResponse( + reviewId = 1L, + sourceLanguage = "KO", + targetLanguage = "EN", + translatedContent = "It was delicious.", + provider = "DEEPL", + cached = true, + ) + ) + + then("maps to ReviewTranslation") { + runTest { + val result = repository.getReviewTranslation(1L, "EN") + result?.reviewId shouldBe 1L + result?.translatedContent shouldBe "It was delicious." + result?.cached shouldBe true + } + } + } + + `when`("getReviewTranslation API fails") { + coEvery { service.getReviewTranslation(2L, "EN") } returns ApiResult.Failure(500, "error") + + then("returns null") { + runTest { + repository.getReviewTranslation(2L, "EN") shouldBe null + } + } + } } }) diff --git a/app/src/test/java/com/eatssu/android/domain/usecase/review/ReviewDelegatingUseCasesBehaviorSpec.kt b/app/src/test/java/com/eatssu/android/domain/usecase/review/ReviewDelegatingUseCasesBehaviorSpec.kt index 7ef86c5f3..7a06b5b99 100644 --- a/app/src/test/java/com/eatssu/android/domain/usecase/review/ReviewDelegatingUseCasesBehaviorSpec.kt +++ b/app/src/test/java/com/eatssu/android/domain/usecase/review/ReviewDelegatingUseCasesBehaviorSpec.kt @@ -2,6 +2,7 @@ package com.eatssu.android.domain.usecase.review import androidx.paging.PagingData import com.eatssu.android.domain.model.Review +import com.eatssu.android.domain.model.ReviewTranslation import com.eatssu.android.domain.repository.ReportRepository import com.eatssu.android.domain.repository.ReviewRepository import com.eatssu.android.test.AppBehaviorSpec @@ -110,6 +111,29 @@ class ReviewDelegatingUseCasesBehaviorSpec : AppBehaviorSpec({ } } + given("GetReviewTranslationUseCase") { + val reviewRepository = mockk() + val useCase = GetReviewTranslationUseCase(reviewRepository) + val translation = ReviewTranslation( + reviewId = 1L, + sourceLanguage = "KO", + targetLanguage = "EN", + translatedContent = "It was delicious.", + provider = "DEEPL", + cached = true, + ) + + `when`("repository媛€ 由щ럭 踰덉뿭 寃곌낵瑜?諛섑솚?섎㈃") { + coEvery { reviewRepository.getReviewTranslation(1L, "EN") } returns translation + + then("?숈씪 寃곌낵瑜?諛섑솚?쒕떎") { + runTest { + useCase(1L, "EN") shouldBe translation + } + } + } + } + given("ModifyReviewUseCase") { val reviewRepository = mockk() val useCase = ModifyReviewUseCase(reviewRepository) diff --git a/app/src/test/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListViewModelBehaviorSpec.kt b/app/src/test/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListViewModelBehaviorSpec.kt index 9b97e85f0..dbd1053b2 100644 --- a/app/src/test/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListViewModelBehaviorSpec.kt +++ b/app/src/test/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListViewModelBehaviorSpec.kt @@ -6,6 +6,7 @@ import com.eatssu.android.R import com.eatssu.android.domain.usecase.review.DeleteReviewUseCase import com.eatssu.android.domain.usecase.review.GetReviewInfoUseCase import com.eatssu.android.domain.usecase.review.GetReviewListPagedUseCase +import com.eatssu.android.domain.usecase.review.GetReviewTranslationUseCase import com.eatssu.android.test.AppBehaviorSpec import com.eatssu.android.test.expectToast import com.eatssu.android.test.sampleReviewInfo @@ -29,11 +30,17 @@ class ReviewListViewModelBehaviorSpec : AppBehaviorSpec({ val getReviewInfoUseCase = mockk() val getReviewListPagedUseCase = mockk() val deleteReviewUseCase = mockk() + val getReviewTranslationUseCase = mockk() every { getReviewListPagedUseCase(any(), any()) } returns flowOf(PagingData.empty()) `when`("리뷰 정보를 정상 조회하면") { - val viewModel = ReviewListViewModel(getReviewInfoUseCase, getReviewListPagedUseCase, deleteReviewUseCase) + val viewModel = ReviewListViewModel( + getReviewInfoUseCase, + getReviewListPagedUseCase, + deleteReviewUseCase, + getReviewTranslationUseCase, + ) val info = sampleReviewInfo() coEvery { getReviewInfoUseCase(MenuType.FIXED, 100L) } returns info @@ -48,7 +55,12 @@ class ReviewListViewModelBehaviorSpec : AppBehaviorSpec({ } `when`("리뷰 정보 조회에서 예외가 발생하면") { - val viewModel = ReviewListViewModel(getReviewInfoUseCase, getReviewListPagedUseCase, deleteReviewUseCase) + val viewModel = ReviewListViewModel( + getReviewInfoUseCase, + getReviewListPagedUseCase, + deleteReviewUseCase, + getReviewTranslationUseCase, + ) coEvery { getReviewInfoUseCase(MenuType.VARIABLE, 101L) } throws IllegalStateException("boom") then("Error 상태와 실패 토스트를 보낸다") { @@ -66,7 +78,12 @@ class ReviewListViewModelBehaviorSpec : AppBehaviorSpec({ } `when`("리뷰 삭제가 실패하면") { - val viewModel = ReviewListViewModel(getReviewInfoUseCase, getReviewListPagedUseCase, deleteReviewUseCase) + val viewModel = ReviewListViewModel( + getReviewInfoUseCase, + getReviewListPagedUseCase, + deleteReviewUseCase, + getReviewTranslationUseCase, + ) coEvery { deleteReviewUseCase(55L) } returns false then("실패 토스트를 보낸다") { @@ -83,7 +100,12 @@ class ReviewListViewModelBehaviorSpec : AppBehaviorSpec({ } `when`("리뷰 삭제가 성공하면") { - val viewModel = ReviewListViewModel(getReviewInfoUseCase, getReviewListPagedUseCase, deleteReviewUseCase) + val viewModel = ReviewListViewModel( + getReviewInfoUseCase, + getReviewListPagedUseCase, + deleteReviewUseCase, + getReviewTranslationUseCase, + ) coEvery { getReviewInfoUseCase(MenuType.FIXED, 300L) } returns sampleReviewInfo(count = 3) coEvery { deleteReviewUseCase(56L) } returns true @@ -105,7 +127,12 @@ class ReviewListViewModelBehaviorSpec : AppBehaviorSpec({ } `when`("조회 파라미터 없이 리뷰 삭제가 성공하면") { - val viewModel = ReviewListViewModel(getReviewInfoUseCase, getReviewListPagedUseCase, deleteReviewUseCase) + val viewModel = ReviewListViewModel( + getReviewInfoUseCase, + getReviewListPagedUseCase, + deleteReviewUseCase, + getReviewTranslationUseCase, + ) coEvery { deleteReviewUseCase(77L) } returns true then("ReviewDeleted 이벤트만 발생하고 정보 재조회는 하지 않는다") { diff --git a/app/src/test/java/com/eatssu/android/presentation/mypage/myreview/MyReviewViewModelBehaviorSpec.kt b/app/src/test/java/com/eatssu/android/presentation/mypage/myreview/MyReviewViewModelBehaviorSpec.kt index d3feb67ac..013f05c72 100644 --- a/app/src/test/java/com/eatssu/android/presentation/mypage/myreview/MyReviewViewModelBehaviorSpec.kt +++ b/app/src/test/java/com/eatssu/android/presentation/mypage/myreview/MyReviewViewModelBehaviorSpec.kt @@ -4,6 +4,7 @@ import app.cash.turbine.test import com.eatssu.android.R import com.eatssu.android.domain.usecase.review.DeleteReviewUseCase import com.eatssu.android.domain.usecase.review.GetMyReviewsUseCase +import com.eatssu.android.domain.usecase.review.GetReviewTranslationUseCase import com.eatssu.android.domain.usecase.user.GetUserNickNameUseCase import com.eatssu.android.test.AppBehaviorSpec import com.eatssu.android.test.assertToast @@ -26,10 +27,16 @@ class MyReviewViewModelBehaviorSpec : AppBehaviorSpec({ val getMyReviewsUseCase = mockk() val getUserNickNameUseCase = mockk() val deleteReviewUseCase = mockk() + val getReviewTranslationUseCase = mockk() `when`("리뷰 목록이 비어있으면") { coEvery { getMyReviewsUseCase() } returns emptyList() - val viewModel = MyReviewViewModel(getMyReviewsUseCase, getUserNickNameUseCase, deleteReviewUseCase) + val viewModel = MyReviewViewModel( + getMyReviewsUseCase, + getUserNickNameUseCase, + deleteReviewUseCase, + getReviewTranslationUseCase, + ) then("NoReview 상태가 된다") { runTest { @@ -42,7 +49,12 @@ class MyReviewViewModelBehaviorSpec : AppBehaviorSpec({ `when`("리뷰 목록이 있으면") { val review = sampleReview() coEvery { getMyReviewsUseCase() } returns listOf(review) - val viewModel = MyReviewViewModel(getMyReviewsUseCase, getUserNickNameUseCase, deleteReviewUseCase) + val viewModel = MyReviewViewModel( + getMyReviewsUseCase, + getUserNickNameUseCase, + deleteReviewUseCase, + getReviewTranslationUseCase, + ) then("ReviewExists 상태가 된다") { runTest { @@ -55,7 +67,12 @@ class MyReviewViewModelBehaviorSpec : AppBehaviorSpec({ `when`("닉네임 로드를 호출하면") { coEvery { getMyReviewsUseCase() } returns emptyList() coEvery { getUserNickNameUseCase() } returns "nickname" - val viewModel = MyReviewViewModel(getMyReviewsUseCase, getUserNickNameUseCase, deleteReviewUseCase) + val viewModel = MyReviewViewModel( + getMyReviewsUseCase, + getUserNickNameUseCase, + deleteReviewUseCase, + getReviewTranslationUseCase, + ) then("닉네임 stateFlow를 업데이트한다") { runTest { @@ -69,7 +86,12 @@ class MyReviewViewModelBehaviorSpec : AppBehaviorSpec({ `when`("리뷰 삭제가 실패하면") { coEvery { getMyReviewsUseCase() } returns emptyList() coEvery { deleteReviewUseCase(10L) } returns false - val viewModel = MyReviewViewModel(getMyReviewsUseCase, getUserNickNameUseCase, deleteReviewUseCase) + val viewModel = MyReviewViewModel( + getMyReviewsUseCase, + getUserNickNameUseCase, + deleteReviewUseCase, + getReviewTranslationUseCase, + ) then("실패 토스트를 보낸다") { runTest { @@ -88,7 +110,12 @@ class MyReviewViewModelBehaviorSpec : AppBehaviorSpec({ val review = sampleReview(id = 2L) coEvery { getMyReviewsUseCase() } returnsMany listOf(listOf(review), emptyList()) coEvery { deleteReviewUseCase(2L) } returns true - val viewModel = MyReviewViewModel(getMyReviewsUseCase, getUserNickNameUseCase, deleteReviewUseCase) + val viewModel = MyReviewViewModel( + getMyReviewsUseCase, + getUserNickNameUseCase, + deleteReviewUseCase, + getReviewTranslationUseCase, + ) then("성공 토스트 후 목록을 재조회한다") { runTest { diff --git a/build.gradle.kts b/build.gradle.kts index a22ffe1f9..a25fac4dd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,9 +17,48 @@ buildscript { } } -tasks.register("generateLocalizedStrings") { +tasks.register("generateLocalizedStrings") { group = "localization" description = - "language.csv 파일을 통해 res/values-* 위치에 양식에 맞게 strings.xml 파일을 생성합니다. csv에 기존 strings.xml에 매칭되는 값이 없는 경우 한글 값이 들어갑니다." - commandLine("python3", "scripts/generate_android_strings.py") + "app/language.csv 파일을 통해 지정된 모듈들의 res/values-* 위치에 strings.xml 파일을 생성합니다." + + doLast { + val targets = listOf( + Triple( + project.file("app/src/main/res/values/strings.xml").absolutePath, + project.file("app/src/main/res").absolutePath, + project.file("language.csv").absolutePath + ) + /* Core/Common localization is disabled for now until etc.csv is ready. + Triple( + project.file("core/common/src/main/res/values/strings.xml").absolutePath, + project.file("core/common/src/main/res").absolutePath, + project.file("etc.csv").absolutePath + ) + */ + ) + + targets.forEach { (source, resDir, csvPath) -> + val csvFile = project.file(csvPath) + if (!csvFile.exists()) { + println("Warning: CSV file not found at ${csvFile.absolutePath}. Skipping localization for $source.") + return@forEach + } + + println("Generating localized strings for: $source -> $resDir using CSV: $csvPath") + exec { + commandLine( + "python3", + "scripts/generate_android_strings.py", + csvPath, + "--source", + source, + "--res-dir", + resDir + ) + } + } + } } + + diff --git a/core/common/src/main/java/com/eatssu/common/analytics/AnalyticsEvent.kt b/core/common/src/main/java/com/eatssu/common/analytics/AnalyticsEvent.kt index 1e2c50f4d..97c5ef573 100644 --- a/core/common/src/main/java/com/eatssu/common/analytics/AnalyticsEvent.kt +++ b/core/common/src/main/java/com/eatssu/common/analytics/AnalyticsEvent.kt @@ -23,7 +23,7 @@ sealed interface AppAnalyticsEvent : AnalyticsEvent { override val eventName = "app_launch" override val properties = buildMap { put("launch_path", launchPath.value) - put("localeCode", localeCode) + put("app_locale", localeCode) } } } diff --git a/core/common/src/main/java/com/eatssu/common/enums/AppLanguage.kt b/core/common/src/main/java/com/eatssu/common/enums/AppLanguage.kt index 071dc8430..be056ac69 100644 --- a/core/common/src/main/java/com/eatssu/common/enums/AppLanguage.kt +++ b/core/common/src/main/java/com/eatssu/common/enums/AppLanguage.kt @@ -13,9 +13,10 @@ enum class AppLanguage( // SYSTEM("", "System Default", "시스템 언어"), // 다국어 재활성화 시 주석 해제 KOREAN("ko", "Korean", "한국어"), ENGLISH("en", "English", "English"), // 다국어 재활성화 시 주석 해제 - JAPANESE("ja", "Japanese", "日本語"); // 다국어 재활성화 시 주석 해제 - // CHINESE("zh", "Chinese", "中文"), // 다국어 재활성화 시 주석 해제 - // VIETNAMESE("vi", "Vietnamese", "Tiếng Việt"); // 다국어 재활성화 시 주석 해제 + JAPANESE("ja", "Japanese", "日本語"), // 다국어 재활성화 시 주석 해제 + + // CHINESE("zh", "Chinese", "中文"), // 다국어 재활성화 시 주석 해제 + VIETNAMESE("vi", "Vietnamese", "Tiếng Việt"); // 다국어 재활성화 시 주석 해제 companion object { fun fromCode(code: String): AppLanguage { diff --git a/core/common/src/main/res/values-en/strings.xml b/core/common/src/main/res/values-en/strings.xml new file mode 100644 index 000000000..20ea9da23 --- /dev/null +++ b/core/common/src/main/res/values-en/strings.xml @@ -0,0 +1,31 @@ + + + + + + + + Student Cafeteria + Dodam Restaurant + Dormitory Restaurant + FACULTY (Faculty Only) + 푸드 코트 + Snack Corner + 더 키친 + + + + + Content unrelated to the menu item + Explicit or abusive content + Inappropriate promotion or advertisement + Not review-related (e.g. copied text) + Suspected copyright infringement (e.g. photos) + Other (write details below) + + + + + College + Department + diff --git a/core/common/src/main/res/values-ja/strings.xml b/core/common/src/main/res/values-ja/strings.xml new file mode 100644 index 000000000..270dc7ef5 --- /dev/null +++ b/core/common/src/main/res/values-ja/strings.xml @@ -0,0 +1,31 @@ + + + + + + + + 学生食堂 + ドダム食堂 + 寮食堂 + FACULTY(教職員専用) + 푸드 코트 + スナックコーナー + 더 키친 + + + + + メニューと関係のない内容 + わいせつ、暴言など不適切な内容 + 不適切な宣伝または広告 + レビューの趣旨に合わない内容(コピー文など) + 著作権侵害の疑い(写真など) + その他(下部に内容を入力) + + + + + 学部 + 学科 + diff --git a/core/common/src/main/res/values-vi/strings.xml b/core/common/src/main/res/values-vi/strings.xml new file mode 100644 index 000000000..d2cb0e201 --- /dev/null +++ b/core/common/src/main/res/values-vi/strings.xml @@ -0,0 +1,31 @@ + + + + + + + + Nhà ăn sinh viên + Nhà ăn Dodam + Nhà ăn ký túc xá + FACULTY (Chỉ dành cho giảng viên/nhân viên) + 푸드 코트 + Quầy đồ ăn nhẹ + 더 키친 + + + + + Nội dung không liên quan đến thực đơn + Nội dung nhạy cảm hoặc xúc phạm + Quảng cáo hoặc quảng bá không phù hợp + Không liên quan đến đánh giá + Nghi ngờ vi phạm bản quyền (ảnh, v.v.) + Khác (viết nội dung bên dưới) + + + + + Trường/Khoa + Ngành/Khoa + diff --git a/core/design-system/src/main/java/com/eatssu/design_system/component/Chip.kt b/core/design-system/src/main/java/com/eatssu/design_system/component/Chip.kt index 301fdafcd..d1e76ab1e 100644 --- a/core/design-system/src/main/java/com/eatssu/design_system/component/Chip.kt +++ b/core/design-system/src/main/java/com/eatssu/design_system/component/Chip.kt @@ -40,7 +40,7 @@ fun Chip( Row( modifier = Modifier .padding(horizontal = 6.dp, vertical = 5.dp) // 원하는 상하 여백 명시 - .heightIn(min = 16.dp), // 아이콘 유무와 상관없이 동일한 높이 유지 (16dp 아이콘 + 상하 5dp + 라운드 여유) + .heightIn(min = 20.dp), // 아이콘 유무와 상관없이 동일한 높이 유지 (20dp 아이콘 + 상하 5dp + 라운드 여유) verticalAlignment = Alignment.CenterVertically ) { if (isLike) { @@ -54,7 +54,7 @@ fun Chip( } Text( text = menuName, - style = EatssuTheme.typography.caption3, + style = EatssuTheme.typography.caption2, color = Primary ) } diff --git a/core/design-system/src/main/java/com/eatssu/design_system/component/LikeButton.kt b/core/design-system/src/main/java/com/eatssu/design_system/component/LikeButton.kt index 2b638eca4..f0a702905 100644 --- a/core/design-system/src/main/java/com/eatssu/design_system/component/LikeButton.kt +++ b/core/design-system/src/main/java/com/eatssu/design_system/component/LikeButton.kt @@ -32,7 +32,7 @@ fun LikeButton( Button( onClick = onClick, - modifier = modifier.size(58.dp, 28.dp), + modifier = modifier.size(58.dp, 36.dp), shape = RoundedCornerShape(20.dp), border = BorderStroke(1.dp, borderColor), colors = ButtonDefaults.buttonColors( diff --git a/language.csv b/language.csv index 8c11ed1e0..5c3c1840e 100644 --- a/language.csv +++ b/language.csv @@ -1,228 +1,184 @@ key,ko,en,ja,vi -app_name,EAT-SSU,EAT-SSU,EAT-SSU, -custom_weekdays,월 | 화 | 수 | 목 | 금 | 토 | 일,Mon | Tue | Wed | Thu | Fri | Sat | Sun,月 | 火 | 水 | 木 | 金 | 土 | 日, -title_review_write,리뷰 작성하기,Write a Review,レビューを書く, -title_review_modify,리뷰 수정하기,Edit Review,レビューを編集, -title_sign_out,탈퇴하기,Delete Account,アカウント削除, -title_report,신고하기,Report,通報, -title_widget_setting,위젯 설정,Widget Settings,ウィジェット設定, -title_partnership_map,제휴 지도,Partner Map,提携マップ, -title_force_update,강제 업데이트,Force Update,強制アップデート, -nav_cafeteria_menu,학식,Cafeteria,学食, -nav_map,지도,Map,地図, -nav_anyone_but_me,나만아니면돼~,Lucky Game,ラッキーゲーム, -nav_mypage,마이,My,マイページ, -nav_back,back,Back,戻る, -button_complete,완료하기,Done,完了, -button_modify,수정하기,Edit,編集, -button_delete,삭제하기,Delete,削除, -button_save,저장하기,Save,保存, -button_report,신고하기,Report,通報, -button_cancel,취소,Cancel,キャンセル, -button_confirm,확인,Confirm,確認, -button_update,업데이트,Update,アップデート, -button_check_duplicate,중복확인,Check Availability -> Check,重複確認, -dialog_logout_title,로그아웃,Log Out,ログアウト, -dialog_logout_message,로그아웃 하시겠습니까?,Do you want to log out?,ログアウトしますか?, -dialog_force_update_message,새 버전의 앱을 설치해야 합니다.,You need to install the latest version of the app.,アプリの最新バージョンをインストールする必要があります。, -dialog_location_permission_description,내 위치를 바로 확인하며 제휴 식당을 찾아볼 수 있도록 위치 권한을 허용해 주세요.,Please allow location access so you can quickly check your location and find partner restaurants nearby.,現在地をすばやく確認し、近くの提携店舗を探すために位置情報の利用を許可してください。, -dialog_notification_permission_title,알림 권한 필요,Notification Permission Required,通知権限が必要です, -dialog_notification_permission_description,알림을 받으려면 알림 권한을 활성화해야 합니다.\n설정에서 알림 권한을 허용해주세요.,"To receive notifications, you need to enable notification permission. - Please allow notification access in Settings.","通知を受け取るには、通知権限を有効にする必要があります。 - 設定で通知を許可してください。", -dialog_settings,설정으로 이동,Go to Settings,設定へ移動, -dialog_placeholder_title,제목을 입력해주세요,Please enter a title,タイトルを入力してください, -dialog_placeholder_body,본문을 입력해주세요,Please enter the content,内容を入力してください, -dialog_network_error_title,네트워크 연결 안 됨,No Network Connection,ネットワーク接続なし, -dialog_network_error_message,"Wi-Fi, 모바일 데이터를 확인해주세요",Please check your Wi-Fi or mobile data connection.,Wi-Fiまたはモバイルデータ接続を確認してください。, -dialog_notice_title,공지,Notice,お知らせ, -toast_review_write_success,리뷰가 등록되었어요.,Your review has been posted.,レビューを投稿しました。, -toast_review_write_failed,리뷰 작성에 실패하였습니다.,Failed to post your review.,レビューの投稿に失敗しました。, -toast_review_modify_success,리뷰를 수정했습니다.,Your review has been updated.,レビューを更新しました。, -toast_review_modify_failed,리뷰 수정이 실패했습니다.,Failed to update your review.,レビューの更新に失敗しました。, -toast_review_delete_success,리뷰가 삭제되었어요.,Your review has been deleted.,レビューを削除しました。, -toast_review_delete_failed,리뷰 삭제에 실패했습니다.,Failed to delete your review.,レビューの削除に失敗しました。, -toast_review_load_failed,리뷰를 불러오지 못했습니다.,Could not load reviews.,レビューを読み込めませんでした。, -toast_image_upload_success,이미지가 업로드되었습니다.,Image uploaded successfully.,画像をアップロードしました。, -toast_image_upload_failed,이미지 업로드에 실패하였습니다.,Failed to upload image.,画像のアップロードに失敗しました。, -toast_image_compress_failed,이미지 압축에 실패하였습니다.,Failed to compress image.,画像の圧縮に失敗しました。, -toast_image_not_found,이미지 파일을 찾을 수 없습니다.,Could not find the image file.,画像ファイルが見つかりませんでした。, -toast_report_success,신고가 성공적으로 접수되었어요!,Your report has been submitted successfully!,通報が正常に送信されました!, -toast_report_failed,신고가 실패하였습니다.,Failed to submit your report.,通報の送信に失敗しました。, -toast_nickname_change_failed,닉네임 변경에 실패했어요.,Failed to change your nickname.,ニックネームの変更に失敗しました。, -toast_college_required,단과대를 먼저 선택해주세요.,Please select your college first.,先に学部を選択してください。, -toast_department_required,학과를 선택해주세요.,Please select your department.,学科を選択してください。, -toast_info_updated,정보가 업데이트되었습니다.,Your information has been updated.,情報が更新されました。, -toast_nickname_changed,닉네임이 변경되었습니다.,Your nickname has been changed.,ニックネームが変更されました。, -toast_department_updated,학과 정보가 업데이트되었습니다.,Your department information has been updated.,学科情報が更新されました。, -toast_no_changes,변경사항이 없습니다.,No changes were made.,変更内容はありません。, -nickname_error_length,%1$d~%2$d글자를 입력해 주세요.,Please enter %1$d to %2$d characters.,%1$d〜%2$d文字で入力してください。, -nickname_error_whitespace,띄어쓰기를 제외한 공백 문자는 사용할 수 없어요.,Whitespace characters other than spaces are not allowed.,スペース以外の空白文字は使用できません。, -nickname_error_consecutive_space,연속된 띄어쓰기는 사용할 수 없어요.,Consecutive spaces are not allowed.,連続したスペースは使用できません。, -nickname_error_allowed_chars,허용 문자(한글/영문/숫자)만 사용할 수 있어요.,"Only Korean, English letters, and numbers are allowed.",韓国語、英字、数字のみ使用できます。, -nickname_error_consecutive_special,"연속된 특수문자(--, __)는 사용할 수 없어요.","Consecutive special characters (--, __) are not allowed.",連続した特殊文字(--、__)は使用できません。, -nickname_error_only_numbers,숫자만으로 된 닉네임은 사용할 수 없어요.,A nickname cannot contain only numbers.,数字だけのニックネームは使用できません。, -nickname_error_special_position,특수문자로 시작/끝나는 닉네임은 사용할 수 없어요.,A nickname cannot start or end with a special character.,ニックネームの先頭または末尾に特殊文字は使用できません。, -nickname_error_profanity,"욕설, 비속어 등의 표현이 포함된 닉네임은 사용할 수 없어요.",Nicknames containing profanity or abusive language are not allowed.,悪口や不適切な表現を含むニックネームは使用できません。, -nickname_error_invalid,올바르지 않은 닉네임이에요.,This nickname is not valid.,このニックネームは使用できません。, -toast_app_init_error,앱 초기화 중 오류가 발생했습니다,An error occurred while initializing the app.,アプリの初期化中にエラーが発生しました。, -toast_app_update_required,앱을 업데이트해주세요,Please update the app.,アプリをアップデートしてください。, -toast_partnership_info_not_found,제휴 정보가 없습니다.,No partner information found.,提携情報が見つかりません。, -toast_login_failed,로그인에 실패했어요.,Login failed.,ログインに失敗しました。, -toast_logout_success,로그아웃했어요.,You have been logged out.,ログアウトしました。, -toast_token_invalid,다시 로그인해주세요.,Please log in again.,もう一度ログインしてください。, -toast_token_expired,세션이 만료되어 다시 로그인해주세요.,Your session has expired. Please log in again.,セッションの有効期限が切れました。もう一度ログインしてください。, -toast_require_nickname,닉네임을 설정해주세요.,Please set a nickname.,ニックネームを設定してください。, -toast_notification_enable,EAT-SSU 수신 동의 (%1$s),EAT-SSU notifications enabled (%1$s),EAT-SSU通知が有効になりました(%1$s), -toast_notification_disable,EAT-SSU 수신 거절 (%1$s),EAT-SSU notifications disabled (%1$s),EAT-SSU通知が無効になりました(%1$s), -toast_oss_load_fail,오픈소스 라이브러리를 불러올 수 없어요.,Could not load open-source libraries.,オープンソースライブラリを読み込めませんでした。, -toast_sign_out_success,회원탈퇴되었어요.,Your account has been deleted.,アカウントを削除しました。, -toast_sign_out_fail,회원탈퇴에 실패했어요.,Failed to delete your account.,アカウントの削除に失敗しました。, -review_how_was_meal,오늘의 식사는 어땠나요?,How was your meal today?,今日の食事はいかがでしたか?, -review_recommend_menu,추천하고 싶은 메뉴가 있나요?,Any dishes you'd recommend?,おすすめしたいメニューはありますか?, -review_placeholder,메뉴에 대한 상세한 리뷰를 작성해주세요,Write a detailed review of the menu,メニューについて詳しくレビューを書いてください, -review_preparing,화면을 준비하는 중입니다.,Preparing the screen.,画面を準備しています。, -review_posting,작성 중...,Posting...,投稿中..., -review_modifying,수정 중...,Updating...,更新中..., -review_photo_delete_hint,"사진 클릭 시, 삭제됩니다.",Tap a photo to delete it.,写真をタップすると削除できます。, -review_photo_count,사진 추가(%1$d/%2$d),Add Photo (%1$d/%2$d),写真を追加(%1$d/%2$d), -review_settings,리뷰 설정,Review Settings,レビュー設定, -review_error_occurred,에러가 발생했습니다.,An error occurred.,エラーが発生しました。, -review_write,리뷰 작성하기,Write a Review,レビューを書く, -review,리뷰,Reviews,レビュー, -my_review,내 리뷰,My Reviews,自分のレビュー, -none_review,아직 작성한 리뷰가 없어요,No reviews yet,まだレビューがありません, -none_review_my,첫 리뷰를 남겨 주세요!,Be the first to leave a review!,最初のレビューを書いてみましょう!, -none_review_list_detail,메뉴에 가장 먼저 리뷰를 남겨주세요!,Be the first to review this menu item!,このメニューに最初のレビューを書いてみましょう!, -rate_n,%1$d점,%1$d pts,%1$d点, -today_menu,오늘의 메뉴,Today's Menu,今日のメニュー, -price,가격,Price,価格, -rate,평점,Rating → ★,評価, -student_cafeteria,학생식당,Student Cafeteria,学生食堂, -widget_select_restaurant,확인하고 싶은 식당을 선택하세요.,Select the cafeteria you want to check.,確認したい食堂を選択してください。, -widget_select,선택하기,Select,選択, -widget_morning,아침,Breakfast,朝食, -widget_lunch,점심,Lunch,昼食, -widget_dinner,저녁,Dinner,夕食, -widget_no_menu,오늘의 메뉴가 없습니다.,No menu available today.,本日のメニューはありません。, -widget_loading,로딩 중,Loading,読み込み中, -widget_network_error,네트워크 연결 상태를 확인해주세요.,Please check your network connection.,ネットワーク接続を確認してください。, -widget_setup_required,설정 필요,Setup Required,設定が必要です, -widget_select_prompt,위젯 설정에서 식당을 선택해주세요.,Please select a cafeteria in Widget Settings.,ウィジェット設定で食堂を選択してください。, -mypage,마이페이지,My Page,マイページ, -my_info,내 정보,My Info,自分の情報, -logout,로그아웃,Log Out,ログアウト, -signout,회원탈퇴,Delete Account,アカウント削除, -app_version,앱 버전,App Version,アプリバージョン, -mypage_push_notification_title,푸시 알림 설정,Push Notification Settings,プッシュ通知設定, -mypage_push_notification_description,매일 오전 11시에 알림을 보내드려요,We'll send you a notification every day at 11:00 AM.,毎日午前11時に通知をお送りします。, -language_setting,언어 설정,Language Settings,言語設定, -language_select_description,사용할 언어를 선택하세요.,Choose the language you want to use.,使用する言語を選択してください。, -language_system_default,시스템 언어 (기본값),System Language (Default),システム言語(デフォルト), -kakao,카카오,Kakao,Kakao, -connect_account,연결된 계정,Connected Account,連携済みアカウント, -nickname,닉네임,Nickname,ニックネーム, -set_nickname,닉네임을 설정해주세요,Please set a nickname,ニックネームを設定してください, -set_nickname_length,%1$d~%2$d글자를 입력해주세요.,Please enter %1$d to %2$d characters.,%1$d〜%2$d文字で入力してください。, -set_nickname_able,사용 가능한 닉네임입니다!,This nickname is available!,このニックネームは使用できます!, -userinfo_nickname_setting,닉네임 설정,Edit Nickname,ニックネーム編集, -userinfo_affiliation_setting,소속 설정,Set Affiliation -> College & Department,所属設定, -not_found,정보를 불러올 수 없어요.,Could not load the information.,情報を読み込めませんでした。, -server_error_title,통신 오류,Connection Error,接続エラー, -server_error_message,서버와 통신할 수 없습니다.\n잠시 후 다시 시도해주세요.,"Unable to communicate with the server. - Please try again later.","サーバーと通信できません。 - しばらくしてからもう一度お試しください。", -report_title,리뷰를 신고하는 이유를 선택해주세요.,Please choose why you are reporting this review.,このレビューを通報する理由を選択してください。, -report_sub,하나의 리뷰에 대해 24시간 내 한 번만 신고 가능합니다.,You can report the same review only once within 24 hours.,同じレビューは24時間以内に1回のみ通報できます。, -report_write_hint,리뷰 신고 사유를 작성해 주세요,Please write the reason for reporting this review,通報理由を入力してください, -max_150,최대 150자,Up to 150 characters,最大150文字, -app_slogan_part1,숭실대에서,Eat at,, -app_slogan_part2,먹자,Soongsil,숭실대에서 먹자, -inquire,문의하기,Contact Us,お問い合わせ, -service_rule,서비스 이용약관,Terms of Service,利用規約, -private_information,개인정보 처리방침,Privacy Policy,プライバシーポリシー, -policy,개인정보 처리방침,Privacy Policy,プライバシーポリシー, -terms,서비스 이용약관,Terms of Service,利用規約, -developer,만든 사람들,Credits,クレジット, -oss_licenses,오픈소스 라이브러리,Open-Source Libraries,オープンソースライブラリ, -signout_question,정말 탈퇴하시겠습니까?,Are you sure you want to delete your account?,本当にアカウントを削除しますか?, -signout_description,"작성한 리뷰는 삭제되지 않으며, (알수없음)으로 표시됩니다.\n자세한 내용은 서비스 이용약관 및 개인정보처리방침을 확인해 주세요.","Your reviews will not be deleted and will be displayed as (Unknown). - For details, please check the Terms of Service and Privacy Policy.","レビューは削除されず、(不明)として表示されます。 - 詳細は利用規約とプライバシーポリシーをご確認ください。", -signout_nickname,닉네임을 입력해주세요,Please enter your nickname,ニックネームを入力してください, -notification_context_title,🤔 오늘 밥 뭐 먹지,🤔 What should I eat today?,🤔 今日なに食べる?, -notification_context_text,오늘의 학식을 확인해보세요!,Check today's cafeteria menu!,今日の学食メニューを確認しよう!, -notification_channel_lunch_name,점심시간 전 알림,Before-Lunch Notifications,昼食前通知, -notification_channel_lunch_description,"점심시간 전, 푸시알림을 발송합니다.",Sends push notifications before lunchtime.,昼食時間前にプッシュ通知を送信します。, -notification_channel_server_name,서버가 보낸 알림,Server Notifications,サーバー通知, -notification_channel_server_description,잇슈 서버가 보낸 알림을 표시합니다.,Shows notifications sent by the EAT-SSU server.,EAT-SSUサーバーから送信された通知を表示します。, -input_department,학과 입력하기,Enter your department,学科を入力, -input_string_description,학과를 입력하고\n나만의 제휴를 확인해보세요!,"Enter your department - and check your personalized partnerships!","学科を入力して - 自分に合った提携情報を確認しましょう!", -map_unknown_college_department,단과대/학과 정보를 알 수 없음,College/Department information unavailable,学部/学科情報なし, -location,식당 위치,Location,場所, -etc,비고,Notes,備考, -time,영업 시간,Hours,営業時間, -favorite_partnership,찜한 제휴,Favorite Partners,お気に入り提携先, -partnership_filter_mine,내 제휴,My Partners,自分の提携先, -partnership_filter_all,전체,All,すべて, -partnership_filter_department_placeholder,학과,Department,学科, -policy_url,https://github.com/EAT-SSU/Docs/wiki/EAT%E2%80%90SSU-%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC%EB%B0%A9%EC%B9%A8,https://github.com/EAT-SSU/Docs/wiki/EAT%E2%80%90SSU-%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC%EB%B0%A9%EC%B9%A8,https://github.com/EAT-SSU/Docs/wiki/EAT%E2%80%90SSU-%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC%EB%B0%A9%EC%B9%A8, -terms_url,https://github.com/EAT-SSU/Docs/wiki/EAT%E2%80%90SSU-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%9D%B4%EC%9A%A9%EC%95%BD%EA%B4%80,https://github.com/EAT-SSU/Docs/wiki/EAT%E2%80%90SSU-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%9D%B4%EC%9A%A9%EC%95%BD%EA%B4%80,https://github.com/EAT-SSU/Docs/wiki/EAT%E2%80%90SSU-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%9D%B4%EC%9A%A9%EC%95%BD%EA%B4%80, -anyone_but_me_url,https://eatssu-coffee.figma.site/,https://eatssu-coffee.figma.site/,https://eatssu-coffee.figma.site/, -eatssu_instagram,eatssu.official,eatssu.official,eatssu.official, -eatssu_instagram_url,https://www.instagram.com/eatssu.official/,https://www.instagram.com/eatssu.official/,https://www.instagram.com/eatssu.official/, -eatssu_event_instagram_url,https://www.instagram.com/p/DVu1n6SEs5b/,https://www.instagram.com/p/DVu1n6SEs5b/,https://www.instagram.com/p/DVu1n6SEs5b/, -recruiting_url,https://eat-ssu.notion.site/1d2eeef75a1681ae800cf6ffa6faa37d?pvs=74,https://eat-ssu.notion.site/1d2eeef75a1681ae800cf6ffa6faa37d?pvs=74,https://eat-ssu.notion.site/1d2eeef75a1681ae800cf6ffa6faa37d?pvs=74, -((학생식당)) 식당 위치,학생회관 3층,"Student Union, 3F",, -((학생식당)) 영업 시간,"08:00~09:00(천원의아침밥) -11:20~14:00(점심) -14:00~17:00(공간 개방)","08:00 – 09:00(1,000-Won Breakfast) -11:20 – 14:00(Lunch) -14:00 – 17:00(Space Open)",, -((학생식당)) 비고,"3개 코너 운영 -뚝배기, 덮밥, 양식 -주말 휴무","3 Food Stations -Hot Pot(Ttukbaegi), Rice Bowls, Western food -Closed on weekends",, -((도담식당)) 식당 위치,신양관 2층,"Shinyang Hall, 2F",, -((도담식당)) 영업 시간,"평일 -11:20~14:00(점심) -17:00~18:30(저녁) - -주말 -11:20~13:30(점심)","Weekdays -11:20 – 14:00(Lunch) -17:00 – 18:30(Dinner) - -Weekends -11:20 –13:30(Lunch)",, -((도담식당)) 비고,"2개 코너 운영 -일반식, 웰빙코너","2 Food Stations -Standard Meal, Healthy Meal Corner",, -((기숙사 식당)) 식당 위치,레지던스홀 지하 1층,"Residence Hall, B1",, -((기숙사 식당)) 영업 시간,"평일 -11:20~13:50(점심) -17:00~18:30(저녁) - -주말 -11:20~13:30(점심) -17:00~18:20(저녁)","Weekdays -11:20 – 13:50(Lunch) -17:00 – 18:30(Dinner) - -Weekends -11:20~13:30(Lunch) -17:00 – 18:20(Dinner)",, -((기숙사 식당)) 비고,조식 미운영,No Breakfast Service,, -((FACULTY(교직원전용))) 식당 위치,전산관 지하 1층,"Jeonsan Hall, B1",, -((FACULTY(교직원전용))) 영업 시간,"11:30~14:00(점심) -14:00~17:00(공간개방)","11:20 – 14:00(Lunch) -14:00 – 17:00(Space Open)",, -((FACULTY(교직원전용))) 비고,주말 휴무,Closed on weekends,, -(스낵코너)) 식당 위치,학생회관 3층,"Student Union, 3F",, -(스낵코너)) 영업 시간,11:00~15:30(점심),11:00 – 15:30(Lunch),, -(스낵코너)) 비고,"분식류, 옛날도시락, 컵밥 등 -주말 휴무","Bunsik(Korean snack food), Old School Korean Lunch Box, Cup-bab, etc. -Closed on weekends",, \ No newline at end of file +app_name,EAT-SSU,EAT-SSU,EAT-SSU,EAT-SSU +custom_weekdays,월 | 화 | 수 | 목 | 금 | 토 | 일,Mon | Tue | Wed | Thu | Fri | Sat | Sun,月 | 火 | 水 | 木 | 金 | 土 | 日,T2 | T3 | T4 | T5 | T6 | T7 | CN +title_review_write,리뷰 작성하기,Write a Review,レビューを書く,Viết đánh giá +title_review_modify,리뷰 수정하기,Edit Review,レビューを編集,Chỉnh sửa đánh giá +title_sign_out,탈퇴하기,Delete Account,アカウント削除,Xóa tài khoản +title_report,신고하기,Report,通報,Báo cáo +title_widget_setting,위젯 설정,Widget Settings,ウィジェット設定,Cài đặt widget +title_partnership_map,제휴 지도,Partner Map,提携マップ,Bản đồ đối tác +title_force_update,강제 업데이트,Force Update,強制アップデート,Bắt buộc cập nhật +nav_cafeteria_menu,학식,Cafeteria,学食,Nhà ăn +nav_map,지도,Map,マップ,Bản đồ +nav_anyone_but_me,나만아니면돼~,Lucky Game,ラッキーゲーム,Game may mắn +nav_mypage,마이,My Page,マイページ,Trang của tôi +nav_back,back,Back,戻る,Quay lại +button_complete,완료하기,Done,完了,Xong +button_modify,수정하기,Edit,編集,Chỉnh sửa +button_delete,삭제하기,Delete,削除,Xóa +button_save,저장하기,Save,保存,Lưu +button_report,신고하기,Report,通報,Báo cáo +button_cancel,취소,Cancel,キャンセル,Hủy +button_confirm,확인,Confirm,確認,Xác nhận +button_update,업데이트,Update,アップデート,Cập nhật +button_check_duplicate,중복확인,Check,確認,Kiểm tra +dialog_logout_title,로그아웃,Log Out,ログアウト,Đăng xuất +dialog_logout_message,로그아웃 하시겠습니까?,Do you want to log out?,ログアウトしますか?,Bạn có muốn đăng xuất không? +dialog_force_update_message,새 버전의 앱을 설치해야 합니다.,You need to install the latest version of the app.,最新バージョンのアプリをインストールする必要があります。,Bạn cần cài đặt phiên bản mới nhất của ứng dụng. +dialog_location_permission_description,내 위치를 바로 확인하며 제휴 식당을 찾아볼 수 있도록 위치 권한을 허용해 주세요.,Please allow location access so you can quickly check your location and find partner restaurants nearby.,現在地をすぐに確認し、近くの提携レストランを探せるように、位置情報へのアクセスを許可してください。,Vui lòng cho phép truy cập vị trí để nhanh chóng xem vị trí của bạn và tìm các nhà hàng đối tác gần đó. +dialog_notification_permission_title,알림 권한 필요,Notification Permission Required,通知権限が必要です,Cần quyền thông báo +dialog_notification_permission_description,알림을 받으려면 알림 권한을 활성화해야 합니다.\n설정에서 알림 권한을 허용해주세요.,"To receive notifications, you need to enable notification permission.\nPlease allow notification access in Settings.",通知を受け取るには、通知権限を有効にする必要があります。\n設定で通知を許可してください。,"Để nhận thông báo, bạn cần bật quyền thông báo.\nVui lòng cho phép thông báo trong Cài đặt." +dialog_settings,설정으로 이동,Go to Settings,設定へ移動,Đi tới Cài đặt +dialog_placeholder_title,제목을 입력해주세요,Please enter a title,タイトルを入力してください,Vui lòng nhập tiêu đề +dialog_placeholder_body,본문을 입력해주세요,Please enter the content,本文を入力してください,Vui lòng nhập nội dung +dialog_network_error_title,네트워크 연결 안 됨,No Network Connection,ネットワークに接続されていません,Không có kết nối mạng +dialog_network_error_message,"Wi-Fi, 모바일 데이터를 확인해주세요",Please check your Wi-Fi or mobile data connection.,Wi-Fiまたはモバイルデータ通信を確認してください。,Vui lòng kiểm tra kết nối Wi-Fi hoặc dữ liệu di động. +dialog_notice_title,공지,Notice,お知らせ,Thông báo +toast_review_write_success,리뷰가 등록되었어요.,Your review has been posted.,レビューを投稿しました。,Đánh giá của bạn đã được đăng. +toast_review_write_failed,리뷰 작성에 실패하였습니다.,Failed to post your review.,レビューの投稿に失敗しました。,Không thể đăng đánh giá. +toast_review_modify_success,리뷰를 수정했습니다.,Your review has been updated.,レビューを更新しました。,Đánh giá của bạn đã được cập nhật. +toast_review_modify_failed,리뷰 수정이 실패했습니다.,Failed to update your review.,レビューの更新に失敗しました。,Không thể cập nhật đánh giá. +toast_review_delete_success,리뷰가 삭제되었어요.,Your review has been deleted.,レビューを削除しました。,Đánh giá của bạn đã được xóa. +toast_review_delete_failed,리뷰 삭제에 실패했습니다.,Failed to delete your review.,レビューの削除に失敗しました。,Không thể xóa đánh giá. +toast_review_load_failed,리뷰를 불러오지 못했습니다.,Could not load reviews.,レビューを読み込めませんでした。,Không thể tải đánh giá. +toast_image_upload_success,이미지가 업로드되었습니다.,Image uploaded successfully.,画像をアップロードしました。,Đã tải ảnh lên thành công. +toast_image_upload_failed,이미지 업로드에 실패하였습니다.,Failed to upload image.,画像のアップロードに失敗しました。,Không thể tải ảnh lên. +toast_image_compress_failed,이미지 압축에 실패하였습니다.,Failed to compress image.,画像の圧縮に失敗しました。,Không thể nén ảnh. +toast_image_not_found,이미지 파일을 찾을 수 없습니다.,Could not find the image file.,画像ファイルが見つかりません。,Không tìm thấy tệp ảnh. +toast_report_success,신고가 성공적으로 접수되었어요!,Your report has been submitted successfully!,通報が正常に送信されました!,Báo cáo của bạn đã được gửi thành công! +toast_report_failed,신고가 실패하였습니다.,Failed to submit your report.,通報の送信に失敗しました。,Không thể gửi báo cáo. +toast_nickname_change_failed,닉네임 변경에 실패했어요.,Failed to change your nickname.,ニックネームの変更に失敗しました。,Không thể đổi biệt danh. +toast_college_required,단과대를 먼저 선택해주세요.,Please select your college first.,先に学部を選択してください。,Vui lòng chọn trường/khoa trước. +toast_department_required,학과를 선택해주세요.,Please select your department.,学科を選択してください。,Vui lòng chọn ngành học. +toast_info_updated,정보가 업데이트되었습니다.,Your information has been updated.,情報を更新しました。,Thông tin của bạn đã được cập nhật. +toast_nickname_changed,닉네임이 변경되었습니다.,Your nickname has been changed.,ニックネームを変更しました。,Biệt danh của bạn đã được thay đổi. +toast_department_updated,학과 정보가 업데이트되었습니다.,Your department information has been updated.,学科情報を更新しました。,Thông tin ngành/khoa của bạn đã được cập nhật. +toast_no_changes,변경사항이 없습니다.,No changes were made.,変更はありません。,Không có thay đổi nào. +nickname_error_length,%1$d~%2$d글자를 입력해 주세요.,Please enter %1$d to %2$d characters.,%1$d〜%2$d文字で入力してください。,Vui lòng nhập từ %1$d đến %2$d ký tự. +nickname_error_whitespace,띄어쓰기를 제외한 공백 문자는 사용할 수 없어요.,Whitespace characters other than spaces are not allowed.,スペース以外の空白文字は使用できません。,Không được sử dụng ký tự khoảng trắng ngoài dấu cách. +nickname_error_consecutive_space,연속된 띄어쓰기는 사용할 수 없어요.,Consecutive spaces are not allowed.,連続したスペースは使用できません。,Không được nhập nhiều dấu cách liên tiếp. +nickname_error_allowed_chars,허용 문자(한글/영문/숫자)만 사용할 수 있어요.,"Only Korean, English letters, and numbers are allowed.",韓国語、英字、数字のみ使用できます。,"Chỉ được dùng chữ Hàn, chữ tiếng Anh và số." +nickname_error_consecutive_special,"연속된 특수문자(--, __)는 사용할 수 없어요.","Consecutive special characters (--, __) are not allowed.",連続した特殊文字(--、__)は使用できません。,"Không được dùng ký tự đặc biệt liên tiếp (--, __)." +nickname_error_only_numbers,숫자만으로 된 닉네임은 사용할 수 없어요.,A nickname cannot contain only numbers.,数字だけのニックネームは使用できません。,Biệt danh không được chỉ gồm số. +nickname_error_special_position,특수문자로 시작/끝나는 닉네임은 사용할 수 없어요.,A nickname cannot start or end with a special character.,特殊文字で始まる、または終わるニックネームは使用できません。,Biệt danh không được bắt đầu hoặc kết thúc bằng ký tự đặc biệt. +nickname_error_profanity,"욕설, 비속어 등의 표현이 포함된 닉네임은 사용할 수 없어요.",Nicknames containing profanity or abusive language are not allowed.,暴言や不適切な表現を含むニックネームは使用できません。,Không được dùng biệt danh chứa từ ngữ thô tục hoặc xúc phạm. +nickname_error_invalid,올바르지 않은 닉네임이에요.,This nickname is not valid.,このニックネームは使用できません。,Biệt danh này không hợp lệ. +toast_app_init_error,앱 초기화 중 오류가 발생했습니다,An error occurred while initializing the app.,アプリの初期化中にエラーが発生しました。,Đã xảy ra lỗi khi khởi tạo ứng dụng. +toast_app_update_required,앱을 업데이트해주세요,Please update the app.,アプリをアップデートしてください。,Vui lòng cập nhật ứng dụng. +toast_partnership_info_not_found,제휴 정보가 없습니다.,No partner information found.,提携情報がありません。,Không có thông tin đối tác. +toast_login_failed,로그인에 실패했어요.,Login failed.,ログインに失敗しました。,Đăng nhập thất bại. +toast_logout_success,로그아웃했어요.,You have been logged out.,ログアウトしました。,Bạn đã đăng xuất. +toast_token_invalid,다시 로그인해주세요.,Please log in again.,もう一度ログインしてください。,Vui lòng đăng nhập lại. +toast_token_expired,세션이 만료되어 다시 로그인해주세요.,Your session has expired. Please log in again.,セッションの有効期限が切れました。もう一度ログインしてください。,Phiên đăng nhập đã hết hạn. Vui lòng đăng nhập lại. +toast_require_nickname,닉네임을 설정해주세요.,Please set a nickname.,ニックネームを設定してください。,Vui lòng đặt biệt danh. +toast_notification_enable,EAT-SSU 수신 동의 (%1$s),EAT-SSU notifications enabled (%1$s),EAT-SSUの通知をオンにしました(%1$s),Đã bật thông báo EAT-SSU (%1$s) +toast_notification_disable,EAT-SSU 수신 거절 (%1$s),EAT-SSU notifications disabled (%1$s),EAT-SSUの通知をオフにしました(%1$s),Đã tắt thông báo EAT-SSU (%1$s) +toast_oss_load_fail,오픈소스 라이브러리를 불러올 수 없어요.,Could not load open-source libraries.,オープンソースライブラリを読み込めませんでした。,Không thể tải thư viện mã nguồn mở. +toast_sign_out_success,회원탈퇴되었어요.,Your account has been deleted.,アカウントを削除しました。,Tài khoản của bạn đã được xóa. +toast_sign_out_fail,회원탈퇴에 실패했어요.,Failed to delete your account.,アカウントの削除に失敗しました。,Không thể xóa tài khoản. +review_how_was_meal,오늘의 식사는 어땠나요?,How was your meal today?,今日の食事はいかがでしたか?,Bữa ăn hôm nay của bạn thế nào? +review_recommend_menu,추천하고 싶은 메뉴가 있나요?,Any dishes you'd recommend?,おすすめしたいメニューはありますか?,Bạn muốn giới thiệu món nào không? +review_placeholder,메뉴에 대한 상세한 리뷰를 작성해주세요,Write a detailed review of the menu,メニューについて詳しいレビューを書いてください,Hãy viết đánh giá chi tiết về món ăn +review_preparing,화면을 준비하는 중입니다.,Preparing the screen.,画面を準備しています。,Đang chuẩn bị màn hình. +review_posting,작성 중...,Posting...,投稿中...,Đang đăng... +review_modifying,수정 중...,Updating...,更新中...,Đang cập nhật... +review_photo_delete_hint,"사진 클릭 시, 삭제됩니다.",Tap a photo to delete it.,写真をタップすると削除されます。,Nhấn vào ảnh để xóa. +review_photo_count,사진 추가(%1$d/%2$d),Add Photo (%1$d/%2$d),写真を追加(%1$d/%2$d),Thêm ảnh (%1$d/%2$d) +review_settings,리뷰 설정,Review Settings,レビュー設定,Cài đặt đánh giá +review_error_occurred,에러가 발생했습니다.,An error occurred.,エラーが発生しました。,Đã xảy ra lỗi. +review_show_translation,번역 보기,Show translation,翻訳を見る,Xem bản dịch +review_show_original,원문 보기,Show original,原文を見る,Xem bản gốc +review_translating,번역 중...,Translating...,翻訳中...,Đang dịch... +toast_review_translation_failed,리뷰 번역에 실패했습니다.,Failed to translate the review.,レビューの翻訳に失敗しました。,Không thể dịch đánh giá. +review_write,리뷰 작성하기,Write a Review,レビューを書く,Viết đánh giá +review,리뷰,Reviews,レビュー,Đánh giá +my_review,내 리뷰,My Reviews,自分のレビュー,Đánh giá của tôi +none_review,아직 작성한 리뷰가 없어요,No reviews yet,まだレビューがありません,Chưa có đánh giá +none_review_my,첫 리뷰를 남겨 주세요!,Be the first to leave a review!,最初のレビューを投稿しましょう!,Hãy là người đầu tiên để lại đánh giá! +none_review_list_detail,메뉴에 가장 먼저 리뷰를 남겨주세요!,Be the first to review this menu item!,このメニューに最初のレビューを残しましょう!,Hãy là người đầu tiên đánh giá món này! +rate_n,%1$d점,%1$d pts,%1$d点,%1$d điểm +today_menu,오늘의 메뉴,Today's Menu,今日のメニュー,Thực đơn hôm nay +price,가격,Price,価格,Giá +rate,평점,★,★,★ +student_cafeteria,학생식당,Student Cafeteria,学生食堂,Nhà ăn sinh viên +widget_select_restaurant,확인하고 싶은 식당을 선택하세요.,Select the cafeteria you want to check.,確認したい食堂を選択してください。,Chọn nhà ăn bạn muốn xem. +widget_select,선택하기,Select,選択,Chọn +widget_morning,아침,Breakfast,朝食,Bữa sáng +widget_lunch,점심,Lunch,昼食,Bữa trưa +widget_dinner,저녁,Dinner,夕食,Bữa tối +widget_no_menu,오늘의 메뉴가 없습니다.,No menu available today.,今日のメニューはありません。,Hôm nay không có thực đơn. +widget_loading,로딩 중,Loading,読み込み中,Đang tải +widget_network_error,네트워크 연결 상태를 확인해주세요.,Please check your network connection.,ネットワーク接続を確認してください。,Vui lòng kiểm tra kết nối mạng. +widget_setup_required,설정 필요,Setup Required,設定が必要です,Cần thiết lập +widget_select_prompt,위젯 설정에서 식당을 선택해주세요.,Please select a cafeteria in Widget Settings.,ウィジェット設定で食堂を選択してください。,Vui lòng chọn nhà ăn trong Cài đặt widget. +mypage,마이페이지,My Page,マイページ,Trang của tôi +my_info,내 정보,My Info,自分の情報,Thông tin của tôi +logout,로그아웃,Log Out,ログアウト,Đăng xuất +signout,회원탈퇴,Delete Account,アカウント削除,Xóa tài khoản +app_version,앱 버전,App Version,アプリバージョン,Phiên bản ứng dụng +mypage_push_notification_title,푸시 알림 설정,Push Notification Settings,プッシュ通知設定,Cài đặt thông báo đẩy +mypage_push_notification_description,매일 오전 11시에 알림을 보내드려요,We'll send you a notification every day at 11:00 AM.,毎日午前11時に通知をお送りします。,Bạn sẽ nhận được thông báo mỗi ngày lúc 11:00. +language_setting,언어 설정,Language Settings,言語設定,Cài đặt ngôn ngữ +language_select_description,사용할 언어를 선택하세요.,Choose the language you want to use.,使用する言語を選択してください。,Chọn ngôn ngữ bạn muốn sử dụng. +language_system_default,시스템 언어 (기본값),System Language (Default),システム言語(デフォルト),Ngôn ngữ hệ thống (mặc định) +kakao,카카오,Kakao,カカオ,Kakao +connect_account,연결된 계정,Connected Account,連携済みアカウント,Tài khoản đã liên kết +nickname,닉네임,Nickname,ニックネーム,Biệt danh +set_nickname,닉네임을 설정해주세요,Please set a nickname,ニックネームを設定してください,Vui lòng đặt biệt danh +set_nickname_length,%1$d~%2$d글자를 입력해주세요.,Please enter %1$d to %2$d characters.,%1$d〜%2$d文字で入力してください。,Vui lòng nhập từ %1$d đến %2$d ký tự. +set_nickname_able,사용 가능한 닉네임입니다!,This nickname is available!,使用できるニックネームです!,Biệt danh này có thể sử dụng! +userinfo_nickname_setting,닉네임 설정,Edit Nickname,ニックネーム編集,Chỉnh sửa biệt danh +userinfo_affiliation_setting,소속 설정,College & Department,学部・学科,Trường & Ngành +not_found,정보를 불러올 수 없어요.,Could not load the information.,情報を読み込めませんでした。,Không thể tải thông tin. +server_error_title,통신 오류,Connection Error,通信エラー,Lỗi kết nối +server_error_message,서버와 통신할 수 없습니다.\n잠시 후 다시 시도해주세요.,Unable to communicate with the server.\nPlease try again later.,サーバーと通信できません。\nしばらくしてからもう一度お試しください。,Không thể kết nối với máy chủ.\nVui lòng thử lại sau. +report_title,리뷰를 신고하는 이유를 선택해주세요.,Please choose why you are reporting this review.,このレビューを通報する理由を選択してください。,Vui lòng chọn lý do bạn báo cáo đánh giá này. +report_sub,하나의 리뷰에 대해 24시간 내 한 번만 신고 가능합니다.,You can report the same review only once within 24 hours.,同じレビューは24時間以内に1回のみ通報できます。,Bạn chỉ có thể báo cáo cùng một đánh giá một lần trong vòng 24 giờ. +report_write_hint,리뷰 신고 사유를 작성해 주세요,Please write the reason for reporting this review,レビューを通報する理由を入力してください,Vui lòng nhập lý do báo cáo đánh giá này +max_150,최대 150자,Up to 150 characters,最大150文字,Tối đa 150 ký tự +app_slogan_part1,숭실대에서,Eat at,崇実大で,Ăn tại +app_slogan_part2,먹자,Soongsil,食べよう,Soongsil +inquire,문의하기,Contact Us,お問い合わせ,Liên hệ +service_rule,서비스 이용약관,Terms of Service,利用規約,Điều khoản dịch vụ +private_information,개인정보 처리방침,Privacy Policy,プライバシーポリシー,Chính sách quyền riêng tư +policy,개인정보 처리방침,Privacy Policy,プライバシーポリシー,Chính sách quyền riêng tư +terms,서비스 이용약관,Terms of Service,利用規約,Điều khoản dịch vụ +developer,만든 사람들,Credits,開発者,Thông tin đội ngũ +oss_licenses,오픈소스 라이브러리,Open-Source Libraries,オープンソースライブラリ,Thư viện mã nguồn mở +signout_question,정말 탈퇴하시겠습니까?,Are you sure you want to delete your account?,本当にアカウントを削除しますか?,Bạn có chắc muốn xóa tài khoản không? +signout_description,"작성한 리뷰는 삭제되지 않으며, (알수없음)으로 표시됩니다.\n자세한 내용은 서비스 이용약관 및 개인정보처리방침을 확인해 주세요.","Your reviews will not be deleted and will appear as (Unknown).\nFor details, please check the Terms of Service and Privacy Policy.",投稿したレビューは削除されず、(不明)として表示されます。\n詳細は利用規約およびプライバシーポリシーをご確認ください。,Đánh giá của bạn sẽ vẫn hiển thị là (Không xác định).\nXem Điều khoản và Chính sách để biết thêm. +signout_nickname,닉네임을 입력해주세요,Please enter your nickname,ニックネームを入力してください,Vui lòng nhập biệt danh của bạn +notification_context_title,🤔 오늘 밥 뭐 먹지,🤔 What should I eat today?,🤔 今日何食べよう?,🤔 Hôm nay ăn gì đây? +notification_context_text,오늘의 학식을 확인해보세요!,Check today's cafeteria menu!,今日の学食メニューをチェックしましょう!,Xem thực đơn nhà ăn hôm nay! +notification_channel_lunch_name,점심시간 전 알림,Before-Lunch Notifications,昼食前通知,Thông báo trước bữa trưa +notification_channel_lunch_description,"점심시간 전, 푸시알림을 발송합니다.",Sends push notifications before lunchtime.,昼食前にプッシュ通知を送信します。,Gửi thông báo đẩy trước giờ ăn trưa. +notification_channel_server_name,서버가 보낸 알림,Server Notifications,サーバー通知,Thông báo từ máy chủ +notification_channel_server_description,잇슈 서버가 보낸 알림을 표시합니다.,Shows notifications sent by the EAT-SSU server.,EAT-SSUサーバーから送信された通知を表示します。,Hiển thị thông báo được gửi từ máy chủ EAT-SSU. +input_department,학과 입력하기,Enter your department,学科を入力,Nhập ngành học của bạn +input_string_description,학과를 입력하고\n나만의 제휴를 확인해보세요!,Enter your department\nand check your personalized partnerships!,学科を入力して\n自分だけの提携特典を確認しましょう!,Nhập ngành học của bạn\nvà xem các ưu đãi dành riêng cho bạn! +map_unknown_college_department,단과대/학과 정보를 알 수 없음,College/Department information unavailable,学部・学科情報を確認できません,Không có thông tin khoa/ngành +location,식당 위치,Location,場所,Vị trí +etc,비고,Notes,備考,Ghi chú +time,영업 시간,Hours,営業時間,Giờ mở cửa +favorite_partnership,찜한 제휴,Favorite Partners,お気に入りの提携,Ưu đãi yêu thích +partnership_filter_mine,내 제휴,My Partners,自分の提携,Ưu đãi của tôi +partnership_filter_all,전체,All,すべて,Tất cả +partnership_filter_department_placeholder,학과,Department,学科,Ngành/Khoa +policy_url,https://github.com/EAT-SSU/Docs/wiki/EAT%E2%80%90SSU-%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC%EB%B0%A9%EC%B9%A8,,, +terms_url,https://github.com/EAT-SSU/Docs/wiki/EAT%E2%80%90SSU-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%9D%B4%EC%9A%A9%EC%95%BD%EA%B4%80,,, +anyone_but_me_url,https://eatssu-coffee.figma.site/,,, +eatssu_instagram,eatssu.official,,, +eatssu_instagram_url,https://www.instagram.com/eatssu.official/,,, +eatssu_event_instagram_url,https://www.instagram.com/p/DVu1n6SEs5b/,,, +recruiting_url,https://eat-ssu.notion.site/1d2eeef75a1681ae800cf6ffa6faa37d?pvs=74,,, +,EAT SSU 팀에게 보내기,Send to the EAT SSU Team,EAT SSUチームに送信,Gửi đến nhóm EAT SSU +,신고하기,Report,通報,Báo cáo +,사유를 선택해주세요!,Please select a reason!,理由を選択してください!,Vui lòng chọn lý do! +,신고가 성공적으로 접수되었어요!,Your report has been submitted successfully!,通報が正常に送信されました!,Báo cáo của bạn đã được gửi thành công! +,메뉴와 관련없는 내용,Content unrelated to the menu item,メニューと関係のない内容,Nội dung không liên quan đến thực đơn +,"음란성, 욕설 등 부적절한 내용",Explicit or abusive content,わいせつ、暴言など不適切な内容,Nội dung nhạy cảm hoặc xúc phạm +,부적절한 홍보 또는 광고,Inappropriate promotion or advertisement,不適切な宣伝または広告,Quảng cáo hoặc quảng bá không phù hợp +,리뷰 작성 취지에 맞지 않는 내용 (복사글 등),Not review-related (e.g. copied text),レビューの趣旨に合わない内容(コピー文など),Không liên quan đến đánh giá +,저작권 도용 의심 (사진 등),Suspected copyright infringement (e.g. photos),著作権侵害の疑い(写真など),"Nghi ngờ vi phạm bản quyền (ảnh, v.v.)" +,기타 (하단 내용 작성),Other (write details below),その他(下部に内容を入力),Khác (viết nội dung bên dưới) +,리뷰 신고 사유를 알려주세요,Tell us why you are reporting this review,レビューを通報する理由を教えてください,Vui lòng cho biết lý do báo cáo đánh giá này