Skip to content

Conversation

@PeraSite
Copy link
Member

@PeraSite PeraSite commented Dec 29, 2025

Summary

백엔드에서 하드코딩된 한국어를 반환해주는 부분, 정확한 번역 테이블 준비해 플랫폼간 라이팅 통일화 등
실제 배포하려면 먼저 해야하는 작업이 많지만 PoC 컨셉으로 먼저 만들어봤습니다!

말은 다국어 지원이지만 이를 수행하기 위해서 엄청나게 많은 리팩토링이 진행됐습니다. 리뷰어에게 먼저 도게자를 박겠습니다 허허

지원하는 언어는 한국어, 영어, 중국어, 베트남어입니다. 임의로 제가 교내에 가장 많을 것 같은 학생분이 사용할 것 같은 언어로 정한 것인데, 기획 회의에서 확실히 정해야합니다!

Describe your changes

Screen_recording_20251229_203557.mp4

1. UiText 도입

ViewModel에서 ApplicationContext를 주입받아 getString하는 더러운 코드를 완전히 개선했습니다.

UiText는 문자열 리소스를 감싸는 StringResource, 문자열을 감싸는 DynamicString, Empty로 나뉘는 sealed class입니다.

sealed class UiText {
    data class StringResource(
        @StringRes val resId: Int,
        val args: List<Any> = emptyList()
    ) : UiText()

    data class DynamicString(val value: String) : UiText()

    data object Empty : UiText()
}

Toast를 보낼 때를 예시를 들면,
ViewModel에서는

_uiEvent.emit(UiEvent.ShowToast(UiText.StringResource(R.string.toast_review_write_success), ToastType.SUCCESS))

처럼 UiText.StringResource로 ShowToast event를 만들어 emit하고

Activity에서는

showToast(event.message.asString(this), event.type)

이렇게 현재 context에서 UiText를 문자열로 변환(asString)해서 보여주는 방식입니다.

이러한 수정에 맞게 UiEvent.ShowToast의 정의도 다음과 같이 변경되었습니다.

data class ShowToast(
    val message: UiText, // Before - val message: String
    val type: ToastType
) : UiEvent

2. 하드 코딩된 한국어 문자열을 리소스를 사용하게 수정

하드 코딩된 부분의 양이 매우 많아 점진적으로 개선 중입니다. 한 90% 정도 된 것 같아요.
대부분 String Literal로 쓰인 한국어를 getString로, Compose의 경우 stringResource로 대체하는 Presentation 레이어의 리팩토링이지만, 이외에 특별히 확인해야 할 부분은 다음과 같습니다.

  • CollegeResponse, DepartmentResponse, UserCollegeDepartmentResponse의 toDomain 함수가 fallback 시 하드 코딩된 문자열을 사용하지 않게, fallback을 null로 변경했습니다. 이들을 호출하는 UserRepositoryImpl에서 null인 경우 무시하게끔 mapNotNull을 사용합니다.

  • 메인 식단 화면 상단의 캘린더 중 weekday를 표시하는 CalendarAdapter.kt가 현재 언어에 대응하게 수정했습니다.

image
  • 상단의 캘린더 중 식사 종류를 표시하는 CafeteriaFragment.kt 에서 하드코딩되어있던 ("아침", "점심", "저녁") 을 개선했습니다.
image
  • common enum인 ReportType과 Restaurant가 String Resource를 쓰게 정의를 바꾸었습니다. 이에 Restaurant.korean 처럼 하드 코딩된 문자열을 참조하는 모든 코드도 변경했어요.

  • strings.xml에서 레거시인 것 같은 수많은 entry를 삭제했습니다. 명확한 목적을 가진 경우 string name의 접두사로 toast_, notification_ 등을 붙여서 naming inconsistency를 해결했어요. 이외에도 사용 목적에 맞지 않거나 애매한 이름을 가진 entry를 수정했습니다.

3. 언어 변경 페이지 추가

디자인이 나오진 않았지만 기존 디자인 시스템을 사용해 만들었어요! 설정 기본 값은 시스템 언어라서 사용자 기기 언어 설정을 존중합니다.
image

Issue

To reviewers

2500줄의 코드 변화인 아주 커다란 PR인데... 이것도 묵혀놓으면 아주 머지가 힘들어지겠죠? 그래도 리뷰 V2 같은 커다란 feature 개발은 없을테니 괜찮?을 것? 같습니다

TODO:

  • 이미지 외국어화 (카카오 로그인 등)
  • "숭실대에서 먹자" 슬로건 다국어화

