From d084705a7fbbb09e251c544e96dd4909b9a4b1ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A2=85=EC=88=98?= Date: Sun, 28 Jun 2026 16:46:23 +0900 Subject: [PATCH 01/15] =?UTF-8?q?Chip=20=EC=8A=A4=ED=8E=99=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20caption2,=2026dp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/eatssu/design_system/component/Chip.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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..9e510a3f8 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), // 아이콘 유무와 상관없이 동일한 높이 유지 (16dp 아이콘 + 상하 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 ) } From 90620417f172ac015758203740b9c1e738c6129b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A2=85=EC=88=98?= Date: Sun, 28 Jun 2026 16:49:47 +0900 Subject: [PATCH 02/15] =?UTF-8?q?Like=20=EC=8A=A4=ED=8E=99=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=2028=20->=2036?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/eatssu/design_system/component/Chip.kt | 2 +- .../main/java/com/eatssu/design_system/component/LikeButton.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 9e510a3f8..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 = 20.dp), // 아이콘 유무와 상관없이 동일한 높이 유지 (16dp 아이콘 + 상하 5dp + 라운드 여유) + .heightIn(min = 20.dp), // 아이콘 유무와 상관없이 동일한 높이 유지 (20dp 아이콘 + 상하 5dp + 라운드 여유) verticalAlignment = Alignment.CenterVertically ) { if (isLike) { 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( From 65af4c8cc5e7829bb842ecfef82c0cbc663463fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A2=85=EC=88=98?= Date: Sun, 28 Jun 2026 17:18:37 +0900 Subject: [PATCH 03/15] =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=AA=A9=EB=A1=9D?= =?UTF-8?q?=20=EC=8A=A4=ED=8E=99=20=EC=88=98=EC=A0=95=20=EC=97=AC=EB=B0=B1?= =?UTF-8?q?=EC=9D=80=20=EC=BB=B4=ED=8F=AC=EC=A6=88=20=EC=9E=90=EC=B2=B4=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=EB=A1=9C=20=EB=B6=80=EC=97=AC=20=EB=B6=80?= =?UTF-8?q?=EC=9E=90=EC=97=B0=EC=8A=A4=EB=9F=AC=EC=9A=B4=20=EC=A4=84?= =?UTF-8?q?=EB=B0=94=EA=BF=88=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/list/component/ReviewItem.kt | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) 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..ed80fa512 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,13 @@ 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.minimumInteractiveComponentSize import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -64,7 +64,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 +80,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 - ) } } From b08c1676ad5813131b5be4d02f805ccf4d5aed3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A2=85=EC=88=98?= Date: Sun, 28 Jun 2026 17:23:35 +0900 Subject: [PATCH 04/15] =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=ED=92=88=EB=AA=A9?= =?UTF-8?q?=20=EA=B0=84=20=EA=B0=AD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cafeteria/review/write/component/MenuLikeButtonItem.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 5d132cb8915789f3caffcc48727466139a12b460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A2=85=EC=88=98?= Date: Sun, 28 Jun 2026 17:52:54 +0900 Subject: [PATCH 05/15] =?UTF-8?q?=EC=8B=A0=EA=B3=A0=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?=ED=95=AD=EB=AA=A9=20=EA=B0=84=20=EA=B0=AD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/res/layout/activity_report.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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" From 78a31bf5ef700924c65e5269e12d69bb14d6b498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A2=85=EC=88=98?= Date: Sun, 28 Jun 2026 20:44:05 +0900 Subject: [PATCH 06/15] =?UTF-8?q?common=20=EB=AA=A8=EB=93=88=20=EB=8B=A4?= =?UTF-8?q?=EA=B5=AD=EC=96=B4=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/src/main/res/values-en/strings.xml | 31 +++++++++++++++++++ .../common/src/main/res/values-ja/strings.xml | 31 +++++++++++++++++++ .../common/src/main/res/values-vi/strings.xml | 31 +++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 core/common/src/main/res/values-en/strings.xml create mode 100644 core/common/src/main/res/values-ja/strings.xml create mode 100644 core/common/src/main/res/values-vi/strings.xml 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 + From da21950be9d567b37ce0702018a910db884819df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A2=85=EC=88=98?= Date: Sun, 28 Jun 2026 20:57:17 +0900 Subject: [PATCH 07/15] =?UTF-8?q?=EB=88=84=EB=9D=BD=20=EB=8B=A4=EA=B5=AD?= =?UTF-8?q?=EC=96=B4=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/res/values-en/strings.xml | 30 +-- app/src/main/res/values-ja/strings.xml | 92 +++---- app/src/main/res/values-vi/strings.xml | 342 ++++++++++++------------- 3 files changed, 232 insertions(+), 232 deletions(-) diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index a1296bd5e..488f443b8 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 @@ -169,7 +169,7 @@ Today\'s Menu Price - Rating → ★ + Student Cafeteria @@ -194,9 +194,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 +223,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 +261,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 +286,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,7 +297,7 @@ Hours Favorite Partners My Partners - 축제 + Festival All Department diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index b897ca8fd..8b8d804ca 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,11 +140,11 @@ 今日の食事はいかがでしたか? おすすめしたいメニューはありますか? - メニューについて詳しくレビューを書いてください + メニューについて詳しいレビューを書いてください 画面を準備しています。 投稿中... 更新中... - 写真をタップすると削除できます。 + 写真をタップすると削除されます。 写真を追加(%1$d/%2$d) レビュー設定 エラーが発生しました。 @@ -156,8 +156,8 @@ レビュー 自分のレビュー まだレビューがありません - 最初のレビューを書いてみましょう! - このメニューに最初のレビューを書いてみましょう! + 最初のレビューを投稿しましょう! + このメニューに最初のレビューを残しましょう! @@ -169,7 +169,7 @@ 今日のメニュー 価格 - 評価 + 学生食堂 @@ -180,7 +180,7 @@ 朝食 昼食 夕食 - 本日のメニューはありません。 + 今日のメニューはありません。 読み込み中 ネットワーク接続を確認してください。 設定が必要です @@ -194,9 +194,9 @@ ログアウト アカウント削除 アプリバージョン - 알림 및 활동 - 서비스 정보 - 기타 + Notifications and Activity + Service Info + など プッシュ通知設定 毎日午前11時に通知をお送りします。 @@ -208,7 +208,7 @@ - Kakao + カカオ 連携済みアカウント @@ -217,27 +217,27 @@ ニックネーム ニックネームを設定してください %1$d〜%2$d文字で入力してください。 - このニックネームは使用できます! + 使用できるニックネームです! ニックネーム編集 - 所属設定 + 学部・学科 情報を読み込めませんでした。 - 接続エラー - サーバーと通信できません。\n しばらくしてからもう一度お試しください。 + 通信エラー + サーバーと通信できません。\nしばらくしてからもう一度お試しください。 このレビューを通報する理由を選択してください。 同じレビューは24時間以内に1回のみ通報できます。 - 通報理由を入力してください + レビューを通報する理由を入力してください @@ -250,8 +250,8 @@ - 숭실대에서 - 숭실대에서 먹자 + 崇実大で + 食べよう @@ -261,24 +261,24 @@ プライバシーポリシー プライバシーポリシー 利用規約 - 약관 및 정책 - クレジット + 規約と方針 + 開発者 オープンソースライブラリ 本当にアカウントを削除しますか? - レビューは削除されず、(不明)として表示されます。\n 詳細は利用規約とプライバシーポリシーをご確認ください。 + 投稿したレビューは削除されず、(不明)として表示されます。\n詳細は利用規約およびプライバシーポリシーをご確認ください。 ニックネームを入力してください - 🤔 今日なに食べる? - 今日の学食メニューを確認しよう! + 🤔 今日何食べよう? + 今日の学食メニューをチェックしましょう! 昼食前通知 - 昼食時間前にプッシュ通知を送信します。 + 昼食前にプッシュ通知を送信します。 サーバー通知 EAT-SSUサーバーから送信された通知を表示します。 @@ -286,8 +286,8 @@ 学科を入力 - 学科を入力して\n 自分に合った提携情報を確認しましょう! - 学部/学科情報なし + 学科を入力して\n自分だけの提携特典を確認しましょう! + 学部・学科情報を確認できません @@ -295,9 +295,9 @@ 場所 備考 営業時間 - お気に入り提携先 - 自分の提携先 - 축제 + お気に入りの提携 + 自分の提携 + 祭り すべて 学科 diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 7ea7f3f49..a5eb0f032 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -11,238 +11,238 @@ - - - - - - - + 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. + 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 +250,56 @@ - 숭실대에서 - 먹자 + Ă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 From 88c70410c0659e7c15b8e260b111dfdee4928a20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A2=85=EC=88=98?= Date: Sun, 28 Jun 2026 20:57:41 +0900 Subject: [PATCH 08/15] =?UTF-8?q?=EB=8B=A4=EA=B5=AD=EC=96=B4=20Gradle=20co?= =?UTF-8?q?mmon=20xml=EB=8F=84=20=EB=B0=98=EC=98=81=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 45 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) 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 + ) + } + } + } } + + From 28f4e2e7004b843541d42d4d0e269971e3081d81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A2=85=EC=88=98?= Date: Sun, 28 Jun 2026 20:57:51 +0900 Subject: [PATCH 09/15] =?UTF-8?q?=EB=8B=A4=EA=B5=AD=EC=96=B4=20csv?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- language.csv | 406 +++++++++++++++++++++++---------------------------- 1 file changed, 179 insertions(+), 227 deletions(-) diff --git a/language.csv b/language.csv index 8c11ed1e0..9108b1a39 100644 --- a/language.csv +++ b/language.csv @@ -1,228 +1,180 @@ 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_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 \ No newline at end of file From b1fc012b7e5474506e95d3707dcdd5e85601d11a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A2=85=EC=88=98?= Date: Sun, 28 Jun 2026 21:05:23 +0900 Subject: [PATCH 10/15] =?UTF-8?q?=EC=83=81=EB=8B=A8=20=ED=86=A0=EA=B8=80?= =?UTF-8?q?=20'All=20|=20=ED=95=99=EA=B3=BC'=20->=20'=ED=95=99=EA=B3=BC=20?= =?UTF-8?q?|=20All'=20=EC=88=9C=EC=84=9C=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/eatssu/android/presentation/map/MapViewModel.kt | 2 +- .../android/presentation/map/component/PartnershipToggleItem.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 From b32c3d6b04b8f65fcb6726392df8a1e66e084ea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A2=85=EC=88=98?= Date: Sun, 28 Jun 2026 21:20:16 +0900 Subject: [PATCH 11/15] =?UTF-8?q?=ED=83=88=ED=87=B4=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EA=B8=80=EC=9E=90=EA=B0=80=20=EC=9E=98?= =?UTF-8?q?=EB=A0=A4=EB=B3=B4=EC=9D=BC=20=EC=88=98=20=EC=9E=88=EB=8A=94=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/res/layout/activity_sign_out.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 @@ Date: Sun, 28 Jun 2026 21:20:22 +0900 Subject: [PATCH 12/15] =?UTF-8?q?Instagram=20=EB=B2=88=EC=97=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/res/values-en/strings.xml | 1 + app/src/main/res/values-ja/strings.xml | 1 + app/src/main/res/values-vi/strings.xml | 1 + app/src/main/res/values/strings.xml | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 488f443b8..75e302b8b 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -300,6 +300,7 @@ 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 8b8d804ca..095ae4b71 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -300,6 +300,7 @@ 祭り すべて 学科 + EAT-SSU Instagram diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index a5eb0f032..e210649b8 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -300,6 +300,7 @@ 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..7ce5cf535 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -306,7 +306,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/ From d138186d20c02f66aafdf21258df111ed5372b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=EC=9E=AC=EC=B0=AC?= Date: Sun, 12 Jul 2026 21:22:31 +0900 Subject: [PATCH 13/15] =?UTF-8?q?feat:=20=EB=A6=AC=EB=B7=B0=20=EB=B2=88?= =?UTF-8?q?=EC=97=AD=20=EA=B8=B0=EB=8A=A5=EC=9D=84=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=B4=EC=9A=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/ReviewTranslationResponse.kt | 24 +++++++++ .../remote/repository/ReviewRepositoryImpl.kt | 7 +++ .../data/remote/service/ReviewService.kt | 7 +++ .../android/domain/model/ReviewTranslation.kt | 10 ++++ .../domain/repository/ReviewRepository.kt | 6 +++ .../review/GetReviewTranslationUseCase.kt | 14 ++++++ .../cafeteria/review/list/ReviewListScreen.kt | 31 +++++++++++- .../review/list/ReviewListViewModel.kt | 48 ++++++++++++++++++ .../review/list/component/ReviewItem.kt | 36 ++++++++++++- .../translation/ReviewTranslationLanguage.kt | 20 ++++++++ .../translation/ReviewTranslationUiState.kt | 7 +++ .../mypage/myreview/MyReviewListScreen.kt | 23 +++++++++ .../mypage/myreview/MyReviewViewModel.kt | 50 ++++++++++++++++++- app/src/main/res/values-en/strings.xml | 4 ++ app/src/main/res/values-ja/strings.xml | 4 ++ app/src/main/res/values-vi/strings.xml | 4 ++ app/src/main/res/values/strings.xml | 4 ++ .../ReviewRepositoryImplBehaviorSpec.kt | 32 ++++++++++++ .../ReviewDelegatingUseCasesBehaviorSpec.kt | 24 +++++++++ .../list/ReviewListViewModelBehaviorSpec.kt | 37 ++++++++++++-- .../myreview/MyReviewViewModelBehaviorSpec.kt | 37 ++++++++++++-- language.csv | 6 ++- 22 files changed, 420 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/com/eatssu/android/data/remote/dto/response/ReviewTranslationResponse.kt create mode 100644 app/src/main/java/com/eatssu/android/domain/model/ReviewTranslation.kt create mode 100644 app/src/main/java/com/eatssu/android/domain/usecase/review/GetReviewTranslationUseCase.kt create mode 100644 app/src/main/java/com/eatssu/android/presentation/cafeteria/review/translation/ReviewTranslationLanguage.kt create mode 100644 app/src/main/java/com/eatssu/android/presentation/cafeteria/review/translation/ReviewTranslationUiState.kt 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 ed80fa512..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 @@ -16,6 +16,7 @@ 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 @@ -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)) { @@ -131,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") { @@ -200,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/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/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 75e302b8b..a52dbc4b1 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -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 diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 095ae4b71..99bfd5e89 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -148,6 +148,10 @@ 写真を追加(%1$d/%2$d) レビュー設定 エラーが発生しました。 + 翻訳を見る + 原文を見る + 翻訳中... + レビューの翻訳に失敗しました。 レビューを書く diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index e210649b8..ba1ebe9c9 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -148,6 +148,10 @@ 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á diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7ce5cf535..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) 리뷰 설정 에러가 발생했습니다. + 번역 보기 + 원문 보기 + 번역 중... + 리뷰 번역에 실패했습니다. 리뷰 작성하기 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/language.csv b/language.csv index 9108b1a39..5c3c1840e 100644 --- a/language.csv +++ b/language.csv @@ -86,6 +86,10 @@ review_photo_delete_hint,"사진 클릭 시, 삭제됩니다.",Tap a photo to de 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 @@ -177,4 +181,4 @@ recruiting_url,https://eat-ssu.notion.site/1d2eeef75a1681ae800cf6ffa6faa37d?pvs= ,리뷰 작성 취지에 맞지 않는 내용 (복사글 등),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 \ No newline at end of file +,리뷰 신고 사유를 알려주세요,Tell us why you are reporting this review,レビューを通報する理由を教えてください,Vui lòng cho biết lý do báo cáo đánh giá này From a41e5a6d63699dd0575c233cee94069f4c8b8385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A2=85=EC=88=98?= Date: Sun, 19 Jul 2026 20:14:37 +0900 Subject: [PATCH 14/15] =?UTF-8?q?Posthog=20QA=20-=20=EC=98=A4=ED=83=88?= =?UTF-8?q?=EC=9E=90=20=EC=88=98=EC=A0=95=20-=20=EC=9D=B8=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EA=B7=B8=EB=9E=A8=20=EC=B6=94=EC=A0=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20-=20=EB=B2=A0=ED=8A=B8=EB=82=A8=EC=96=B4=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EC=B6=94=EC=A0=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eatssu/android/presentation/mypage/MyPageFragment.kt | 4 +++- .../java/com/eatssu/common/analytics/AnalyticsEvent.kt | 2 +- .../src/main/java/com/eatssu/common/enums/AppLanguage.kt | 7 ++++--- 3 files changed, 8 insertions(+), 5 deletions(-) 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/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 { From 41ba839b0e1a0afa2cfca833429e85d039ca22ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A2=85=EC=88=98?= Date: Sun, 19 Jul 2026 20:34:31 +0900 Subject: [PATCH 15/15] =?UTF-8?q?=EC=8B=9D=EB=8B=B9=20=EC=9D=B8=ED=8F=AC?= =?UTF-8?q?=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../res/layout/fragment_bottomsheet_info.xml | 110 ++++++++---------- 1 file changed, 47 insertions(+), 63 deletions(-) diff --git a/app/src/main/res/layout/fragment_bottomsheet_info.xml b/app/src/main/res/layout/fragment_bottomsheet_info.xml index 652b066b1..c30b0c633 100644 --- a/app/src/main/res/layout/fragment_bottomsheet_info.xml +++ b/app/src/main/res/layout/fragment_bottomsheet_info.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" - android:padding="16dp" + android:padding="20dp" app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"> + - + + + - + 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 +