-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] 다국어 지원 #439
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
[Feat] 다국어 지원 #439
Conversation
# Conflicts: # app/src/main/java/com/eatssu/android/presentation/common/NetworkConnection.kt
Summary of ChangesHello @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
🧠 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 AssistThe 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
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 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
|
There was a problem hiding this 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)
문자열 리소스를 리팩토링하는 훌륭한 작업입니다. 다만, 리팩토링 과정에서 더 이상 사용되지 않는 이전 문자열들(login_done, set_nickname_unable, recomented 등)이 일부 남아있는 것으로 보입니다. 유지보수성을 높이고 APK 크기를 줄이기 위해 사용하지 않는 리소스를 제거하는 것이 좋습니다. Android Studio의 "Refactor > Remove Unused Resources..." 기능을 사용하면 쉽게 찾아 삭제할 수 있습니다.
There was a problem hiding this 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
UiTextsealed 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.
| // TODO: 기본값 대신에 Department?, College? 로 변경 | ||
| val selectedCollege: College = College(-1, "단과대"), | ||
| val originalCollege: College = College(-1, "단과대"), |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
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.
| isCollegeChanged = isCollegeChanged, | ||
| // 단과대가 변경되면 학과 초기화 | ||
| // TODO: 기본값 대신에 Department? 로 변경 | ||
| selectedDepartment = Department(-1, "학과"), |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
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.
| @Deprecated("다국어 지원을 위해 displayNameResId 사용", ReplaceWith("entries.find { it.name == enumName }?.displayNameResId")) | ||
| fun fromRestaurantEnumName(enumName: String): String { | ||
| return "" | ||
| } |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
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.
| THE_KITCHEN("the_kitchen", "더 키친", MenuType.FIXED); | ||
| enum class Restaurant( | ||
| val value: String, | ||
| @field:StringRes val displayNameResId: Int, |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
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.
| // onClick 람다에서 LocalContext 접근이 불가하므로 Composable 레벨에서 미리 매핑 생성 | ||
| val restaurantDisplayNameMap = Restaurant.getVariableRestaurantList() | ||
| .associateBy { it.toUiText().asString() } |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
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.
| @Composable | ||
| fun MealWidgetPreviewError() { | ||
| MealWidgetError("저녁", Restaurant.DODAM.korean, "에러임") | ||
| MealWidgetError("저녁", Restaurant.DODAM.toUiText().asString(), "에러임") |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
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.
| MealWidgetContent( | ||
| "저녁", | ||
| listOf(listOf("밥", "국", "반찬", "음료")), | ||
| Restaurant.DODAM.toUiText().asString() | ||
| ) |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
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.
Summary
백엔드에서 하드코딩된 한국어를 반환해주는 부분, 정확한 번역 테이블 준비해 플랫폼간 라이팅 통일화 등
실제 배포하려면 먼저 해야하는 작업이 많지만 PoC 컨셉으로 먼저 만들어봤습니다!
말은 다국어 지원이지만 이를 수행하기 위해서 엄청나게 많은 리팩토링이 진행됐습니다. 리뷰어에게 먼저 도게자를 박겠습니다 허허
지원하는 언어는 한국어, 영어, 중국어, 베트남어입니다. 임의로 제가 교내에 가장 많을 것 같은 학생분이 사용할 것 같은 언어로 정한 것인데, 기획 회의에서 확실히 정해야합니다!
Describe your changes
Screen_recording_20251229_203557.mp4
1. UiText 도입
ViewModel에서 ApplicationContext를 주입받아 getString하는 더러운 코드를 완전히 개선했습니다.
UiText는 문자열 리소스를 감싸는 StringResource, 문자열을 감싸는 DynamicString, Empty로 나뉘는 sealed class입니다.
Toast를 보낼 때를 예시를 들면,
ViewModel에서는
처럼 UiText.StringResource로 ShowToast event를 만들어 emit하고
Activity에서는
showToast(event.message.asString(this), event.type)이렇게 현재 context에서 UiText를 문자열로 변환(asString)해서 보여주는 방식입니다.
이러한 수정에 맞게 UiEvent.ShowToast의 정의도 다음과 같이 변경되었습니다.
2. 하드 코딩된 한국어 문자열을 리소스를 사용하게 수정
하드 코딩된 부분의 양이 매우 많아 점진적으로 개선 중입니다. 한 90% 정도 된 것 같아요.
대부분 String Literal로 쓰인 한국어를 getString로, Compose의 경우 stringResource로 대체하는 Presentation 레이어의 리팩토링이지만, 이외에 특별히 확인해야 할 부분은 다음과 같습니다.
CollegeResponse, DepartmentResponse, UserCollegeDepartmentResponse의 toDomain 함수가 fallback 시 하드 코딩된 문자열을 사용하지 않게, fallback을 null로 변경했습니다. 이들을 호출하는 UserRepositoryImpl에서 null인 경우 무시하게끔 mapNotNull을 사용합니다.
메인 식단 화면 상단의 캘린더 중 weekday를 표시하는 CalendarAdapter.kt가 현재 언어에 대응하게 수정했습니다.
common enum인 ReportType과 Restaurant가 String Resource를 쓰게 정의를 바꾸었습니다. 이에 Restaurant.korean 처럼 하드 코딩된 문자열을 참조하는 모든 코드도 변경했어요.
strings.xml에서 레거시인 것 같은 수많은 entry를 삭제했습니다. 명확한 목적을 가진 경우 string name의 접두사로 toast_, notification_ 등을 붙여서 naming inconsistency를 해결했어요. 이외에도 사용 목적에 맞지 않거나 애매한 이름을 가진 entry를 수정했습니다.
3. 언어 변경 페이지 추가
디자인이 나오진 않았지만 기존 디자인 시스템을 사용해 만들었어요! 설정 기본 값은 시스템 언어라서 사용자 기기 언어 설정을 존중합니다.

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