-
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.
app/src/main/java/com/eatssu/android/presentation/mypage/userinfo/UserInfoViewModel.kt
Outdated
Show resolved
Hide resolved
app/src/main/java/com/eatssu/android/presentation/mypage/userinfo/UserInfoViewModel.kt
Outdated
Show resolved
Hide resolved
| // 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.
2 3 develop의 주요 변경사항 통합: 4 - Paging3 무한 스크롤 적용 5 - EventLogger 리뷰 작성 추적 6 - Authenticator 패턴 도입 7 - 레거시 리소스 파일 삭제 8 9 feat/i18n-safe의 변경사항 유지: 10 - UiText 기반 문자열 리소스화 11 - 구조화된 strings.xml 체계 12 - 다국어 지원 강화
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
Copilot reviewed 66 out of 70 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| tvCollege.text = data.selectedCollege?.collegeName ?: "단과대" | ||
| tvCollege.setTextColor( | ||
| getColor( | ||
| if (data.selectedCollege.collegeId != -1) R.color.gray700 else R.color.gray400 | ||
| if (data.selectedCollege != null) R.color.gray700 else R.color.gray400 | ||
| ) | ||
| ) | ||
|
|
||
| tvDepartment.text = data.selectedDepartment.departmentName | ||
| tvDepartment.text = data.selectedDepartment?.departmentName ?: "학과" |
Copilot
AI
Feb 1, 2026
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.
Hardcoded Korean strings should use string resources for consistency with multi-language support. The fallback values "단과대" and "학과" should reference the existing string resources R.string.college_placeholder and R.string.department_placeholder.
| MealWidgetContent( | ||
| "저녁", | ||
| listOf(listOf("밥", "국", "반찬", "음료")), | ||
| Restaurant.DODAM.toUiText().asString() | ||
| ) | ||
| } | ||
|
|
||
| @OptIn(ExperimentalGlancePreviewApi::class) | ||
| @Preview | ||
| @Composable | ||
| fun MealWidgetPreviewError() { | ||
| MealWidgetError("저녁", Restaurant.DODAM.korean, "에러임") | ||
| MealWidgetError("저녁", Restaurant.DODAM.toUiText().asString(), "에러임") |
Copilot
AI
Feb 1, 2026
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.
Preview functions contain hardcoded Korean strings ("저녁", "밥", "국", "반찬", "음료", "에러임") which should be replaced with mock English text or removed, as they won't work correctly in multi-language environments. Preview functions execute at design time when context is not available.
| if (departmentName.isBlank() || departmentName == placeholderDepartment) { | ||
| stringResource(labelResId) | ||
| } else { | ||
| departmentName | ||
| } |
Copilot
AI
Feb 1, 2026
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 comparison departmentName == placeholderDepartment compares the actual department name string with the localized placeholder text. This is fragile and will break when the language changes. Consider using a null or empty check instead, or using a sentinel value from the data layer rather than comparing UI strings.
| val nativeDisplayName: String | ||
| ) { | ||
| // SYSTEM("", "System Default", "시스템 언어"), // 다국어 재활성화 시 주석 해제 | ||
| KOREAN("ko", "Korean", "한국어"); |
Copilot
AI
Feb 1, 2026
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 semicolon on line 14 should be removed since KOREAN is currently the only active enum entry (the commented-out entries don't count). In Kotlin, a semicolon after the last enum entry is only needed when enum body members (like companion object) follow multiple entries.
Summary
하드코딩된 문자열을 대체하는 리팩토링을 진행했습니다.
Describe your changes
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. 하드 코딩된 한국어 문자열을 리소스를 사용하게 수정
대부분 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를 수정했습니다.
ValidateNicknameLocalUseCase.kt 과 UserInfoViewModel.kt 에서 하드코딩되어있던 닉네임 규칙 미준수 오류 메세지를 대체했습니다.
Issue