From 0b0b6831886d097f048aab50ec526d0f4882efa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=8A=E3=81=8A=E3=81=A1=E3=82=93?= <118029.ichikama@gmail.com> Date: Sun, 9 Nov 2025 03:27:45 +0900 Subject: [PATCH] chore: localization --- ios/escape/escape/Localizable.xcstrings | 1094 ++++++++++++++--- .../Components/Home/MissionSection.swift | 2 +- .../Components/Home/StatsComponents.swift | 4 +- .../Views/Components/Map/CompleteView.swift | 20 +- .../Views/Components/Rating/RatingCard.swift | 4 +- .../Components/Rating/RatingFormView.swift | 12 +- .../Components/Rating/RatingSummaryCard.swift | 10 +- .../Rating/ShelterReviewsView.swift | 12 +- .../Shared/ActivityHeatmapView.swift | 20 +- .../Shared/UserProfileBottomSheetView.swift | 20 +- 10 files changed, 986 insertions(+), 212 deletions(-) diff --git a/ios/escape/escape/Localizable.xcstrings b/ios/escape/escape/Localizable.xcstrings index 09909a8..c149ed8 100644 --- a/ios/escape/escape/Localizable.xcstrings +++ b/ios/escape/escape/Localizable.xcstrings @@ -49,17 +49,9 @@ } } }, - "(edited)": { - "comment": "A text indicating that a rating was edited by the user.", - "isCommentAutoGenerated": true - }, "+%lld": {}, "ABCD1234": {}, "AI will automatically generate the description and color theme": {}, - "All Reviews": { - "comment": "A label displayed above the list of all reviews.", - "isCommentAutoGenerated": true - }, "Badge No. %@": { "extractionState": "manual", "localizations": { @@ -77,14 +69,6 @@ } } }, - "Be the first to review this shelter": { - "comment": "A text that appears below a star icon in the \"No reviews yet\" state of the rating summary card, encouraging users to leave a review.", - "isCommentAutoGenerated": true - }, - "Cancel": { - "comment": "A button that dismisses an edit rating sheet.", - "isCommentAutoGenerated": true - }, "Controls how close you need to be to a shelter to trigger detection.": { "comment": "A description of the functionality of the \"Show Radius Area on Map\" toggle.", "isCommentAutoGenerated": true @@ -94,26 +78,12 @@ "comment": "A label describing the current state of the mission.", "isCommentAutoGenerated": true }, - "Delete Rating": { - "comment": "A button label that allows a user to delete their own shelter rating.", - "isCommentAutoGenerated": true - }, - "Disaster Type": {}, "Disaster Type (optional)": {}, "Disaster Type:": {}, "Displays a blue circle around your location showing the detection radius": { "comment": "A tooltip explaining that the \"Show Radius Area on Map\" feature displays a blue circle around the user's location, indicating the detection radius.", "isCommentAutoGenerated": true }, - "Distance": {}, - "Edit Rating": { - "comment": "The title of the sheet used to edit a shelter rating.", - "isCommentAutoGenerated": true - }, - "Edit Your Rating": { - "comment": "A title for the edit rating form.", - "isCommentAutoGenerated": true - }, "Error": {}, "Generate Mission": { "comment": "A button label that generates a mission.", @@ -164,20 +134,13 @@ "comment": "The header for the section related to map settings in the developer view.", "isCommentAutoGenerated": true }, - "Mission Complete!": {}, - "Mission Completed": {}, "Mission Generator": {}, "Mission Parameters": {}, - "Mission Summary": {}, "Mission context (optional)": {}, "No missions found": { "comment": "A message displayed when no missions are found in the system.", "isCommentAutoGenerated": true }, - "No reviews yet": { - "comment": "A message displayed when a shelter has no ratings.", - "isCommentAutoGenerated": true - }, "None": {}, "OK": { "comment": "The text on an OK button.", @@ -189,18 +152,8 @@ }, "Overview:": {}, "Period": {}, - "Points Earned": {}, "Radius: %lld m": {}, "Random": {}, - "Rate This Shelter": {}, - "Rating": { - "comment": "A label describing the rating they can give for a shelter.", - "isCommentAutoGenerated": true - }, - "Ratings & Reviews": { - "comment": "A heading that describes the ratings and reviews section of a view.", - "isCommentAutoGenerated": true - }, "Refresh Latest Mission": {}, "Reloads your most recent mission from the database": {}, "Reset Mission": { @@ -211,42 +164,18 @@ "comment": "A button that resets a developer setting to its default value.", "isCommentAutoGenerated": true }, - "Retry": {}, - "Return to Map": {}, - "Review": { - "comment": "A singular label for a review.", - "isCommentAutoGenerated": true - }, - "Review (Optional)": { - "comment": "A label for a text field where users can leave a review about a shelter.", - "isCommentAutoGenerated": true - }, - "Review must be 500 characters or less": { - "comment": "An error message that appears when a shelter rating review exceeds 500 characters.", - "isCommentAutoGenerated": true - }, - "Reviews": { - "comment": "A noun that can refer to a collection of reviews.", - "isCommentAutoGenerated": true - }, - "Share Results": {}, - "Shelter Information": {}, "Show Radius Area on Map": { "comment": "A toggle that allows users to show a blue circle around their location indicating the detection radius.", "isCommentAutoGenerated": true }, "Status:": {}, - "Steps": {}, - "Tap a star to rate": { - "comment": "A text that appears inside the star rating view, instructing the user to tap a star to rate.", - "isCommentAutoGenerated": true - }, "Test: %@": {}, "These settings persist across app launches and affect shelter detection behavior.": { "comment": "A footer text under the developer settings section of the developer view, explaining that the settings persist across app launches and affect shelter detection behavior.", "isCommentAutoGenerated": true }, "Title:": {}, + "Try Again": {}, "Update Mission Status in Supabase": { "comment": "A button that updates the status of the current mission in the Supabase database.", "isCommentAutoGenerated": true @@ -265,15 +194,61 @@ "comment": "The label for the \"Yahoo Mail\" option in the \"Open Mail\" menu.", "isCommentAutoGenerated": true }, - "You've reached the shelter safely": {}, - "Your Rating": { - "comment": "A label displayed above the user's rating section.", - "isCommentAutoGenerated": true - }, "Your rating:": { "comment": "A label above the star rating input field.", "isCommentAutoGenerated": true }, + "activity.legend.less": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Less" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "少" + } + } + } + }, + "activity.legend.more": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "More" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "多" + } + } + } + }, + "activity.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Activity in the last 30 days" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "過去30日間のアクティビティ" + } + } + } + }, "auth.already_have_account": { "extractionState": "manual", "localizations": { @@ -1055,6 +1030,74 @@ } } }, + "common.anonymous": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Anonymous" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "匿名ユーザー" + } + } + } + }, + "common.cancel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "キャンセル" + } + } + } + }, + "common.edited": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "(edited)" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "(編集済み)" + } + } + } + }, + "common.retry": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Retry" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "再試行" + } + } + } + }, "creating (in progress)": { "comment": "A mission status option that indicates a mission is currently in progress.", "isCommentAutoGenerated": true @@ -3446,6 +3489,23 @@ } } }, + "home.stats.mission_completed": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mission Completed" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ミッション完了" + } + } + } + }, "home.stats.mission_history": { "extractionState": "manual", "localizations": { @@ -4311,332 +4371,502 @@ } } }, - "mission.detail.beginMission": { - "extractionState": "manual" - }, - "nav.dev": { + "mission.complete.disaster_type": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "Dev" + "value": "Disaster Type" } }, "ja": { "stringUnit": { "state": "translated", - "value": "開発" + "value": "災害種別" } } } }, - "nav.home": { + "mission.complete.distance": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "Home" + "value": "Distance" } }, "ja": { "stringUnit": { "state": "translated", - "value": "ホーム" + "value": "距離" } } } }, - "nav.map": { + "mission.complete.mission_summary": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "Map" + "value": "Mission Summary" } }, "ja": { "stringUnit": { "state": "translated", - "value": "マップ" + "value": "ミッション概要" } } } }, - "nav.setting": { + "mission.complete.points_earned": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "Setting" + "value": "Points Earned" } }, "ja": { "stringUnit": { "state": "translated", - "value": "設定" + "value": "獲得ポイント" } } } }, - "none (no mission)": { - "comment": "A case label for the \"none\" mission status.", - "isCommentAutoGenerated": true - }, - "profile.profile": { + "mission.complete.return_to_map": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "Profile" + "value": "Return to Map" } }, "ja": { "stringUnit": { "state": "translated", - "value": "プロフィール" + "value": "マップに戻る" } } } }, - "profile.sign_out": { + "mission.complete.share_results": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "Sign out" + "value": "Share Results" } }, "ja": { "stringUnit": { "state": "translated", - "value": "サインアウト" + "value": "結果を共有" } } } }, - "profile.update_profile": { + "mission.complete.shelter_info": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "Update profile" + "value": "Shelter Information" } }, "ja": { "stringUnit": { "state": "translated", - "value": "プロフィールを更新" + "value": "避難所情報" } } } }, - "profile.username": { + "mission.complete.steps": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "Username" + "value": "Steps" } }, "ja": { "stringUnit": { "state": "translated", - "value": "ユーザー名" + "value": "歩数" } } } }, - "ranking.champion": { + "mission.complete.subtitle": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "Champion" + "value": "You've reached the shelter safely" } }, "ja": { "stringUnit": { "state": "translated", - "value": "チャンピオン" + "value": "避難所に安全に到着しました" } } } }, - "ranking.complete_mission_prompt": { + "mission.complete.title": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "Complete missions to appear on the leaderboard!" + "value": "Mission Complete!" } }, "ja": { "stringUnit": { "state": "translated", - "value": "ミッションをクリアしてランキングに参加しよう!" + "value": "ミッション完了!" } } } }, - "ranking.join_team_to_compete": { + "mission.detail.beginMission": { + "extractionState": "manual" + }, + "nav.dev": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "Join a Group to see Group rankings" + "value": "Dev" } }, "ja": { "stringUnit": { "state": "translated", - "value": "グループに参加してランキングを見よう" + "value": "開発" } } } }, - "ranking.loading": { + "nav.home": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "Loading Rankings..." + "value": "Home" } }, "ja": { "stringUnit": { "state": "translated", - "value": "ランキングを読み込み中..." + "value": "ホーム" } } } }, - "ranking.members": { + "nav.map": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "members" + "value": "Map" } }, "ja": { "stringUnit": { "state": "translated", - "value": "人" + "value": "マップ" } } } }, - "ranking.national": { + "nav.setting": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "National" + "value": "Setting" } }, "ja": { "stringUnit": { "state": "translated", - "value": "全国" + "value": "設定" } } } }, - "ranking.national_rank_format": { + "none (no mission)": { + "comment": "A case label for the \"none\" mission status.", + "isCommentAutoGenerated": true + }, + "profile.profile": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "#%d" + "value": "Profile" } }, "ja": { "stringUnit": { "state": "translated", - "value": "全国%d位" + "value": "プロフィール" } } } }, - "ranking.no_data": { + "profile.sign_out": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "No ranking data available" + "value": "Sign out" } }, "ja": { "stringUnit": { "state": "translated", - "value": "ランキングデータがありません" + "value": "サインアウト" } } } }, - "ranking.no_rankings_yet": { + "profile.update_profile": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "No Rankings Yet" + "value": "Update profile" } }, "ja": { "stringUnit": { "state": "translated", - "value": "まだランキングがありません" + "value": "プロフィールを更新" } } } }, - "ranking.no_team_description": { + "profile.username": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "Join or create a Group to compete with others and climb the group rankings!" + "value": "Username" } }, "ja": { "stringUnit": { "state": "translated", - "value": "グループに参加または作成して、他のプレイヤーと競い合い、グループランキングを駆け上がろう!" + "value": "ユーザー名" } } } }, - "ranking.no_team_preview": { + "ranking.champion": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "No Group" + "value": "Champion" } }, "ja": { "stringUnit": { "state": "translated", - "value": "グループ未加入" + "value": "チャンピオン" + } + } + } + }, + "ranking.complete_mission_prompt": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Complete missions to appear on the leaderboard!" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ミッションをクリアしてランキングに参加しよう!" + } + } + } + }, + "ranking.join_team_to_compete": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Join a Group to see Group rankings" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "グループに参加してランキングを見よう" + } + } + } + }, + "ranking.loading": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading Rankings..." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ランキングを読み込み中..." + } + } + } + }, + "ranking.members": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "members" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "人" + } + } + } + }, + "ranking.national": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "National" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "全国" + } + } + } + }, + "ranking.national_rank_format": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "#%d" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "全国%d位" + } + } + } + }, + "ranking.no_data": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No ranking data available" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ランキングデータがありません" + } + } + } + }, + "ranking.no_rankings_yet": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No Rankings Yet" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "まだランキングがありません" + } + } + } + }, + "ranking.no_team_description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Join or create a Group to compete with others and climb the group rankings!" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "グループに参加または作成して、他のプレイヤーと競い合い、グループランキングを駆け上がろう!" + } + } + } + }, + "ranking.no_team_preview": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No Group" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "グループ未加入" } } } @@ -4896,6 +5126,40 @@ } } }, + "rating.all_reviews": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "All Reviews" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "すべてのレビュー" + } + } + } + }, + "rating.be_first_to_review": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Be the first to review this shelter" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "この避難所に最初のレビューを書きましょう" + } + } + } + }, "rating.button.submit": { "extractionState": "manual", "localizations": { @@ -4947,6 +5211,57 @@ } } }, + "rating.delete_rating": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Delete Rating" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "評価を削除" + } + } + } + }, + "rating.edit_rating": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Edit Rating" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "評価を編集" + } + } + } + }, + "rating.edit_your_rating": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Edit Your Rating" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "評価を編集" + } + } + } + }, "rating.empty.no_ratings_yet": { "extractionState": "manual", "localizations": { @@ -5070,88 +5385,275 @@ "ja": { "stringUnit": { "state": "translated", - "value": "レビューは500文字以内で入力してください" + "value": "レビューは500文字以内で入力してください" + } + } + } + }, + "rating.error.shelter_not_found": { + "comment": "Error message when a shelter with the given ID is not found in the database.", + "extractionState": "extracted_with_value", + "isCommentAutoGenerated": true, + "localizations": { + "en": { + "stringUnit": { + "state": "new", + "value": "Shelter not found" + } + } + } + }, + "rating.error.submit_failed": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Failed to submit rating. Please try again." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "評価の送信に失敗しました。もう一度お試しください。" + } + } + } + }, + "rating.no_reviews_be_first": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No reviews yet. Be the first to review this shelter!" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "まだレビューがありません。この避難所への最初のレビューを書きましょう!" + } + } + } + }, + "rating.no_reviews_yet": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No reviews yet" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "まだレビューがありません" + } + } + } + }, + "rating.permission.can_rate": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "You can rate this shelter!" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "この避難所を評価できます!" + } + } + } + }, + "rating.permission.has_rating": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "You have rated this shelter" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "この避難所を評価済みです" + } + } + } + }, + "rating.permission.no_badge": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Visit this shelter and collect its badge to leave a rating" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "この避難所を訪問してバッジを集めると評価を投稿できます" + } + } + } + }, + "rating.rate_this_shelter": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rate This Shelter" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "この避難所を評価" + } + } + } + }, + "rating.rating_label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rating" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "評価" + } + } + } + }, + "rating.ratings_and_reviews": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Ratings & Reviews" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "評価とレビュー" + } + } + } + }, + "rating.review_lowercase_plural": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "reviews" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "レビュー" } } } }, - "rating.error.shelter_not_found": { - "comment": "Error message when a shelter with the given ID is not found in the database.", - "extractionState": "extracted_with_value", - "isCommentAutoGenerated": true, + "rating.review_lowercase_singular": { + "extractionState": "manual", "localizations": { "en": { "stringUnit": { - "state": "new", - "value": "Shelter not found" + "state": "translated", + "value": "review" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "レビュー" } } } }, - "rating.error.submit_failed": { + "rating.review_optional": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "Failed to submit rating. Please try again." + "value": "Review (Optional)" } }, "ja": { "stringUnit": { "state": "translated", - "value": "評価の送信に失敗しました。もう一度お試しください。" + "value": "レビュー(任意)" } } } }, - "rating.permission.can_rate": { + "rating.review_plural": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "You can rate this shelter!" + "value": "Reviews" } }, "ja": { "stringUnit": { "state": "translated", - "value": "この避難所を評価できます!" + "value": "レビュー" } } } }, - "rating.permission.has_rating": { + "rating.review_singular": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "You have rated this shelter" + "value": "Review" } }, "ja": { "stringUnit": { "state": "translated", - "value": "この避難所を評価済みです" + "value": "レビュー" } } } }, - "rating.permission.no_badge": { + "rating.review_too_long": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "Visit this shelter and collect its badge to leave a rating" + "value": "Review must be 500 characters or less" } }, "ja": { "stringUnit": { "state": "translated", - "value": "この避難所を訪問してバッジを集めると評価を投稿できます" + "value": "レビューは500文字以内で入力してください" } } } @@ -5224,6 +5726,40 @@ } } }, + "rating.tap_star_to_rate": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tap a star to rate" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "星をタップして評価" + } + } + } + }, + "rating.your_rating": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Your Rating" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "あなたの評価" + } + } + } + }, "result.address": { "extractionState": "manual", "localizations": { @@ -6443,6 +6979,244 @@ } } }, + "userprofile.badge_collection": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Badge Collection" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "バッジコレクション" + } + } + } + }, + "userprofile.error_title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Error Loading Profile" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "プロフィール読み込みエラー" + } + } + } + }, + "userprofile.loading": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading profile..." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "プロフィールを読み込み中..." + } + } + } + }, + "userprofile.no_badges": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No badges collected yet" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "まだバッジを獲得していません" + } + } + } + }, + "userprofile.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "User Profile" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ユーザープロフィール" + } + } + } + }, + "userprofile.user_not_found": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "User Not Found" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ユーザーが見つかりません" + } + } + } + }, + "userprofile.user_not_found_description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This user profile could not be loaded." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このユーザーのプロフィールを読み込めませんでした。" + } + } + } + }, + "weekday.fri": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Fri" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "金" + } + } + } + }, + "weekday.mon": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mon" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "月" + } + } + } + }, + "weekday.sat": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sat" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "土" + } + } + } + }, + "weekday.sun": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sun" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "日" + } + } + } + }, + "weekday.thu": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Thu" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "木" + } + } + } + }, + "weekday.tue": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tue" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "火" + } + } + } + }, + "weekday.wed": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Wed" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "水" + } + } + } + }, "×%@": {}, "•••": {} }, diff --git a/ios/escape/escape/Views/Components/Home/MissionSection.swift b/ios/escape/escape/Views/Components/Home/MissionSection.swift index c27751f..6a389ad 100644 --- a/ios/escape/escape/Views/Components/Home/MissionSection.swift +++ b/ios/escape/escape/Views/Components/Home/MissionSection.swift @@ -31,7 +31,7 @@ struct MissionSection: View { .font(.caption) .foregroundColor(.secondary) .multilineTextAlignment(.center) - Button("Retry") { + Button(String(localized: "common.retry", table: "Localizable")) { loadCurrentMission() } .buttonStyle(.bordered) diff --git a/ios/escape/escape/Views/Components/Home/StatsComponents.swift b/ios/escape/escape/Views/Components/Home/StatsComponents.swift index cde2d84..4c504fa 100644 --- a/ios/escape/escape/Views/Components/Home/StatsComponents.swift +++ b/ios/escape/escape/Views/Components/Home/StatsComponents.swift @@ -41,7 +41,7 @@ struct StatsView: View { .font(.caption) .foregroundColor(.secondary) .multilineTextAlignment(.center) - Button("Retry") { + Button(String(localized: "common.retry", table: "Localizable")) { loadRecentMissions() } .buttonStyle(.bordered) @@ -534,7 +534,7 @@ struct MissionHistoryRow: View { var body: some View { HStack { VStack(alignment: .leading, spacing: 4) { - Text("Mission Completed") + Text("home.stats.mission_completed", tableName: "Localizable") .font(.subheadline) .fontWeight(.medium) diff --git a/ios/escape/escape/Views/Components/Map/CompleteView.swift b/ios/escape/escape/Views/Components/Map/CompleteView.swift index 29c9921..0fcd4e9 100644 --- a/ios/escape/escape/Views/Components/Map/CompleteView.swift +++ b/ios/escape/escape/Views/Components/Map/CompleteView.swift @@ -24,11 +24,11 @@ struct CompleteView: View { .font(.system(size: 80)) .foregroundColor(.green) - Text("Mission Complete!") + Text("mission.complete.title", tableName: "Localizable") .font(.title) .fontWeight(.bold) - Text("You've reached the shelter safely") + Text("mission.complete.subtitle", tableName: "Localizable") .font(.subheadline) .foregroundColor(.secondary) .multilineTextAlignment(.center) @@ -37,7 +37,7 @@ struct CompleteView: View { // Shelter Information Card VStack(alignment: .leading, spacing: 16) { - Text("Shelter Information") + Text("mission.complete.shelter_info", tableName: "Localizable") .font(.headline) .fontWeight(.semibold) @@ -109,7 +109,7 @@ struct CompleteView: View { // Mission Stats (if available) if let mission = missionStateService.currentMission { VStack(alignment: .leading, spacing: 16) { - Text("Mission Summary") + Text("mission.complete.mission_summary", tableName: "Localizable") .font(.headline) .fontWeight(.semibold) @@ -118,7 +118,7 @@ struct CompleteView: View { HStack { Image(systemName: disasterType.emergencyIcon) .foregroundColor(disasterType.color) - Text("Disaster Type") + Text("mission.complete.disaster_type", tableName: "Localizable") .foregroundColor(.secondary) Spacer() Text(disasterType.localizedName) @@ -131,7 +131,7 @@ struct CompleteView: View { HStack { Image(systemName: "figure.walk") .foregroundColor(.orange) - Text("Steps") + Text("mission.complete.steps", tableName: "Localizable") .foregroundColor(.secondary) Spacer() Text("\(steps)") @@ -143,7 +143,7 @@ struct CompleteView: View { HStack { Image(systemName: "map") .foregroundColor(.blue) - Text("Distance") + Text("mission.complete.distance", tableName: "Localizable") .foregroundColor(.secondary) Spacer() Text(String(format: "%.2f km", distance / 1000)) @@ -155,7 +155,7 @@ struct CompleteView: View { HStack { Image(systemName: "star.fill") .foregroundColor(.yellow) - Text("Points Earned") + Text("mission.complete.points_earned", tableName: "Localizable") .foregroundColor(.secondary) Spacer() Text("\(points)") @@ -177,7 +177,7 @@ struct CompleteView: View { Button(action: { dismiss() }) { - Text("Return to Map") + Text("mission.complete.return_to_map", tableName: "Localizable") .font(.headline) .foregroundColor(.white) .frame(maxWidth: .infinity) @@ -190,7 +190,7 @@ struct CompleteView: View { // TODO: Share or save mission report print("Share mission report") }) { - Text("Share Results") + Text("mission.complete.share_results", tableName: "Localizable") .font(.headline) .foregroundColor(.blue) .frame(maxWidth: .infinity) diff --git a/ios/escape/escape/Views/Components/Rating/RatingCard.swift b/ios/escape/escape/Views/Components/Rating/RatingCard.swift index 1e6a74c..b2d51dc 100644 --- a/ios/escape/escape/Views/Components/Rating/RatingCard.swift +++ b/ios/escape/escape/Views/Components/Rating/RatingCard.swift @@ -53,7 +53,7 @@ struct RatingCard: View { StarRatingView.compact(rating: Double(ratingWithUser.rating.rating)) if ratingWithUser.rating.wasEdited { - Text("(edited)") + Text("common.edited", tableName: "Localizable") .font(.caption2) .foregroundColor(.secondary) } @@ -128,7 +128,7 @@ struct RatingCardSkeleton: View { struct RatingEmptyState: View { let message: String - init(message: String = "No reviews yet. Be the first to review this shelter!") { + init(message: String = String(localized: "rating.no_reviews_be_first", table: "Localizable")) { self.message = message } diff --git a/ios/escape/escape/Views/Components/Rating/RatingFormView.swift b/ios/escape/escape/Views/Components/Rating/RatingFormView.swift index 0efccff..e1cbd50 100644 --- a/ios/escape/escape/Views/Components/Rating/RatingFormView.swift +++ b/ios/escape/escape/Views/Components/Rating/RatingFormView.swift @@ -26,12 +26,12 @@ struct RatingFormView: View { var body: some View { VStack(alignment: .leading, spacing: 20) { // Section header - Text(isEditing ? "Edit Your Rating" : "Rate This Shelter") + Text(isEditing ? String(localized: "rating.edit_your_rating", table: "Localizable") : String(localized: "rating.rate_this_shelter", table: "Localizable")) .font(.headline) // Star rating picker VStack(alignment: .leading, spacing: 8) { - Text("Rating") + Text("rating.rating_label", tableName: "Localizable") .font(.subheadline) .foregroundColor(.secondary) @@ -43,7 +43,7 @@ struct RatingFormView: View { ) if viewModel.formState.rating == 0 { - Text("Tap a star to rate") + Text("rating.tap_star_to_rate", tableName: "Localizable") .font(.caption) .foregroundColor(.secondary) } @@ -51,7 +51,7 @@ struct RatingFormView: View { // Review text field VStack(alignment: .leading, spacing: 8) { - Text("Review (Optional)") + Text("rating.review_optional", tableName: "Localizable") .font(.subheadline) .foregroundColor(.secondary) @@ -79,7 +79,7 @@ struct RatingFormView: View { } if viewModel.formState.isReviewTooLong { - Text("Review must be 500 characters or less") + Text("rating.review_too_long", tableName: "Localizable") .font(.caption) .foregroundColor(.red) } @@ -121,7 +121,7 @@ struct RatingFormView: View { .progressViewStyle(CircularProgressViewStyle(tint: .red)) } - Text("Delete Rating") + Text("rating.delete_rating", tableName: "Localizable") .fontWeight(.semibold) } .frame(maxWidth: .infinity) diff --git a/ios/escape/escape/Views/Components/Rating/RatingSummaryCard.swift b/ios/escape/escape/Views/Components/Rating/RatingSummaryCard.swift index 0164dd1..f6daf68 100644 --- a/ios/escape/escape/Views/Components/Rating/RatingSummaryCard.swift +++ b/ios/escape/escape/Views/Components/Rating/RatingSummaryCard.swift @@ -55,7 +55,7 @@ struct RatingSummaryCard: View { private func fullView(summary: ShelterRatingSummary) -> some View { VStack(alignment: .leading, spacing: 12) { HStack { - Text("Ratings & Reviews") + Text("rating.ratings_and_reviews", tableName: "Localizable") .font(.headline) Spacer() } @@ -79,7 +79,7 @@ struct RatingSummaryCard: View { .font(.title2) .fontWeight(.semibold) - Text(summary.totalRatings == 1 ? "Review" : "Reviews") + Text(summary.totalRatings == 1 ? String(localized: "rating.review_singular", table: "Localizable") : String(localized: "rating.review_plural", table: "Localizable")) .font(.caption) .foregroundColor(.secondary) } @@ -101,7 +101,7 @@ struct RatingSummaryCard: View { StarRatingView.compact(rating: summary.averageRating) - Text("(\(summary.totalRatings) \(summary.totalRatings == 1 ? "review" : "reviews"))") + Text("(\(summary.totalRatings) \(summary.totalRatings == 1 ? String(localized: "rating.review_lowercase_singular", table: "Localizable") : String(localized: "rating.review_lowercase_plural", table: "Localizable")))") .font(.subheadline) .foregroundColor(.secondary) } @@ -114,11 +114,11 @@ struct RatingSummaryCard: View { .font(.title2) .foregroundColor(.secondary) - Text("No reviews yet") + Text("rating.no_reviews_yet", tableName: "Localizable") .font(.subheadline) .foregroundColor(.secondary) - Text("Be the first to review this shelter") + Text("rating.be_first_to_review", tableName: "Localizable") .font(.caption) .foregroundColor(.secondary) } diff --git a/ios/escape/escape/Views/Components/Rating/ShelterReviewsView.swift b/ios/escape/escape/Views/Components/Rating/ShelterReviewsView.swift index 702da72..dfc0ee8 100644 --- a/ios/escape/escape/Views/Components/Rating/ShelterReviewsView.swift +++ b/ios/escape/escape/Views/Components/Rating/ShelterReviewsView.swift @@ -31,7 +31,7 @@ struct ShelterReviewsView: View { } .padding() } - .navigationTitle("Reviews") + .navigationTitle(String(localized: "rating.review_plural", table: "Localizable")) .navigationBarTitleDisplayMode(.inline) .sheet(isPresented: $showEditSheet) { EditRatingSheet(viewModel: viewModel) @@ -71,7 +71,7 @@ struct ShelterReviewsView: View { if viewModel.hasExistingRating { // Show user's existing rating with edit option VStack(alignment: .leading, spacing: 12) { - Text("Your Rating") + Text("rating.your_rating", tableName: "Localizable") .font(.headline) if let userRating = viewModel.userRating { @@ -103,7 +103,7 @@ struct ShelterReviewsView: View { viewModel.startEditingRating() showEditSheet = true }) { - Text("Edit Your Rating") + Text("rating.edit_your_rating", tableName: "Localizable") .font(.subheadline) .fontWeight(.medium) } @@ -139,7 +139,7 @@ struct ShelterReviewsView: View { @ViewBuilder private var allReviewsSection: some View { VStack(alignment: .leading, spacing: 16) { - Text("All Reviews") + Text("rating.all_reviews", tableName: "Localizable") .font(.headline) if viewModel.isLoadingRatings { @@ -183,11 +183,11 @@ struct EditRatingSheet: View { RatingFormView(viewModel: viewModel, isEditing: true) .padding() } - .navigationTitle("Edit Rating") + .navigationTitle(String(localized: "rating.edit_rating", table: "Localizable")) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { - Button("Cancel") { + Button(String(localized: "common.cancel", table: "Localizable")) { dismiss() } } diff --git a/ios/escape/escape/Views/Components/Shared/ActivityHeatmapView.swift b/ios/escape/escape/Views/Components/Shared/ActivityHeatmapView.swift index a40ccab..4bab37e 100644 --- a/ios/escape/escape/Views/Components/Shared/ActivityHeatmapView.swift +++ b/ios/escape/escape/Views/Components/Shared/ActivityHeatmapView.swift @@ -23,7 +23,7 @@ struct ActivityHeatmapView: View { var body: some View { VStack(alignment: .leading, spacing: 12) { - Text("Activity in the last 30 days") + Text("activity.title", tableName: "Localizable") .font(.headline) .foregroundColor(.primary) @@ -57,18 +57,18 @@ struct ActivityHeatmapView: View { // Color legend HStack(spacing: 4) { - Text("Less") + Text("activity.legend.less", tableName: "Localizable") .font(.caption2) .foregroundColor(.secondary) - ForEach(0..<5) { index in + ForEach(0 ..< 5) { index in Rectangle() .fill(getColor(for: index, max: 4)) .frame(width: 12, height: 12) .cornerRadius(2) } - Text("More") + Text("activity.legend.more", tableName: "Localizable") .font(.caption2) .foregroundColor(.secondary) } @@ -82,7 +82,7 @@ struct ActivityHeatmapView: View { private var weekdayLabels: some View { LazyVGrid(columns: columns, spacing: 4) { - ForEach(["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], id: \.self) { day in + ForEach([String(localized: "weekday.mon", table: "Localizable"), String(localized: "weekday.tue", table: "Localizable"), String(localized: "weekday.wed", table: "Localizable"), String(localized: "weekday.thu", table: "Localizable"), String(localized: "weekday.fri", table: "Localizable"), String(localized: "weekday.sat", table: "Localizable"), String(localized: "weekday.sun", table: "Localizable")], id: \.self) { day in Text(day) .font(.caption2) .foregroundColor(.secondary) @@ -93,7 +93,7 @@ struct ActivityHeatmapView: View { // MARK: - Day Cell View - private func dayCellView(for date: Date, index: Int) -> some View { + private func dayCellView(for date: Date, index _: Int) -> some View { let dateString = formatDate(date) let points = dailyPoints[dateString] ?? 0 let color = getColorForPoints(points) @@ -143,7 +143,7 @@ struct ActivityHeatmapView: View { // MARK: - Helper Methods - private func handleDayTap(date: String, points: Int, frame: CGRect) { + private func handleDayTap(date: String, points _: Int, frame: CGRect) { selectedDate = date tooltipFrame = frame @@ -187,7 +187,7 @@ struct ActivityHeatmapView: View { let calendar = Calendar.current let today = calendar.startOfDay(for: Date()) - return (0..<30).reversed().compactMap { daysAgo in + return (0 ..< 30).reversed().compactMap { daysAgo in calendar.date(byAdding: .day, value: -daysAgo, to: today) } } @@ -253,7 +253,7 @@ extension Color { .sRGB, red: Double(r) / 255, green: Double(g) / 255, - blue: Double(b) / 255, + blue: Double(b) / 255, opacity: Double(a) / 255 ) } @@ -270,7 +270,7 @@ extension Color { "2025-10-25": 100, "2025-11-01": 220, "2025-11-05": 160, - "2025-11-08": 190 + "2025-11-08": 190, ] return ActivityHeatmapView(dailyPoints: sampleData) diff --git a/ios/escape/escape/Views/Components/Shared/UserProfileBottomSheetView.swift b/ios/escape/escape/Views/Components/Shared/UserProfileBottomSheetView.swift index a4e6315..c876615 100644 --- a/ios/escape/escape/Views/Components/Shared/UserProfileBottomSheetView.swift +++ b/ios/escape/escape/Views/Components/Shared/UserProfileBottomSheetView.swift @@ -28,7 +28,7 @@ struct UserProfileBottomSheetView: View { } .padding() } - .navigationTitle("User Profile") + .navigationTitle(String(localized: "userprofile.title", table: "Localizable")) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { @@ -53,12 +53,12 @@ struct UserProfileBottomSheetView: View { // Header Section - Avatar & Username VStack(spacing: 16) { UserAvatarView.profile( - username: user.name ?? "Anonymous", + username: user.name ?? String(localized: "common.anonymous", table: "Localizable"), badgeImageUrl: viewModel.getProfileBadge()?.imageUrl, size: .large ) - Text(user.name ?? "Anonymous") + Text(user.name ?? String(localized: "common.anonymous", table: "Localizable")) .font(.title2) .fontWeight(.bold) .foregroundColor(.primary) @@ -81,7 +81,7 @@ struct UserProfileBottomSheetView: View { private var badgeCollectionSection: some View { VStack(alignment: .leading, spacing: 12) { - Text("Badge Collection") + Text("userprofile.badge_collection", tableName: "Localizable") .font(.headline) .foregroundColor(.primary) @@ -119,7 +119,7 @@ struct UserProfileBottomSheetView: View { Image(systemName: "tray") .font(.largeTitle) .foregroundColor(.secondary) - Text("No badges collected yet") + Text("userprofile.no_badges", tableName: "Localizable") .font(.subheadline) .foregroundColor(.secondary) } @@ -134,7 +134,7 @@ struct UserProfileBottomSheetView: View { VStack(spacing: 16) { ProgressView() .scaleEffect(1.5) - Text("Loading profile...") + Text("userprofile.loading", tableName: "Localizable") .font(.subheadline) .foregroundColor(.secondary) } @@ -146,13 +146,13 @@ struct UserProfileBottomSheetView: View { Image(systemName: "exclamationmark.triangle") .font(.largeTitle) .foregroundColor(.red) - Text("Error Loading Profile") + Text("userprofile.error_title", tableName: "Localizable") .font(.headline) Text(message) .font(.subheadline) .foregroundColor(.secondary) .multilineTextAlignment(.center) - Button("Try Again") { + Button(String(localized: "Try Again", table: "Localizable")) { Task { await viewModel.fetchUserProfile(userId: userId) } @@ -168,9 +168,9 @@ struct UserProfileBottomSheetView: View { Image(systemName: "person.crop.circle.badge.questionmark") .font(.largeTitle) .foregroundColor(.secondary) - Text("User Not Found") + Text("userprofile.user_not_found", tableName: "Localizable") .font(.headline) - Text("This user profile could not be loaded.") + Text("userprofile.user_not_found_description", tableName: "Localizable") .font(.subheadline) .foregroundColor(.secondary) }