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": "历史",