Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
aa4e247
feat: Context 없이 문자열을 다룰 수 있는 UiText 추가
PeraSite Dec 28, 2025
f97e1f8
refactor: 하드코딩된 문자열이나 context 사용하던 코드를 UiText 사용하도록 리팩토링
PeraSite Dec 28, 2025
ec43552
chore: 임시 번역 파일 추가
PeraSite Dec 29, 2025
c5eab8a
refactor: Presentation 레이어의 하드 코딩 문자열 개선
PeraSite Dec 29, 2025
91402ef
feat: 언어 변경 페이지 추가
PeraSite Dec 29, 2025
8e12f09
chore: 누락된 베트남어 번역 추가
PeraSite Dec 29, 2025
bbb364f
refactor: fallback으로 사용하던 한국어 단어를 nullable 객체 반환으로 변경 후 화면 표기에서 제외되게 수정
PeraSite Dec 29, 2025
7ed3c7a
refactor: 안쓰는 string 삭제
PeraSite Dec 29, 2025
fb6a284
fix: 달력의 weekday가 한국어로 고정되던 문제 수정
PeraSite Dec 29, 2025
cbdc4d1
refactor: 하드코딩된 toast 수정
PeraSite Dec 29, 2025
425cd47
refactor: 하드코딩된 User Info Title 수정
PeraSite Dec 29, 2025
2e3c858
Merge remote-tracking branch 'origin/develop' into feat/i18n
PeraSite Dec 29, 2025
49bd387
docs: nullable로 변경한 함수의 의도 설명 주석 추가
PeraSite Dec 29, 2025
d182d9f
chore: TODO 코멘트 수정
PeraSite Dec 29, 2025
f1ef2ec
refactor: Context 사용 삭제
PeraSite Dec 29, 2025
0fd9bb1
chore: 사라진 주석 추가
PeraSite Dec 29, 2025
5e700ad
feat: 영어 카카오 로그인 버튼 추가
PeraSite Dec 31, 2025
fc2166b
refactor: Department 기본값 삭제(하드코딩 문자열 제거)
PeraSite Dec 31, 2025
0830f97
feat: 숭실대에서 먹자 슬로건 번역 추가
PeraSite Dec 31, 2025
990bdb0
fix: 타입 관련 컴파일 오류 개선
PeraSite Dec 31, 2025
3e017ad
refactor: AppLanguage enum을 common으로 이동
PeraSite Dec 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@
<activity
android:name=".presentation.mypage.terms.WebViewActivity"
android:exported="false" />
<activity
android:name=".presentation.mypage.language.LanguageSelectorActivity"
android:exported="false" />
<activity
android:name=".presentation.intro.IntroActivity"
android:exported="true">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ class EatSsuFirebaseMessagingService : FirebaseMessagingService() {

val channel = NotificationChannel(
CHANNEL_ID,
"서버가 보낸 알림",
getString(R.string.notification_channel_server_name),
NotificationManager.IMPORTANCE_HIGH
).apply {
description = "잇슈 서버가 보낸 알림을 표시합니다."
description = getString(R.string.notification_channel_server_description)
enableLights(true)
enableVibration(true) // 진동도 활성화
lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC // 잠금 화면에서도 표시
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ class NotificationReceiver : BroadcastReceiver() {

val channel = NotificationChannel(
CHANNEL_ID,
"점심시간 전 알림",
context.getString(R.string.notification_channel_lunch_name),
NotificationManager.IMPORTANCE_HIGH // 중요도를 높게 설정
).apply {
description = "점심시간 전, 푸시알림을 발송합니다."
description = context.getString(R.string.notification_channel_lunch_description)
enableLights(true)
enableVibration(true) // 진동도 활성화
lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC // 잠금 화면에서도 표시
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.eatssu.android.domain.model.AppLanguage
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
Expand All @@ -19,6 +21,7 @@ class SettingDataStore @Inject constructor(

companion object {
private val DAILY_NOTIFICATION_KEY = booleanPreferencesKey("daily_notification")
private val LANGUAGE_KEY = stringPreferencesKey("app_language")
}

val dailyNotificationStatus: Flow<Boolean> = context.settingDataStore.data
Expand All @@ -32,5 +35,17 @@ class SettingDataStore @Inject constructor(
}
}

val appLanguage: Flow<AppLanguage> = context.settingDataStore.data
.map { preferences ->
val code = preferences[LANGUAGE_KEY] ?: ""
AppLanguage.fromCode(code)
}

suspend fun setAppLanguage(language: AppLanguage) {
context.settingDataStore.edit { preferences ->
preferences[LANGUAGE_KEY] = language.code
}
}

suspend fun clear() = context.settingDataStore.edit { it.clear() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ data class CollegeResponse(
val collegeName: String?
)

fun CollegeResponse.toDomain() = College(
collegeId = this.collegeId ?: -1,
collegeName = this.collegeName ?: "단과대",
)
// 이 함수가 null을 반환하는 경우, 이 함수를 호출하는 UserRepositoryImpl에서 mapNotNull로 걸러짐
fun CollegeResponse.toDomain(): College? {
val id = collegeId ?: return null
val name = collegeName ?: return null
return College(collegeId = id, collegeName = name)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ data class DepartmentResponse(
val departmentName: String?,
)

fun DepartmentResponse.toDomain() = Department(
departmentId = this.departmentId ?: -1,
departmentName = this.departmentName ?: "학과",
)
// 이 함수가 null을 반환하는 경우, 이 함수를 호출하는 UserRepositoryImpl에서 mapNotNull로 걸러짐
fun DepartmentResponse.toDomain(): Department? {
val id = departmentId ?: return null
val name = departmentName ?: return null
return Department(departmentId = id, departmentName = name)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ data class UserCollegeDepartmentResponse(
val collegeName: String?,
)

fun UserCollegeDepartmentResponse.toDomain(): Pair<College, Department> =
Pair(
College(
collegeId = this.collegeId ?: -1,
collegeName = this.collegeName ?: "단과대"
),
Department(
departmentId = this.departmentId ?: -1,
departmentName = this.departmentName ?: "학과"
)
)
// 이 함수가 null을 반환하는 경우, 이 함수를 호출하는 UserRepositoryImpl에서 mapNotNull로 걸러짐
fun UserCollegeDepartmentResponse.toDomain(): Pair<College, Department>? {
val colId = collegeId ?: return null
val colName = collegeName ?: return null
val deptId = departmentId ?: return null
val deptName = departmentName ?: return null
return Pair(
College(collegeId = colId, collegeName = colName),
Department(departmentId = deptId, departmentName = deptName)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,16 @@ class UserRepositoryImpl @Inject constructor(

override suspend fun getTotalColleges(): List<College> =
userService.getCollegeList()
.map { list -> list.map { it.toDomain() } }
.map { list -> list.mapNotNull { it.toDomain() } }
.orEmptyList()

override suspend fun getTotalDepartments(collegeId: Int): List<Department> =
userService.getDepartmentsByCollege(collegeId)
.map { list -> list.map { it.toDomain() } }
.map { list -> list.mapNotNull { it.toDomain() } }
.orEmptyList()

override suspend fun getUserCollegeDepartment(): Pair<College, Department>? =
userService.getUserCollegeDepartment().map { it.toDomain() }.orNull()
userService.getUserCollegeDepartment().orNull()?.toDomain()

override suspend fun setUserDepartment(departmentId: Int): Boolean {
return userService.setUserDepartment(UserDepartmentRequest(departmentId)).isSuccess()
Expand Down
30 changes: 30 additions & 0 deletions app/src/main/java/com/eatssu/android/domain/model/AppLanguage.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.eatssu.android.domain.model

import java.util.Locale

/**
* 앱에서 지원하는 언어 목록
* SYSTEM은 기기 언어를 따르며, 다른 옵션은 사용자가 직접 선택한 언어
*/
enum class AppLanguage(
val code: String,
val displayName: String,
val nativeDisplayName: String
) {
SYSTEM("", "System Default", "시스템 언어"),
KOREAN("ko", "Korean", "한국어"),
ENGLISH("en", "English", "English"),
JAPANESE("ja", "Japanese", "日本語"),
CHINESE("zh", "Chinese", "中文"),
VIETNAMESE("vi", "Vietnamese", "Tiếng Việt");

companion object {
fun fromCode(code: String): AppLanguage {
return entries.find { it.code == code } ?: SYSTEM
}
}

fun toLocale(): Locale? {
return if (code.isEmpty()) null else Locale(code)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.eatssu.android.presentation

import android.content.Context
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
Expand All @@ -13,9 +12,9 @@ import com.eatssu.android.domain.usecase.user.GetUserNickNameUseCase
import com.eatssu.android.domain.usecase.user.SetUserCollegeDepartmentUseCase
import com.eatssu.common.UiEvent
import com.eatssu.common.UiState
import com.eatssu.common.UiText
import com.eatssu.common.enums.ToastType
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
Expand All @@ -32,8 +31,7 @@ class MainViewModel @Inject constructor(
private val getUserNickNameUseCase: GetUserNickNameUseCase,
private val setUserCollegeDepartmentUseCase: SetUserCollegeDepartmentUseCase,
private val userRepository: UserRepository,
private val getUserCollegeDepartmentUseCase: GetUserCollegeDepartmentUseCase,
@ApplicationContext private val context: Context
private val getUserCollegeDepartmentUseCase: GetUserCollegeDepartmentUseCase
) : ViewModel() {

private val _uiState: MutableStateFlow<UiState<MainState>> = MutableStateFlow(UiState.Init)
Expand Down Expand Up @@ -68,7 +66,7 @@ class MainViewModel @Inject constructor(
// 1) 닉네임 없음
if (nickname.isBlank()) {
_uiState.value = UiState.Success(MainState.NicknameNull)
_uiEvent.emit(UiEvent.ShowToast(context.getString(R.string.set_nickname), ToastType.ERROR))
_uiEvent.emit(UiEvent.ShowToast(UiText.StringResource(R.string.set_nickname), ToastType.ERROR))
return@launch
}

Expand All @@ -83,7 +81,7 @@ class MainViewModel @Inject constructor(
_uiState.value = UiState.Success(MainState.LoggedOut)
_uiEvent.emit(
UiEvent.ShowToast(
context.getString(R.string.toast_logout_success), ToastType.SUCCESS
UiText.StringResource(R.string.toast_logout_success), ToastType.SUCCESS
)
)
}
Expand Down Expand Up @@ -120,7 +118,7 @@ class MainViewModel @Inject constructor(
_uiState.value = UiState.Error
_uiEvent.emit(
UiEvent.ShowToast(
context.getString(R.string.not_found),
UiText.StringResource(R.string.not_found),
ToastType.ERROR
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.eatssu.android.R
import com.eatssu.android.databinding.FragmentCafeteriaBinding
import com.eatssu.android.presentation.MainViewModel
import com.eatssu.android.presentation.base.BaseFragment
Expand Down Expand Up @@ -49,7 +50,7 @@ class CafeteriaFragment : BaseFragment<FragmentCafeteriaBinding>(
viewPager.adapter = viewpagerFragmentAdapter
viewPager.setCurrentItem(viewpagerFragmentAdapter.getDefaultFragmentPosition(), false)

val tabTitles = listOf("아침", "점심", "저녁")
val tabTitles = listOf(getString(R.string.widget_morning), getString(R.string.widget_lunch), getString(R.string.widget_dinner))
TabLayoutMediator(tabLayout, viewPager) { tab, position -> tab.text = tabTitles[position] }.attach()

// ViewPager 페이지 변경 감지
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import com.eatssu.android.R
import com.eatssu.android.databinding.ItemCalendarListBinding
import com.eatssu.android.presentation.util.CalendarUtil
import java.time.LocalDate
import java.time.format.TextStyle
import java.util.Locale


internal class CalendarAdapter(
Expand All @@ -33,10 +31,14 @@ internal class CalendarAdapter(


override fun onBindViewHolder(holder: CalendarViewHolder, position: Int) {
val context = holder.itemView.context

val date = days[position]
holder.dayOfMonth.text = date.dayOfMonth.toString()
holder.dayText.text =
date.dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.KOREAN).toString()

// custom_weekdays 사용해 weekday 표기
val weekdayNames = context.resources.getStringArray(R.array.custom_weekdays)
holder.dayText.text = weekdayNames[date.dayOfWeek.value - 1]

/**
* iOS의 FSCalendar를 Custom으로 만들었습니다.
Expand All @@ -48,15 +50,15 @@ internal class CalendarAdapter(
holder.dayOfMonth.setBackgroundResource(R.drawable.selector_background_blue)
holder.dayOfMonth.setTextColor(
ContextCompat.getColor(
holder.itemView.context,
context,
R.color.selector_calendar_colortext
)
)
} else if (date == LocalDate.now() && date != CalendarUtil.selectedDate) {
//오늘 날짜가 선택 되지 않았을 때, 오늘 날 text 색 지정
holder.dayOfMonth.setTextColor(
ContextCompat.getColor(
holder.itemView.context,
context,
R.color.primary
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class InfoBottomSheetFragment : BottomSheetDialogFragment() {

EventLogger.clickRestaurantInfo(restaurantType)

binding.tvName.text = restaurantType.korean
binding.tvName.text = getString(restaurantType.displayNameResId)

CoroutineScope(Dispatchers.Main).launch {
val restaurantInfo = infoViewModel.getRestaurantInfo(restaurantType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class MenuAdapter(
Log.d("MenuAdapter", "bind: ${sectionModel.cafeteria}")
}

binding.tvCafeteria.text = sectionModel.cafeteria.korean
binding.tvCafeteria.text = binding.root.context.getString(sectionModel.cafeteria.displayNameResId)
binding.tvCafeteriaLocation.text = sectionModel.cafeteriaLocation

binding.rvMenu.apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
Expand Down Expand Up @@ -144,13 +145,13 @@ internal fun ReviewListScreen(
Scaffold(
topBar = {
EatSsuTopBar(
title = "리뷰",
title = stringResource(R.string.review),
onBack = onBack
)
},
bottomBar = { // 하단에 버튼을 고정하기 위함
EatSsuButton(
text = "리뷰 작성하기",
text = stringResource(R.string.review_write),
onClick = {
onReviewWriteButtonClick()
},
Expand Down Expand Up @@ -235,7 +236,7 @@ internal fun ReviewListScreen(

Row(Modifier.padding(horizontal = 24.dp)) {
Text(
"리뷰",
stringResource(R.string.review),
style = EatssuTheme.typography.h2,
)
Spacer(modifier = Modifier.width(6.dp))
Expand Down Expand Up @@ -320,7 +321,7 @@ internal fun ReviewListScreen(
.padding(top = 100.dp)
) {
Text(
"에러가 발생했습니다.",
stringResource(R.string.review_error_occurred),
style = EatssuTheme.typography.body1,
modifier = Modifier.align(Alignment.Center)
)
Expand Down Expand Up @@ -365,7 +366,7 @@ fun ReviewInfoContent(
)
Spacer(Modifier.width(4.dp))
Text(
"오늘의 메뉴",
stringResource(R.string.today_menu),
style = EatssuTheme.typography.subtitle1
)
}
Expand Down Expand Up @@ -441,13 +442,13 @@ fun EmptyReviewContent(modifier: Modifier) {
)
Spacer(Modifier.height(16.dp))
Text(
"아직 작성된 리뷰가 없어요",
stringResource(R.string.none_review),
style = EatssuTheme.typography.subtitle2,
color = Gray600
)
Spacer(Modifier.height(8.dp))
Text(
"메뉴에 가장 먼저 리뷰를 남겨주세요!",
stringResource(R.string.none_review_list_detail),
style = EatssuTheme.typography.caption2,
color = Gray600
)
Expand Down
Loading