@PeraSite PeraSite marked this pull request as ready for review December 29, 2025 10:56
@PeraSite PeraSite marked this pull request as draft December 29, 2025 10:57
@PeraSite PeraSite self-assigned this Dec 29, 2025
@PeraSite PeraSite changed the title feat/i18n [Feat] 다국어 지원 Dec 29, 2025
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @PeraSite, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 Pull Request는 앱에 다국어(i18n) 지원을 추가하고, 사용자 인터페이스 텍스트를 효율적으로 관리하기 위해 기존의 하드코딩된 문자열들을 리소스 파일로 외부화하는 것을 목표로 합니다. 또한, ViewModel에서 Context 의존성을 줄이고 데이터 변환 로직의 안정성을 향상시키는 리팩토링도 포함되어 있습니다.

Highlights

  • 다국어(i18n) 지원 구현: 앱에 다국어 지원을 추가하기 위해 AppLanguage enum을 도입하고, 사용자가 언어를 선택할 수 있는 LanguageSelectorActivityLanguageSelectorScreen을 구현했습니다. 사용자 선택은 SettingDataStore에 저장되며, AppCompatDelegate.setApplicationLocales를 통해 앱의 로케일을 동적으로 변경합니다.
  • 문자열 리소스 외부화 및 번역 추가: 앱 전반에 걸쳐 하드코딩된 수많은 문자열을 strings.xml 리소스 파일로 이동시켰습니다. 또한, 영어, 일본어, 베트남어, 중국어 번역 파일을 새로 추가하여 다국어 환경을 지원합니다. ReportTypeRestaurant enum도 문자열 리소스 ID를 사용하도록 업데이트되었습니다.
  • ViewModel Context 의존성 제거 및 UiText 도입: MainViewModel, IntroViewModel, LoginViewModel, MyPageViewModel, SignOutViewModel 등 여러 ViewModel에서 Context에 대한 직접적인 의존성을 제거했습니다. 대신 UiText sealed class를 도입하여 ViewModel이 UI 텍스트를 Context 없이 처리할 수 있도록 추상화했습니다.
  • 데이터 변환 로직 개선: CollegeResponse, DepartmentResponse, UserCollegeDepartmentResponsetoDomain() 확장 함수를 nullable 타입으로 변경하여 null 안전성을 강화했습니다. UserRepositoryImpl에서는 mapNotNull을 사용하여 유효한 도메인 객체만 처리하도록 개선했습니다.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

이 PR은 앱에 다국어 지원을 추가하기 위한 대규모 리팩토링을 포함하고 있습니다. 하드코딩된 문자열을 문자열 리소스로 추출하고, 영어, 일본어, 중국어, 베트남어 번역을 추가한 점이 인상적입니다. 특히 UiText sealed class를 도입하여 ViewModel에서 Context 의존성을 제거하고 문자열을 안전하게 처리하도록 개선한 점은 매우 훌륭한 아키텍처 개선입니다. 또한 언어 선택 화면을 추가하고 사용자의 언어 설정을 저장 및 적용하는 기능 구현도 잘 되었습니다. 전반적으로 코드 품질을 크게 향상시키는 매우 중요한 변경이라고 생각합니다. 몇 가지 추가 개선을 위한 제안 사항을 아래 주석으로 남겼습니다. 훌륭한 작업입니다!

I am having trouble creating individual review comments. Click here to see my feedback.

app/src/main/res/values/strings.xml (13-42)

medium

문자열 리소스를 리팩토링하는 훌륭한 작업입니다. 다만, 리팩토링 과정에서 더 이상 사용되지 않는 이전 문자열들(login_done, set_nickname_unable, recomented 등)이 일부 남아있는 것으로 보입니다. 유지보수성을 높이고 APK 크기를 줄이기 위해 사용하지 않는 리소스를 제거하는 것이 좋습니다. Android Studio의 "Refactor > Remove Unused Resources..." 기능을 사용하면 쉽게 찾아 삭제할 수 있습니다.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements comprehensive multi-language support for the EAT-SSU Android application, supporting Korean, English, Chinese, Japanese, and Vietnamese. The implementation involves significant architectural refactoring to eliminate hardcoded strings and introduce a clean separation between ViewModels and UI layer for string resource handling.

Key Changes:

  • Introduced UiText sealed class to handle string resources without Context in ViewModels
  • Migrated all hardcoded Korean strings to string resource files with translations
  • Added language selection feature in MyPage settings
  • Refactored enums (Restaurant, ReportType) to use string resource IDs instead of hardcoded strings

Reviewed changes

