From 5820e3bf2f8801fa0cd249b855d4d32dcbeb6b3e Mon Sep 17 00:00:00 2001 From: ghostcoder42 Date: Tue, 8 Sep 2026 00:00:23 +0800 Subject: [PATCH] fix(i18n): localize the library screen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Library screen (history / downloads / favorites tabs) was fully hardcoded English — including two strings whose translations already existed (history.empty, history.clear_all, favorites.empty) but were never wired. Everything now goes through i18n: tab labels (emoji kept in code, text translated), empty states, the delete-download dialog, the 'By ' row line, active-download status lines (failed / cancelled / downloading with progress) and their Retry / Cancel buttons. New keys added across all seven languages; common.delete joins the shared vocabulary. --- src/app/(app)/library.tsx | 56 +++++++++++++++++++++++-------------- src/translations/en.json | 17 +++++++++-- src/translations/es.json | 17 +++++++++-- src/translations/ja.json | 17 +++++++++-- src/translations/ko.json | 17 +++++++++-- src/translations/pt.json | 17 +++++++++-- src/translations/zh-TW.json | 17 +++++++++-- src/translations/zh.json | 17 +++++++++-- 8 files changed, 140 insertions(+), 35 deletions(-) diff --git a/src/app/(app)/library.tsx b/src/app/(app)/library.tsx index b985dc9..5dc357e 100644 --- a/src/app/(app)/library.tsx +++ b/src/app/(app)/library.tsx @@ -11,6 +11,7 @@ import { VideoTile } from '@/components/video-tile'; import type { DownloadMetadata } from '@/lib/download'; import { cancelDownload, retryDownload } from '@/lib/download/download-video'; import { useColumns } from '@/lib/hooks/use-columns'; +import { useTranslate } from '@/lib/i18n'; import { type ActiveDownload, useActiveDownloadsStore } from '@/lib/stores/active-downloads-store'; import { baseIdOf, useDownloadedStore } from '@/lib/stores/downloaded-store'; import type { FavoriteItem } from '@/lib/stores/favorites-store'; @@ -18,6 +19,7 @@ import { useFavoritesStore } from '@/lib/stores/favorites-store'; import { useHistoryStore } from '@/lib/stores/history-store'; function HistoryTab(): React.ReactElement { + const t = useTranslate(); const { history, clearHistory } = useHistoryStore(); const numColumns = useColumns(); const [refreshing, setRefreshing] = React.useState(false); @@ -25,7 +27,7 @@ function HistoryTab(): React.ReactElement { if (history.length === 0) { return ( - No watch history + {t('history.empty')} ); } @@ -34,7 +36,7 @@ function HistoryTab(): React.ReactElement { <> - Clear All + {t('history.clear_all')} void; }): React.ReactElement { + const t = useTranslate(); const baseId = baseIdOf(item.videoId); const isFav = useFavoritesStore((s) => s.favorites.some((f) => f.id === baseId)); const swipeableRef = React.useRef(null); const confirmRemove = () => { swipeableRef.current?.close(); - Alert.alert('Delete Download', `Remove "${item.title}" from downloads?`, [ - { text: 'Cancel', style: 'cancel' }, - { text: 'Delete', style: 'destructive', onPress: () => onRemove(item.videoId) }, + Alert.alert(t('library.delete_title'), t('library.delete_msg', { title: item.title }), [ + { text: t('common.cancel'), style: 'cancel' }, + { text: t('common.delete'), style: 'destructive', onPress: () => onRemove(item.videoId) }, ]); }; @@ -120,7 +123,7 @@ function DownloadRow({ {item.uploader ? ( - By {item.uploader} + {t('library.by_uploader', { uploader: item.uploader })} ) : null} @@ -134,6 +137,7 @@ function DownloadRow({ } function ActiveDownloadRow({ task }: { task: ActiveDownload }): React.ReactElement { + const t = useTranslate(); const [cancelling, setCancelling] = React.useState(false); const [retrying, setRetrying] = React.useState(false); const indeterminate = task.progress < 0; @@ -173,12 +177,12 @@ function ActiveDownloadRow({ task }: { task: ActiveDownload }): React.ReactEleme {task.status === 'error' - ? `Failed${task.error ? `: ${task.error}` : ''} — tap Retry to try again` + ? `${t('library.status_failed', { error: task.error ? `: ${task.error}` : '' })}${t('library.tap_retry_hint')}` : task.status === 'cancelled' - ? 'Cancelled' + ? t('library.status_cancelled') : indeterminate - ? 'Downloading…' - : `Downloading · ${pct}%`} + ? t('library.status_downloading_indeterminate') + : t('library.status_downloading', { progress: pct })} - {retrying ? '…' : 'Retry'} + + {retrying ? '…' : t('library.retry')} + - {cancelling ? '…' : 'Cancel'} + {cancelling ? '…' : t('common.cancel')} @@ -215,7 +221,7 @@ function ActiveDownloadRow({ task }: { task: ActiveDownload }): React.ReactEleme testID="active-download-cancel" > - {cancelling ? '…' : 'Cancel'} + {cancelling ? '…' : t('common.cancel')} )} @@ -228,6 +234,7 @@ type DownloadListItem = | { kind: 'done'; entry: DownloadMetadata }; function DownloadsTab(): React.ReactElement { + const t = useTranslate(); const entries = useDownloadedStore((s) => s.entries); const loaded = useDownloadedStore((s) => s.loaded); const hydrate = useDownloadedStore((s) => s.hydrate); @@ -265,7 +272,7 @@ function DownloadsTab(): React.ReactElement { if (loaded && data.length === 0) { return ( - No downloads yet + {t('library.no_downloads')} ); } @@ -290,6 +297,7 @@ function DownloadsTab(): React.ReactElement { } function FavoritesTab(): React.ReactElement { + const t = useTranslate(); const { favorites, removeFavorite } = useFavoritesStore(); const numColumns = useColumns(); const [refreshing, setRefreshing] = React.useState(false); @@ -297,9 +305,9 @@ function FavoritesTab(): React.ReactElement { if (favorites.length === 0) { return ( - No favorites yet + {t('favorites.empty')} - Tap the heart on a video to save it here. + {t('favorites.empty_hint')} ); @@ -331,13 +339,19 @@ function FavoritesTab(): React.ReactElement { } type Tab = 'history' | 'downloads' | 'favorites'; -const TABS: { key: Tab; label: string }[] = [ - { key: 'history', label: '🕐 History' }, - { key: 'downloads', label: '⬇️ Downloads' }, - { key: 'favorites', label: '❤️ Favorites' }, +// Emojis stay in code (language-neutral); labels come from the translations. +const TABS: { + key: Tab; + emoji: string; + tx: 'history.title' | 'library.downloads' | 'favorites.title'; +}[] = [ + { key: 'history', emoji: '🕐', tx: 'history.title' }, + { key: 'downloads', emoji: '⬇️', tx: 'library.downloads' }, + { key: 'favorites', emoji: '❤️', tx: 'favorites.title' }, ]; export default function Library(): React.ReactElement { + const t = useTranslate(); const [activeTab, setActiveTab] = React.useState('history'); const pagerRef = React.useRef(null); const { width } = useWindowDimensions(); @@ -388,7 +402,7 @@ export default function Library(): React.ReactElement { - {tab.label} + {`${tab.emoji} ${t(tab.tx)}`} ))} diff --git a/src/translations/en.json b/src/translations/en.json index 5a21a6a..30652b9 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2,6 +2,7 @@ "common": { "cancel": "Cancel", "clear": "Clear", + "delete": "Delete", "done": "Done", "error": "Error" }, @@ -89,11 +90,23 @@ "searching": "Searching..." }, "library": { + "by_uploader": "By {{uploader}}", + "delete_msg": "Remove \"{{title}}\" from downloads?", + "delete_title": "Delete Download", + "downloads": "Downloads", + "no_downloads": "No downloads yet", + "retry": "Retry", + "status_cancelled": "Cancelled", + "status_downloading": "Downloading · {{progress}}%", + "status_downloading_indeterminate": "Downloading…", + "status_failed": "Failed{{error}}", + "tap_retry_hint": " — tap Retry to try again", "title": "Library" }, "favorites": { - "title": "Favorites", - "empty": "No favorites yet" + "empty": "No favorites yet", + "empty_hint": "Tap the heart on a video to save it here.", + "title": "Favorites" }, "history": { "title": "History", diff --git a/src/translations/es.json b/src/translations/es.json index b8fdc7c..13efecd 100644 --- a/src/translations/es.json +++ b/src/translations/es.json @@ -2,6 +2,7 @@ "common": { "cancel": "Cancelar", "clear": "Borrar", + "delete": "Eliminar", "done": "Listo", "error": "Error" }, @@ -89,11 +90,23 @@ "searching": "Buscando..." }, "library": { + "by_uploader": "De {{uploader}}", + "delete_msg": "¿Eliminar \"{{title}}\" de las descargas?", + "delete_title": "Eliminar descarga", + "downloads": "Descargas", + "no_downloads": "Aún no hay descargas", + "retry": "Reintentar", + "status_cancelled": "Cancelada", + "status_downloading": "Descargando · {{progress}}%", + "status_downloading_indeterminate": "Descargando…", + "status_failed": "Error{{error}}", + "tap_retry_hint": " — toca Reintentar para volver a intentarlo", "title": "Biblioteca" }, "favorites": { - "title": "Favoritos", - "empty": "Aún no hay favoritos" + "empty": "Aún no hay favoritos", + "empty_hint": "Toca el corazón de un vídeo para guardarlo aquí.", + "title": "Favoritos" }, "history": { "title": "Historial", diff --git a/src/translations/ja.json b/src/translations/ja.json index 74fccb6..1104223 100644 --- a/src/translations/ja.json +++ b/src/translations/ja.json @@ -2,6 +2,7 @@ "common": { "cancel": "キャンセル", "clear": "クリア", + "delete": "削除", "done": "完了", "error": "エラー" }, @@ -89,11 +90,23 @@ "searching": "検索中..." }, "library": { + "by_uploader": "アップローダー:{{uploader}}", + "delete_msg": "「{{title}}」をダウンロードから削除しますか?", + "delete_title": "ダウンロードを削除", + "downloads": "ダウンロード", + "no_downloads": "ダウンロードはまだありません", + "retry": "再試行", + "status_cancelled": "キャンセル済み", + "status_downloading": "ダウンロード中 · {{progress}}%", + "status_downloading_indeterminate": "ダウンロード中…", + "status_failed": "失敗{{error}}", + "tap_retry_hint": " — 「再試行」をタップしてもう一度", "title": "ライブラリ" }, "favorites": { - "title": "お気に入り", - "empty": "お気に入りはまだありません" + "empty": "お気に入りはまだありません", + "empty_hint": "動画のハートをタップするとここに保存されます。", + "title": "お気に入り" }, "history": { "title": "履歴", diff --git a/src/translations/ko.json b/src/translations/ko.json index 3d79d32..edeec28 100644 --- a/src/translations/ko.json +++ b/src/translations/ko.json @@ -2,6 +2,7 @@ "common": { "cancel": "취소", "clear": "삭제", + "delete": "삭제", "done": "완료", "error": "오류" }, @@ -89,11 +90,23 @@ "searching": "검색 중..." }, "library": { + "by_uploader": "업로더: {{uploader}}", + "delete_msg": "\"{{title}}\"을(를) 다운로드에서 제거할까요?", + "delete_title": "다운로드 삭제", + "downloads": "다운로드", + "no_downloads": "다운로드가 없습니다", + "retry": "다시 시도", + "status_cancelled": "취소됨", + "status_downloading": "다운로드 중 · {{progress}}%", + "status_downloading_indeterminate": "다운로드 중…", + "status_failed": "실패{{error}}", + "tap_retry_hint": " — 다시 시도를 눌러 재시도하세요", "title": "보관함" }, "favorites": { - "title": "즐겨찾기", - "empty": "즐겨찾기가 없습니다" + "empty": "즐겨찾기가 없습니다", + "empty_hint": "동영상의 하트를 누르면 여기에 저장됩니다.", + "title": "즐겨찾기" }, "history": { "title": "시청 기록", diff --git a/src/translations/pt.json b/src/translations/pt.json index 4457d30..083eeae 100644 --- a/src/translations/pt.json +++ b/src/translations/pt.json @@ -2,6 +2,7 @@ "common": { "cancel": "Cancelar", "clear": "Limpar", + "delete": "Excluir", "done": "Concluído", "error": "Erro" }, @@ -89,11 +90,23 @@ "searching": "Pesquisando..." }, "library": { + "by_uploader": "Por {{uploader}}", + "delete_msg": "Remover \"{{title}}\" dos downloads?", + "delete_title": "Excluir download", + "downloads": "Downloads", + "no_downloads": "Nenhum download ainda", + "retry": "Repetir", + "status_cancelled": "Cancelado", + "status_downloading": "Baixando · {{progress}}%", + "status_downloading_indeterminate": "Baixando…", + "status_failed": "Falhou{{error}}", + "tap_retry_hint": " — toque em Repetir para tentar de novo", "title": "Biblioteca" }, "favorites": { - "title": "Favoritos", - "empty": "Nenhum favorito ainda" + "empty": "Nenhum favorito ainda", + "empty_hint": "Toque no coração de um vídeo para salvá-lo aqui.", + "title": "Favoritos" }, "history": { "title": "Histórico", diff --git a/src/translations/zh-TW.json b/src/translations/zh-TW.json index 812c74b..dc1f8f0 100644 --- a/src/translations/zh-TW.json +++ b/src/translations/zh-TW.json @@ -2,6 +2,7 @@ "common": { "cancel": "取消", "clear": "清除", + "delete": "刪除", "done": "完成", "error": "錯誤" }, @@ -89,11 +90,23 @@ "searching": "搜尋中..." }, "library": { + "by_uploader": "上傳者:{{uploader}}", + "delete_msg": "從下載中移除「{{title}}」?", + "delete_title": "刪除下載", + "downloads": "下載", + "no_downloads": "暫無下載", + "retry": "重試", + "status_cancelled": "已取消", + "status_downloading": "下載中 · {{progress}}%", + "status_downloading_indeterminate": "下載中…", + "status_failed": "失敗{{error}}", + "tap_retry_hint": " — 點擊重試再試一次", "title": "媒體庫" }, "favorites": { - "title": "收藏", - "empty": "尚無收藏" + "empty": "尚無收藏", + "empty_hint": "點擊影片上的愛心即可收藏到這裡。", + "title": "收藏" }, "history": { "title": "歷史", diff --git a/src/translations/zh.json b/src/translations/zh.json index 86a49c7..50a8127 100644 --- a/src/translations/zh.json +++ b/src/translations/zh.json @@ -2,6 +2,7 @@ "common": { "cancel": "取消", "clear": "清除", + "delete": "删除", "done": "完成", "error": "错误" }, @@ -89,11 +90,23 @@ "searching": "搜索中..." }, "library": { + "by_uploader": "上传者:{{uploader}}", + "delete_msg": "从下载中移除“{{title}}”?", + "delete_title": "删除下载", + "downloads": "下载", + "no_downloads": "暂无下载", + "retry": "重试", + "status_cancelled": "已取消", + "status_downloading": "下载中 · {{progress}}%", + "status_downloading_indeterminate": "下载中…", + "status_failed": "失败{{error}}", + "tap_retry_hint": " — 点击重试再试一次", "title": "媒体库" }, "favorites": { - "title": "收藏", - "empty": "暂无收藏" + "empty": "暂无收藏", + "empty_hint": "点击视频上的爱心即可收藏到这里。", + "title": "收藏" }, "history": { "title": "历史",