diff --git a/apps/web/src/routes/_app/upcoming.tsx b/apps/web/src/routes/_app/upcoming.tsx
new file mode 100644
index 0000000..4900ebb
--- /dev/null
+++ b/apps/web/src/routes/_app/upcoming.tsx
@@ -0,0 +1,122 @@
+import { Trans, useLingui } from "@lingui/react/macro";
+import { IconCalendarEvent } from "@tabler/icons-react";
+import { useInfiniteQuery } from "@tanstack/react-query";
+import { createFileRoute } from "@tanstack/react-router";
+
+import { UpcomingRow } from "@/components/dashboard/upcoming-item";
+import { Skeleton } from "@/components/ui/skeleton";
+import { useInfiniteScroll } from "@/hooks/use-infinite-scroll";
+import { orpc } from "@/lib/orpc/client";
+import { groupByDateBucket } from "@sofa/i18n/date-buckets";
+
+export const Route = createFileRoute("/_app/upcoming")({
+ staleTime: 30_000,
+ head: () => ({ meta: [{ title: "Upcoming — Sofa" }] }),
+ loader: async ({ context }) => {
+ await context.queryClient.ensureInfiniteQueryData(
+ orpc.dashboard.upcoming.infiniteOptions({
+ input: (pageParam: string | undefined) => ({
+ days: 90,
+ limit: 20,
+ cursor: pageParam,
+ }),
+ initialPageParam: undefined as string | undefined,
+ getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
+ }),
+ );
+ },
+ pendingComponent: UpcomingSkeleton,
+ component: UpcomingPage,
+});
+
+function UpcomingSkeleton() {
+ return (
+
+
+
+
+
+ {Array.from({ length: 5 }, (_, i) => (
+
+ ))}
+
+ );
+}
+
+function UpcomingPage() {
+ const { data, isPending, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery(
+ orpc.dashboard.upcoming.infiniteOptions({
+ input: (pageParam: string | undefined) => ({
+ days: 90,
+ limit: 20,
+ cursor: pageParam,
+ }),
+ initialPageParam: undefined as string | undefined,
+ getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
+ }),
+ );
+
+ const sentinelRef = useInfiniteScroll({
+ fetchNextPage,
+ hasNextPage,
+ isFetchingNextPage,
+ });
+
+ if (isPending) return
;
+
+ const allItems = data?.pages.flatMap((p) => p.items) ?? [];
+
+ if (allItems.length === 0) {
+ return (
+
+
+
+
+
+ No upcoming episodes or releases in the next 90 days.
+
+
+
+ );
+ }
+
+ const buckets = groupByDateBucket(allItems);
+
+ return (
+
+
+ {buckets.map((bucket) => (
+
+
+ {bucket.label}
+
+
+ {bucket.items.map((item, i) => (
+
+ ))}
+
+
+ ))}
+
+ {isFetchingNextPage &&
}
+
+ );
+}
+
+function UpcomingHeader() {
+ const { t } = useLingui();
+ return (
+
+
{t`Upcoming`}
+
+ Episodes and movies coming up in the next 90 days.
+
+
+ );
+}
diff --git a/docs/content/docs/api/dashboard/dashboard.upcoming.mdx b/docs/content/docs/api/dashboard/dashboard.upcoming.mdx
new file mode 100644
index 0000000..ea24f44
--- /dev/null
+++ b/docs/content/docs/api/dashboard/dashboard.upcoming.mdx
@@ -0,0 +1,19 @@
+---
+title: Get upcoming episodes and movies
+full: true
+_openapi:
+ method: GET
+ toc: []
+ structuredData:
+ headings: []
+ contents:
+ - content: >-
+ Fetch upcoming episodes and movie releases for titles in the user's
+ library, sorted by date. Supports cursor-based pagination.
+---
+
+{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
+
+Fetch upcoming episodes and movie releases for titles in the user's library, sorted by date. Supports cursor-based pagination.
+
+
\ No newline at end of file
diff --git a/docs/public/openapi.json b/docs/public/openapi.json
index 208e15b..f4872fa 100644
--- a/docs/public/openapi.json
+++ b/docs/public/openapi.json
@@ -1787,8 +1787,7 @@
"anyOf": [
{
"enum": [
- "in_progress",
- "completed"
+ "watchlist"
],
"type": "string"
},
@@ -1796,13 +1795,13 @@
"type": "null"
}
],
- "description": "Tracking status: in_progress (currently watching), completed (finished), or null to remove from library"
+ "description": "Set to watchlist to add to library, or null to remove. Other transitions happen via watch endpoints."
}
},
"required": [
"status"
],
- "description": "Update the user's tracking status for a title"
+ "description": "Add a title to the user's watchlist or remove from library"
}
}
}
@@ -1989,8 +1988,9 @@
"anyOf": [
{
"enum": [
- "watchlist",
- "in_progress",
+ "in_watchlist",
+ "watching",
+ "caught_up",
"completed"
],
"type": "string"
@@ -1999,7 +1999,7 @@
"type": "null"
}
],
- "description": "User's tracking status, or null if not in library"
+ "description": "User's display status, or null if not in library"
},
"rating": {
"anyOf": [
@@ -2079,13 +2079,14 @@
},
"additionalProperties": {
"enum": [
- "watchlist",
- "in_progress",
+ "in_watchlist",
+ "watching",
+ "caught_up",
"completed"
],
"type": "string"
},
- "description": "Map of title ID to the user's tracking status"
+ "description": "Map of title ID to the user's display status"
}
},
"required": [
@@ -2525,13 +2526,14 @@
},
"additionalProperties": {
"enum": [
- "watchlist",
- "in_progress",
+ "in_watchlist",
+ "watching",
+ "caught_up",
"completed"
],
"type": "string"
},
- "description": "Map of title ID to the user's tracking status"
+ "description": "Map of title ID to the user's display status"
},
"page": {
"type": "number",
@@ -2848,6 +2850,252 @@
]
}
},
+ "/dashboard/upcoming": {
+ "get": {
+ "operationId": "dashboard.upcoming",
+ "summary": "Get upcoming episodes and movies",
+ "description": "Fetch upcoming episodes and movie releases for titles in the user's library, sorted by date. Supports cursor-based pagination.",
+ "tags": [
+ "Dashboard"
+ ],
+ "parameters": [
+ {
+ "name": "days",
+ "in": "query",
+ "schema": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 90,
+ "default": 90,
+ "description": "How many days into the future to look"
+ },
+ "allowEmptyValue": true,
+ "allowReserved": true
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "schema": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 50,
+ "default": 20,
+ "description": "Maximum items per page"
+ },
+ "allowEmptyValue": true,
+ "allowReserved": true
+ },
+ {
+ "name": "cursor",
+ "in": "query",
+ "schema": {
+ "type": "string",
+ "description": "Pagination cursor"
+ },
+ "allowEmptyValue": true,
+ "allowReserved": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Upcoming items sorted by date with streaming info",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "items": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "episodeId": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Episode ID (TV only, null for movies and collapsed batches)"
+ },
+ "titleId": {
+ "type": "string",
+ "description": "Internal title ID"
+ },
+ "titleName": {
+ "type": "string",
+ "description": "Display title"
+ },
+ "titleType": {
+ "enum": [
+ "movie",
+ "tv"
+ ],
+ "type": "string",
+ "description": "Media type"
+ },
+ "posterPath": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Poster image path"
+ },
+ "posterThumbHash": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "ThumbHash blur placeholder"
+ },
+ "seasonNumber": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Season number (TV only)"
+ },
+ "episodeNumber": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Episode number (TV only)"
+ },
+ "episodeName": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Episode title (TV only)"
+ },
+ "episodeCount": {
+ "type": "number",
+ "description": "Number of episodes (1 for single, >1 for collapsed batch drops)"
+ },
+ "date": {
+ "type": "string",
+ "description": "Air date or release date (YYYY-MM-DD)"
+ },
+ "userStatus": {
+ "enum": [
+ "in_watchlist",
+ "watching",
+ "caught_up",
+ "completed"
+ ],
+ "type": "string",
+ "description": "User's display status"
+ },
+ "isNewSeason": {
+ "type": "boolean",
+ "description": "Whether this is a new season for a completed show"
+ },
+ "streamingProvider": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "providerId": {
+ "type": "number"
+ },
+ "providerName": {
+ "type": "string"
+ },
+ "logoPath": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "providerId",
+ "providerName",
+ "logoPath"
+ ]
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Primary streaming provider, or null"
+ }
+ },
+ "required": [
+ "episodeId",
+ "titleId",
+ "titleName",
+ "titleType",
+ "posterPath",
+ "posterThumbHash",
+ "seasonNumber",
+ "episodeNumber",
+ "episodeName",
+ "episodeCount",
+ "date",
+ "userStatus",
+ "isNewSeason",
+ "streamingProvider"
+ ],
+ "description": "An upcoming episode or movie release"
+ }
+ },
+ "nextCursor": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Cursor for the next page, or null if no more items"
+ }
+ },
+ "required": [
+ "items",
+ "nextCursor"
+ ],
+ "description": "Upcoming episodes and movie releases for tracked titles"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "session": []
+ }
+ ]
+ }
+ },
"/dashboard/library": {
"get": {
"operationId": "dashboard.library",
@@ -2976,8 +3224,9 @@
"anyOf": [
{
"enum": [
- "watchlist",
- "in_progress",
+ "in_watchlist",
+ "watching",
+ "caught_up",
"completed"
],
"type": "string"
@@ -2986,7 +3235,7 @@
"type": "null"
}
],
- "description": "User's tracking status"
+ "description": "User's display status"
}
},
"required": [
@@ -3292,13 +3541,14 @@
},
"additionalProperties": {
"enum": [
- "watchlist",
- "in_progress",
+ "in_watchlist",
+ "watching",
+ "caught_up",
"completed"
],
"type": "string"
},
- "description": "Map of title ID to the user's tracking status"
+ "description": "Map of title ID to the user's display status"
},
"episodeProgress": {
"type": "object",
@@ -3488,13 +3738,14 @@
},
"additionalProperties": {
"enum": [
- "watchlist",
- "in_progress",
+ "in_watchlist",
+ "watching",
+ "caught_up",
"completed"
],
"type": "string"
},
- "description": "Map of title ID to the user's tracking status"
+ "description": "Map of title ID to the user's display status"
},
"episodeProgress": {
"type": "object",
@@ -4124,13 +4375,14 @@
},
"additionalProperties": {
"enum": [
- "watchlist",
- "in_progress",
+ "in_watchlist",
+ "watching",
+ "caught_up",
"completed"
],
"type": "string"
},
- "description": "Map of title ID to the user's tracking status"
+ "description": "Map of title ID to the user's display status"
},
"episodeProgress": {
"type": "object",
diff --git a/packages/api/src/contract.ts b/packages/api/src/contract.ts
index a4667bb..c6ac4c4 100644
--- a/packages/api/src/contract.ts
+++ b/packages/api/src/contract.ts
@@ -57,6 +57,8 @@ import {
UpdateRatingInput,
UpdateScheduleInput,
UpdateStatusInput,
+ UpcomingInput,
+ UpcomingOutput,
UploadAvatarInput,
UploadAvatarOutput,
UserInfoOutput,
@@ -290,6 +292,18 @@ export const contract = {
successDescription: "Recommended titles",
})
.output(DashboardRecommendationsOutput),
+ upcoming: oc
+ .route({
+ method: "GET",
+ path: "/dashboard/upcoming",
+ tags: ["Dashboard"],
+ summary: "Get upcoming episodes and movies",
+ description:
+ "Fetch upcoming episodes and movie releases for titles in the user's library, sorted by date. Supports cursor-based pagination.",
+ successDescription: "Upcoming items sorted by date with streaming info",
+ })
+ .input(UpcomingInput)
+ .output(UpcomingOutput),
watchHistory: oc
.route({
method: "GET",
diff --git a/packages/api/src/schemas.ts b/packages/api/src/schemas.ts
index 600ad31..da4e703 100644
--- a/packages/api/src/schemas.ts
+++ b/packages/api/src/schemas.ts
@@ -540,6 +540,63 @@ export const DashboardRecommendationsOutput = z
description: "Personalized title recommendations for the dashboard",
});
+// ─── Upcoming outputs ─────────────────────────────────────────
+
+export const UpcomingInput = z
+ .object({
+ days: z
+ .number()
+ .int()
+ .min(1)
+ .max(90)
+ .default(90)
+ .describe("How many days into the future to look"),
+ limit: z.number().int().min(1).max(50).default(20).describe("Maximum items per page"),
+ cursor: z.string().optional().describe("Pagination cursor"),
+ })
+ .meta({ description: "Filters for the upcoming feed" });
+
+export const UpcomingItemSchema = z
+ .object({
+ episodeId: z
+ .string()
+ .nullable()
+ .describe("Episode ID (TV only, null for movies and collapsed batches)"),
+ titleId: z.string().describe("Internal title ID"),
+ titleName: z.string().describe("Display title"),
+ titleType: z.enum(["movie", "tv"]).describe("Media type"),
+ posterPath: z.string().nullable().describe("Poster image path"),
+ posterThumbHash: z.string().nullable().describe("ThumbHash blur placeholder"),
+ seasonNumber: z.number().nullable().describe("Season number (TV only)"),
+ episodeNumber: z.number().nullable().describe("Episode number (TV only)"),
+ episodeName: z.string().nullable().describe("Episode title (TV only)"),
+ episodeCount: z
+ .number()
+ .describe("Number of episodes (1 for single, >1 for collapsed batch drops)"),
+ date: z.string().describe("Air date or release date (YYYY-MM-DD)"),
+ userStatus: displayStatusEnum.describe("User's display status"),
+ isNewSeason: z.boolean().describe("Whether this is a new season for a completed show"),
+ streamingProvider: z
+ .object({
+ providerId: z.number(),
+ providerName: z.string(),
+ logoPath: z.string().nullable(),
+ })
+ .nullable()
+ .describe("Primary streaming provider, or null"),
+ })
+ .meta({ description: "An upcoming episode or movie release" });
+
+export const UpcomingOutput = z
+ .object({
+ items: z.array(UpcomingItemSchema),
+ nextCursor: z
+ .string()
+ .nullable()
+ .describe("Cursor for the next page, or null if no more items"),
+ })
+ .meta({ description: "Upcoming episodes and movie releases for tracked titles" });
+
// ─── Explore outputs ───────────────────────────────────────────
export const TrendingOutput = z
@@ -1036,4 +1093,5 @@ export type PaginationInfo = z.infer
;
export type TimePeriod = z.infer["period"];
export type ImportJob = z.infer;
export type NormalizedImport = z.infer;
+export type UpcomingItem = z.infer;
export type UpdateCheckResult = z.infer;
diff --git a/packages/core/src/discovery.ts b/packages/core/src/discovery.ts
index 7f55c49..befd318 100644
--- a/packages/core/src/discovery.ts
+++ b/packages/core/src/discovery.ts
@@ -1,5 +1,7 @@
+import type { DisplayStatus } from "@sofa/api/display-status";
import {
getAllTrackedTitleIds,
+ getAvailabilityByTitleIds,
getEngagedTitleIds,
getEpisodesBySeasonIds,
getEpisodeWatchCountSince,
@@ -17,10 +19,18 @@ import {
getTitleByIdOrNull,
getTitlesByIds,
getTvTitlesByIds,
+ getUpcomingEpisodes,
+ getUpcomingMovies,
getUserStatusCounts,
} from "@sofa/db/queries/discovery";
import { tmdbImageUrl } from "@sofa/tmdb/image";
+import { getDisplayStatusesByTitleIds } from "./tracking";
+
+function formatLocalDate(d: Date): string {
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
+}
+
export type TimePeriod = "today" | "this_week" | "this_month" | "this_year";
export function periodStartTimestamp(period: TimePeriod): number {
@@ -340,6 +350,217 @@ export function getRecommendationsFeed(userId: string) {
return sorted.map((r) => recTitleMap.get(r.titleId)).filter(Boolean);
}
+// ─── Upcoming feed ──────────────────────────────────────────────────
+
+export interface UpcomingItem {
+ episodeId: string | null;
+ titleId: string;
+ titleName: string;
+ titleType: "movie" | "tv";
+ posterPath: string | null;
+ posterThumbHash: string | null;
+ seasonNumber: number | null;
+ episodeNumber: number | null;
+ episodeName: string | null;
+ episodeCount: number;
+ date: string;
+ userStatus: DisplayStatus;
+ isNewSeason: boolean;
+ streamingProvider: {
+ providerId: number;
+ providerName: string;
+ logoPath: string | null;
+ } | null;
+}
+
+export interface UpcomingFeedResult {
+ items: UpcomingItem[];
+ nextCursor: string | null;
+}
+
+export function getUpcomingFeed(
+ userId: string,
+ options: { days?: number; limit?: number; cursor?: string } = {},
+): UpcomingFeedResult {
+ const { days = 90, limit = 20, cursor } = options;
+
+ const now = new Date();
+ const today = formatLocalDate(now);
+ const horizon = new Date();
+ horizon.setDate(horizon.getDate() + days);
+ const toDate = formatLocalDate(horizon);
+
+ // Use the cursor date as the lower bound so later pages skip already-seen dates,
+ // but don't apply a DB-level LIMIT so same-day items aren't truncated.
+ let cursorDate: string | undefined;
+ let cursorName: string | undefined;
+ let cursorId: string | undefined;
+ if (cursor) {
+ try {
+ const parsed = JSON.parse(atob(cursor));
+ cursorDate = parsed.d;
+ cursorName = parsed.n;
+ cursorId = parsed.i;
+ } catch {
+ // Invalid cursor — ignore
+ }
+ }
+ const fromDate = cursorDate ?? today;
+ const episodeRows = getUpcomingEpisodes(userId, fromDate, toDate);
+ const movieRows = getUpcomingMovies(userId, fromDate, toDate);
+
+ // Merge into unified items
+ type RawItem = { date: string; titleId: string; titleName: string } & (
+ | { type: "tv"; row: (typeof episodeRows)[number] }
+ | { type: "movie"; row: (typeof movieRows)[number] }
+ );
+
+ const merged: RawItem[] = [
+ ...episodeRows.map((r) => ({
+ date: r.airDate!,
+ titleId: r.titleId,
+ titleName: r.titleName,
+ type: "tv" as const,
+ row: r,
+ })),
+ ...movieRows.map((r) => ({
+ date: r.releaseDate!,
+ titleId: r.titleId,
+ titleName: r.titleName,
+ type: "movie" as const,
+ row: r,
+ })),
+ ];
+
+ // Sort by date ASC, then title ASC, then titleId for deterministic tiebreak
+ merged.sort(
+ (a, b) =>
+ a.date.localeCompare(b.date) ||
+ a.titleName.localeCompare(b.titleName) ||
+ a.titleId.localeCompare(b.titleId),
+ );
+
+ // Collapse batch drops: group 3+ episodes from the same title on the same date into one item
+ const collapsed: (RawItem & { episodeCount: number })[] = [];
+ let i = 0;
+ while (i < merged.length) {
+ const current = merged[i]!;
+ if (current.type === "tv") {
+ // Count consecutive episodes with the same (titleId, date)
+ let groupEnd = i + 1;
+ while (
+ groupEnd < merged.length &&
+ merged[groupEnd]!.type === "tv" &&
+ merged[groupEnd]!.titleId === current.titleId &&
+ merged[groupEnd]!.date === current.date
+ ) {
+ groupEnd++;
+ }
+ const groupSize = groupEnd - i;
+ if (groupSize >= 3) {
+ // Collapse: keep first item with episodeCount and clear episode name
+ collapsed.push({ ...current, episodeCount: groupSize });
+ i = groupEnd;
+ } else {
+ // Keep individual items
+ for (let j = i; j < groupEnd; j++) {
+ collapsed.push({ ...merged[j]!, episodeCount: 1 });
+ }
+ i = groupEnd;
+ }
+ } else {
+ collapsed.push({ ...current, episodeCount: 1 });
+ i++;
+ }
+ }
+
+ // Apply cursor: skip items at or before the cursor position.
+ // Cursor is base64-encoded JSON {d, n, i} matching the sort key (date, titleName, titleId).
+ let startIdx = 0;
+ if (cursorDate && cursorName && cursorId) {
+ startIdx = collapsed.findIndex(
+ (item) =>
+ item.date > cursorDate ||
+ (item.date === cursorDate && item.titleName > cursorName) ||
+ (item.date === cursorDate && item.titleName === cursorName && item.titleId > cursorId),
+ );
+ if (startIdx === -1) startIdx = collapsed.length;
+ }
+
+ const pageItems = collapsed.slice(startIdx, startIdx + limit);
+ const hasMore = startIdx + limit < collapsed.length;
+
+ // Compute cursor from the last item on this page
+ let nextCursor: string | null = null;
+ if (hasMore && pageItems.length > 0) {
+ const last = pageItems[pageItems.length - 1]!;
+ nextCursor = btoa(JSON.stringify({ d: last.date, n: last.titleName, i: last.titleId }));
+ }
+
+ // Batch-fetch display statuses and streaming providers
+ const titleIds = [...new Set(pageItems.map((item) => item.titleId))];
+ const displayStatuses = getDisplayStatusesByTitleIds(userId, titleIds);
+ const providerRows = getAvailabilityByTitleIds(titleIds);
+ const providerMap = new Map<
+ string,
+ { providerId: number; providerName: string; logoPath: string | null }
+ >();
+ for (const p of providerRows) {
+ if (!providerMap.has(p.titleId)) {
+ providerMap.set(p.titleId, {
+ providerId: p.providerId,
+ providerName: p.providerName,
+ logoPath: p.logoPath,
+ });
+ }
+ }
+
+ const items: UpcomingItem[] = pageItems.map((item) => {
+ if (item.type === "tv") {
+ const r = item.row;
+ const isCollapsed = item.episodeCount > 1;
+ return {
+ episodeId: isCollapsed ? null : r.episodeId,
+ titleId: r.titleId,
+ titleName: r.titleName,
+ titleType: "tv",
+ posterPath: r.posterPath,
+ posterThumbHash: r.posterThumbHash,
+ seasonNumber: r.seasonNumber,
+ episodeNumber: r.episodeNumber,
+ episodeName: isCollapsed ? null : r.episodeName,
+ episodeCount: item.episodeCount,
+ date: r.airDate!,
+ userStatus: displayStatuses[r.titleId] ?? "watching",
+ isNewSeason:
+ (displayStatuses[r.titleId] === "caught_up" ||
+ displayStatuses[r.titleId] === "completed") &&
+ r.episodeNumber === 1,
+ streamingProvider: providerMap.get(r.titleId) ?? null,
+ };
+ }
+ const r = item.row;
+ return {
+ episodeId: null,
+ titleId: r.titleId,
+ titleName: r.titleName,
+ titleType: "movie",
+ posterPath: r.posterPath,
+ posterThumbHash: r.posterThumbHash,
+ seasonNumber: null,
+ episodeNumber: null,
+ episodeName: null,
+ episodeCount: 1,
+ date: r.releaseDate!,
+ userStatus: displayStatuses[r.titleId] ?? "in_watchlist",
+ isNewSeason: false,
+ streamingProvider: providerMap.get(r.titleId) ?? null,
+ };
+ });
+
+ return { items, nextCursor };
+}
+
export function getRecommendationsForTitle(titleId: string) {
const title = getTitleByIdOrNull(titleId);
if (!title) return [];
diff --git a/packages/core/test/upcoming.test.ts b/packages/core/test/upcoming.test.ts
new file mode 100644
index 0000000..619d6b7
--- /dev/null
+++ b/packages/core/test/upcoming.test.ts
@@ -0,0 +1,293 @@
+import { beforeEach, describe, expect, test } from "bun:test";
+
+import {
+ clearAllTables,
+ insertAvailabilityOffer,
+ insertEpisodeWatch,
+ insertStatus,
+ insertTitle,
+ insertTvShow,
+ insertUser,
+} from "@sofa/db/test-utils";
+
+import { getUpcomingFeed } from "../src/discovery";
+
+function daysFromNow(offset: number): string {
+ const d = new Date();
+ d.setDate(d.getDate() + offset);
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
+}
+
+beforeEach(() => {
+ clearAllTables();
+ insertUser();
+});
+
+// ── Basic feed ──────────────────────────────────────────────────────
+
+describe("getUpcomingFeed", () => {
+ test("returns upcoming episodes for tracked shows", () => {
+ const tomorrow = daysFromNow(1);
+ insertTvShow("tv-1", 100, 1, 2, {
+ title: "Breaking Bad",
+ airDates: [tomorrow, tomorrow],
+ });
+ insertStatus("user-1", "tv-1", "in_progress");
+
+ const result = getUpcomingFeed("user-1", { days: 7 });
+ expect(result.items).toHaveLength(2);
+ expect(result.items[0].titleName).toBe("Breaking Bad");
+ expect(result.items[0].titleType).toBe("tv");
+ expect(result.items[0].date).toBe(tomorrow);
+ });
+
+ test("returns upcoming movies for tracked titles", () => {
+ const nextWeek = daysFromNow(5);
+ insertTitle({ id: "m1", tmdbId: 1, type: "movie", title: "Dune 3", releaseDate: nextWeek });
+ insertStatus("user-1", "m1", "watchlist");
+
+ const result = getUpcomingFeed("user-1", { days: 7 });
+ expect(result.items).toHaveLength(1);
+ expect(result.items[0].titleName).toBe("Dune 3");
+ expect(result.items[0].titleType).toBe("movie");
+ expect(result.items[0].date).toBe(nextWeek);
+ });
+
+ test("includes items airing today", () => {
+ const today = daysFromNow(0);
+ insertTvShow("tv-1", 100, 1, 1, { airDates: [today] });
+ insertStatus("user-1", "tv-1", "in_progress");
+
+ const result = getUpcomingFeed("user-1", { days: 7 });
+ expect(result.items).toHaveLength(1);
+ expect(result.items[0].date).toBe(today);
+ });
+
+ test("excludes items beyond the days window", () => {
+ const farFuture = daysFromNow(91);
+ insertTvShow("tv-1", 100, 1, 1, { airDates: [farFuture] });
+ insertStatus("user-1", "tv-1", "in_progress");
+
+ const result = getUpcomingFeed("user-1", { days: 90 });
+ expect(result.items).toHaveLength(0);
+ });
+
+ test("excludes titles not in user's library", () => {
+ const tomorrow = daysFromNow(1);
+ insertTvShow("tv-1", 100, 1, 1, { airDates: [tomorrow] });
+ // No insertStatus — not tracked
+
+ const result = getUpcomingFeed("user-1", { days: 7 });
+ expect(result.items).toHaveLength(0);
+ });
+
+ test("returns empty when no upcoming items", () => {
+ const result = getUpcomingFeed("user-1", { days: 7 });
+ expect(result.items).toHaveLength(0);
+ expect(result.nextCursor).toBeNull();
+ });
+});
+
+// ── Sorting ─────────────────────────────────────────────────────────
+
+describe("sorting", () => {
+ test("sorts by date ascending, then title name", () => {
+ const day1 = daysFromNow(1);
+ const day2 = daysFromNow(2);
+
+ insertTvShow("tv-z", 101, 1, 1, { title: "Zebra Show", airDates: [day1] });
+ insertTvShow("tv-a", 102, 1, 1, { title: "Alpha Show", airDates: [day2] });
+ insertTvShow("tv-b", 103, 1, 1, { title: "Alpha Show 2", airDates: [day1] });
+ insertStatus("user-1", "tv-z", "in_progress");
+ insertStatus("user-1", "tv-a", "in_progress");
+ insertStatus("user-1", "tv-b", "in_progress");
+
+ const result = getUpcomingFeed("user-1", { days: 7 });
+ expect(result.items.map((i) => i.titleName)).toEqual([
+ "Alpha Show 2",
+ "Zebra Show",
+ "Alpha Show",
+ ]);
+ });
+
+ test("merges movies and episodes in date order", () => {
+ const day1 = daysFromNow(1);
+ const day2 = daysFromNow(2);
+
+ insertTitle({ id: "m1", tmdbId: 1, type: "movie", title: "A Movie", releaseDate: day2 });
+ insertTvShow("tv-1", 100, 1, 1, { title: "A Show", airDates: [day1] });
+ insertStatus("user-1", "m1", "watchlist");
+ insertStatus("user-1", "tv-1", "in_progress");
+
+ const result = getUpcomingFeed("user-1", { days: 7 });
+ expect(result.items[0].titleType).toBe("tv");
+ expect(result.items[1].titleType).toBe("movie");
+ });
+});
+
+// ── Batch collapse ──────────────────────────────────────────────────
+
+describe("batch collapse", () => {
+ test("collapses 3+ same-day episodes from the same title", () => {
+ const tomorrow = daysFromNow(1);
+ insertTvShow("tv-1", 100, 1, 4, {
+ title: "Batch Show",
+ airDates: [tomorrow, tomorrow, tomorrow, tomorrow],
+ });
+ insertStatus("user-1", "tv-1", "in_progress");
+
+ const result = getUpcomingFeed("user-1", { days: 7 });
+ expect(result.items).toHaveLength(1);
+ expect(result.items[0].episodeCount).toBe(4);
+ expect(result.items[0].episodeName).toBeNull();
+ });
+
+ test("does not collapse fewer than 3 same-day episodes", () => {
+ const tomorrow = daysFromNow(1);
+ insertTvShow("tv-1", 100, 1, 2, {
+ title: "Small Drop",
+ airDates: [tomorrow, tomorrow],
+ });
+ insertStatus("user-1", "tv-1", "in_progress");
+
+ const result = getUpcomingFeed("user-1", { days: 7 });
+ expect(result.items).toHaveLength(2);
+ expect(result.items[0].episodeCount).toBe(1);
+ expect(result.items[0].episodeName).toBe("S1E1");
+ });
+
+ test("does not collapse episodes on different dates", () => {
+ const day1 = daysFromNow(1);
+ const day2 = daysFromNow(2);
+ const day3 = daysFromNow(3);
+ insertTvShow("tv-1", 100, 1, 3, {
+ title: "Spread Show",
+ airDates: [day1, day2, day3],
+ });
+ insertStatus("user-1", "tv-1", "in_progress");
+
+ const result = getUpcomingFeed("user-1", { days: 7 });
+ expect(result.items).toHaveLength(3);
+ });
+});
+
+// ── Cursor pagination ───────────────────────────────────────────────
+
+describe("cursor pagination", () => {
+ test("paginates with limit and returns nextCursor", () => {
+ const day1 = daysFromNow(1);
+ const day2 = daysFromNow(2);
+ const day3 = daysFromNow(3);
+
+ insertTvShow("tv-a", 101, 1, 1, { title: "Show A", airDates: [day1] });
+ insertTvShow("tv-b", 102, 1, 1, { title: "Show B", airDates: [day2] });
+ insertTvShow("tv-c", 103, 1, 1, { title: "Show C", airDates: [day3] });
+ insertStatus("user-1", "tv-a", "in_progress");
+ insertStatus("user-1", "tv-b", "in_progress");
+ insertStatus("user-1", "tv-c", "in_progress");
+
+ const page1 = getUpcomingFeed("user-1", { days: 7, limit: 2 });
+ expect(page1.items).toHaveLength(2);
+ expect(page1.items[0].titleName).toBe("Show A");
+ expect(page1.items[1].titleName).toBe("Show B");
+ expect(page1.nextCursor).not.toBeNull();
+
+ const page2 = getUpcomingFeed("user-1", { days: 7, limit: 2, cursor: page1.nextCursor! });
+ expect(page2.items).toHaveLength(1);
+ expect(page2.items[0].titleName).toBe("Show C");
+ expect(page2.nextCursor).toBeNull();
+ });
+
+ test("handles invalid cursor gracefully", () => {
+ const tomorrow = daysFromNow(1);
+ insertTvShow("tv-1", 100, 1, 1, { airDates: [tomorrow] });
+ insertStatus("user-1", "tv-1", "in_progress");
+
+ const result = getUpcomingFeed("user-1", { days: 7, cursor: "not-valid-base64!" });
+ expect(result.items).toHaveLength(1);
+ });
+});
+
+// ── isNewSeason ─────────────────────────────────────────────────────
+
+describe("isNewSeason", () => {
+ test("marks episode 1 as new season for caught_up shows", () => {
+ const yesterday = daysFromNow(-1);
+ const tomorrow = daysFromNow(1);
+ // 2 seasons, 1 ep each; S1E1 aired yesterday, S2E1 airs tomorrow
+ const { episodeIds } = insertTvShow("tv-1", 100, 2, 1, {
+ title: "Returning Show",
+ airDates: [yesterday, tomorrow],
+ status: "Returning Series",
+ });
+ insertStatus("user-1", "tv-1", "in_progress");
+ // Watch S1E1 (the only aired episode) → caught_up display status
+ insertEpisodeWatch("user-1", episodeIds[0]);
+
+ const result = getUpcomingFeed("user-1", { days: 7 });
+ expect(result.items).toHaveLength(1);
+ expect(result.items[0].isNewSeason).toBe(true);
+ });
+
+ test("does not mark non-episode-1 as new season", () => {
+ const yesterday = daysFromNow(-1);
+ const tomorrow = daysFromNow(1);
+ // 1 season, 3 episodes — ep 1 aired yesterday, ep 2 and 3 air tomorrow
+ const { episodeIds } = insertTvShow("tv-1", 100, 1, 3, {
+ title: "Mid Show",
+ airDates: [yesterday, tomorrow, tomorrow],
+ status: "Returning Series",
+ });
+ insertStatus("user-1", "tv-1", "in_progress");
+ // Watch ep 1 (only aired episode) → caught_up
+ insertEpisodeWatch("user-1", episodeIds[0]);
+
+ const result = getUpcomingFeed("user-1", { days: 7 });
+ // episodes 2 and 3 air tomorrow (not collapsed since only 2)
+ expect(result.items.every((i) => i.isNewSeason === false)).toBe(true);
+ });
+
+ test("does not mark episode 1 as new season for watching shows", () => {
+ const yesterday = daysFromNow(-1);
+ const tomorrow = daysFromNow(1);
+ // 2 seasons, 1 ep each; S1E1 aired yesterday, S2E1 airs tomorrow
+ insertTvShow("tv-1", 100, 2, 1, {
+ airDates: [yesterday, tomorrow],
+ status: "Returning Series",
+ });
+ insertStatus("user-1", "tv-1", "in_progress");
+ // No episodes watched → display status is "watching", not "caught_up"
+
+ const result = getUpcomingFeed("user-1", { days: 7 });
+ expect(result.items).toHaveLength(1);
+ expect(result.items[0].isNewSeason).toBe(false);
+ });
+});
+
+// ── Streaming provider ──────────────────────────────────────────────
+
+describe("streaming provider", () => {
+ test("attaches flatrate streaming provider", () => {
+ const tomorrow = daysFromNow(1);
+ insertTvShow("tv-1", 100, 1, 1, { airDates: [tomorrow] });
+ insertStatus("user-1", "tv-1", "in_progress");
+ insertAvailabilityOffer("tv-1", { providerName: "Netflix", providerId: 8 });
+
+ const result = getUpcomingFeed("user-1", { days: 7 });
+ expect(result.items[0].streamingProvider).toEqual({
+ providerId: 8,
+ providerName: "Netflix",
+ logoPath: null,
+ });
+ });
+
+ test("returns null when no flatrate provider exists", () => {
+ const tomorrow = daysFromNow(1);
+ insertTvShow("tv-1", 100, 1, 1, { airDates: [tomorrow] });
+ insertStatus("user-1", "tv-1", "in_progress");
+ insertAvailabilityOffer("tv-1", { offerType: "rent" });
+
+ const result = getUpcomingFeed("user-1", { days: 7 });
+ expect(result.items[0].streamingProvider).toBeNull();
+ });
+});
diff --git a/packages/db/src/queries/discovery.ts b/packages/db/src/queries/discovery.ts
index 6721f4a..ff29054 100644
--- a/packages/db/src/queries/discovery.ts
+++ b/packages/db/src/queries/discovery.ts
@@ -1,4 +1,4 @@
-import { and, desc, eq, inArray, sql } from "drizzle-orm";
+import { and, asc, desc, eq, gte, inArray, lte, sql } from "drizzle-orm";
import { db } from "../client";
import {
@@ -312,3 +312,76 @@ export function getRecommendationRowsForTitle(titleId: string) {
export function getTitleByIdOrNull(titleId: string) {
return db.select().from(titles).where(eq(titles.id, titleId)).get() ?? null;
}
+
+// ─── Upcoming feed queries ──────────────────────────────────────────
+
+export function getUpcomingEpisodes(userId: string, fromDate: string, toDate: string) {
+ return db
+ .select({
+ episodeId: episodes.id,
+ titleId: titles.id,
+ titleName: titles.title,
+ posterPath: titles.posterPath,
+ posterThumbHash: titles.posterThumbHash,
+ seasonNumber: seasons.seasonNumber,
+ episodeNumber: episodes.episodeNumber,
+ episodeName: episodes.name,
+ airDate: episodes.airDate,
+ userStatus: userTitleStatus.status,
+ })
+ .from(episodes)
+ .innerJoin(seasons, eq(episodes.seasonId, seasons.id))
+ .innerJoin(titles, and(eq(seasons.titleId, titles.id), eq(titles.type, "tv")))
+ .innerJoin(
+ userTitleStatus,
+ and(eq(userTitleStatus.titleId, titles.id), eq(userTitleStatus.userId, userId)),
+ )
+ .where(and(gte(episodes.airDate, fromDate), lte(episodes.airDate, toDate)))
+ .orderBy(asc(episodes.airDate), asc(titles.title))
+ .all();
+}
+
+export function getUpcomingMovies(userId: string, fromDate: string, toDate: string) {
+ return db
+ .select({
+ titleId: titles.id,
+ titleName: titles.title,
+ posterPath: titles.posterPath,
+ posterThumbHash: titles.posterThumbHash,
+ releaseDate: titles.releaseDate,
+ userStatus: userTitleStatus.status,
+ })
+ .from(titles)
+ .innerJoin(
+ userTitleStatus,
+ and(eq(userTitleStatus.titleId, titles.id), eq(userTitleStatus.userId, userId)),
+ )
+ .where(
+ and(
+ eq(titles.type, "movie"),
+ gte(titles.releaseDate, fromDate),
+ lte(titles.releaseDate, toDate),
+ ),
+ )
+ .orderBy(asc(titles.releaseDate), asc(titles.title))
+ .all();
+}
+
+export function getAvailabilityByTitleIds(titleIds: string[]) {
+ if (titleIds.length === 0) return [];
+ return db
+ .select({
+ titleId: availabilityOffers.titleId,
+ providerId: availabilityOffers.providerId,
+ providerName: availabilityOffers.providerName,
+ logoPath: availabilityOffers.logoPath,
+ })
+ .from(availabilityOffers)
+ .where(
+ and(
+ inArray(availabilityOffers.titleId, titleIds),
+ eq(availabilityOffers.offerType, "flatrate"),
+ ),
+ )
+ .all();
+}
diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts
index 7090790..6fb96db 100644
--- a/packages/db/src/schema.ts
+++ b/packages/db/src/schema.ts
@@ -160,6 +160,7 @@ export const episodes = sqliteTable(
},
(table) => [
uniqueIndex("episodes_seasonId_episodeNumber").on(table.seasonId, table.episodeNumber),
+ index("episodes_airDate").on(table.airDate),
],
);
diff --git a/packages/db/src/test-utils.ts b/packages/db/src/test-utils.ts
index 1fde1b2..c7c90e5 100644
--- a/packages/db/src/test-utils.ts
+++ b/packages/db/src/test-utils.ts
@@ -63,6 +63,9 @@ export function insertTitle(
tvdbId?: number;
type?: "movie" | "tv";
title?: string;
+ releaseDate?: string;
+ posterPath?: string;
+ status?: string;
} = {},
) {
const id = overrides.id ?? "title-1";
@@ -74,14 +77,30 @@ export function insertTitle(
tvdbId: overrides.tvdbId,
type: overrides.type ?? "movie",
title: overrides.title ?? "Test Movie",
+ releaseDate: overrides.releaseDate,
+ posterPath: overrides.posterPath,
+ status: overrides.status,
})
.run();
return id;
}
-export function insertTvShow(titleId = "tv-1", tmdbId = 99999, seasonCount = 1, epsPerSeason = 3) {
- insertTitle({ id: titleId, tmdbId, type: "tv", title: "Test Show" });
+export function insertTvShow(
+ titleId = "tv-1",
+ tmdbId = 99999,
+ seasonCount = 1,
+ epsPerSeason = 3,
+ options: { title?: string; airDates?: string[]; status?: string } = {},
+) {
+ insertTitle({
+ id: titleId,
+ tmdbId,
+ type: "tv",
+ title: options.title ?? "Test Show",
+ status: options.status,
+ });
const episodeIds: string[] = [];
+ let airDateIdx = 0;
for (let s = 1; s <= seasonCount; s++) {
const seasonId = `${titleId}-s${s}`;
testDb.insert(seasons).values({ id: seasonId, titleId, seasonNumber: s }).run();
@@ -94,9 +113,11 @@ export function insertTvShow(titleId = "tv-1", tmdbId = 99999, seasonCount = 1,
seasonId,
episodeNumber: e,
name: `S${s}E${e}`,
+ airDate: options.airDates?.[airDateIdx],
})
.run();
episodeIds.push(epId);
+ airDateIdx++;
}
}
return { titleId, episodeIds };
diff --git a/packages/i18n/package.json b/packages/i18n/package.json
index 446fd63..81f7b56 100644
--- a/packages/i18n/package.json
+++ b/packages/i18n/package.json
@@ -7,6 +7,7 @@
".": "./src/index.ts",
"./locales": "./src/locales.ts",
"./format": "./src/format.ts",
+ "./date-buckets": "./src/date-buckets.ts",
"./test-utils": "./src/test-utils.tsx"
},
"scripts": {
diff --git a/packages/i18n/src/date-buckets.ts b/packages/i18n/src/date-buckets.ts
new file mode 100644
index 0000000..4ea5da6
--- /dev/null
+++ b/packages/i18n/src/date-buckets.ts
@@ -0,0 +1,78 @@
+import { msg } from "@lingui/core/macro";
+
+import { i18n } from "./index";
+
+export type DateBucket = {
+ key: string;
+ label: string;
+ items: T[];
+};
+
+export function formatLocalDate(d: Date): string {
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
+}
+
+function getToday(): string {
+ return formatLocalDate(new Date());
+}
+
+function addDays(dateStr: string, days: number): string {
+ const d = new Date(`${dateStr}T00:00:00`);
+ d.setDate(d.getDate() + days);
+ return formatLocalDate(d);
+}
+
+function getEndOfWeek(today: string): string {
+ return addDays(today, 6);
+}
+
+function getMonthLabel(dateStr: string): string {
+ const d = new Date(`${dateStr}T00:00:00`);
+ return new Intl.DateTimeFormat(i18n.locale, { month: "long" }).format(d);
+}
+
+type BucketKey = "today" | "tomorrow" | "this_week" | "next_week" | string;
+
+function getBucketKey(dateStr: string, today: string): BucketKey {
+ if (dateStr === today) return "today";
+ const tomorrow = addDays(today, 1);
+ if (dateStr === tomorrow) return "tomorrow";
+ const endOfWeek = getEndOfWeek(today);
+ if (dateStr <= endOfWeek) return "this_week";
+ const endOfNextWeek = addDays(endOfWeek, 7);
+ if (dateStr <= endOfNextWeek) return "next_week";
+ return `month_${dateStr.slice(0, 7)}`;
+}
+
+function getBucketLabel(key: BucketKey): string {
+ if (key === "today") return i18n._(msg`Today`);
+ if (key === "tomorrow") return i18n._(msg`Tomorrow`);
+ if (key === "this_week") return i18n._(msg`This Week`);
+ if (key === "next_week") return i18n._(msg`Next Week`);
+ if (key.startsWith("month_")) {
+ return getMonthLabel(`${key.slice(6)}-01`);
+ }
+ return key;
+}
+
+export function groupByDateBucket(items: T[]): DateBucket[] {
+ const today = getToday();
+ const bucketMap = new Map();
+ const bucketOrder: string[] = [];
+
+ for (const item of items) {
+ const key = getBucketKey(item.date, today);
+ let bucket = bucketMap.get(key);
+ if (!bucket) {
+ bucket = { label: getBucketLabel(key), items: [] };
+ bucketMap.set(key, bucket);
+ bucketOrder.push(key);
+ }
+ bucket.items.push(item);
+ }
+
+ return bucketOrder.map((key) => {
+ const bucket = bucketMap.get(key)!;
+ return { key, label: bucket.label, items: bucket.items };
+ });
+}
diff --git a/packages/i18n/src/po/ar.po b/packages/i18n/src/po/ar.po
index c07e897..0d8a278 100644
--- a/packages/i18n/src/po/ar.po
+++ b/packages/i18n/src/po/ar.po
@@ -413,6 +413,7 @@ msgid "Catch up"
msgstr "اللحاق"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Caught Up"
@@ -514,6 +515,7 @@ msgstr "إغلاق"
#: apps/native/src/app/(tabs)/(home)/index.tsx
#: apps/native/src/components/titles/status-action-button.tsx
#: apps/web/src/components/dashboard/stats-display.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Completed"
@@ -838,6 +840,10 @@ msgstr "حلقات {period}"
msgid "Episodes {select}"
msgstr "حلقات {select}"
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Episodes and movies coming up in the next 90 days."
+msgstr ""
+
#: apps/native/src/app/(tabs)/(home)/index.tsx
#~ msgid "Episodes this week"
#~ msgstr ""
@@ -1321,6 +1327,7 @@ msgstr "تحديد الكل كمشاهدة"
#~ msgid "Mark as Completed"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Mark as Watched"
msgstr "تحديد كمشاهدة"
@@ -1329,6 +1336,10 @@ msgstr "تحديد كمشاهدة"
#~ msgid "Mark as Watching"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+msgid "Mark Episode Watched"
+msgstr ""
+
#: apps/native/src/app/title/[id].tsx
#: apps/web/src/components/titles/title-actions.tsx
msgid "Mark Watched"
@@ -1439,6 +1450,10 @@ msgstr "أبداً"
msgid "Never run"
msgstr "لم يُشغَّل قط"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#~ msgid "New"
+#~ msgstr ""
+
#: apps/native/src/app/(auth)/register.tsx
msgid "New account creation is currently disabled."
msgstr "إنشاء الحسابات الجديدة معطّل حالياً."
@@ -1460,6 +1475,11 @@ msgstr "كلمة المرور الجديدة"
msgid "New password must be at least 8 characters"
msgstr "يجب أن تتكون كلمة المرور الجديدة من 8 أحرف على الأقل"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "New Season"
+msgstr ""
+
#: apps/web/src/components/people/filmography-grid.tsx
#: apps/web/src/components/people/filmography-grid.tsx
msgid "Newest"
@@ -1501,6 +1521,11 @@ msgstr "لم يتم العثور على نتائج."
msgid "No titles found for this genre."
msgstr "لم يتم العثور على عناوين لهذا النوع."
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "No upcoming episodes or releases in the next 90 days."
+msgstr ""
+
#: apps/native/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/system-health-section.tsx
@@ -1511,6 +1536,7 @@ msgstr "غير مُهيَّأ"
msgid "Not Found"
msgstr "غير موجود"
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
msgid "On Watchlist"
msgstr "في قائمة المشاهدة"
@@ -1839,6 +1865,7 @@ msgid "Remove from library"
msgstr "إزالة من المكتبة"
#: apps/native/src/components/dashboard/continue-watching-card.tsx
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Remove from Library"
msgstr "إزالة من المكتبة"
@@ -1938,6 +1965,16 @@ msgstr "راجع ما تم العثور عليه واختر ما تريد است
msgid "Run now"
msgstr "تشغيل الآن"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+#~ msgid "S{0} · {1} {2, plural, one {episode} other {episodes}}"
+#~ msgstr ""
+
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "S{seasonNum} · {epCount, plural, one {# episode} other {# episodes}}"
+msgstr ""
+
#: apps/web/src/components/settings/backup-schedule-section.tsx
msgid "Saturday"
msgstr "السبت"
@@ -2033,6 +2070,11 @@ msgstr "المواسم"
msgid "Security"
msgstr "الأمان"
+#: apps/native/src/components/ui/section-header.tsx
+#: apps/web/src/components/dashboard/feed-section.tsx
+msgid "See all"
+msgstr ""
+
#: apps/web/src/components/landing-page.tsx
msgid "Self-hosted movie & TV tracker"
msgstr "تطبيق تتبع الأفلام والمسلسلات ذاتي الاستضافة"
@@ -2453,6 +2495,15 @@ msgstr "تم إلغاء مشاهدة م{seasonNum} ح{epNum}"
msgid "Up next"
msgstr "التالي"
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/native/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Upcoming"
+msgstr ""
+
#: apps/native/src/app/(tabs)/(settings)/index.tsx
#~ msgid "Update available: {0}"
#~ msgstr ""
@@ -2571,6 +2622,7 @@ msgid "Watched S{seasonNum} E{epNum}"
msgstr "تمت مشاهدة م{seasonNum} ح{epNum}"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Watching"
@@ -2645,4 +2697,3 @@ msgstr "مكتبتك فارغة"
#: apps/native/src/app/(auth)/register.tsx
msgid "Your name"
msgstr "اسمك"
-
diff --git a/packages/i18n/src/po/de.po b/packages/i18n/src/po/de.po
index ec389ae..c840354 100644
--- a/packages/i18n/src/po/de.po
+++ b/packages/i18n/src/po/de.po
@@ -413,6 +413,7 @@ msgid "Catch up"
msgstr "Aufholen"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Caught Up"
@@ -514,6 +515,7 @@ msgstr "Schließen"
#: apps/native/src/app/(tabs)/(home)/index.tsx
#: apps/native/src/components/titles/status-action-button.tsx
#: apps/web/src/components/dashboard/stats-display.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Completed"
@@ -838,6 +840,10 @@ msgstr ""
msgid "Episodes {select}"
msgstr "Episoden {select}"
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Episodes and movies coming up in the next 90 days."
+msgstr ""
+
#: apps/native/src/app/(tabs)/(home)/index.tsx
#~ msgid "Episodes this week"
#~ msgstr ""
@@ -1321,6 +1327,7 @@ msgstr "Alle als gesehen markieren"
#~ msgid "Mark as Completed"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Mark as Watched"
msgstr "Als gesehen markieren"
@@ -1329,6 +1336,10 @@ msgstr "Als gesehen markieren"
#~ msgid "Mark as Watching"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+msgid "Mark Episode Watched"
+msgstr ""
+
#: apps/native/src/app/title/[id].tsx
#: apps/web/src/components/titles/title-actions.tsx
msgid "Mark Watched"
@@ -1439,6 +1450,10 @@ msgstr "Nie"
msgid "Never run"
msgstr "Noch nie ausgeführt"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#~ msgid "New"
+#~ msgstr ""
+
#: apps/native/src/app/(auth)/register.tsx
msgid "New account creation is currently disabled."
msgstr "Das Erstellen neuer Konten ist derzeit deaktiviert."
@@ -1460,6 +1475,11 @@ msgstr "Neues Passwort"
msgid "New password must be at least 8 characters"
msgstr "Das neue Passwort muss mindestens 8 Zeichen lang sein"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "New Season"
+msgstr ""
+
#: apps/web/src/components/people/filmography-grid.tsx
#: apps/web/src/components/people/filmography-grid.tsx
msgid "Newest"
@@ -1501,6 +1521,11 @@ msgstr "Keine Ergebnisse gefunden."
msgid "No titles found for this genre."
msgstr "Keine Titel für dieses Genre gefunden."
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "No upcoming episodes or releases in the next 90 days."
+msgstr ""
+
#: apps/native/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/system-health-section.tsx
@@ -1511,6 +1536,7 @@ msgstr "Nicht konfiguriert"
msgid "Not Found"
msgstr "Nicht gefunden"
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
msgid "On Watchlist"
msgstr "Auf der Merkliste"
@@ -1839,6 +1865,7 @@ msgid "Remove from library"
msgstr "Aus Bibliothek entfernen"
#: apps/native/src/components/dashboard/continue-watching-card.tsx
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Remove from Library"
msgstr "Aus Bibliothek entfernen"
@@ -1938,6 +1965,16 @@ msgstr "Überprüfe die gefundenen Einträge und wähle aus, was importiert werd
msgid "Run now"
msgstr "Jetzt ausführen"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+#~ msgid "S{0} · {1} {2, plural, one {episode} other {episodes}}"
+#~ msgstr ""
+
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "S{seasonNum} · {epCount, plural, one {# episode} other {# episodes}}"
+msgstr ""
+
#: apps/web/src/components/settings/backup-schedule-section.tsx
msgid "Saturday"
msgstr "Samstag"
@@ -2033,6 +2070,11 @@ msgstr "Staffeln"
msgid "Security"
msgstr "Sicherheit"
+#: apps/native/src/components/ui/section-header.tsx
+#: apps/web/src/components/dashboard/feed-section.tsx
+msgid "See all"
+msgstr ""
+
#: apps/web/src/components/landing-page.tsx
msgid "Self-hosted movie & TV tracker"
msgstr "Selbstgehosteter Film- & Serien-Tracker"
@@ -2453,6 +2495,15 @@ msgstr "S{seasonNum} E{epNum} als ungesehen markiert"
msgid "Up next"
msgstr "Als Nächstes"
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/native/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Upcoming"
+msgstr ""
+
#: apps/native/src/app/(tabs)/(settings)/index.tsx
#~ msgid "Update available: {0}"
#~ msgstr ""
@@ -2571,6 +2622,7 @@ msgid "Watched S{seasonNum} E{epNum}"
msgstr "S{seasonNum} E{epNum} als gesehen markiert"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Watching"
@@ -2645,4 +2697,3 @@ msgstr "Deine Bibliothek ist leer"
#: apps/native/src/app/(auth)/register.tsx
msgid "Your name"
msgstr "Dein Name"
-
diff --git a/packages/i18n/src/po/en.po b/packages/i18n/src/po/en.po
index 64196e0..2eb98fd 100644
--- a/packages/i18n/src/po/en.po
+++ b/packages/i18n/src/po/en.po
@@ -413,6 +413,7 @@ msgid "Catch up"
msgstr ""
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Caught Up"
@@ -514,6 +515,7 @@ msgstr ""
#: apps/native/src/app/(tabs)/(home)/index.tsx
#: apps/native/src/components/titles/status-action-button.tsx
#: apps/web/src/components/dashboard/stats-display.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Completed"
@@ -838,6 +840,10 @@ msgstr "Episodes {period}"
msgid "Episodes {select}"
msgstr ""
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Episodes and movies coming up in the next 90 days."
+msgstr "Episodes and movies coming up in the next 90 days."
+
#: apps/native/src/app/(tabs)/(home)/index.tsx
#~ msgid "Episodes this week"
#~ msgstr ""
@@ -1321,6 +1327,7 @@ msgstr ""
#~ msgid "Mark as Completed"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Mark as Watched"
msgstr ""
@@ -1329,6 +1336,10 @@ msgstr ""
#~ msgid "Mark as Watching"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+msgid "Mark Episode Watched"
+msgstr "Mark Episode Watched"
+
#: apps/native/src/app/title/[id].tsx
#: apps/web/src/components/titles/title-actions.tsx
msgid "Mark Watched"
@@ -1439,6 +1450,10 @@ msgstr ""
msgid "Never run"
msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#~ msgid "New"
+#~ msgstr "New"
+
#: apps/native/src/app/(auth)/register.tsx
msgid "New account creation is currently disabled."
msgstr ""
@@ -1460,6 +1475,11 @@ msgstr ""
msgid "New password must be at least 8 characters"
msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "New Season"
+msgstr "New Season"
+
#: apps/web/src/components/people/filmography-grid.tsx
#: apps/web/src/components/people/filmography-grid.tsx
msgid "Newest"
@@ -1501,6 +1521,11 @@ msgstr ""
msgid "No titles found for this genre."
msgstr ""
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "No upcoming episodes or releases in the next 90 days."
+msgstr "No upcoming episodes or releases in the next 90 days."
+
#: apps/native/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/system-health-section.tsx
@@ -1511,6 +1536,7 @@ msgstr ""
msgid "Not Found"
msgstr ""
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
msgid "On Watchlist"
msgstr ""
@@ -1839,6 +1865,7 @@ msgid "Remove from library"
msgstr ""
#: apps/native/src/components/dashboard/continue-watching-card.tsx
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Remove from Library"
msgstr ""
@@ -1938,6 +1965,16 @@ msgstr ""
msgid "Run now"
msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+#~ msgid "S{0} · {1} {2, plural, one {episode} other {episodes}}"
+#~ msgstr "S{0} · {1} {2, plural, one {episode} other {episodes}}"
+
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "S{seasonNum} · {epCount, plural, one {# episode} other {# episodes}}"
+msgstr "S{seasonNum} · {epCount, plural, one {# episode} other {# episodes}}"
+
#: apps/web/src/components/settings/backup-schedule-section.tsx
msgid "Saturday"
msgstr ""
@@ -2033,6 +2070,11 @@ msgstr ""
msgid "Security"
msgstr ""
+#: apps/native/src/components/ui/section-header.tsx
+#: apps/web/src/components/dashboard/feed-section.tsx
+msgid "See all"
+msgstr "See all"
+
#: apps/web/src/components/landing-page.tsx
msgid "Self-hosted movie & TV tracker"
msgstr ""
@@ -2453,6 +2495,15 @@ msgstr ""
msgid "Up next"
msgstr ""
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/native/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Upcoming"
+msgstr "Upcoming"
+
#: apps/native/src/app/(tabs)/(settings)/index.tsx
#~ msgid "Update available: {0}"
#~ msgstr ""
@@ -2571,6 +2622,7 @@ msgid "Watched S{seasonNum} E{epNum}"
msgstr ""
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Watching"
diff --git a/packages/i18n/src/po/es.po b/packages/i18n/src/po/es.po
index ae307f8..cecab03 100644
--- a/packages/i18n/src/po/es.po
+++ b/packages/i18n/src/po/es.po
@@ -413,6 +413,7 @@ msgid "Catch up"
msgstr "Ponerse al día"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Caught Up"
@@ -514,6 +515,7 @@ msgstr "Cerrar"
#: apps/native/src/app/(tabs)/(home)/index.tsx
#: apps/native/src/components/titles/status-action-button.tsx
#: apps/web/src/components/dashboard/stats-display.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Completed"
@@ -838,6 +840,10 @@ msgstr ""
msgid "Episodes {select}"
msgstr "Episodios {select}"
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Episodes and movies coming up in the next 90 days."
+msgstr ""
+
#: apps/native/src/app/(tabs)/(home)/index.tsx
#~ msgid "Episodes this week"
#~ msgstr ""
@@ -1321,6 +1327,7 @@ msgstr "Marcar todos como vistos"
#~ msgid "Mark as Completed"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Mark as Watched"
msgstr "Marcar como visto"
@@ -1329,6 +1336,10 @@ msgstr "Marcar como visto"
#~ msgid "Mark as Watching"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+msgid "Mark Episode Watched"
+msgstr ""
+
#: apps/native/src/app/title/[id].tsx
#: apps/web/src/components/titles/title-actions.tsx
msgid "Mark Watched"
@@ -1439,6 +1450,10 @@ msgstr "Nunca"
msgid "Never run"
msgstr "Nunca ejecutado"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#~ msgid "New"
+#~ msgstr ""
+
#: apps/native/src/app/(auth)/register.tsx
msgid "New account creation is currently disabled."
msgstr "La creación de nuevas cuentas está desactivada actualmente."
@@ -1460,6 +1475,11 @@ msgstr "Nueva contraseña"
msgid "New password must be at least 8 characters"
msgstr "La nueva contraseña debe tener al menos 8 caracteres"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "New Season"
+msgstr ""
+
#: apps/web/src/components/people/filmography-grid.tsx
#: apps/web/src/components/people/filmography-grid.tsx
msgid "Newest"
@@ -1501,6 +1521,11 @@ msgstr "No se encontraron resultados."
msgid "No titles found for this genre."
msgstr "No se encontraron títulos para este género."
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "No upcoming episodes or releases in the next 90 days."
+msgstr ""
+
#: apps/native/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/system-health-section.tsx
@@ -1511,6 +1536,7 @@ msgstr "No configurado"
msgid "Not Found"
msgstr "No encontrado"
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
msgid "On Watchlist"
msgstr "En la lista"
@@ -1839,6 +1865,7 @@ msgid "Remove from library"
msgstr "Eliminar de la biblioteca"
#: apps/native/src/components/dashboard/continue-watching-card.tsx
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Remove from Library"
msgstr "Eliminar de la biblioteca"
@@ -1938,6 +1965,16 @@ msgstr "Revisa lo encontrado y elige qué importar."
msgid "Run now"
msgstr "Ejecutar ahora"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+#~ msgid "S{0} · {1} {2, plural, one {episode} other {episodes}}"
+#~ msgstr ""
+
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "S{seasonNum} · {epCount, plural, one {# episode} other {# episodes}}"
+msgstr ""
+
#: apps/web/src/components/settings/backup-schedule-section.tsx
msgid "Saturday"
msgstr "Sábado"
@@ -2033,6 +2070,11 @@ msgstr "Temporadas"
msgid "Security"
msgstr "Seguridad"
+#: apps/native/src/components/ui/section-header.tsx
+#: apps/web/src/components/dashboard/feed-section.tsx
+msgid "See all"
+msgstr ""
+
#: apps/web/src/components/landing-page.tsx
msgid "Self-hosted movie & TV tracker"
msgstr "Seguimiento de películas y series autoalojado"
@@ -2453,6 +2495,15 @@ msgstr "No visto T{seasonNum} E{epNum}"
msgid "Up next"
msgstr "A continuación"
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/native/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Upcoming"
+msgstr ""
+
#: apps/native/src/app/(tabs)/(settings)/index.tsx
#~ msgid "Update available: {0}"
#~ msgstr ""
@@ -2571,6 +2622,7 @@ msgid "Watched S{seasonNum} E{epNum}"
msgstr "Visto T{seasonNum} E{epNum}"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Watching"
@@ -2645,4 +2697,3 @@ msgstr "Tu biblioteca está vacía"
#: apps/native/src/app/(auth)/register.tsx
msgid "Your name"
msgstr "Tu nombre"
-
diff --git a/packages/i18n/src/po/fr.po b/packages/i18n/src/po/fr.po
index edfd9d2..ccacaff 100644
--- a/packages/i18n/src/po/fr.po
+++ b/packages/i18n/src/po/fr.po
@@ -413,6 +413,7 @@ msgid "Catch up"
msgstr "Rattraper"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Caught Up"
@@ -514,6 +515,7 @@ msgstr "Fermer"
#: apps/native/src/app/(tabs)/(home)/index.tsx
#: apps/native/src/components/titles/status-action-button.tsx
#: apps/web/src/components/dashboard/stats-display.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Completed"
@@ -838,6 +840,10 @@ msgstr ""
msgid "Episodes {select}"
msgstr "Épisodes {select}"
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Episodes and movies coming up in the next 90 days."
+msgstr ""
+
#: apps/native/src/app/(tabs)/(home)/index.tsx
#~ msgid "Episodes this week"
#~ msgstr ""
@@ -1321,6 +1327,7 @@ msgstr "Tout marquer comme visionné"
#~ msgid "Mark as Completed"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Mark as Watched"
msgstr "Marquer comme visionné"
@@ -1329,6 +1336,10 @@ msgstr "Marquer comme visionné"
#~ msgid "Mark as Watching"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+msgid "Mark Episode Watched"
+msgstr ""
+
#: apps/native/src/app/title/[id].tsx
#: apps/web/src/components/titles/title-actions.tsx
msgid "Mark Watched"
@@ -1439,6 +1450,10 @@ msgstr "Jamais"
msgid "Never run"
msgstr "Jamais exécuté"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#~ msgid "New"
+#~ msgstr ""
+
#: apps/native/src/app/(auth)/register.tsx
msgid "New account creation is currently disabled."
msgstr "La création de nouveaux comptes est actuellement désactivée."
@@ -1460,6 +1475,11 @@ msgstr "Nouveau mot de passe"
msgid "New password must be at least 8 characters"
msgstr "Le nouveau mot de passe doit comporter au moins 8 caractères"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "New Season"
+msgstr ""
+
#: apps/web/src/components/people/filmography-grid.tsx
#: apps/web/src/components/people/filmography-grid.tsx
msgid "Newest"
@@ -1501,6 +1521,11 @@ msgstr "Aucun résultat trouvé."
msgid "No titles found for this genre."
msgstr "Aucun titre trouvé pour ce genre."
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "No upcoming episodes or releases in the next 90 days."
+msgstr ""
+
#: apps/native/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/system-health-section.tsx
@@ -1511,6 +1536,7 @@ msgstr "Non configuré"
msgid "Not Found"
msgstr "Introuvable"
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
msgid "On Watchlist"
msgstr "Dans la liste de suivi"
@@ -1839,6 +1865,7 @@ msgid "Remove from library"
msgstr "Supprimer de la bibliothèque"
#: apps/native/src/components/dashboard/continue-watching-card.tsx
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Remove from Library"
msgstr "Supprimer de la bibliothèque"
@@ -1938,6 +1965,16 @@ msgstr "Vérifiez ce qui a été trouvé et choisissez ce que vous souhaitez imp
msgid "Run now"
msgstr "Exécuter maintenant"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+#~ msgid "S{0} · {1} {2, plural, one {episode} other {episodes}}"
+#~ msgstr ""
+
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "S{seasonNum} · {epCount, plural, one {# episode} other {# episodes}}"
+msgstr ""
+
#: apps/web/src/components/settings/backup-schedule-section.tsx
msgid "Saturday"
msgstr "Samedi"
@@ -2033,6 +2070,11 @@ msgstr "Saisons"
msgid "Security"
msgstr "Sécurité"
+#: apps/native/src/components/ui/section-header.tsx
+#: apps/web/src/components/dashboard/feed-section.tsx
+msgid "See all"
+msgstr ""
+
#: apps/web/src/components/landing-page.tsx
msgid "Self-hosted movie & TV tracker"
msgstr "Suivi de films et séries auto-hébergé"
@@ -2453,6 +2495,15 @@ msgstr "Démarqué S{seasonNum} E{epNum}"
msgid "Up next"
msgstr "Suivant"
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/native/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Upcoming"
+msgstr ""
+
#: apps/native/src/app/(tabs)/(settings)/index.tsx
#~ msgid "Update available: {0}"
#~ msgstr ""
@@ -2571,6 +2622,7 @@ msgid "Watched S{seasonNum} E{epNum}"
msgstr "Visionné S{seasonNum} E{epNum}"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Watching"
@@ -2645,4 +2697,3 @@ msgstr "Votre bibliothèque est vide"
#: apps/native/src/app/(auth)/register.tsx
msgid "Your name"
msgstr "Votre nom"
-
diff --git a/packages/i18n/src/po/he.po b/packages/i18n/src/po/he.po
index dc9d785..b925190 100644
--- a/packages/i18n/src/po/he.po
+++ b/packages/i18n/src/po/he.po
@@ -413,6 +413,7 @@ msgid "Catch up"
msgstr "השלם צפייה"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Caught Up"
@@ -514,6 +515,7 @@ msgstr "סגור"
#: apps/native/src/app/(tabs)/(home)/index.tsx
#: apps/native/src/components/titles/status-action-button.tsx
#: apps/web/src/components/dashboard/stats-display.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Completed"
@@ -838,6 +840,10 @@ msgstr "פרקים {period}"
msgid "Episodes {select}"
msgstr "פרקים {select}"
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Episodes and movies coming up in the next 90 days."
+msgstr ""
+
#: apps/native/src/app/(tabs)/(home)/index.tsx
#~ msgid "Episodes this week"
#~ msgstr ""
@@ -1321,6 +1327,7 @@ msgstr "סמן הכל כנצפה"
#~ msgid "Mark as Completed"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Mark as Watched"
msgstr "סמן כנצפה"
@@ -1329,6 +1336,10 @@ msgstr "סמן כנצפה"
#~ msgid "Mark as Watching"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+msgid "Mark Episode Watched"
+msgstr ""
+
#: apps/native/src/app/title/[id].tsx
#: apps/web/src/components/titles/title-actions.tsx
msgid "Mark Watched"
@@ -1439,6 +1450,10 @@ msgstr "אף פעם"
msgid "Never run"
msgstr "מעולם לא הורץ"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#~ msgid "New"
+#~ msgstr ""
+
#: apps/native/src/app/(auth)/register.tsx
msgid "New account creation is currently disabled."
msgstr "יצירת חשבון חדש כרגע מושבתת."
@@ -1460,6 +1475,11 @@ msgstr "סיסמה חדשה"
msgid "New password must be at least 8 characters"
msgstr "הסיסמה החדשה חייבת להכיל לפחות 8 תווים"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "New Season"
+msgstr ""
+
#: apps/web/src/components/people/filmography-grid.tsx
#: apps/web/src/components/people/filmography-grid.tsx
msgid "Newest"
@@ -1501,6 +1521,11 @@ msgstr "לא נמצאו תוצאות."
msgid "No titles found for this genre."
msgstr "לא נמצאו כותרים לז'אנר זה."
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "No upcoming episodes or releases in the next 90 days."
+msgstr ""
+
#: apps/native/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/system-health-section.tsx
@@ -1511,6 +1536,7 @@ msgstr "לא מוגדר"
msgid "Not Found"
msgstr "לא נמצא"
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
msgid "On Watchlist"
msgstr "ברשימת הצפייה"
@@ -1839,6 +1865,7 @@ msgid "Remove from library"
msgstr "הסר מהספרייה"
#: apps/native/src/components/dashboard/continue-watching-card.tsx
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Remove from Library"
msgstr "הסר מהספרייה"
@@ -1938,6 +1965,16 @@ msgstr "בדוק מה נמצא ובחר מה לייבא."
msgid "Run now"
msgstr "הפעל עכשיו"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+#~ msgid "S{0} · {1} {2, plural, one {episode} other {episodes}}"
+#~ msgstr ""
+
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "S{seasonNum} · {epCount, plural, one {# episode} other {# episodes}}"
+msgstr ""
+
#: apps/web/src/components/settings/backup-schedule-section.tsx
msgid "Saturday"
msgstr "שבת"
@@ -2033,6 +2070,11 @@ msgstr "עונות"
msgid "Security"
msgstr "אבטחה"
+#: apps/native/src/components/ui/section-header.tsx
+#: apps/web/src/components/dashboard/feed-section.tsx
+msgid "See all"
+msgstr ""
+
#: apps/web/src/components/landing-page.tsx
msgid "Self-hosted movie & TV tracker"
msgstr "מעקב סרטים וטלוויזיה לאירוח עצמי"
@@ -2453,6 +2495,15 @@ msgstr "בוטלה צפייה ב-S{seasonNum} E{epNum}"
msgid "Up next"
msgstr "הבא בתור"
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/native/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Upcoming"
+msgstr ""
+
#: apps/native/src/app/(tabs)/(settings)/index.tsx
#~ msgid "Update available: {0}"
#~ msgstr ""
@@ -2571,6 +2622,7 @@ msgid "Watched S{seasonNum} E{epNum}"
msgstr "נצפה S{seasonNum} E{epNum}"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Watching"
@@ -2645,4 +2697,3 @@ msgstr "הספרייה שלך ריקה"
#: apps/native/src/app/(auth)/register.tsx
msgid "Your name"
msgstr "שמך"
-
diff --git a/packages/i18n/src/po/it.po b/packages/i18n/src/po/it.po
index 1633b99..56150cf 100644
--- a/packages/i18n/src/po/it.po
+++ b/packages/i18n/src/po/it.po
@@ -413,6 +413,7 @@ msgid "Catch up"
msgstr "Recupera episodi"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Caught Up"
@@ -514,6 +515,7 @@ msgstr "Chiudi"
#: apps/native/src/app/(tabs)/(home)/index.tsx
#: apps/native/src/components/titles/status-action-button.tsx
#: apps/web/src/components/dashboard/stats-display.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Completed"
@@ -838,6 +840,10 @@ msgstr ""
msgid "Episodes {select}"
msgstr "Episodi {select}"
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Episodes and movies coming up in the next 90 days."
+msgstr ""
+
#: apps/native/src/app/(tabs)/(home)/index.tsx
#~ msgid "Episodes this week"
#~ msgstr ""
@@ -1321,6 +1327,7 @@ msgstr "Segna tutti come visti"
#~ msgid "Mark as Completed"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Mark as Watched"
msgstr "Segna come visto"
@@ -1329,6 +1336,10 @@ msgstr "Segna come visto"
#~ msgid "Mark as Watching"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+msgid "Mark Episode Watched"
+msgstr ""
+
#: apps/native/src/app/title/[id].tsx
#: apps/web/src/components/titles/title-actions.tsx
msgid "Mark Watched"
@@ -1439,6 +1450,10 @@ msgstr "Mai"
msgid "Never run"
msgstr "Mai eseguito"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#~ msgid "New"
+#~ msgstr ""
+
#: apps/native/src/app/(auth)/register.tsx
msgid "New account creation is currently disabled."
msgstr "La creazione di nuovi account è attualmente disabilitata."
@@ -1460,6 +1475,11 @@ msgstr "Nuova password"
msgid "New password must be at least 8 characters"
msgstr "La nuova password deve contenere almeno 8 caratteri"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "New Season"
+msgstr ""
+
#: apps/web/src/components/people/filmography-grid.tsx
#: apps/web/src/components/people/filmography-grid.tsx
msgid "Newest"
@@ -1501,6 +1521,11 @@ msgstr "Nessun risultato trovato."
msgid "No titles found for this genre."
msgstr "Nessun titolo trovato per questo genere."
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "No upcoming episodes or releases in the next 90 days."
+msgstr ""
+
#: apps/native/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/system-health-section.tsx
@@ -1511,6 +1536,7 @@ msgstr "Non configurato"
msgid "Not Found"
msgstr "Non trovato"
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
msgid "On Watchlist"
msgstr "In watchlist"
@@ -1839,6 +1865,7 @@ msgid "Remove from library"
msgstr "Rimuovi dalla libreria"
#: apps/native/src/components/dashboard/continue-watching-card.tsx
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Remove from Library"
msgstr "Rimuovi dalla libreria"
@@ -1938,6 +1965,16 @@ msgstr "Rivedi i risultati trovati e scegli cosa importare."
msgid "Run now"
msgstr "Esegui ora"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+#~ msgid "S{0} · {1} {2, plural, one {episode} other {episodes}}"
+#~ msgstr ""
+
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "S{seasonNum} · {epCount, plural, one {# episode} other {# episodes}}"
+msgstr ""
+
#: apps/web/src/components/settings/backup-schedule-section.tsx
msgid "Saturday"
msgstr "Sabato"
@@ -2033,6 +2070,11 @@ msgstr "Stagioni"
msgid "Security"
msgstr "Sicurezza"
+#: apps/native/src/components/ui/section-header.tsx
+#: apps/web/src/components/dashboard/feed-section.tsx
+msgid "See all"
+msgstr ""
+
#: apps/web/src/components/landing-page.tsx
msgid "Self-hosted movie & TV tracker"
msgstr "Tracker film & TV self-hosted"
@@ -2453,6 +2495,15 @@ msgstr "Non visto S{seasonNum} E{epNum}"
msgid "Up next"
msgstr "Prossimo"
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/native/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Upcoming"
+msgstr ""
+
#: apps/native/src/app/(tabs)/(settings)/index.tsx
#~ msgid "Update available: {0}"
#~ msgstr ""
@@ -2571,6 +2622,7 @@ msgid "Watched S{seasonNum} E{epNum}"
msgstr "Visto S{seasonNum} E{epNum}"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Watching"
@@ -2645,4 +2697,3 @@ msgstr "La tua libreria è vuota"
#: apps/native/src/app/(auth)/register.tsx
msgid "Your name"
msgstr "Il tuo nome"
-
diff --git a/packages/i18n/src/po/ja.po b/packages/i18n/src/po/ja.po
index d331cb9..adaa31b 100644
--- a/packages/i18n/src/po/ja.po
+++ b/packages/i18n/src/po/ja.po
@@ -413,6 +413,7 @@ msgid "Catch up"
msgstr "追いつく"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Caught Up"
@@ -514,6 +515,7 @@ msgstr "閉じる"
#: apps/native/src/app/(tabs)/(home)/index.tsx
#: apps/native/src/components/titles/status-action-button.tsx
#: apps/web/src/components/dashboard/stats-display.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Completed"
@@ -838,6 +840,10 @@ msgstr "エピソード {period}"
msgid "Episodes {select}"
msgstr "エピソード {select}"
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Episodes and movies coming up in the next 90 days."
+msgstr ""
+
#: apps/native/src/app/(tabs)/(home)/index.tsx
#~ msgid "Episodes this week"
#~ msgstr ""
@@ -1321,6 +1327,7 @@ msgstr "すべて視聴済みにする"
#~ msgid "Mark as Completed"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Mark as Watched"
msgstr "視聴済みにする"
@@ -1329,6 +1336,10 @@ msgstr "視聴済みにする"
#~ msgid "Mark as Watching"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+msgid "Mark Episode Watched"
+msgstr ""
+
#: apps/native/src/app/title/[id].tsx
#: apps/web/src/components/titles/title-actions.tsx
msgid "Mark Watched"
@@ -1439,6 +1450,10 @@ msgstr "なし"
msgid "Never run"
msgstr "未実行"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#~ msgid "New"
+#~ msgstr ""
+
#: apps/native/src/app/(auth)/register.tsx
msgid "New account creation is currently disabled."
msgstr "現在、新規アカウントの作成は無効になっています。"
@@ -1460,6 +1475,11 @@ msgstr "新しいパスワード"
msgid "New password must be at least 8 characters"
msgstr "新しいパスワードは8文字以上である必要があります"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "New Season"
+msgstr ""
+
#: apps/web/src/components/people/filmography-grid.tsx
#: apps/web/src/components/people/filmography-grid.tsx
msgid "Newest"
@@ -1501,6 +1521,11 @@ msgstr "結果が見つかりませんでした。"
msgid "No titles found for this genre."
msgstr "このジャンルの作品は見つかりませんでした。"
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "No upcoming episodes or releases in the next 90 days."
+msgstr ""
+
#: apps/native/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/system-health-section.tsx
@@ -1511,6 +1536,7 @@ msgstr "未設定"
msgid "Not Found"
msgstr "見つかりません"
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
msgid "On Watchlist"
msgstr "ウォッチリスト内"
@@ -1839,6 +1865,7 @@ msgid "Remove from library"
msgstr "ライブラリから削除"
#: apps/native/src/components/dashboard/continue-watching-card.tsx
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Remove from Library"
msgstr "ライブラリから削除"
@@ -1938,6 +1965,16 @@ msgstr "見つかったものを確認し、インポートする項目を選択
msgid "Run now"
msgstr "今すぐ実行"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+#~ msgid "S{0} · {1} {2, plural, one {episode} other {episodes}}"
+#~ msgstr ""
+
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "S{seasonNum} · {epCount, plural, one {# episode} other {# episodes}}"
+msgstr ""
+
#: apps/web/src/components/settings/backup-schedule-section.tsx
msgid "Saturday"
msgstr "土曜日"
@@ -2033,6 +2070,11 @@ msgstr "シーズン"
msgid "Security"
msgstr "セキュリティ"
+#: apps/native/src/components/ui/section-header.tsx
+#: apps/web/src/components/dashboard/feed-section.tsx
+msgid "See all"
+msgstr ""
+
#: apps/web/src/components/landing-page.tsx
msgid "Self-hosted movie & TV tracker"
msgstr "セルフホスト型映画・TVトラッカー"
@@ -2453,6 +2495,15 @@ msgstr "S{seasonNum} E{epNum}を未視聴にしました"
msgid "Up next"
msgstr "次のエピソード"
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/native/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Upcoming"
+msgstr ""
+
#: apps/native/src/app/(tabs)/(settings)/index.tsx
#~ msgid "Update available: {0}"
#~ msgstr ""
@@ -2571,6 +2622,7 @@ msgid "Watched S{seasonNum} E{epNum}"
msgstr "S{seasonNum} E{epNum}を視聴済みにしました"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Watching"
@@ -2645,4 +2697,3 @@ msgstr "ライブラリは空です"
#: apps/native/src/app/(auth)/register.tsx
msgid "Your name"
msgstr "お名前"
-
diff --git a/packages/i18n/src/po/ko.po b/packages/i18n/src/po/ko.po
index e1d1e73..4c202ed 100644
--- a/packages/i18n/src/po/ko.po
+++ b/packages/i18n/src/po/ko.po
@@ -413,6 +413,7 @@ msgid "Catch up"
msgstr "따라잡기"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Caught Up"
@@ -514,6 +515,7 @@ msgstr "닫기"
#: apps/native/src/app/(tabs)/(home)/index.tsx
#: apps/native/src/components/titles/status-action-button.tsx
#: apps/web/src/components/dashboard/stats-display.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Completed"
@@ -838,6 +840,10 @@ msgstr "에피소드 {period}"
msgid "Episodes {select}"
msgstr "에피소드 {select}"
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Episodes and movies coming up in the next 90 days."
+msgstr ""
+
#: apps/native/src/app/(tabs)/(home)/index.tsx
#~ msgid "Episodes this week"
#~ msgstr ""
@@ -1321,6 +1327,7 @@ msgstr "모두 시청 완료로 표시"
#~ msgid "Mark as Completed"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Mark as Watched"
msgstr "시청 완료로 표시"
@@ -1329,6 +1336,10 @@ msgstr "시청 완료로 표시"
#~ msgid "Mark as Watching"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+msgid "Mark Episode Watched"
+msgstr ""
+
#: apps/native/src/app/title/[id].tsx
#: apps/web/src/components/titles/title-actions.tsx
msgid "Mark Watched"
@@ -1439,6 +1450,10 @@ msgstr "없음"
msgid "Never run"
msgstr "실행된 적 없음"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#~ msgid "New"
+#~ msgstr ""
+
#: apps/native/src/app/(auth)/register.tsx
msgid "New account creation is currently disabled."
msgstr "현재 새 계정 생성이 비활성화되어 있습니다."
@@ -1460,6 +1475,11 @@ msgstr "새 비밀번호"
msgid "New password must be at least 8 characters"
msgstr "새 비밀번호는 최소 8자 이상이어야 합니다"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "New Season"
+msgstr ""
+
#: apps/web/src/components/people/filmography-grid.tsx
#: apps/web/src/components/people/filmography-grid.tsx
msgid "Newest"
@@ -1501,6 +1521,11 @@ msgstr "결과를 찾을 수 없습니다."
msgid "No titles found for this genre."
msgstr "이 장르에 해당하는 작품이 없습니다."
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "No upcoming episodes or releases in the next 90 days."
+msgstr ""
+
#: apps/native/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/system-health-section.tsx
@@ -1511,6 +1536,7 @@ msgstr "구성되지 않음"
msgid "Not Found"
msgstr "찾을 수 없음"
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
msgid "On Watchlist"
msgstr "찜 목록에 있음"
@@ -1839,6 +1865,7 @@ msgid "Remove from library"
msgstr "라이브러리에서 제거"
#: apps/native/src/components/dashboard/continue-watching-card.tsx
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Remove from Library"
msgstr "라이브러리에서 제거"
@@ -1938,6 +1965,16 @@ msgstr "찾은 항목을 검토하고 가져올 항목을 선택하세요."
msgid "Run now"
msgstr "지금 실행"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+#~ msgid "S{0} · {1} {2, plural, one {episode} other {episodes}}"
+#~ msgstr ""
+
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "S{seasonNum} · {epCount, plural, one {# episode} other {# episodes}}"
+msgstr ""
+
#: apps/web/src/components/settings/backup-schedule-section.tsx
msgid "Saturday"
msgstr "토요일"
@@ -2033,6 +2070,11 @@ msgstr "시즌"
msgid "Security"
msgstr "보안"
+#: apps/native/src/components/ui/section-header.tsx
+#: apps/web/src/components/dashboard/feed-section.tsx
+msgid "See all"
+msgstr ""
+
#: apps/web/src/components/landing-page.tsx
msgid "Self-hosted movie & TV tracker"
msgstr "자체 호스팅 영화 & TV 트래커"
@@ -2453,6 +2495,15 @@ msgstr "S{seasonNum} E{epNum} 시청 취소됨"
msgid "Up next"
msgstr "다음 에피소드"
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/native/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Upcoming"
+msgstr ""
+
#: apps/native/src/app/(tabs)/(settings)/index.tsx
#~ msgid "Update available: {0}"
#~ msgstr ""
@@ -2571,6 +2622,7 @@ msgid "Watched S{seasonNum} E{epNum}"
msgstr "S{seasonNum} E{epNum} 시청 완료"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Watching"
@@ -2645,4 +2697,3 @@ msgstr "라이브러리가 비어 있습니다"
#: apps/native/src/app/(auth)/register.tsx
msgid "Your name"
msgstr "이름"
-
diff --git a/packages/i18n/src/po/nl.po b/packages/i18n/src/po/nl.po
index 15a7947..6bab205 100644
--- a/packages/i18n/src/po/nl.po
+++ b/packages/i18n/src/po/nl.po
@@ -413,6 +413,7 @@ msgid "Catch up"
msgstr "Inhalen"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Caught Up"
@@ -514,6 +515,7 @@ msgstr "Sluiten"
#: apps/native/src/app/(tabs)/(home)/index.tsx
#: apps/native/src/components/titles/status-action-button.tsx
#: apps/web/src/components/dashboard/stats-display.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Completed"
@@ -838,6 +840,10 @@ msgstr "Afleveringen {period}"
msgid "Episodes {select}"
msgstr "Afleveringen {select}"
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Episodes and movies coming up in the next 90 days."
+msgstr ""
+
#: apps/native/src/app/(tabs)/(home)/index.tsx
#~ msgid "Episodes this week"
#~ msgstr ""
@@ -1321,6 +1327,7 @@ msgstr "Alles als bekeken markeren"
#~ msgid "Mark as Completed"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Mark as Watched"
msgstr "Markeren als bekeken"
@@ -1329,6 +1336,10 @@ msgstr "Markeren als bekeken"
#~ msgid "Mark as Watching"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+msgid "Mark Episode Watched"
+msgstr ""
+
#: apps/native/src/app/title/[id].tsx
#: apps/web/src/components/titles/title-actions.tsx
msgid "Mark Watched"
@@ -1439,6 +1450,10 @@ msgstr "Nooit"
msgid "Never run"
msgstr "Nooit uitgevoerd"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#~ msgid "New"
+#~ msgstr ""
+
#: apps/native/src/app/(auth)/register.tsx
msgid "New account creation is currently disabled."
msgstr "Aanmaken van nieuwe accounts is momenteel uitgeschakeld."
@@ -1460,6 +1475,11 @@ msgstr "Nieuw wachtwoord"
msgid "New password must be at least 8 characters"
msgstr "Nieuw wachtwoord moet minimaal 8 tekens bevatten"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "New Season"
+msgstr ""
+
#: apps/web/src/components/people/filmography-grid.tsx
#: apps/web/src/components/people/filmography-grid.tsx
msgid "Newest"
@@ -1501,6 +1521,11 @@ msgstr "Geen resultaten gevonden."
msgid "No titles found for this genre."
msgstr "Geen titels gevonden voor dit genre."
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "No upcoming episodes or releases in the next 90 days."
+msgstr ""
+
#: apps/native/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/system-health-section.tsx
@@ -1511,6 +1536,7 @@ msgstr "Niet geconfigureerd"
msgid "Not Found"
msgstr "Niet gevonden"
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
msgid "On Watchlist"
msgstr "Op kijklijst"
@@ -1839,6 +1865,7 @@ msgid "Remove from library"
msgstr "Verwijderen uit bibliotheek"
#: apps/native/src/components/dashboard/continue-watching-card.tsx
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Remove from Library"
msgstr "Verwijderen uit bibliotheek"
@@ -1938,6 +1965,16 @@ msgstr "Bekijk wat er gevonden is en kies wat je wilt importeren."
msgid "Run now"
msgstr "Nu uitvoeren"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+#~ msgid "S{0} · {1} {2, plural, one {episode} other {episodes}}"
+#~ msgstr ""
+
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "S{seasonNum} · {epCount, plural, one {# episode} other {# episodes}}"
+msgstr ""
+
#: apps/web/src/components/settings/backup-schedule-section.tsx
msgid "Saturday"
msgstr "Zaterdag"
@@ -2033,6 +2070,11 @@ msgstr "Seizoenen"
msgid "Security"
msgstr "Beveiliging"
+#: apps/native/src/components/ui/section-header.tsx
+#: apps/web/src/components/dashboard/feed-section.tsx
+msgid "See all"
+msgstr ""
+
#: apps/web/src/components/landing-page.tsx
msgid "Self-hosted movie & TV tracker"
msgstr "Zelfgehoste film- & tv-tracker"
@@ -2453,6 +2495,15 @@ msgstr "Niet bekeken S{seasonNum} E{epNum}"
msgid "Up next"
msgstr "Volgende"
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/native/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Upcoming"
+msgstr ""
+
#: apps/native/src/app/(tabs)/(settings)/index.tsx
#~ msgid "Update available: {0}"
#~ msgstr ""
@@ -2571,6 +2622,7 @@ msgid "Watched S{seasonNum} E{epNum}"
msgstr "Bekeken S{seasonNum} E{epNum}"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Watching"
@@ -2645,4 +2697,3 @@ msgstr "Je bibliotheek is leeg"
#: apps/native/src/app/(auth)/register.tsx
msgid "Your name"
msgstr "Jouw naam"
-
diff --git a/packages/i18n/src/po/pt.po b/packages/i18n/src/po/pt.po
index c967bbd..0e88e61 100644
--- a/packages/i18n/src/po/pt.po
+++ b/packages/i18n/src/po/pt.po
@@ -413,6 +413,7 @@ msgid "Catch up"
msgstr "Colocar em dia"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Caught Up"
@@ -514,6 +515,7 @@ msgstr "Fechar"
#: apps/native/src/app/(tabs)/(home)/index.tsx
#: apps/native/src/components/titles/status-action-button.tsx
#: apps/web/src/components/dashboard/stats-display.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Completed"
@@ -838,6 +840,10 @@ msgstr ""
msgid "Episodes {select}"
msgstr "Episódios {select}"
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Episodes and movies coming up in the next 90 days."
+msgstr ""
+
#: apps/native/src/app/(tabs)/(home)/index.tsx
#~ msgid "Episodes this week"
#~ msgstr ""
@@ -1321,6 +1327,7 @@ msgstr "Marcar Todos como Assistidos"
#~ msgid "Mark as Completed"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Mark as Watched"
msgstr "Marcar como Assistido"
@@ -1329,6 +1336,10 @@ msgstr "Marcar como Assistido"
#~ msgid "Mark as Watching"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+msgid "Mark Episode Watched"
+msgstr ""
+
#: apps/native/src/app/title/[id].tsx
#: apps/web/src/components/titles/title-actions.tsx
msgid "Mark Watched"
@@ -1439,6 +1450,10 @@ msgstr "Nunca"
msgid "Never run"
msgstr "Nunca executado"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#~ msgid "New"
+#~ msgstr ""
+
#: apps/native/src/app/(auth)/register.tsx
msgid "New account creation is currently disabled."
msgstr "A criação de novas contas está desativada no momento."
@@ -1460,6 +1475,11 @@ msgstr "Nova senha"
msgid "New password must be at least 8 characters"
msgstr "A nova senha deve ter pelo menos 8 caracteres"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "New Season"
+msgstr ""
+
#: apps/web/src/components/people/filmography-grid.tsx
#: apps/web/src/components/people/filmography-grid.tsx
msgid "Newest"
@@ -1501,6 +1521,11 @@ msgstr "Nenhum resultado encontrado."
msgid "No titles found for this genre."
msgstr "Nenhum título encontrado para este gênero."
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "No upcoming episodes or releases in the next 90 days."
+msgstr ""
+
#: apps/native/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/system-health-section.tsx
@@ -1511,6 +1536,7 @@ msgstr "Não configurado"
msgid "Not Found"
msgstr "Não Encontrado"
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
msgid "On Watchlist"
msgstr "Na Lista de Desejos"
@@ -1839,6 +1865,7 @@ msgid "Remove from library"
msgstr "Remover da biblioteca"
#: apps/native/src/components/dashboard/continue-watching-card.tsx
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Remove from Library"
msgstr "Remover da Biblioteca"
@@ -1938,6 +1965,16 @@ msgstr "Revise o que foi encontrado e escolha o que importar."
msgid "Run now"
msgstr "Executar agora"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+#~ msgid "S{0} · {1} {2, plural, one {episode} other {episodes}}"
+#~ msgstr ""
+
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "S{seasonNum} · {epCount, plural, one {# episode} other {# episodes}}"
+msgstr ""
+
#: apps/web/src/components/settings/backup-schedule-section.tsx
msgid "Saturday"
msgstr "Sábado"
@@ -2033,6 +2070,11 @@ msgstr "Temporadas"
msgid "Security"
msgstr "Segurança"
+#: apps/native/src/components/ui/section-header.tsx
+#: apps/web/src/components/dashboard/feed-section.tsx
+msgid "See all"
+msgstr ""
+
#: apps/web/src/components/landing-page.tsx
msgid "Self-hosted movie & TV tracker"
msgstr "Rastreador de filmes e séries auto-hospedado"
@@ -2453,6 +2495,15 @@ msgstr "Desmarcado T{seasonNum} E{epNum}"
msgid "Up next"
msgstr "A seguir"
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/native/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Upcoming"
+msgstr ""
+
#: apps/native/src/app/(tabs)/(settings)/index.tsx
#~ msgid "Update available: {0}"
#~ msgstr ""
@@ -2571,6 +2622,7 @@ msgid "Watched S{seasonNum} E{epNum}"
msgstr "Assistido T{seasonNum} E{epNum}"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Watching"
@@ -2645,4 +2697,3 @@ msgstr "Sua biblioteca está vazia"
#: apps/native/src/app/(auth)/register.tsx
msgid "Your name"
msgstr "Seu nome"
-
diff --git a/packages/i18n/src/po/zh.po b/packages/i18n/src/po/zh.po
index 2843b76..797ea5c 100644
--- a/packages/i18n/src/po/zh.po
+++ b/packages/i18n/src/po/zh.po
@@ -413,6 +413,7 @@ msgid "Catch up"
msgstr "追上"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Caught Up"
@@ -514,6 +515,7 @@ msgstr "关闭"
#: apps/native/src/app/(tabs)/(home)/index.tsx
#: apps/native/src/components/titles/status-action-button.tsx
#: apps/web/src/components/dashboard/stats-display.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Completed"
@@ -838,6 +840,10 @@ msgstr "剧集 {period}"
msgid "Episodes {select}"
msgstr "剧集 {select}"
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Episodes and movies coming up in the next 90 days."
+msgstr ""
+
#: apps/native/src/app/(tabs)/(home)/index.tsx
#~ msgid "Episodes this week"
#~ msgstr ""
@@ -1321,6 +1327,7 @@ msgstr "全部标记为已观看"
#~ msgid "Mark as Completed"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Mark as Watched"
msgstr "标记为已观看"
@@ -1329,6 +1336,10 @@ msgstr "标记为已观看"
#~ msgid "Mark as Watching"
#~ msgstr ""
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+msgid "Mark Episode Watched"
+msgstr ""
+
#: apps/native/src/app/title/[id].tsx
#: apps/web/src/components/titles/title-actions.tsx
msgid "Mark Watched"
@@ -1439,6 +1450,10 @@ msgstr "从未"
msgid "Never run"
msgstr "从未运行"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#~ msgid "New"
+#~ msgstr ""
+
#: apps/native/src/app/(auth)/register.tsx
msgid "New account creation is currently disabled."
msgstr "当前已禁用新账户创建。"
@@ -1460,6 +1475,11 @@ msgstr "新密码"
msgid "New password must be at least 8 characters"
msgstr "新密码至少需要 8 个字符"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "New Season"
+msgstr ""
+
#: apps/web/src/components/people/filmography-grid.tsx
#: apps/web/src/components/people/filmography-grid.tsx
msgid "Newest"
@@ -1501,6 +1521,11 @@ msgstr "未找到结果。"
msgid "No titles found for this genre."
msgstr "此类型下未找到任何内容。"
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "No upcoming episodes or releases in the next 90 days."
+msgstr ""
+
#: apps/native/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/integration-card.tsx
#: apps/web/src/components/settings/system-health-section.tsx
@@ -1511,6 +1536,7 @@ msgstr "未配置"
msgid "Not Found"
msgstr "未找到"
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
msgid "On Watchlist"
msgstr "在待看列表中"
@@ -1839,6 +1865,7 @@ msgid "Remove from library"
msgstr "从媒体库中移除"
#: apps/native/src/components/dashboard/continue-watching-card.tsx
+#: apps/native/src/components/dashboard/upcoming-row.tsx
#: apps/native/src/components/ui/poster-card.tsx
msgid "Remove from Library"
msgstr "从媒体库中移除"
@@ -1938,6 +1965,16 @@ msgstr "查看已发现的内容,选择要导入的项目。"
msgid "Run now"
msgstr "立即运行"
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+#~ msgid "S{0} · {1} {2, plural, one {episode} other {episodes}}"
+#~ msgstr ""
+
+#: apps/native/src/components/dashboard/upcoming-row.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
+msgid "S{seasonNum} · {epCount, plural, one {# episode} other {# episodes}}"
+msgstr ""
+
#: apps/web/src/components/settings/backup-schedule-section.tsx
msgid "Saturday"
msgstr "星期六"
@@ -2033,6 +2070,11 @@ msgstr "季"
msgid "Security"
msgstr "安全"
+#: apps/native/src/components/ui/section-header.tsx
+#: apps/web/src/components/dashboard/feed-section.tsx
+msgid "See all"
+msgstr ""
+
#: apps/web/src/components/landing-page.tsx
msgid "Self-hosted movie & TV tracker"
msgstr "自托管电影和电视剧追踪器"
@@ -2453,6 +2495,15 @@ msgstr "第 {seasonNum} 季第 {epNum} 集已取消标记"
msgid "Up next"
msgstr "接下来"
+#: apps/native/src/app/(tabs)/(home)/upcoming.tsx
+#: apps/native/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/dashboard/upcoming-section.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/components/nav-bar.tsx
+#: apps/web/src/routes/_app/upcoming.tsx
+msgid "Upcoming"
+msgstr ""
+
#: apps/native/src/app/(tabs)/(settings)/index.tsx
#~ msgid "Update available: {0}"
#~ msgstr ""
@@ -2571,6 +2622,7 @@ msgid "Watched S{seasonNum} E{epNum}"
msgstr "第 {seasonNum} 季第 {epNum} 集已标记为已看"
#: apps/native/src/components/titles/status-action-button.tsx
+#: apps/web/src/components/dashboard/upcoming-item.tsx
#: apps/web/src/components/title-card.tsx
#: apps/web/src/components/titles/status-button.tsx
msgid "Watching"
@@ -2645,4 +2697,3 @@ msgstr "您的媒体库为空"
#: apps/native/src/app/(auth)/register.tsx
msgid "Your name"
msgstr "您的姓名"
-