Copilot reviewed 72 out of 72 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
core/common/src/main/java/com/eatssu/common/UiText.kt New sealed class for Context-independent string handling in ViewModels
core/common/src/main/java/com/eatssu/common/UiEvent.kt Updated ShowToast to use UiText instead of String
core/common/src/main/java/com/eatssu/common/enums/Restaurant.kt Refactored to use displayNameResId instead of hardcoded Korean strings
core/common/src/main/java/com/eatssu/common/enums/ReportType.kt Refactored to use descriptionResId for multi-language support
app/src/main/res/values*/strings.xml Added comprehensive string resources for 5 languages (ko, en, ja, zh, vi)
app/src/main/java/com/eatssu/android/presentation/util/UiTextUtil.kt Composable extension function for UiText resolution
app/src/main/java/com/eatssu/android/presentation/mypage/language/* New language selector feature with ViewModel, Screen, and Activity
app/src/main/java/com/eatssu/android/domain/model/AppLanguage.kt Enum defining supported languages with locale codes
app/src/main/java/com/eatssu/android/data/local/SettingDataStore.kt Added language preference persistence
app/src/main/java/com/eatssu/android/presentation/cafeteria/calendar/CalendarAdapter.kt Localized weekday display using string arrays
Multiple ViewModels Removed ApplicationContext injections, migrated to UiText for messages
Multiple Activities/Fragments Updated to use getString() and stringResource() for UI text
app/src/main/java/com/eatssu/android/data/remote/dto/response/*Response.kt Changed toDomain() to return nullable types for proper fallback handling

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +269 to 271
// TODO: 기본값 대신에 Department?, College? 로 변경
val selectedCollege: College = College(-1, "단과대"),
val originalCollege: College = College(-1, "단과대"),
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded default values "단과대" and "학과" in the TODO comments should be replaced with proper null handling or references to string resources to maintain consistency with the internationalization approach.

Copilot uses AI. Check for mistakes.
isCollegeChanged = isCollegeChanged,
// 단과대가 변경되면 학과 초기화
// TODO: 기본값 대신에 Department? 로 변경
selectedDepartment = Department(-1, "학과"),
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, the hardcoded default value "학과" on line 162 should be addressed as part of the TODO to use nullable Department type instead of a hardcoded Korean string.

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +32
@Deprecated("다국어 지원을 위해 displayNameResId 사용", ReplaceWith("entries.find { it.name == enumName }?.displayNameResId"))
fun fromRestaurantEnumName(enumName: String): String {
return ""
}
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deprecated function fromRestaurantEnumName returns an empty string but the deprecation message suggests using displayNameResId which returns an Int. This inconsistency could lead to confusion. Consider either removing the function body entirely and throwing NotImplementedError, or updating the deprecation message to clarify the migration path.

Copilot uses AI. Check for mistakes.
THE_KITCHEN("the_kitchen", "더 키친", MenuType.FIXED);
enum class Restaurant(
val value: String,
@field:StringRes val displayNameResId: Int,
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @field:StringRes annotation on displayNameResId appears unnecessary. Since this is a constructor parameter, @StringRes annotation should be sufficient. The field target is typically used for properties with backing fields.

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +38
// onClick 람다에서 LocalContext 접근이 불가하므로 Composable 레벨에서 미리 매핑 생성
val restaurantDisplayNameMap = Restaurant.getVariableRestaurantList()
.associateBy { it.toUiText().asString() }
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the WidgetSettingScreen, the restaurantDisplayNameMap is created by calling asString() on each restaurant's UiText within the Composable. However, this assumes the locale won't change during the screen's lifetime. If the user changes language while this screen is open, the map will contain outdated strings. Consider regenerating this map when the language changes or using a different approach that resolves the strings lazily.

Copilot uses AI. Check for mistakes.
@Composable
fun MealWidgetPreviewError() {
MealWidgetError("저녁", Restaurant.DODAM.korean, "에러임")
MealWidgetError("저녁", Restaurant.DODAM.toUiText().asString(), "에러임")
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded string "에러임" (meaning "This is an error") in the preview function should be replaced with a string resource or a more descriptive English placeholder for consistency with the internationalization effort.

Copilot uses AI. Check for mistakes.
Comment on lines +300 to +304
MealWidgetContent(
"저녁",
listOf(listOf("", "", "반찬", "음료")),
Restaurant.DODAM.toUiText().asString()
)
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the preview function, calling asString() without a Context will fail since it's not a Composable function. The preview should use stringResource(R.string.restaurant_dodam) instead, or provide a proper context for the UiText resolution.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

다국어 지원

2 participants