From c9657bad51276ff19cdd8bf51f21268aa5413afa Mon Sep 17 00:00:00 2001 From: Peifan Li Date: Fri, 2 Jan 2026 13:25:02 -0500 Subject: [PATCH] refactor: Update formatUtils to use formatRelativeDownloadTime function --- frontend/src/components/CollectionCard.tsx | 7 +- .../components/VideoCard/VideoCardContent.tsx | 4 +- .../__tests__/VideoCardContent.test.tsx | 1 + .../__tests__/CollectionCard.test.tsx | 4 +- .../src/utils/__tests__/formatUtils.test.ts | 422 ++++++++++++------ frontend/src/utils/formatUtils.ts | 109 ++++- frontend/src/utils/locales/ar.ts | 5 + frontend/src/utils/locales/de.ts | 5 + frontend/src/utils/locales/en.ts | 5 + frontend/src/utils/locales/es.ts | 5 + frontend/src/utils/locales/fr.ts | 5 + frontend/src/utils/locales/ja.ts | 5 + frontend/src/utils/locales/ko.ts | 5 + frontend/src/utils/locales/pt.ts | 5 + frontend/src/utils/locales/ru.ts | 5 + frontend/src/utils/locales/zh.ts | 5 + 16 files changed, 452 insertions(+), 145 deletions(-) diff --git a/frontend/src/components/CollectionCard.tsx b/frontend/src/components/CollectionCard.tsx index 30a3050..2f8cc99 100644 --- a/frontend/src/components/CollectionCard.tsx +++ b/frontend/src/components/CollectionCard.tsx @@ -41,12 +41,11 @@ const CollectionCard: React.FC = ({ collection, videos }) = display: 'flex', flexDirection: 'column', position: 'relative', - transition: 'transform 0.2s, box-shadow 0.2s, background-color 0.3s, color 0.3s, border-color 0.3s', + transition: 'transform 0.2s, box-shadow 0.2s, background-color 0.3s, color 0.3s', '&:hover': { transform: 'translateY(-4px)', boxShadow: theme.shadows[8], - }, - border: `1px solid ${theme.palette.secondary.main}` + } }} > @@ -76,7 +75,7 @@ const CollectionCard: React.FC = ({ collection, videos }) = } - label={`${collection.videos.length} videos`} + label={collection.videos.length} color="secondary" size="small" sx={{ position: 'absolute', bottom: 8, right: 8 }} diff --git a/frontend/src/components/VideoCard/VideoCardContent.tsx b/frontend/src/components/VideoCard/VideoCardContent.tsx index 48ef086..be0e809 100644 --- a/frontend/src/components/VideoCard/VideoCardContent.tsx +++ b/frontend/src/components/VideoCard/VideoCardContent.tsx @@ -2,7 +2,7 @@ import { Box, CardContent, Typography } from '@mui/material'; import React from 'react'; import { useLanguage } from '../../contexts/LanguageContext'; import { Video } from '../../types'; -import { formatDate } from '../../utils/formatUtils'; +import { formatRelativeDownloadTime } from '../../utils/formatUtils'; import { VideoCardCollectionInfo } from '../../utils/videoCardUtils'; interface VideoCardContentProps { @@ -72,7 +72,7 @@ export const VideoCardContent: React.FC = ({ - {formatDate(video.date)} + {formatRelativeDownloadTime(video.addedAt, video.date, t)} {video.viewCount || 0} {t('views')} diff --git a/frontend/src/components/VideoCard/__tests__/VideoCardContent.test.tsx b/frontend/src/components/VideoCard/__tests__/VideoCardContent.test.tsx index 7442150..b92e2ec 100644 --- a/frontend/src/components/VideoCard/__tests__/VideoCardContent.test.tsx +++ b/frontend/src/components/VideoCard/__tests__/VideoCardContent.test.tsx @@ -10,6 +10,7 @@ vi.mock('../../../contexts/LanguageContext', () => ({ vi.mock('../../../utils/formatUtils', () => ({ formatDate: () => '2023-01-01', + formatRelativeDownloadTime: () => '2023-01-01', })); diff --git a/frontend/src/components/__tests__/CollectionCard.test.tsx b/frontend/src/components/__tests__/CollectionCard.test.tsx index 47049a8..4354617 100644 --- a/frontend/src/components/__tests__/CollectionCard.test.tsx +++ b/frontend/src/components/__tests__/CollectionCard.test.tsx @@ -86,7 +86,7 @@ describe('CollectionCard', () => { ); expect(screen.getByText(/Test Collection/i)).toBeInTheDocument(); - expect(screen.getByText(/2 videos/i)).toBeInTheDocument(); + expect(screen.getByText('2')).toBeInTheDocument(); }); it('renders collection creation date', () => { @@ -131,7 +131,7 @@ describe('CollectionCard', () => { ); // Should show folder icon (via Material-UI icon) - expect(screen.getByText(/0 videos/i)).toBeInTheDocument(); + expect(screen.getByText('0')).toBeInTheDocument(); }); it('displays up to 4 thumbnails in grid', () => { diff --git a/frontend/src/utils/__tests__/formatUtils.test.ts b/frontend/src/utils/__tests__/formatUtils.test.ts index bd2a3e4..bdcf189 100644 --- a/frontend/src/utils/__tests__/formatUtils.test.ts +++ b/frontend/src/utils/__tests__/formatUtils.test.ts @@ -1,143 +1,297 @@ -import { describe, expect, it } from 'vitest'; -import { formatDate, formatDuration, formatSize, parseDuration } from '../formatUtils'; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + formatDate, + formatDuration, + formatRelativeDownloadTime, + formatSize, + parseDuration, +} from "../formatUtils"; -describe('formatUtils', () => { - describe('parseDuration', () => { - it('should return 0 for undefined', () => { - expect(parseDuration(undefined)).toBe(0); - }); - - it('should return number as-is', () => { - expect(parseDuration(100)).toBe(100); - expect(parseDuration(0)).toBe(0); - }); - - it('should parse HH:MM:SS format', () => { - expect(parseDuration('1:30:45')).toBe(5445); // 1*3600 + 30*60 + 45 = 3600 + 1800 + 45 - expect(parseDuration('0:5:30')).toBe(330); // 0*3600 + 5*60 + 30 = 0 + 300 + 30 - expect(parseDuration('2:0:0')).toBe(7200); // 2*3600 + 0*60 + 0 = 7200 - }); - - it('should parse MM:SS format', () => { - expect(parseDuration('5:30')).toBe(330); // 5*60 + 30 - expect(parseDuration('10:15')).toBe(615); // 10*60 + 15 - expect(parseDuration('0:45')).toBe(45); - }); - - it('should parse numeric string', () => { - expect(parseDuration('100')).toBe(100); - expect(parseDuration('0')).toBe(0); - }); - - it('should return 0 for invalid string', () => { - expect(parseDuration('invalid')).toBe(0); - // 'abc:def' will be parsed as NaN for each part, but the function - // will try parseInt on the whole string which also returns NaN -> 0 - expect(parseDuration('abc:def')).toBe(0); - expect(parseDuration('not-a-number')).toBe(0); - }); +describe("formatUtils", () => { + describe("parseDuration", () => { + it("should return 0 for undefined", () => { + expect(parseDuration(undefined)).toBe(0); }); - describe('formatDuration', () => { - it('should return 00:00 for undefined', () => { - expect(formatDuration(undefined)).toBe('00:00'); - }); - - it('should return formatted string as-is if already formatted', () => { - expect(formatDuration('1:30:45')).toBe('1:30:45'); - expect(formatDuration('5:30')).toBe('5:30'); - }); - - it('should format seconds to MM:SS', () => { - expect(formatDuration(65)).toBe('1:05'); // 1 minute 5 seconds - expect(formatDuration(125)).toBe('2:05'); // 2 minutes 5 seconds - expect(formatDuration(45)).toBe('0:45'); // 45 seconds - expect(formatDuration(0)).toBe('00:00'); - }); - - it('should format seconds to H:MM:SS for hours', () => { - expect(formatDuration(3665)).toBe('1:01:05'); // 1 hour 1 minute 5 seconds - expect(formatDuration(3600)).toBe('1:00:00'); // 1 hour - expect(formatDuration(7325)).toBe('2:02:05'); // 2 hours 2 minutes 5 seconds - }); - - it('should format numeric string', () => { - expect(formatDuration('65')).toBe('1:05'); - expect(formatDuration('3665')).toBe('1:01:05'); - }); - - it('should return 00:00 for invalid input', () => { - expect(formatDuration('invalid')).toBe('00:00'); - expect(formatDuration(NaN)).toBe('00:00'); - }); + it("should return number as-is", () => { + expect(parseDuration(100)).toBe(100); + expect(parseDuration(0)).toBe(0); }); - describe('formatSize', () => { - it('should return "0 B" for undefined', () => { - expect(formatSize(undefined)).toBe('0 B'); - }); - - it('should format bytes', () => { - expect(formatSize(0)).toBe('0 B'); - expect(formatSize(500)).toBe('500 B'); - expect(formatSize(1023)).toBe('1023 B'); - }); - - it('should format kilobytes', () => { - expect(formatSize(1024)).toBe('1 KB'); - expect(formatSize(1536)).toBe('1.5 KB'); - expect(formatSize(2048)).toBe('2 KB'); - expect(formatSize(10240)).toBe('10 KB'); - }); - - it('should format megabytes', () => { - expect(formatSize(1048576)).toBe('1 MB'); // 1024 * 1024 - expect(formatSize(1572864)).toBe('1.5 MB'); - expect(formatSize(5242880)).toBe('5 MB'); - }); - - it('should format gigabytes', () => { - expect(formatSize(1073741824)).toBe('1 GB'); // 1024^3 - expect(formatSize(2147483648)).toBe('2 GB'); - }); - - it('should format terabytes', () => { - expect(formatSize(1099511627776)).toBe('1 TB'); // 1024^4 - }); - - it('should format numeric string', () => { - expect(formatSize('1024')).toBe('1 KB'); - expect(formatSize('1048576')).toBe('1 MB'); - }); - - it('should return "0 B" for invalid input', () => { - expect(formatSize('invalid')).toBe('0 B'); - expect(formatSize(NaN)).toBe('0 B'); - }); + it("should parse HH:MM:SS format", () => { + expect(parseDuration("1:30:45")).toBe(5445); // 1*3600 + 30*60 + 45 = 3600 + 1800 + 45 + expect(parseDuration("0:5:30")).toBe(330); // 0*3600 + 5*60 + 30 = 0 + 300 + 30 + expect(parseDuration("2:0:0")).toBe(7200); // 2*3600 + 0*60 + 0 = 7200 }); - describe('formatDate', () => { - it('should return "Unknown date" for undefined', () => { - expect(formatDate(undefined)).toBe('Unknown date'); - }); - - it('should return "Unknown date" for invalid length', () => { - expect(formatDate('202301')).toBe('Unknown date'); - expect(formatDate('202301011')).toBe('Unknown date'); - expect(formatDate('2023')).toBe('Unknown date'); - }); - - it('should format YYYYMMDD to YYYY-MM-DD', () => { - expect(formatDate('20230101')).toBe('2023-01-01'); - expect(formatDate('20231225')).toBe('2023-12-25'); - expect(formatDate('20200101')).toBe('2020-01-01'); - expect(formatDate('20230228')).toBe('2023-02-28'); - }); - - it('should handle edge cases', () => { - expect(formatDate('19991231')).toBe('1999-12-31'); - expect(formatDate('20991231')).toBe('2099-12-31'); - }); + it("should parse MM:SS format", () => { + expect(parseDuration("5:30")).toBe(330); // 5*60 + 30 + expect(parseDuration("10:15")).toBe(615); // 10*60 + 15 + expect(parseDuration("0:45")).toBe(45); }); + + it("should parse numeric string", () => { + expect(parseDuration("100")).toBe(100); + expect(parseDuration("0")).toBe(0); + }); + + it("should return 0 for invalid string", () => { + expect(parseDuration("invalid")).toBe(0); + // 'abc:def' will be parsed as NaN for each part, but the function + // will try parseInt on the whole string which also returns NaN -> 0 + expect(parseDuration("abc:def")).toBe(0); + expect(parseDuration("not-a-number")).toBe(0); + }); + }); + + describe("formatDuration", () => { + it("should return 00:00 for undefined", () => { + expect(formatDuration(undefined)).toBe("00:00"); + }); + + it("should return formatted string as-is if already formatted", () => { + expect(formatDuration("1:30:45")).toBe("1:30:45"); + expect(formatDuration("5:30")).toBe("5:30"); + }); + + it("should format seconds to MM:SS", () => { + expect(formatDuration(65)).toBe("1:05"); // 1 minute 5 seconds + expect(formatDuration(125)).toBe("2:05"); // 2 minutes 5 seconds + expect(formatDuration(45)).toBe("0:45"); // 45 seconds + expect(formatDuration(0)).toBe("00:00"); + }); + + it("should format seconds to H:MM:SS for hours", () => { + expect(formatDuration(3665)).toBe("1:01:05"); // 1 hour 1 minute 5 seconds + expect(formatDuration(3600)).toBe("1:00:00"); // 1 hour + expect(formatDuration(7325)).toBe("2:02:05"); // 2 hours 2 minutes 5 seconds + }); + + it("should format numeric string", () => { + expect(formatDuration("65")).toBe("1:05"); + expect(formatDuration("3665")).toBe("1:01:05"); + }); + + it("should return 00:00 for invalid input", () => { + expect(formatDuration("invalid")).toBe("00:00"); + expect(formatDuration(NaN)).toBe("00:00"); + }); + }); + + describe("formatSize", () => { + it('should return "0 B" for undefined', () => { + expect(formatSize(undefined)).toBe("0 B"); + }); + + it("should format bytes", () => { + expect(formatSize(0)).toBe("0 B"); + expect(formatSize(500)).toBe("500 B"); + expect(formatSize(1023)).toBe("1023 B"); + }); + + it("should format kilobytes", () => { + expect(formatSize(1024)).toBe("1 KB"); + expect(formatSize(1536)).toBe("1.5 KB"); + expect(formatSize(2048)).toBe("2 KB"); + expect(formatSize(10240)).toBe("10 KB"); + }); + + it("should format megabytes", () => { + expect(formatSize(1048576)).toBe("1 MB"); // 1024 * 1024 + expect(formatSize(1572864)).toBe("1.5 MB"); + expect(formatSize(5242880)).toBe("5 MB"); + }); + + it("should format gigabytes", () => { + expect(formatSize(1073741824)).toBe("1 GB"); // 1024^3 + expect(formatSize(2147483648)).toBe("2 GB"); + }); + + it("should format terabytes", () => { + expect(formatSize(1099511627776)).toBe("1 TB"); // 1024^4 + }); + + it("should format numeric string", () => { + expect(formatSize("1024")).toBe("1 KB"); + expect(formatSize("1048576")).toBe("1 MB"); + }); + + it('should return "0 B" for invalid input', () => { + expect(formatSize("invalid")).toBe("0 B"); + expect(formatSize(NaN)).toBe("0 B"); + }); + }); + + describe("formatDate", () => { + it('should return "Unknown date" for undefined', () => { + expect(formatDate(undefined)).toBe("Unknown date"); + }); + + it('should return "Unknown date" for invalid length', () => { + expect(formatDate("202301")).toBe("Unknown date"); + expect(formatDate("202301011")).toBe("Unknown date"); + expect(formatDate("2023")).toBe("Unknown date"); + }); + + it("should format YYYYMMDD to YYYY-MM-DD", () => { + expect(formatDate("20230101")).toBe("2023-01-01"); + expect(formatDate("20231225")).toBe("2023-12-25"); + expect(formatDate("20200101")).toBe("2020-01-01"); + expect(formatDate("20230228")).toBe("2023-02-28"); + }); + + it("should handle edge cases", () => { + expect(formatDate("19991231")).toBe("1999-12-31"); + expect(formatDate("20991231")).toBe("2099-12-31"); + }); + }); + + describe("formatRelativeDownloadTime", () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + const mockTranslation = ( + key: string, + replacements?: Record + ) => { + const translations: Record = { + justNow: "Just now", + hoursAgo: `${replacements?.hours || 0} hours ago`, + today: "Today", + thisWeek: "This week", + weeksAgo: `${replacements?.weeks || 0} weeks ago`, + unknownDate: "Unknown date", + }; + return translations[key] || key; + }; + + it('should return "Just now" for less than 1 hour', () => { + const now = new Date("2023-01-01T12:00:00Z"); + vi.setSystemTime(now); + const thirtyMinutesAgo = new Date("2023-01-01T11:30:00Z").toISOString(); + expect( + formatRelativeDownloadTime(thirtyMinutesAgo, undefined, mockTranslation) + ).toBe("Just now"); + }); + + it('should return "X hours ago" for 1-5 hours', () => { + const now = new Date("2023-01-01T12:00:00Z"); + vi.setSystemTime(now); + const twoHoursAgo = new Date("2023-01-01T10:00:00Z").toISOString(); + expect( + formatRelativeDownloadTime(twoHoursAgo, undefined, mockTranslation) + ).toBe("2 hours ago"); + }); + + it('should return "Today" for 5-24 hours', () => { + const now = new Date("2023-01-01T12:00:00Z"); + vi.setSystemTime(now); + const tenHoursAgo = new Date("2023-01-01T02:00:00Z").toISOString(); + expect( + formatRelativeDownloadTime(tenHoursAgo, undefined, mockTranslation) + ).toBe("Today"); + }); + + it('should return "This week" for 1-7 days', () => { + const now = new Date("2023-01-01T12:00:00Z"); + vi.setSystemTime(now); + const threeDaysAgo = new Date("2022-12-29T12:00:00Z").toISOString(); + expect( + formatRelativeDownloadTime(threeDaysAgo, undefined, mockTranslation) + ).toBe("This week"); + }); + + it('should return "X weeks ago" for 1-4 weeks', () => { + const now = new Date("2023-01-01T12:00:00Z"); + vi.setSystemTime(now); + const twoWeeksAgo = new Date("2022-12-18T12:00:00Z").toISOString(); + expect( + formatRelativeDownloadTime(twoWeeksAgo, undefined, mockTranslation) + ).toBe("2 weeks ago"); + }); + + it("should return formatted date for > 4 weeks", () => { + const now = new Date("2023-01-01T12:00:00Z"); + vi.setSystemTime(now); + const sixWeeksAgo = new Date("2022-11-20T12:00:00Z").toISOString(); + const result = formatRelativeDownloadTime( + sixWeeksAgo, + "20221120", + mockTranslation + ); + expect(result).toBe("2022-11-20"); + }); + + it("should use originalDate when provided for > 4 weeks", () => { + const now = new Date("2023-01-01T12:00:00Z"); + vi.setSystemTime(now); + const sixWeeksAgo = new Date("2022-11-20T12:00:00Z").toISOString(); + expect( + formatRelativeDownloadTime(sixWeeksAgo, "20221120", mockTranslation) + ).toBe("2022-11-20"); + }); + + it('should fallback to "Unknown date" when no timestamp provided', () => { + expect( + formatRelativeDownloadTime(undefined, undefined, mockTranslation) + ).toBe("Unknown date"); + }); + + it("should use originalDate when no timestamp provided", () => { + expect( + formatRelativeDownloadTime(undefined, "20230101", mockTranslation) + ).toBe("2023-01-01"); + }); + + it("should fallback to English when no translation function provided", () => { + const now = new Date("2023-01-01T12:00:00Z"); + vi.setSystemTime(now); + const thirtyMinutesAgo = new Date("2023-01-01T11:30:00Z").toISOString(); + expect(formatRelativeDownloadTime(thirtyMinutesAgo)).toBe("Just now"); + }); + + it("should handle invalid date", () => { + expect( + formatRelativeDownloadTime("invalid-date", undefined, mockTranslation) + ).toBe("Unknown date"); + }); + + it("should use originalDate when date is invalid", () => { + expect( + formatRelativeDownloadTime("invalid-date", "20230101", mockTranslation) + ).toBe("2023-01-01"); + }); + + it("should format date in UTC to avoid timezone issues", () => { + const now = new Date("2023-01-01T12:00:00Z"); + vi.setSystemTime(now); + // Use a date that could be affected by timezone (midnight UTC) + const sixWeeksAgo = new Date("2022-11-20T00:00:00Z").toISOString(); + // Should format as 2022-11-20 regardless of system timezone + const result = formatRelativeDownloadTime( + sixWeeksAgo, + undefined, + mockTranslation + ); + expect(result).toBe("2022-11-20"); + }); + + it("should handle date formatting across timezone boundaries", () => { + const now = new Date("2023-01-01T12:00:00Z"); + vi.setSystemTime(now); + // Test with a date near midnight UTC to catch timezone edge cases + const sixWeeksAgo = new Date("2022-11-20T23:59:59Z").toISOString(); + const result = formatRelativeDownloadTime( + sixWeeksAgo, + undefined, + mockTranslation + ); + expect(result).toBe("2022-11-20"); + }); + }); }); - diff --git a/frontend/src/utils/formatUtils.ts b/frontend/src/utils/formatUtils.ts index 3a88176..dfdb3e3 100644 --- a/frontend/src/utils/formatUtils.ts +++ b/frontend/src/utils/formatUtils.ts @@ -81,6 +81,106 @@ export const formatDate = (dateString?: string) => { return `${year}-${month}-${day}`; }; +/** + * Format relative time from download timestamp to current time + * 0 - 1 hour: "Just now" + * 1 hour - 5 hours: "X hours ago" + * 5 hours - 24 hours: "Today" + * 1 day - 7 days: "This week" + * 1 week - 4 weeks: "X weeks ago" + * > 4 weeks: show actual date + */ +export const formatRelativeDownloadTime = ( + downloadTimestamp?: string, + originalDate?: string, + t?: (key: string, replacements?: Record) => string +): string => { + const getTranslation = ( + key: string, + replacements?: Record + ): string => { + if (t) { + return t(key as any, replacements); + } + // Fallback to English if no translation function provided + const fallbacks: Record = { + justNow: "Just now", + hoursAgo: "{hours} hours ago", + today: "Today", + thisWeek: "This week", + weeksAgo: "{weeks} weeks ago", + unknownDate: "Unknown date", + }; + let text = fallbacks[key] || key; + if (replacements) { + Object.entries(replacements).forEach(([placeholder, value]) => { + text = text.replace(`{${placeholder}}`, String(value)); + }); + } + return text; + }; + + if (!downloadTimestamp) { + // Fallback to original date format if no download timestamp + return originalDate + ? formatDate(originalDate) + : getTranslation("unknownDate"); + } + + const downloadDate = new Date(downloadTimestamp); + const now = new Date(); + + // Check if date is valid + if (isNaN(downloadDate.getTime())) { + return originalDate + ? formatDate(originalDate) + : getTranslation("unknownDate"); + } + + const diffMs = now.getTime() - downloadDate.getTime(); + const diffHours = diffMs / (1000 * 60 * 60); + const diffDays = diffMs / (1000 * 60 * 60 * 24); + const diffWeeks = diffDays / 7; + + // 0 - 1 hour: "Just now" + if (diffHours < 1) { + return getTranslation("justNow"); + } + + // 1 hour - 5 hours: "X hours ago" + if (diffHours >= 1 && diffHours < 5) { + const hours = Math.floor(diffHours); + return getTranslation("hoursAgo", { hours }); + } + + // 5 hours - 24 hours: "Today" + if (diffHours >= 5 && diffHours < 24) { + return getTranslation("today"); + } + + // 1 day - 7 days: "This week" + if (diffDays >= 1 && diffDays < 7) { + return getTranslation("thisWeek"); + } + + // 1 week - 4 weeks: "X周前" / "X weeks ago" + if (diffWeeks >= 1 && diffWeeks < 4) { + const weeks = Math.floor(diffWeeks); + return getTranslation("weeksAgo", { weeks }); + } + + // > 4 weeks: show actual date + if (originalDate) { + return formatDate(originalDate); + } + // Format download date as YYYY-MM-DD if no original date + // Use UTC methods to ensure timezone independence + const year = downloadDate.getUTCFullYear(); + const month = String(downloadDate.getUTCMonth() + 1).padStart(2, "0"); + const day = String(downloadDate.getUTCDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; +}; + /** * Generate timestamp string in format YYYY-MM-DD-HH-MM-SS * Matches the backend generateTimestamp() function format @@ -101,14 +201,17 @@ export const generateTimestamp = (): string => { * If path is already a full URL (starts with http:// or https://), return it as is * Otherwise, prepend BACKEND_URL */ -export const getFileUrl = (path: string | null | undefined, backendUrl: string): string | undefined => { +export const getFileUrl = ( + path: string | null | undefined, + backendUrl: string +): string | undefined => { if (!path) return undefined; - + // Check if path is already a full URL if (path.startsWith("http://") || path.startsWith("https://")) { return path; } - + // Otherwise, prepend backend URL return `${backendUrl}${path}`; }; diff --git a/frontend/src/utils/locales/ar.ts b/frontend/src/utils/locales/ar.ts index 3a8a52c..94ba7bf 100644 --- a/frontend/src/utils/locales/ar.ts +++ b/frontend/src/utils/locales/ar.ts @@ -366,6 +366,11 @@ export const ar = { unknownDate: "تاريخ غير معروف", part: "جزء", collection: "مجموعة", + justNow: "الآن", + hoursAgo: "منذ {hours} ساعة", + today: "اليوم", + thisWeek: "هذا الأسبوع", + weeksAgo: "منذ {weeks} أسبوع", // Upload Modal selectVideoFile: "اختر ملف فيديو", diff --git a/frontend/src/utils/locales/de.ts b/frontend/src/utils/locales/de.ts index 778d0c2..fa8e15c 100644 --- a/frontend/src/utils/locales/de.ts +++ b/frontend/src/utils/locales/de.ts @@ -334,6 +334,11 @@ export const de = { unknownDate: "Unbekanntes Datum", part: "Teil", collection: "Sammlung", + justNow: "Gerade eben", + hoursAgo: "vor {hours} Stunden", + today: "Heute", + thisWeek: "Diese Woche", + weeksAgo: "vor {weeks} Wochen", selectVideoFile: "Videodatei Auswählen", pleaseSelectVideo: "Bitte wählen Sie eine Videodatei aus", uploadFailed: "Upload fehlgeschlagen", diff --git a/frontend/src/utils/locales/en.ts b/frontend/src/utils/locales/en.ts index 24ccb86..8f47def 100644 --- a/frontend/src/utils/locales/en.ts +++ b/frontend/src/utils/locales/en.ts @@ -375,6 +375,11 @@ export const en = { part: "Part", collection: "Collection", new: "NEW", + justNow: "Just now", + hoursAgo: "{hours} hours ago", + today: "Today", + thisWeek: "This week", + weeksAgo: "{weeks} weeks ago", // Upload Modal selectVideoFile: "Select Video File", diff --git a/frontend/src/utils/locales/es.ts b/frontend/src/utils/locales/es.ts index 05cd88b..ab27ebb 100644 --- a/frontend/src/utils/locales/es.ts +++ b/frontend/src/utils/locales/es.ts @@ -358,6 +358,11 @@ export const es = { unknownDate: "Fecha desconocida", part: "Parte", collection: "Colección", + justNow: "Ahora mismo", + hoursAgo: "Hace {hours} horas", + today: "Hoy", + thisWeek: "Esta semana", + weeksAgo: "Hace {weeks} semanas", selectVideoFile: "Seleccionar Archivo de Video", pleaseSelectVideo: "Por favor seleccione un archivo de video", uploadFailed: "Carga fallida", diff --git a/frontend/src/utils/locales/fr.ts b/frontend/src/utils/locales/fr.ts index 643e71c..e56295a 100644 --- a/frontend/src/utils/locales/fr.ts +++ b/frontend/src/utils/locales/fr.ts @@ -381,6 +381,11 @@ export const fr = { unknownDate: "Date inconnue", part: "Partie", collection: "Collection", + justNow: "À l'instant", + hoursAgo: "Il y a {hours} heures", + today: "Aujourd'hui", + thisWeek: "Cette semaine", + weeksAgo: "Il y a {weeks} semaines", // Upload Modal selectVideoFile: "Sélectionner un fichier vidéo", diff --git a/frontend/src/utils/locales/ja.ts b/frontend/src/utils/locales/ja.ts index b3cb0a7..b793411 100644 --- a/frontend/src/utils/locales/ja.ts +++ b/frontend/src/utils/locales/ja.ts @@ -359,6 +359,11 @@ export const ja = { unknownDate: "不明な日付", part: "パート", collection: "コレクション", + justNow: "たった今", + hoursAgo: "{hours}時間前", + today: "今日", + thisWeek: "今週", + weeksAgo: "{weeks}週間前", // Upload Modal selectVideoFile: "動画ファイルを選択", diff --git a/frontend/src/utils/locales/ko.ts b/frontend/src/utils/locales/ko.ts index b55bf19..56c3c7e 100644 --- a/frontend/src/utils/locales/ko.ts +++ b/frontend/src/utils/locales/ko.ts @@ -356,6 +356,11 @@ export const ko = { unknownDate: "알 수 없는 날짜", part: "파트", collection: "컬렉션", + justNow: "방금", + hoursAgo: "{hours}시간 전", + today: "오늘", + thisWeek: "이번 주", + weeksAgo: "{weeks}주 전", // Upload Modal selectVideoFile: "동영상 파일 선택", diff --git a/frontend/src/utils/locales/pt.ts b/frontend/src/utils/locales/pt.ts index 28c3a89..0d06e93 100644 --- a/frontend/src/utils/locales/pt.ts +++ b/frontend/src/utils/locales/pt.ts @@ -364,6 +364,11 @@ export const pt = { unknownDate: "Data desconhecida", part: "Parte", collection: "Coleção", + justNow: "Agora mesmo", + hoursAgo: "Há {hours} horas", + today: "Hoje", + thisWeek: "Esta semana", + weeksAgo: "Há {weeks} semanas", // Upload Modal selectVideoFile: "Selecionar Arquivo de Vídeo", diff --git a/frontend/src/utils/locales/ru.ts b/frontend/src/utils/locales/ru.ts index 58c3cad..4ba780f 100644 --- a/frontend/src/utils/locales/ru.ts +++ b/frontend/src/utils/locales/ru.ts @@ -374,6 +374,11 @@ export const ru = { unknownDate: "Неизвестная дата", part: "Часть", collection: "Коллекция", + justNow: "Только что", + hoursAgo: "{hours} часов назад", + today: "Сегодня", + thisWeek: "На этой неделе", + weeksAgo: "{weeks} недель назад", // Upload Modal selectVideoFile: "Выберите видеофайл", diff --git a/frontend/src/utils/locales/zh.ts b/frontend/src/utils/locales/zh.ts index 1c049ec..d6760c1 100644 --- a/frontend/src/utils/locales/zh.ts +++ b/frontend/src/utils/locales/zh.ts @@ -357,6 +357,11 @@ export const zh = { unknownDate: "未知日期", part: "分P", collection: "合集", + justNow: "刚刚", + hoursAgo: "{hours}小时前", + today: "今天", + thisWeek: "本周", + weeksAgo: "{weeks}周前", // Upload Modal selectVideoFile: "选择视频文件",