mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-10-15 19:45:42 -04:00
refactor(web): share one import-job flow between import dialogs
Extract the job loop into runImportJob (with tests), hold its UI state in useImportJob, and move the importing/done steps and stat/option components into import-dialog-parts. Both dialogs now close when an import continues in the background or the connection drops. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
18 files changed
+783
-900
No files matched your search
@@ -12,13 +12,20 @@ import {
|
||||
IconTrash,
|
||||
IconX,
|
||||
} from "@tabler/icons-react";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useNavigate, useRouter } from "@tanstack/react-router";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
DoneStep,
|
||||
ImportingStep,
|
||||
OptionCheckbox,
|
||||
StatBadge,
|
||||
} from "@/components/settings/import-dialog-parts";
|
||||
import { useImportJob } from "@/components/settings/use-import-job";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@@ -36,15 +43,13 @@ import {
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { useResetUserState } from "@/hooks/use-reset-user-state";
|
||||
import { authClient, signOut } from "@/lib/auth/client";
|
||||
import { getErrorMessage } from "@/lib/error-messages";
|
||||
import { useAppForm } from "@/lib/form";
|
||||
import { client, orpc } from "@/lib/orpc/client";
|
||||
import { invalidateTrackingQueries } from "@/lib/orpc/invalidate";
|
||||
import { orpc } from "@/lib/orpc/client";
|
||||
import type { NormalizedImport } from "@sofa/api/schemas";
|
||||
import { formatDate } from "@sofa/i18n/format";
|
||||
|
||||
@@ -370,28 +375,12 @@ interface ImportPreview {
|
||||
stats: { movies: number; episodes: number; watchlist: number; ratings: number };
|
||||
}
|
||||
|
||||
interface ImportResult {
|
||||
imported: number;
|
||||
skipped: number;
|
||||
failed: number;
|
||||
errors: string[];
|
||||
warnings: string[];
|
||||
}
|
||||
|
||||
function SofaImportDialog() {
|
||||
const { t } = useLingui();
|
||||
const queryClient = useQueryClient();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const importAbortRef = useRef<AbortController | null>(null);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [step, setStep] = useState<"preview" | "importing" | "done">("preview");
|
||||
const [preview, setPreview] = useState<ImportPreview | null>(null);
|
||||
const [result, setResult] = useState<ImportResult | null>(null);
|
||||
const [progress, setProgress] = useState<{
|
||||
current: number;
|
||||
total: number;
|
||||
message: string;
|
||||
} | null>(null);
|
||||
const [options, setOptions] = useState({
|
||||
importWatches: true,
|
||||
importWatchlist: true,
|
||||
@@ -408,109 +397,41 @@ function SofaImportDialog() {
|
||||
onError: (err) => {
|
||||
toast.error(getErrorMessage(err, t`Failed to parse file`));
|
||||
},
|
||||
onSettled: () => {
|
||||
if (fileInputRef.current) fileInputRef.current.value = "";
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
function handleFileSelect(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
parseMutation.mutate({ source: "sofa", file });
|
||||
parseMutation.mutate(
|
||||
{ source: "sofa", file },
|
||||
{
|
||||
onSettled: () => {
|
||||
if (fileInputRef.current) fileInputRef.current.value = "";
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const job = useImportJob({
|
||||
successMessage: (importedCount) => t`Imported ${importedCount} items from Sofa export`,
|
||||
onDetached: handleClose,
|
||||
});
|
||||
|
||||
async function handleImport() {
|
||||
if (!preview) return;
|
||||
setStep("importing");
|
||||
setProgress(null);
|
||||
|
||||
const abort = new AbortController();
|
||||
importAbortRef.current = abort;
|
||||
|
||||
try {
|
||||
const job = await client.imports.createJob({
|
||||
data: preview.data,
|
||||
options,
|
||||
});
|
||||
|
||||
const eventSource = await client.imports.jobEvents({ id: job.id }, { signal: abort.signal });
|
||||
|
||||
let receivedComplete = false;
|
||||
|
||||
for await (const event of eventSource) {
|
||||
if (abort.signal.aborted) break;
|
||||
if (event.type === "complete") {
|
||||
receivedComplete = true;
|
||||
setResult({
|
||||
imported: event.job.importedCount,
|
||||
skipped: event.job.skippedCount,
|
||||
failed: event.job.failedCount,
|
||||
errors: event.job.errors,
|
||||
warnings: event.job.warnings,
|
||||
});
|
||||
setStep("done");
|
||||
void invalidateTrackingQueries(queryClient);
|
||||
const importedCount = event.job.importedCount;
|
||||
if (importedCount > 0) {
|
||||
toast.success(t`Imported ${importedCount} items from Sofa export`);
|
||||
}
|
||||
} else if (event.type === "timeout") {
|
||||
receivedComplete = true;
|
||||
toast.info(t`Import is still running in the background. Check back later.`);
|
||||
handleClose();
|
||||
} else {
|
||||
setProgress({
|
||||
current: event.job.processedItems,
|
||||
total: event.job.totalItems,
|
||||
message: event.job.currentMessage ?? "",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!receivedComplete && !abort.signal.aborted) {
|
||||
try {
|
||||
const finalJob = await client.imports.getJob({ id: job.id });
|
||||
const isTerminal =
|
||||
finalJob.status === "success" ||
|
||||
finalJob.status === "error" ||
|
||||
finalJob.status === "cancelled";
|
||||
if (isTerminal) {
|
||||
setResult({
|
||||
imported: finalJob.importedCount,
|
||||
skipped: finalJob.skippedCount,
|
||||
failed: finalJob.failedCount,
|
||||
errors: finalJob.errors,
|
||||
warnings: finalJob.warnings,
|
||||
});
|
||||
setStep("done");
|
||||
void invalidateTrackingQueries(queryClient);
|
||||
} else {
|
||||
toast.info(t`Import is still running in the background. Check back later.`);
|
||||
handleClose();
|
||||
}
|
||||
} catch {
|
||||
toast.error(t`Lost connection to import. Check status in settings.`);
|
||||
handleClose();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (abort.signal.aborted) return;
|
||||
toast.error(getErrorMessage(err, t`Import failed`));
|
||||
setStep("preview");
|
||||
} finally {
|
||||
importAbortRef.current = null;
|
||||
}
|
||||
const outcome = await job.start({ data: preview.data, options });
|
||||
if (outcome === "done") setStep("done");
|
||||
else if (outcome === "failed") setStep("preview");
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
importAbortRef.current?.abort();
|
||||
importAbortRef.current = null;
|
||||
job.abort();
|
||||
setOpen(false);
|
||||
setStep("preview");
|
||||
setPreview(null);
|
||||
setResult(null);
|
||||
setProgress(null);
|
||||
job.reset();
|
||||
setOptions({ importWatches: true, importWatchlist: true, importRatings: true });
|
||||
}
|
||||
|
||||
@@ -522,9 +443,6 @@ function SofaImportDialog() {
|
||||
(options.importRatings ? preview.stats.ratings : 0)
|
||||
: 0;
|
||||
|
||||
const pct =
|
||||
progress && progress.total > 0 ? Math.round((progress.current / progress.total) * 100) : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
@@ -583,29 +501,32 @@ function SofaImportDialog() {
|
||||
|
||||
<div className="space-y-4 py-2">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<ImportStatBadge label={t`Movies`} count={movieCount} />
|
||||
<ImportStatBadge label={t`Episodes`} count={episodeCount} />
|
||||
<ImportStatBadge label={t`Library`} count={watchlistCount} />
|
||||
<ImportStatBadge label={t`Ratings`} count={ratingCount} />
|
||||
<StatBadge label={t`Movies`} count={movieCount} />
|
||||
<StatBadge label={t`Episodes`} count={episodeCount} />
|
||||
<StatBadge label={t`Library`} count={watchlistCount} />
|
||||
<StatBadge label={t`Ratings`} count={ratingCount} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium">
|
||||
<Trans>Import options</Trans>
|
||||
</p>
|
||||
<ImportOptionCheckbox
|
||||
<OptionCheckbox
|
||||
idPrefix="sofa-import"
|
||||
label={t`Watch history`}
|
||||
description={t`${movieCount} movies, ${episodeCount} episodes`}
|
||||
checked={options.importWatches}
|
||||
onChange={(v) => setOptions({ ...options, importWatches: v })}
|
||||
/>
|
||||
<ImportOptionCheckbox
|
||||
<OptionCheckbox
|
||||
idPrefix="sofa-import"
|
||||
label={t`Library statuses`}
|
||||
description={t`${watchlistCount} items`}
|
||||
checked={options.importWatchlist}
|
||||
onChange={(v) => setOptions({ ...options, importWatchlist: v })}
|
||||
/>
|
||||
<ImportOptionCheckbox
|
||||
<OptionCheckbox
|
||||
idPrefix="sofa-import"
|
||||
label={t`Ratings`}
|
||||
description={t`${ratingCount} ratings`}
|
||||
checked={options.importRatings}
|
||||
@@ -639,147 +560,17 @@ function SofaImportDialog() {
|
||||
);
|
||||
})()}
|
||||
|
||||
{step === "importing" && (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>Importing data</Trans>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
This may take a few minutes for large libraries. Please don't close this tab.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col items-center gap-4 py-8">
|
||||
<Progress value={pct} className="w-full" />
|
||||
<div className="flex flex-col items-center gap-1 text-center">
|
||||
{progress ? (
|
||||
<>
|
||||
<p className="text-sm font-medium">
|
||||
{progress.current} / {progress.total}
|
||||
</p>
|
||||
<p className="text-muted-foreground max-w-[300px] truncate text-xs">
|
||||
{progress.message}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<Spinner className="size-3" />
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<Trans>Starting import...</Trans>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
{step === "importing" && <ImportingStep source="Sofa" progress={job.progress} />}
|
||||
|
||||
{step === "done" && job.result && (
|
||||
<DoneStep source="Sofa" result={job.result} onClose={handleClose} />
|
||||
)}
|
||||
|
||||
{step === "done" &&
|
||||
result &&
|
||||
(() => {
|
||||
const errorCount = result.errors.length;
|
||||
const warningCount = result.warnings.length;
|
||||
const remainingErrors = errorCount - 50;
|
||||
return (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>Import complete</Trans>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
<Trans>Finished importing your Sofa export.</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-2">
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<ImportStatBadge label={t`Imported`} count={result.imported} />
|
||||
<ImportStatBadge label={t`Skipped`} count={result.skipped} />
|
||||
<ImportStatBadge label={t`Failed`} count={result.failed} />
|
||||
</div>
|
||||
|
||||
{errorCount > 0 && (
|
||||
<div className="bg-destructive/10 max-h-40 overflow-y-auto rounded-lg p-3">
|
||||
<p className="text-destructive mb-1 text-xs font-medium">
|
||||
<Trans>Errors ({errorCount})</Trans>
|
||||
</p>
|
||||
<ul className="text-destructive/80 space-y-0.5 text-xs">
|
||||
{result.errors.slice(0, 50).map((e, i) => (
|
||||
<li key={i}>{e}</li>
|
||||
))}
|
||||
{errorCount > 50 && (
|
||||
<li>
|
||||
<Trans>...and {remainingErrors} more</Trans>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{warningCount > 0 && (
|
||||
<div className="max-h-32 overflow-y-auto rounded-lg bg-yellow-500/10 p-3">
|
||||
<p className="mb-1 text-xs font-medium text-yellow-600">
|
||||
<Trans>Warnings ({warningCount})</Trans>
|
||||
</p>
|
||||
<ul className="space-y-0.5 text-xs text-yellow-600/80">
|
||||
{result.warnings.slice(0, 20).map((w, i) => (
|
||||
<li key={i}>{w}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button onClick={handleClose}>
|
||||
<Trans>Done</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ImportStatBadge({ label, count }: { label: string; count: number }) {
|
||||
return (
|
||||
<div className="bg-muted/50 rounded-lg p-2.5 text-center">
|
||||
<p className="text-lg leading-none font-semibold">{count}</p>
|
||||
<p className="text-muted-foreground mt-1 text-xs">{label}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ImportOptionCheckbox({
|
||||
label,
|
||||
description,
|
||||
checked,
|
||||
onChange,
|
||||
}: {
|
||||
label: string;
|
||||
description: string;
|
||||
checked: boolean;
|
||||
onChange: (v: boolean) => void;
|
||||
}) {
|
||||
const id = `sofa-import-${label}`;
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
<Checkbox id={id} checked={checked} onCheckedChange={onChange} />
|
||||
<div>
|
||||
<Label htmlFor={id} className="text-sm">
|
||||
{label}
|
||||
</Label>
|
||||
<p className="text-muted-foreground text-xs">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Change Password Dialog ─────────────────────────────────
|
||||
|
||||
function ChangePasswordDialog() {
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
import { Trans, useLingui } from "@lingui/react/macro";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import type { ImportProgress, ImportResult } from "@/lib/import-job";
|
||||
|
||||
export function ImportingStep({
|
||||
source,
|
||||
progress,
|
||||
}: {
|
||||
source: string;
|
||||
progress: ImportProgress | null;
|
||||
}) {
|
||||
const pct =
|
||||
progress && progress.total > 0 ? Math.round((progress.current / progress.total) * 100) : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>Importing from {source}</Trans>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
This may take a few minutes for large libraries. Please don't close this tab.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col items-center gap-4 py-8">
|
||||
<Progress value={pct} className="w-full" />
|
||||
<div className="flex flex-col items-center gap-1 text-center">
|
||||
{progress ? (
|
||||
<>
|
||||
<p className="text-sm font-medium">
|
||||
{progress.current} / {progress.total}
|
||||
</p>
|
||||
<p className="text-muted-foreground max-w-[300px] truncate text-xs">
|
||||
{progress.message}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<Spinner className="size-3" />
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<Trans>Starting import...</Trans>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function DoneStep({
|
||||
source,
|
||||
result,
|
||||
onClose,
|
||||
}: {
|
||||
source: string;
|
||||
result: ImportResult;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const { t } = useLingui();
|
||||
const errorCount = result.errors.length;
|
||||
const warningCount = result.warnings.length;
|
||||
const remainingErrors = errorCount - 50;
|
||||
return (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>Import complete</Trans>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
<Trans>Finished importing from {source}.</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-2">
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<StatBadge label={t`Imported`} count={result.imported} />
|
||||
<StatBadge label={t`Skipped`} count={result.skipped} />
|
||||
<StatBadge label={t`Failed`} count={result.failed} />
|
||||
</div>
|
||||
|
||||
{errorCount > 0 && (
|
||||
<div className="bg-destructive/10 max-h-40 overflow-y-auto rounded-lg p-3">
|
||||
<p className="text-destructive mb-1 text-xs font-medium">
|
||||
<Trans>Errors ({errorCount})</Trans>
|
||||
</p>
|
||||
<ul className="text-destructive/80 space-y-0.5 text-xs">
|
||||
{result.errors.slice(0, 50).map((e, i) => (
|
||||
<li key={i}>{e}</li>
|
||||
))}
|
||||
{errorCount > 50 && (
|
||||
<li>
|
||||
<Trans>...and {remainingErrors} more</Trans>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{warningCount > 0 && (
|
||||
<div className="max-h-32 overflow-y-auto rounded-lg bg-yellow-500/10 p-3">
|
||||
<p className="mb-1 text-xs font-medium text-yellow-600">
|
||||
<Trans>Warnings ({warningCount})</Trans>
|
||||
</p>
|
||||
<ul className="space-y-0.5 text-xs text-yellow-600/80">
|
||||
{result.warnings.slice(0, 20).map((w, i) => (
|
||||
<li key={i}>{w}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button onClick={onClose}>
|
||||
<Trans>Done</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function StatBadge({ label, count }: { label: string; count: number }) {
|
||||
return (
|
||||
<div className="bg-muted/50 rounded-lg p-2.5 text-center">
|
||||
<p className="text-lg leading-none font-semibold">{count}</p>
|
||||
<p className="text-muted-foreground mt-1 text-xs">{label}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function OptionCheckbox({
|
||||
idPrefix,
|
||||
label,
|
||||
description,
|
||||
checked,
|
||||
onChange,
|
||||
}: {
|
||||
idPrefix: string;
|
||||
label: string;
|
||||
description: string;
|
||||
checked: boolean;
|
||||
onChange: (v: boolean) => void;
|
||||
}) {
|
||||
const id = `${idPrefix}-${label}`;
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
<Checkbox id={id} checked={checked} onCheckedChange={onChange} />
|
||||
<div>
|
||||
<Label htmlFor={id} className="text-sm">
|
||||
{label}
|
||||
</Label>
|
||||
<p className="text-muted-foreground text-xs">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,13 +2,19 @@ import type { MessageDescriptor } from "@lingui/core";
|
||||
import { msg } from "@lingui/core/macro";
|
||||
import { Trans, useLingui } from "@lingui/react/macro";
|
||||
import { IconCloudUpload, IconLink } from "@tabler/icons-react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import {
|
||||
ImportingStep,
|
||||
DoneStep,
|
||||
OptionCheckbox,
|
||||
StatBadge,
|
||||
} from "@/components/settings/import-dialog-parts";
|
||||
import { useImportJob } from "@/components/settings/use-import-job";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardTitle } from "@/components/ui/card";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
@@ -18,13 +24,10 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { startDevicePoll } from "@/lib/device-code-poll";
|
||||
import { getErrorMessage } from "@/lib/error-messages";
|
||||
import { client, orpc } from "@/lib/orpc/client";
|
||||
import { invalidateTrackingQueries } from "@/lib/orpc/invalidate";
|
||||
import { orpc } from "@/lib/orpc/client";
|
||||
import type { NormalizedImport } from "@sofa/api/schemas";
|
||||
import { formatList } from "@sofa/i18n/format";
|
||||
|
||||
@@ -154,14 +157,6 @@ interface ImportPreview {
|
||||
};
|
||||
}
|
||||
|
||||
interface ImportResult {
|
||||
imported: number;
|
||||
skipped: number;
|
||||
failed: number;
|
||||
errors: string[];
|
||||
warnings: string[];
|
||||
}
|
||||
|
||||
interface DeviceCodeInfo {
|
||||
device_code: string;
|
||||
user_code: string;
|
||||
@@ -194,14 +189,12 @@ export function ImportsSection() {
|
||||
|
||||
function ImportSourceCard({ config }: { config: SourceConfig }) {
|
||||
const { i18n, t } = useLingui();
|
||||
const queryClient = useQueryClient();
|
||||
const { data: systemStatus } = useQuery(orpc.system.status.queryOptions());
|
||||
const publicApiUrl = systemStatus?.publicApiUrl ?? "https://public-api.sofa.watch";
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [step, setStep] = useState<DialogStep>(config.supportsOAuth ? "choose" : "preview");
|
||||
const [preview, setPreview] = useState<ImportPreview | null>(null);
|
||||
const [result, setResult] = useState<ImportResult | null>(null);
|
||||
const [options, setOptions] = useState({
|
||||
importWatches: true,
|
||||
importWatchlist: true,
|
||||
@@ -213,7 +206,6 @@ function ImportSourceCard({ config }: { config: SourceConfig }) {
|
||||
const [oauthError, setOauthError] = useState<string | null>(null);
|
||||
const pollRef = useRef<{ stop: () => void } | null>(null);
|
||||
const flowIdRef = useRef(0);
|
||||
const importAbortRef = useRef<AbortController | null>(null);
|
||||
|
||||
const parseMutation = useMutation(
|
||||
orpc.imports.parseFile.mutationOptions({
|
||||
@@ -241,13 +233,6 @@ function ImportSourceCard({ config }: { config: SourceConfig }) {
|
||||
}),
|
||||
);
|
||||
|
||||
// Progress state
|
||||
const [progress, setProgress] = useState<{
|
||||
current: number;
|
||||
total: number;
|
||||
message: string;
|
||||
} | null>(null);
|
||||
|
||||
function handleFileSelect(file: File) {
|
||||
parseMutation.mutate(
|
||||
{ source: config.source, file },
|
||||
@@ -259,89 +244,20 @@ function ImportSourceCard({ config }: { config: SourceConfig }) {
|
||||
);
|
||||
}
|
||||
|
||||
const job = useImportJob({
|
||||
successMessage: (importedCount) => {
|
||||
const sourceLabel = config.label;
|
||||
return t`Imported ${importedCount} items from ${sourceLabel}`;
|
||||
},
|
||||
onDetached: handleClose,
|
||||
});
|
||||
|
||||
async function handleImport() {
|
||||
if (!preview) return;
|
||||
setStep("importing");
|
||||
setProgress(null);
|
||||
|
||||
const abort = new AbortController();
|
||||
importAbortRef.current = abort;
|
||||
|
||||
try {
|
||||
const job = await client.imports.createJob({
|
||||
data: preview.data,
|
||||
options,
|
||||
});
|
||||
|
||||
const eventSource = await client.imports.jobEvents({ id: job.id }, { signal: abort.signal });
|
||||
|
||||
let receivedComplete = false;
|
||||
|
||||
for await (const event of eventSource) {
|
||||
if (abort.signal.aborted) break;
|
||||
if (event.type === "complete") {
|
||||
receivedComplete = true;
|
||||
setResult({
|
||||
imported: event.job.importedCount,
|
||||
skipped: event.job.skippedCount,
|
||||
failed: event.job.failedCount,
|
||||
errors: event.job.errors,
|
||||
warnings: event.job.warnings,
|
||||
});
|
||||
setStep("done");
|
||||
void invalidateTrackingQueries(queryClient);
|
||||
const importedCount = event.job.importedCount;
|
||||
const sourceLabel = config.label;
|
||||
if (importedCount > 0) {
|
||||
toast.success(t`Imported ${importedCount} items from ${sourceLabel}`);
|
||||
}
|
||||
} else if (event.type === "timeout") {
|
||||
receivedComplete = true;
|
||||
toast.info(t`Import is still running in the background. Check back later.`);
|
||||
setStep("preview");
|
||||
} else {
|
||||
setProgress({
|
||||
current: event.job.processedItems,
|
||||
total: event.job.totalItems,
|
||||
message: event.job.currentMessage ?? "",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Stream ended without a complete/timeout event (e.g. connection dropped)
|
||||
if (!receivedComplete && !abort.signal.aborted) {
|
||||
try {
|
||||
const finalJob = await client.imports.getJob({ id: job.id });
|
||||
const isTerminal =
|
||||
finalJob.status === "success" ||
|
||||
finalJob.status === "error" ||
|
||||
finalJob.status === "cancelled";
|
||||
if (isTerminal) {
|
||||
setResult({
|
||||
imported: finalJob.importedCount,
|
||||
skipped: finalJob.skippedCount,
|
||||
failed: finalJob.failedCount,
|
||||
errors: finalJob.errors,
|
||||
warnings: finalJob.warnings,
|
||||
});
|
||||
setStep("done");
|
||||
void invalidateTrackingQueries(queryClient);
|
||||
} else {
|
||||
toast.info(t`Import is still running in the background. Check back later.`);
|
||||
setStep("preview");
|
||||
}
|
||||
} catch {
|
||||
toast.error(t`Lost connection to import. Check status in settings.`);
|
||||
setStep("preview");
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (abort.signal.aborted) return;
|
||||
toast.error(getErrorMessage(err, t`Import failed`));
|
||||
setStep("preview");
|
||||
} finally {
|
||||
importAbortRef.current = null;
|
||||
}
|
||||
const outcome = await job.start({ data: preview.data, options });
|
||||
if (outcome === "done") setStep("done");
|
||||
else if (outcome === "failed") setStep("preview");
|
||||
}
|
||||
|
||||
// Clean up poll timer on unmount or dialog close
|
||||
@@ -353,12 +269,11 @@ function ImportSourceCard({ config }: { config: SourceConfig }) {
|
||||
function handleClose() {
|
||||
stopPolling();
|
||||
flowIdRef.current += 1;
|
||||
importAbortRef.current?.abort();
|
||||
importAbortRef.current = null;
|
||||
job.abort();
|
||||
setDialogOpen(false);
|
||||
setStep(config.supportsOAuth ? "choose" : "preview");
|
||||
setPreview(null);
|
||||
setResult(null);
|
||||
job.reset();
|
||||
setDeviceCode(null);
|
||||
setOauthError(null);
|
||||
setOptions({
|
||||
@@ -538,9 +453,9 @@ function ImportSourceCard({ config }: { config: SourceConfig }) {
|
||||
onCancel={handleClose}
|
||||
/>
|
||||
)}
|
||||
{step === "importing" && <ImportingStep source={config.label} progress={progress} />}
|
||||
{step === "done" && result && (
|
||||
<DoneStep source={config.label} result={result} onClose={handleClose} />
|
||||
{step === "importing" && <ImportingStep source={config.label} progress={job.progress} />}
|
||||
{step === "done" && job.result && (
|
||||
<DoneStep source={config.label} result={job.result} onClose={handleClose} />
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@@ -788,18 +703,21 @@ function PreviewStep({
|
||||
<Trans>Import options</Trans>
|
||||
</p>
|
||||
<OptionCheckbox
|
||||
idPrefix="import-opt"
|
||||
label={t`Watch history`}
|
||||
description={t`${movieCount} movies, ${episodeCount} episodes`}
|
||||
checked={options.importWatches}
|
||||
onChange={(v) => setOptions({ ...options, importWatches: v })}
|
||||
/>
|
||||
<OptionCheckbox
|
||||
idPrefix="import-opt"
|
||||
label={t`Watchlist`}
|
||||
description={t`${watchlistCount} items`}
|
||||
checked={options.importWatchlist}
|
||||
onChange={(v) => setOptions({ ...options, importWatchlist: v })}
|
||||
/>
|
||||
<OptionCheckbox
|
||||
idPrefix="import-opt"
|
||||
label={t`Ratings`}
|
||||
description={t`${ratingCount} ratings`}
|
||||
checked={options.importRatings}
|
||||
@@ -832,159 +750,3 @@ function PreviewStep({
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ImportingStep({
|
||||
source,
|
||||
progress,
|
||||
}: {
|
||||
source: string;
|
||||
progress: { current: number; total: number; message: string } | null;
|
||||
}) {
|
||||
const pct =
|
||||
progress && progress.total > 0 ? Math.round((progress.current / progress.total) * 100) : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>Importing from {source}</Trans>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
This may take a few minutes for large libraries. Please don't close this tab.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col items-center gap-4 py-8">
|
||||
<Progress value={pct} className="w-full" />
|
||||
<div className="flex flex-col items-center gap-1 text-center">
|
||||
{progress ? (
|
||||
<>
|
||||
<p className="text-sm font-medium">
|
||||
{progress.current} / {progress.total}
|
||||
</p>
|
||||
<p className="text-muted-foreground max-w-[300px] truncate text-xs">
|
||||
{progress.message}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<Spinner className="size-3" />
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<Trans>Starting import...</Trans>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function DoneStep({
|
||||
source,
|
||||
result,
|
||||
onClose,
|
||||
}: {
|
||||
source: string;
|
||||
result: ImportResult;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const { t } = useLingui();
|
||||
const errorCount = result.errors.length;
|
||||
const warningCount = result.warnings.length;
|
||||
const remainingErrors = errorCount - 50;
|
||||
return (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>Import complete</Trans>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
<Trans>Finished importing from {source}.</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-2">
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<StatBadge label={t`Imported`} count={result.imported} />
|
||||
<StatBadge label={t`Skipped`} count={result.skipped} />
|
||||
<StatBadge label={t`Failed`} count={result.failed} />
|
||||
</div>
|
||||
|
||||
{errorCount > 0 && (
|
||||
<div className="bg-destructive/10 max-h-40 overflow-y-auto rounded-lg p-3">
|
||||
<p className="text-destructive mb-1 text-xs font-medium">
|
||||
<Trans>Errors ({errorCount})</Trans>
|
||||
</p>
|
||||
<ul className="text-destructive/80 space-y-0.5 text-xs">
|
||||
{result.errors.slice(0, 50).map((e, i) => (
|
||||
<li key={i}>{e}</li>
|
||||
))}
|
||||
{errorCount > 50 && (
|
||||
<li>
|
||||
<Trans>...and {remainingErrors} more</Trans>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{warningCount > 0 && (
|
||||
<div className="max-h-32 overflow-y-auto rounded-lg bg-yellow-500/10 p-3">
|
||||
<p className="mb-1 text-xs font-medium text-yellow-600">
|
||||
<Trans>Warnings ({warningCount})</Trans>
|
||||
</p>
|
||||
<ul className="space-y-0.5 text-xs text-yellow-600/80">
|
||||
{result.warnings.slice(0, 20).map((w, i) => (
|
||||
<li key={i}>{w}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button onClick={onClose}>
|
||||
<Trans>Done</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Helpers ────────────────────────────────────────────────
|
||||
|
||||
function StatBadge({ label, count }: { label: string; count: number }) {
|
||||
return (
|
||||
<div className="bg-muted/50 rounded-lg p-2.5 text-center">
|
||||
<p className="text-lg leading-none font-semibold">{count}</p>
|
||||
<p className="text-muted-foreground mt-1 text-xs">{label}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OptionCheckbox({
|
||||
label,
|
||||
description,
|
||||
checked,
|
||||
onChange,
|
||||
}: {
|
||||
label: string;
|
||||
description: string;
|
||||
checked: boolean;
|
||||
onChange: (v: boolean) => void;
|
||||
}) {
|
||||
const id = `import-opt-${label}`;
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
<Checkbox id={id} checked={checked} onCheckedChange={onChange} />
|
||||
<div>
|
||||
<Label htmlFor={id} className="text-sm">
|
||||
{label}
|
||||
</Label>
|
||||
<p className="text-muted-foreground text-xs">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { getErrorMessage } from "@/lib/error-messages";
|
||||
import { runImportJob } from "@/lib/import-job";
|
||||
import type { ImportOptions, ImportProgress, ImportResult } from "@/lib/import-job";
|
||||
import { client } from "@/lib/orpc/client";
|
||||
import { invalidateTrackingQueries } from "@/lib/orpc/invalidate";
|
||||
import type { NormalizedImport } from "@sofa/api/schemas";
|
||||
|
||||
export type ImportJobStartResult = "done" | "detached" | "failed" | "aborted";
|
||||
|
||||
export function useImportJob({
|
||||
successMessage,
|
||||
onDetached,
|
||||
}: {
|
||||
/** Toast text for a finished import with N > 0 imported items. */
|
||||
successMessage: (importedCount: number) => string;
|
||||
/** Called when the job continues in the background or the connection is lost (close the dialog). */
|
||||
onDetached: () => void;
|
||||
}) {
|
||||
const { t } = useLingui();
|
||||
const queryClient = useQueryClient();
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
const [progress, setProgress] = useState<ImportProgress | null>(null);
|
||||
const [result, setResult] = useState<ImportResult | null>(null);
|
||||
|
||||
async function start(input: {
|
||||
data: NormalizedImport;
|
||||
options: ImportOptions;
|
||||
}): Promise<ImportJobStartResult> {
|
||||
setProgress(null);
|
||||
const controller = new AbortController();
|
||||
abortRef.current = controller;
|
||||
|
||||
try {
|
||||
const outcome = await runImportJob(client.imports, input, controller.signal, setProgress);
|
||||
switch (outcome.kind) {
|
||||
case "done":
|
||||
setResult(outcome.result);
|
||||
void invalidateTrackingQueries(queryClient);
|
||||
if (outcome.result.imported > 0) {
|
||||
toast.success(successMessage(outcome.result.imported));
|
||||
}
|
||||
return "done";
|
||||
case "background":
|
||||
toast.info(t`Import is still running in the background. Check back later.`);
|
||||
onDetached();
|
||||
return "detached";
|
||||
case "lost":
|
||||
toast.error(t`Lost connection to import. Check status in settings.`);
|
||||
onDetached();
|
||||
return "detached";
|
||||
case "aborted":
|
||||
return "aborted";
|
||||
}
|
||||
} catch (err) {
|
||||
if (controller.signal.aborted) return "aborted";
|
||||
toast.error(getErrorMessage(err, t`Import failed`));
|
||||
return "failed";
|
||||
} finally {
|
||||
abortRef.current = null;
|
||||
}
|
||||
}
|
||||
|
||||
function abort() {
|
||||
abortRef.current?.abort();
|
||||
abortRef.current = null;
|
||||
}
|
||||
|
||||
function reset() {
|
||||
setProgress(null);
|
||||
setResult(null);
|
||||
}
|
||||
|
||||
return { progress, result, start, abort, reset };
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import type { NormalizedImport } from "@sofa/api/schemas";
|
||||
|
||||
import { runImportJob } from "./import-job";
|
||||
import type { ImportJobApi, ImportOptions, ImportProgress } from "./import-job";
|
||||
|
||||
type Event =
|
||||
Awaited<ReturnType<ImportJobApi["jobEvents"]>> extends AsyncIterable<infer E> ? E : never;
|
||||
type Job = Awaited<ReturnType<ImportJobApi["getJob"]>>;
|
||||
|
||||
const input = {
|
||||
data: {} as NormalizedImport,
|
||||
options: { importWatches: true, importWatchlist: true, importRatings: true } as ImportOptions,
|
||||
};
|
||||
|
||||
function makeJob(overrides: Partial<Job> = {}): Job {
|
||||
return {
|
||||
status: "running",
|
||||
processedItems: 0,
|
||||
totalItems: 10,
|
||||
currentMessage: null,
|
||||
importedCount: 0,
|
||||
skippedCount: 0,
|
||||
failedCount: 0,
|
||||
errors: [],
|
||||
warnings: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makeApi(options: {
|
||||
events?: Event[];
|
||||
/** Called between events; lets a test abort mid-stream. */
|
||||
beforeEvent?: (index: number) => void;
|
||||
getJob?: () => Promise<Job>;
|
||||
createJob?: () => Promise<{ id: string }>;
|
||||
}): ImportJobApi {
|
||||
const { events = [], beforeEvent, getJob, createJob } = options;
|
||||
return {
|
||||
createJob: createJob ?? (() => Promise.resolve({ id: "job-1" })),
|
||||
jobEvents: () =>
|
||||
Promise.resolve(
|
||||
(async function* () {
|
||||
for (const [index, event] of events.entries()) {
|
||||
beforeEvent?.(index);
|
||||
yield event;
|
||||
}
|
||||
})(),
|
||||
),
|
||||
getJob: getJob ?? (() => Promise.resolve(makeJob())),
|
||||
};
|
||||
}
|
||||
|
||||
describe("runImportJob", () => {
|
||||
test("reports progress and resolves done on a complete event", async () => {
|
||||
const api = makeApi({
|
||||
events: [
|
||||
{ type: "progress", job: makeJob({ processedItems: 1, currentMessage: "one" }) },
|
||||
{ type: "progress", job: makeJob({ processedItems: 2 }) },
|
||||
{
|
||||
type: "complete",
|
||||
job: makeJob({
|
||||
status: "success",
|
||||
importedCount: 7,
|
||||
skippedCount: 2,
|
||||
failedCount: 1,
|
||||
errors: ["e"],
|
||||
warnings: ["w"],
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
const onProgress = vi.fn<(progress: ImportProgress) => void>();
|
||||
|
||||
const outcome = await runImportJob(api, input, new AbortController().signal, onProgress);
|
||||
|
||||
expect(onProgress).toHaveBeenCalledTimes(2);
|
||||
expect(onProgress).toHaveBeenNthCalledWith(1, { current: 1, total: 10, message: "one" });
|
||||
expect(onProgress).toHaveBeenNthCalledWith(2, { current: 2, total: 10, message: "" });
|
||||
expect(outcome).toEqual({
|
||||
kind: "done",
|
||||
result: { imported: 7, skipped: 2, failed: 1, errors: ["e"], warnings: ["w"] },
|
||||
});
|
||||
});
|
||||
|
||||
test("resolves background on a timeout event", async () => {
|
||||
const api = makeApi({ events: [{ type: "timeout", job: makeJob() }] });
|
||||
|
||||
const outcome = await runImportJob(api, input, new AbortController().signal, () => {});
|
||||
|
||||
expect(outcome).toEqual({ kind: "background" });
|
||||
});
|
||||
|
||||
test("falls back to getJob when the stream ends: terminal status is done", async () => {
|
||||
const api = makeApi({
|
||||
getJob: () => Promise.resolve(makeJob({ status: "success", importedCount: 3 })),
|
||||
});
|
||||
|
||||
const outcome = await runImportJob(api, input, new AbortController().signal, () => {});
|
||||
|
||||
expect(outcome).toEqual({
|
||||
kind: "done",
|
||||
result: { imported: 3, skipped: 0, failed: 0, errors: [], warnings: [] },
|
||||
});
|
||||
});
|
||||
|
||||
test("falls back to getJob when the stream ends: running status is background", async () => {
|
||||
const api = makeApi({ getJob: () => Promise.resolve(makeJob({ status: "running" })) });
|
||||
|
||||
const outcome = await runImportJob(api, input, new AbortController().signal, () => {});
|
||||
|
||||
expect(outcome).toEqual({ kind: "background" });
|
||||
});
|
||||
|
||||
test("falls back to getJob when the stream ends: getJob failure is lost", async () => {
|
||||
const api = makeApi({ getJob: () => Promise.reject(new Error("network")) });
|
||||
|
||||
const outcome = await runImportJob(api, input, new AbortController().signal, () => {});
|
||||
|
||||
expect(outcome).toEqual({ kind: "lost" });
|
||||
});
|
||||
|
||||
test("resolves aborted when the signal aborts mid-stream", async () => {
|
||||
const abort = new AbortController();
|
||||
const api = makeApi({
|
||||
events: [
|
||||
{ type: "progress", job: makeJob({ processedItems: 1 }) },
|
||||
{ type: "progress", job: makeJob({ processedItems: 2 }) },
|
||||
],
|
||||
beforeEvent: (index) => {
|
||||
if (index === 1) abort.abort();
|
||||
},
|
||||
});
|
||||
const onProgress = vi.fn<(progress: ImportProgress) => void>();
|
||||
|
||||
const outcome = await runImportJob(api, input, abort.signal, onProgress);
|
||||
|
||||
expect(outcome).toEqual({ kind: "aborted" });
|
||||
expect(onProgress).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("rejects with the same error when createJob fails", async () => {
|
||||
const error = new Error("already in progress");
|
||||
const api = makeApi({ createJob: () => Promise.reject(error) });
|
||||
|
||||
await expect(runImportJob(api, input, new AbortController().signal, () => {})).rejects.toBe(
|
||||
error,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
import type { NormalizedImport } from "@sofa/api/schemas";
|
||||
|
||||
export interface ImportProgress {
|
||||
current: number;
|
||||
total: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface ImportResult {
|
||||
imported: number;
|
||||
skipped: number;
|
||||
failed: number;
|
||||
errors: string[];
|
||||
warnings: string[];
|
||||
}
|
||||
|
||||
export interface ImportOptions {
|
||||
importWatches: boolean;
|
||||
importWatchlist: boolean;
|
||||
importRatings: boolean;
|
||||
}
|
||||
|
||||
interface JobSnapshot {
|
||||
status: string;
|
||||
processedItems: number;
|
||||
totalItems: number;
|
||||
currentMessage: string | null;
|
||||
importedCount: number;
|
||||
skippedCount: number;
|
||||
failedCount: number;
|
||||
errors: string[];
|
||||
warnings: string[];
|
||||
}
|
||||
|
||||
/** The subset of `client.imports` the runner needs (injectable for tests). */
|
||||
export interface ImportJobApi {
|
||||
createJob(input: { data: NormalizedImport; options: ImportOptions }): Promise<{ id: string }>;
|
||||
jobEvents(
|
||||
input: { id: string },
|
||||
opts: { signal: AbortSignal },
|
||||
): Promise<AsyncIterable<{ type: "progress" | "complete" | "timeout"; job: JobSnapshot }>>;
|
||||
getJob(input: { id: string }): Promise<JobSnapshot>;
|
||||
}
|
||||
|
||||
export type ImportJobOutcome =
|
||||
| { kind: "done"; result: ImportResult }
|
||||
| { kind: "background" } // still running on the server
|
||||
| { kind: "lost" } // stream dropped and the job status couldn't be read
|
||||
| { kind: "aborted" };
|
||||
|
||||
const TERMINAL = new Set(["success", "error", "cancelled"]);
|
||||
|
||||
function toResult(job: JobSnapshot): ImportResult {
|
||||
return {
|
||||
imported: job.importedCount,
|
||||
skipped: job.skippedCount,
|
||||
failed: job.failedCount,
|
||||
errors: job.errors,
|
||||
warnings: job.warnings,
|
||||
};
|
||||
}
|
||||
|
||||
/** Create an import job and follow it to completion. Throws if the job can't be created. */
|
||||
export async function runImportJob(
|
||||
api: ImportJobApi,
|
||||
input: { data: NormalizedImport; options: ImportOptions },
|
||||
signal: AbortSignal,
|
||||
onProgress: (progress: ImportProgress) => void,
|
||||
): Promise<ImportJobOutcome> {
|
||||
const job = await api.createJob(input);
|
||||
const events = await api.jobEvents({ id: job.id }, { signal });
|
||||
for await (const event of events) {
|
||||
if (signal.aborted) return { kind: "aborted" };
|
||||
if (event.type === "complete") return { kind: "done", result: toResult(event.job) };
|
||||
if (event.type === "timeout") return { kind: "background" };
|
||||
onProgress({
|
||||
current: event.job.processedItems,
|
||||
total: event.job.totalItems,
|
||||
message: event.job.currentMessage ?? "",
|
||||
});
|
||||
}
|
||||
if (signal.aborted) return { kind: "aborted" };
|
||||
try {
|
||||
const finalJob = await api.getJob({ id: job.id });
|
||||
return TERMINAL.has(finalJob.status)
|
||||
? { kind: "done", result: toResult(finalJob) }
|
||||
: { kind: "background" };
|
||||
} catch {
|
||||
return { kind: "lost" };
|
||||
}
|
||||
}
|
||||
+19
-32
@@ -22,8 +22,7 @@ msgstr ""
|
||||
#~ msgid "...and {0} more"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "...and {remainingErrors} more"
|
||||
msgstr "...و{remainingErrors} أخرى"
|
||||
|
||||
@@ -1043,8 +1042,7 @@ msgstr "ليس لديك خادم؟"
|
||||
msgid "Don't have an account?"
|
||||
msgstr "ليس لديك حساب؟"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Done"
|
||||
msgstr "تم"
|
||||
|
||||
@@ -1210,8 +1208,7 @@ msgstr "خطأ"
|
||||
#~ msgid "Errors ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Errors ({errorCount})"
|
||||
msgstr "الأخطاء ({errorCount})"
|
||||
|
||||
@@ -1236,8 +1233,7 @@ msgstr "استكشاف العناوين"
|
||||
msgid "Export"
|
||||
msgstr "تصدير"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Failed"
|
||||
msgstr "فشل"
|
||||
|
||||
@@ -1450,13 +1446,13 @@ msgstr "فيلموغرافيا"
|
||||
msgid "Filters"
|
||||
msgstr "الفلاتر"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Finished importing from {source}."
|
||||
msgstr "اكتمل الاستيراد من {source}."
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Finished importing your Sofa export."
|
||||
msgstr "تم الانتهاء من استيراد ملف تصدير Sofa."
|
||||
#~ msgid "Finished importing your Sofa export."
|
||||
#~ msgstr "تم الانتهاء من استيراد ملف تصدير Sofa."
|
||||
|
||||
#: apps/web/src/components/titles/title-availability.tsx
|
||||
#~ msgid "Free"
|
||||
@@ -1605,13 +1601,11 @@ msgstr "الاستيراد"
|
||||
msgid "Import {totalItems} items"
|
||||
msgstr "استيراد {totalItems} عنصر"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Import complete"
|
||||
msgstr "اكتمل الاستيراد"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import failed"
|
||||
msgstr "فشل الاستيراد"
|
||||
|
||||
@@ -1627,8 +1621,7 @@ msgstr "الاستيراد من {source}"
|
||||
msgid "Import from {sourceLabel}"
|
||||
msgstr "الاستيراد من {sourceLabel}"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import is still running in the background. Check back later."
|
||||
msgstr "لا يزال الاستيراد قيد التشغيل في الخلفية. تحقق لاحقاً."
|
||||
|
||||
@@ -1646,8 +1639,7 @@ msgstr ""
|
||||
msgid "Import Sofa data"
|
||||
msgstr "استيراد بيانات Sofa"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Imported"
|
||||
msgstr "تم الاستيراد"
|
||||
|
||||
@@ -1664,10 +1656,10 @@ msgid "Imported {importedCount} items from Sofa export"
|
||||
msgstr "تم استيراد {importedCount} عناصر من ملف تصدير Sofa"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Importing data"
|
||||
msgstr "جارٍ استيراد البيانات"
|
||||
#~ msgid "Importing data"
|
||||
#~ msgstr "جارٍ استيراد البيانات"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Importing from {source}"
|
||||
msgstr "جارٍ الاستيراد من {source}"
|
||||
|
||||
@@ -1843,8 +1835,7 @@ msgstr "جارٍ التحميل…"
|
||||
msgid "Login failed"
|
||||
msgstr "فشل تسجيل الدخول"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Lost connection to import. Check status in settings."
|
||||
msgstr "انقطع الاتصال بعملية الاستيراد. تحقق من الحالة في الإعدادات."
|
||||
|
||||
@@ -3062,8 +3053,7 @@ msgstr "تسجيل الخروج من الجلسات الأخرى"
|
||||
msgid "Skip for now"
|
||||
msgstr "تخطي في الوقت الحالي"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Skipped"
|
||||
msgstr "تم التخطي"
|
||||
|
||||
@@ -3148,8 +3138,7 @@ msgstr "ابدأ تتبع مشاهداتك"
|
||||
msgid "Starting at"
|
||||
msgstr "بدءاً من"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Starting import..."
|
||||
msgstr "جارٍ بدء الاستيراد..."
|
||||
|
||||
@@ -3233,8 +3222,7 @@ msgstr ""
|
||||
msgid "This import cannot be cancelled"
|
||||
msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "This may take a few minutes for large libraries. Please don't close this tab."
|
||||
msgstr "قد يستغرق هذا بضع دقائق للمكتبات الكبيرة. يرجى عدم إغلاق هذا التبويب."
|
||||
|
||||
@@ -3646,8 +3634,7 @@ msgstr "تحذيرات"
|
||||
#~ msgid "Warnings ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Warnings ({warningCount})"
|
||||
msgstr "تحذيرات ({warningCount})"
|
||||
|
||||
|
||||
+19
-32
@@ -22,8 +22,7 @@ msgstr ""
|
||||
#~ msgid "...and {0} more"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "...and {remainingErrors} more"
|
||||
msgstr "...und {remainingErrors} weitere"
|
||||
|
||||
@@ -1043,8 +1042,7 @@ msgstr "Noch keinen Server?"
|
||||
msgid "Don't have an account?"
|
||||
msgstr "Noch kein Konto?"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Done"
|
||||
msgstr "Fertig"
|
||||
|
||||
@@ -1210,8 +1208,7 @@ msgstr "Fehler"
|
||||
#~ msgid "Errors ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Errors ({errorCount})"
|
||||
msgstr "Fehler ({errorCount})"
|
||||
|
||||
@@ -1236,8 +1233,7 @@ msgstr "Titel entdecken"
|
||||
msgid "Export"
|
||||
msgstr "Exportieren"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Failed"
|
||||
msgstr "Fehlgeschlagen"
|
||||
|
||||
@@ -1450,13 +1446,13 @@ msgstr "Filmografie"
|
||||
msgid "Filters"
|
||||
msgstr "Filter"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Finished importing from {source}."
|
||||
msgstr "Import von {source} abgeschlossen."
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Finished importing your Sofa export."
|
||||
msgstr "Sofa-Export wurde erfolgreich importiert."
|
||||
#~ msgid "Finished importing your Sofa export."
|
||||
#~ msgstr "Sofa-Export wurde erfolgreich importiert."
|
||||
|
||||
#: apps/web/src/components/titles/title-availability.tsx
|
||||
#~ msgid "Free"
|
||||
@@ -1605,13 +1601,11 @@ msgstr "Importieren"
|
||||
msgid "Import {totalItems} items"
|
||||
msgstr "{totalItems} Einträge importieren"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Import complete"
|
||||
msgstr "Import abgeschlossen"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import failed"
|
||||
msgstr "Import fehlgeschlagen"
|
||||
|
||||
@@ -1627,8 +1621,7 @@ msgstr "Von {source} importieren"
|
||||
msgid "Import from {sourceLabel}"
|
||||
msgstr "Von {sourceLabel} importieren"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import is still running in the background. Check back later."
|
||||
msgstr "Import läuft noch im Hintergrund. Komm später wieder."
|
||||
|
||||
@@ -1646,8 +1639,7 @@ msgstr ""
|
||||
msgid "Import Sofa data"
|
||||
msgstr "Sofa-Daten importieren"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Imported"
|
||||
msgstr "Importiert"
|
||||
|
||||
@@ -1664,10 +1656,10 @@ msgid "Imported {importedCount} items from Sofa export"
|
||||
msgstr "{importedCount} Einträge aus Sofa-Export importiert"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Importing data"
|
||||
msgstr "Daten werden importiert"
|
||||
#~ msgid "Importing data"
|
||||
#~ msgstr "Daten werden importiert"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Importing from {source}"
|
||||
msgstr "Importiere von {source}"
|
||||
|
||||
@@ -1843,8 +1835,7 @@ msgstr "Lade..."
|
||||
msgid "Login failed"
|
||||
msgstr "Anmeldung fehlgeschlagen"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Lost connection to import. Check status in settings."
|
||||
msgstr "Verbindung zum Import verloren. Status in den Einstellungen prüfen."
|
||||
|
||||
@@ -3062,8 +3053,7 @@ msgstr "Von anderen Sitzungen abmelden"
|
||||
msgid "Skip for now"
|
||||
msgstr "Vorerst überspringen"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Skipped"
|
||||
msgstr "Übersprungen"
|
||||
|
||||
@@ -3148,8 +3138,7 @@ msgstr "Verfolge deine Sehgewohnheiten"
|
||||
msgid "Starting at"
|
||||
msgstr "Startzeit"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Starting import..."
|
||||
msgstr "Import wird gestartet..."
|
||||
|
||||
@@ -3233,8 +3222,7 @@ msgstr ""
|
||||
msgid "This import cannot be cancelled"
|
||||
msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "This may take a few minutes for large libraries. Please don't close this tab."
|
||||
msgstr "Dies kann bei großen Bibliotheken einige Minuten dauern. Bitte schließe diesen Tab nicht."
|
||||
|
||||
@@ -3646,8 +3634,7 @@ msgstr "Warnungen"
|
||||
#~ msgid "Warnings ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Warnings ({warningCount})"
|
||||
msgstr "Warnungen ({warningCount})"
|
||||
|
||||
|
||||
+19
-32
@@ -22,8 +22,7 @@ msgstr ""
|
||||
#~ msgid "...and {0} more"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "...and {remainingErrors} more"
|
||||
msgstr "...and {remainingErrors} more"
|
||||
|
||||
@@ -1043,8 +1042,7 @@ msgstr ""
|
||||
msgid "Don't have an account?"
|
||||
msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Done"
|
||||
msgstr ""
|
||||
|
||||
@@ -1210,8 +1208,7 @@ msgstr ""
|
||||
#~ msgid "Errors ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Errors ({errorCount})"
|
||||
msgstr "Errors ({errorCount})"
|
||||
|
||||
@@ -1236,8 +1233,7 @@ msgstr ""
|
||||
msgid "Export"
|
||||
msgstr "Export"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Failed"
|
||||
msgstr ""
|
||||
|
||||
@@ -1450,13 +1446,13 @@ msgstr ""
|
||||
msgid "Filters"
|
||||
msgstr "Filters"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Finished importing from {source}."
|
||||
msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Finished importing your Sofa export."
|
||||
msgstr "Finished importing your Sofa export."
|
||||
#~ msgid "Finished importing your Sofa export."
|
||||
#~ msgstr "Finished importing your Sofa export."
|
||||
|
||||
#: apps/web/src/components/titles/title-availability.tsx
|
||||
#~ msgid "Free"
|
||||
@@ -1605,13 +1601,11 @@ msgstr ""
|
||||
msgid "Import {totalItems} items"
|
||||
msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Import complete"
|
||||
msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import failed"
|
||||
msgstr ""
|
||||
|
||||
@@ -1627,8 +1621,7 @@ msgstr ""
|
||||
msgid "Import from {sourceLabel}"
|
||||
msgstr "Import from {sourceLabel}"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import is still running in the background. Check back later."
|
||||
msgstr ""
|
||||
|
||||
@@ -1646,8 +1639,7 @@ msgstr "Import payload is too large"
|
||||
msgid "Import Sofa data"
|
||||
msgstr "Import Sofa data"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Imported"
|
||||
msgstr ""
|
||||
|
||||
@@ -1664,10 +1656,10 @@ msgid "Imported {importedCount} items from Sofa export"
|
||||
msgstr "Imported {importedCount} items from Sofa export"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Importing data"
|
||||
msgstr "Importing data"
|
||||
#~ msgid "Importing data"
|
||||
#~ msgstr "Importing data"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Importing from {source}"
|
||||
msgstr ""
|
||||
|
||||
@@ -1843,8 +1835,7 @@ msgstr ""
|
||||
msgid "Login failed"
|
||||
msgstr "Login failed"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Lost connection to import. Check status in settings."
|
||||
msgstr ""
|
||||
|
||||
@@ -3062,8 +3053,7 @@ msgstr ""
|
||||
msgid "Skip for now"
|
||||
msgstr "Skip for now"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Skipped"
|
||||
msgstr ""
|
||||
|
||||
@@ -3148,8 +3138,7 @@ msgstr ""
|
||||
msgid "Starting at"
|
||||
msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Starting import..."
|
||||
msgstr ""
|
||||
|
||||
@@ -3233,8 +3222,7 @@ msgstr "This doesn't appear to be a Sofa server. Double-check the URL."
|
||||
msgid "This import cannot be cancelled"
|
||||
msgstr "This import cannot be cancelled"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "This may take a few minutes for large libraries. Please don't close this tab."
|
||||
msgstr ""
|
||||
|
||||
@@ -3646,8 +3634,7 @@ msgstr ""
|
||||
#~ msgid "Warnings ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Warnings ({warningCount})"
|
||||
msgstr "Warnings ({warningCount})"
|
||||
|
||||
|
||||
+19
-32
@@ -22,8 +22,7 @@ msgstr ""
|
||||
#~ msgid "...and {0} more"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "...and {remainingErrors} more"
|
||||
msgstr "...y {remainingErrors} más"
|
||||
|
||||
@@ -1043,8 +1042,7 @@ msgstr "¿No tienes un servidor?"
|
||||
msgid "Don't have an account?"
|
||||
msgstr "¿No tienes una cuenta?"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Done"
|
||||
msgstr "Hecho"
|
||||
|
||||
@@ -1210,8 +1208,7 @@ msgstr ""
|
||||
#~ msgid "Errors ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Errors ({errorCount})"
|
||||
msgstr "Errores ({errorCount})"
|
||||
|
||||
@@ -1236,8 +1233,7 @@ msgstr "Explorar títulos"
|
||||
msgid "Export"
|
||||
msgstr "Exportar"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Failed"
|
||||
msgstr "Fallido"
|
||||
|
||||
@@ -1450,13 +1446,13 @@ msgstr "Filmografía"
|
||||
msgid "Filters"
|
||||
msgstr "Filtros"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Finished importing from {source}."
|
||||
msgstr "Importación desde {source} completada."
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Finished importing your Sofa export."
|
||||
msgstr "Importación de tu exportación de Sofa completada."
|
||||
#~ msgid "Finished importing your Sofa export."
|
||||
#~ msgstr "Importación de tu exportación de Sofa completada."
|
||||
|
||||
#: apps/web/src/components/titles/title-availability.tsx
|
||||
#~ msgid "Free"
|
||||
@@ -1605,13 +1601,11 @@ msgstr "Importar"
|
||||
msgid "Import {totalItems} items"
|
||||
msgstr "Importar {totalItems} elementos"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Import complete"
|
||||
msgstr "Importación completada"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import failed"
|
||||
msgstr "Error en la importación"
|
||||
|
||||
@@ -1627,8 +1621,7 @@ msgstr "Importar desde {source}"
|
||||
msgid "Import from {sourceLabel}"
|
||||
msgstr "Importar desde {sourceLabel}"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import is still running in the background. Check back later."
|
||||
msgstr "La importación sigue en curso en segundo plano. Vuelve más tarde."
|
||||
|
||||
@@ -1646,8 +1639,7 @@ msgstr ""
|
||||
msgid "Import Sofa data"
|
||||
msgstr "Importar datos de Sofa"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Imported"
|
||||
msgstr "Importado"
|
||||
|
||||
@@ -1664,10 +1656,10 @@ msgid "Imported {importedCount} items from Sofa export"
|
||||
msgstr "Se importaron {importedCount} elementos de la exportación de Sofa"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Importing data"
|
||||
msgstr "Importando datos"
|
||||
#~ msgid "Importing data"
|
||||
#~ msgstr "Importando datos"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Importing from {source}"
|
||||
msgstr "Importando desde {source}"
|
||||
|
||||
@@ -1843,8 +1835,7 @@ msgstr "Cargando…"
|
||||
msgid "Login failed"
|
||||
msgstr "Error al iniciar sesión"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Lost connection to import. Check status in settings."
|
||||
msgstr "Se perdió la conexión con la importación. Comprueba el estado en ajustes."
|
||||
|
||||
@@ -3062,8 +3053,7 @@ msgstr "Cerrar otras sesiones"
|
||||
msgid "Skip for now"
|
||||
msgstr "Omitir por ahora"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Skipped"
|
||||
msgstr "Omitido"
|
||||
|
||||
@@ -3148,8 +3138,7 @@ msgstr "Empieza a registrar lo que ves"
|
||||
msgid "Starting at"
|
||||
msgstr "Comenzando a las"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Starting import..."
|
||||
msgstr "Iniciando importación..."
|
||||
|
||||
@@ -3233,8 +3222,7 @@ msgstr ""
|
||||
msgid "This import cannot be cancelled"
|
||||
msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "This may take a few minutes for large libraries. Please don't close this tab."
|
||||
msgstr "Esto puede tardar unos minutos para bibliotecas grandes. Por favor, no cierres esta pestaña."
|
||||
|
||||
@@ -3646,8 +3634,7 @@ msgstr "Advertencias"
|
||||
#~ msgid "Warnings ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Warnings ({warningCount})"
|
||||
msgstr "Advertencias ({warningCount})"
|
||||
|
||||
|
||||
+19
-32
@@ -22,8 +22,7 @@ msgstr ""
|
||||
#~ msgid "...and {0} more"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "...and {remainingErrors} more"
|
||||
msgstr "...et {remainingErrors} de plus"
|
||||
|
||||
@@ -1043,8 +1042,7 @@ msgstr "Pas de serveur ?"
|
||||
msgid "Don't have an account?"
|
||||
msgstr "Pas encore de compte ?"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Done"
|
||||
msgstr "Terminé"
|
||||
|
||||
@@ -1210,8 +1208,7 @@ msgstr "Erreur"
|
||||
#~ msgid "Errors ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Errors ({errorCount})"
|
||||
msgstr "Erreurs ({errorCount})"
|
||||
|
||||
@@ -1236,8 +1233,7 @@ msgstr "Explorer les titres"
|
||||
msgid "Export"
|
||||
msgstr "Exporter"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Failed"
|
||||
msgstr "Échoué"
|
||||
|
||||
@@ -1450,13 +1446,13 @@ msgstr "Filmographie"
|
||||
msgid "Filters"
|
||||
msgstr "Filtres"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Finished importing from {source}."
|
||||
msgstr "Importation depuis {source} terminée."
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Finished importing your Sofa export."
|
||||
msgstr "Importation de votre export Sofa terminée."
|
||||
#~ msgid "Finished importing your Sofa export."
|
||||
#~ msgstr "Importation de votre export Sofa terminée."
|
||||
|
||||
#: apps/web/src/components/titles/title-availability.tsx
|
||||
#~ msgid "Free"
|
||||
@@ -1605,13 +1601,11 @@ msgstr "Importation"
|
||||
msgid "Import {totalItems} items"
|
||||
msgstr "Importer {totalItems} éléments"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Import complete"
|
||||
msgstr "Importation terminée"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import failed"
|
||||
msgstr "Importation échouée"
|
||||
|
||||
@@ -1627,8 +1621,7 @@ msgstr "Importer depuis {source}"
|
||||
msgid "Import from {sourceLabel}"
|
||||
msgstr "Importer depuis {sourceLabel}"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import is still running in the background. Check back later."
|
||||
msgstr "L'importation est toujours en cours en arrière-plan. Revenez plus tard."
|
||||
|
||||
@@ -1646,8 +1639,7 @@ msgstr ""
|
||||
msgid "Import Sofa data"
|
||||
msgstr "Importer des données Sofa"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Imported"
|
||||
msgstr "Importé"
|
||||
|
||||
@@ -1664,10 +1656,10 @@ msgid "Imported {importedCount} items from Sofa export"
|
||||
msgstr "{importedCount} éléments importés depuis l'export Sofa"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Importing data"
|
||||
msgstr "Importation des données"
|
||||
#~ msgid "Importing data"
|
||||
#~ msgstr "Importation des données"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Importing from {source}"
|
||||
msgstr "Importation depuis {source}"
|
||||
|
||||
@@ -1843,8 +1835,7 @@ msgstr "Chargement…"
|
||||
msgid "Login failed"
|
||||
msgstr "Connexion échouée"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Lost connection to import. Check status in settings."
|
||||
msgstr "Connexion à l'importation perdue. Vérifiez l'état dans les paramètres."
|
||||
|
||||
@@ -3062,8 +3053,7 @@ msgstr "Se déconnecter des autres sessions"
|
||||
msgid "Skip for now"
|
||||
msgstr "Passer pour l'instant"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Skipped"
|
||||
msgstr "Ignoré"
|
||||
|
||||
@@ -3148,8 +3138,7 @@ msgstr "Commencez à suivre vos visionnages"
|
||||
msgid "Starting at"
|
||||
msgstr "À partir de"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Starting import..."
|
||||
msgstr "Démarrage de l'importation..."
|
||||
|
||||
@@ -3233,8 +3222,7 @@ msgstr ""
|
||||
msgid "This import cannot be cancelled"
|
||||
msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "This may take a few minutes for large libraries. Please don't close this tab."
|
||||
msgstr "Cela peut prendre quelques minutes pour les grandes bibliothèques. Ne fermez pas cet onglet."
|
||||
|
||||
@@ -3646,8 +3634,7 @@ msgstr "Avertissements"
|
||||
#~ msgid "Warnings ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Warnings ({warningCount})"
|
||||
msgstr "Avertissements ({warningCount})"
|
||||
|
||||
|
||||
+19
-32
@@ -22,8 +22,7 @@ msgstr ""
|
||||
#~ msgid "...and {0} more"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "...and {remainingErrors} more"
|
||||
msgstr "...ועוד {remainingErrors}"
|
||||
|
||||
@@ -1043,8 +1042,7 @@ msgstr "אין לך שרת?"
|
||||
msgid "Don't have an account?"
|
||||
msgstr "אין לך חשבון?"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Done"
|
||||
msgstr "סיום"
|
||||
|
||||
@@ -1210,8 +1208,7 @@ msgstr "שגיאה"
|
||||
#~ msgid "Errors ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Errors ({errorCount})"
|
||||
msgstr "שגיאות ({errorCount})"
|
||||
|
||||
@@ -1236,8 +1233,7 @@ msgstr "גלה כותרות"
|
||||
msgid "Export"
|
||||
msgstr "ייצוא"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Failed"
|
||||
msgstr "נכשל"
|
||||
|
||||
@@ -1450,13 +1446,13 @@ msgstr "פילמוגרפיה"
|
||||
msgid "Filters"
|
||||
msgstr "מסננים"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Finished importing from {source}."
|
||||
msgstr "הייבוא מ-{source} הסתיים."
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Finished importing your Sofa export."
|
||||
msgstr "ייבוא הנתונים מ-Sofa שלך הושלם."
|
||||
#~ msgid "Finished importing your Sofa export."
|
||||
#~ msgstr "ייבוא הנתונים מ-Sofa שלך הושלם."
|
||||
|
||||
#: apps/web/src/components/titles/title-availability.tsx
|
||||
#~ msgid "Free"
|
||||
@@ -1605,13 +1601,11 @@ msgstr "ייבוא"
|
||||
msgid "Import {totalItems} items"
|
||||
msgstr "ייבא {totalItems} פריטים"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Import complete"
|
||||
msgstr "הייבוא הושלם"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import failed"
|
||||
msgstr "הייבוא נכשל"
|
||||
|
||||
@@ -1627,8 +1621,7 @@ msgstr "ייבא מ-{source}"
|
||||
msgid "Import from {sourceLabel}"
|
||||
msgstr "ייבא מ-{sourceLabel}"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import is still running in the background. Check back later."
|
||||
msgstr "הייבוא עדיין רץ ברקע. בדוק שוב מאוחר יותר."
|
||||
|
||||
@@ -1646,8 +1639,7 @@ msgstr ""
|
||||
msgid "Import Sofa data"
|
||||
msgstr "ייבא נתוני Sofa"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Imported"
|
||||
msgstr "יובא"
|
||||
|
||||
@@ -1664,10 +1656,10 @@ msgid "Imported {importedCount} items from Sofa export"
|
||||
msgstr "יובאו {importedCount} פריטים מייצוא Sofa"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Importing data"
|
||||
msgstr "מייבא נתונים"
|
||||
#~ msgid "Importing data"
|
||||
#~ msgstr "מייבא נתונים"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Importing from {source}"
|
||||
msgstr "מייבא מ-{source}"
|
||||
|
||||
@@ -1843,8 +1835,7 @@ msgstr "טוען…"
|
||||
msgid "Login failed"
|
||||
msgstr "הכניסה נכשלה"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Lost connection to import. Check status in settings."
|
||||
msgstr "אבד החיבור לייבוא. בדוק את הסטטוס בהגדרות."
|
||||
|
||||
@@ -3062,8 +3053,7 @@ msgstr "התנתק משאר ההפעלות"
|
||||
msgid "Skip for now"
|
||||
msgstr "דלג לעת עתה"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Skipped"
|
||||
msgstr "דולג"
|
||||
|
||||
@@ -3148,8 +3138,7 @@ msgstr "התחל לעקוב אחרי הצפיות שלך"
|
||||
msgid "Starting at"
|
||||
msgstr "מתחיל ב-"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Starting import..."
|
||||
msgstr "מתחיל ייבוא..."
|
||||
|
||||
@@ -3233,8 +3222,7 @@ msgstr ""
|
||||
msgid "This import cannot be cancelled"
|
||||
msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "This may take a few minutes for large libraries. Please don't close this tab."
|
||||
msgstr "פעולה זו עשויה לקחת מספר דקות עבור ספריות גדולות. אנא אל תסגור לשונית זו."
|
||||
|
||||
@@ -3646,8 +3634,7 @@ msgstr "אזהרות"
|
||||
#~ msgid "Warnings ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Warnings ({warningCount})"
|
||||
msgstr "אזהרות ({warningCount})"
|
||||
|
||||
|
||||
+19
-32
@@ -22,8 +22,7 @@ msgstr ""
|
||||
#~ msgid "...and {0} more"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "...and {remainingErrors} more"
|
||||
msgstr "...e altri {remainingErrors}"
|
||||
|
||||
@@ -1043,8 +1042,7 @@ msgstr "Non hai un server?"
|
||||
msgid "Don't have an account?"
|
||||
msgstr "Non hai un account?"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Done"
|
||||
msgstr "Fatto"
|
||||
|
||||
@@ -1210,8 +1208,7 @@ msgstr "Errore"
|
||||
#~ msgid "Errors ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Errors ({errorCount})"
|
||||
msgstr "Errori ({errorCount})"
|
||||
|
||||
@@ -1236,8 +1233,7 @@ msgstr "Esplora titoli"
|
||||
msgid "Export"
|
||||
msgstr "Esporta"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Failed"
|
||||
msgstr "Non riuscito"
|
||||
|
||||
@@ -1450,13 +1446,13 @@ msgstr "Filmografia"
|
||||
msgid "Filters"
|
||||
msgstr "Filtri"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Finished importing from {source}."
|
||||
msgstr "Importazione da {source} completata."
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Finished importing your Sofa export."
|
||||
msgstr "Importazione del tuo export Sofa completata."
|
||||
#~ msgid "Finished importing your Sofa export."
|
||||
#~ msgstr "Importazione del tuo export Sofa completata."
|
||||
|
||||
#: apps/web/src/components/titles/title-availability.tsx
|
||||
#~ msgid "Free"
|
||||
@@ -1605,13 +1601,11 @@ msgstr "Importazione"
|
||||
msgid "Import {totalItems} items"
|
||||
msgstr "Importa {totalItems} elementi"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Import complete"
|
||||
msgstr "Importazione completata"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import failed"
|
||||
msgstr "Importazione fallita"
|
||||
|
||||
@@ -1627,8 +1621,7 @@ msgstr "Importa da {source}"
|
||||
msgid "Import from {sourceLabel}"
|
||||
msgstr "Importa da {sourceLabel}"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import is still running in the background. Check back later."
|
||||
msgstr "L'importazione è ancora in corso in background. Ricontrolla più tardi."
|
||||
|
||||
@@ -1646,8 +1639,7 @@ msgstr ""
|
||||
msgid "Import Sofa data"
|
||||
msgstr "Importa dati Sofa"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Imported"
|
||||
msgstr "Importati"
|
||||
|
||||
@@ -1664,10 +1656,10 @@ msgid "Imported {importedCount} items from Sofa export"
|
||||
msgstr "Importati {importedCount} elementi dall'export Sofa"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Importing data"
|
||||
msgstr "Importazione dati"
|
||||
#~ msgid "Importing data"
|
||||
#~ msgstr "Importazione dati"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Importing from {source}"
|
||||
msgstr "Importazione da {source} in corso"
|
||||
|
||||
@@ -1843,8 +1835,7 @@ msgstr "Caricamento…"
|
||||
msgid "Login failed"
|
||||
msgstr "Accesso non riuscito"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Lost connection to import. Check status in settings."
|
||||
msgstr "Connessione all'importazione persa. Controlla lo stato nelle impostazioni."
|
||||
|
||||
@@ -3062,8 +3053,7 @@ msgstr "Esci dalle altre sessioni"
|
||||
msgid "Skip for now"
|
||||
msgstr "Salta per ora"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Skipped"
|
||||
msgstr "Ignorati"
|
||||
|
||||
@@ -3148,8 +3138,7 @@ msgstr "Inizia a tracciare le tue visioni"
|
||||
msgid "Starting at"
|
||||
msgstr "Inizia alle"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Starting import..."
|
||||
msgstr "Avvio importazione..."
|
||||
|
||||
@@ -3233,8 +3222,7 @@ msgstr ""
|
||||
msgid "This import cannot be cancelled"
|
||||
msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "This may take a few minutes for large libraries. Please don't close this tab."
|
||||
msgstr "L'operazione potrebbe richiedere alcuni minuti per librerie grandi. Non chiudere questa scheda."
|
||||
|
||||
@@ -3646,8 +3634,7 @@ msgstr "Avvisi"
|
||||
#~ msgid "Warnings ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Warnings ({warningCount})"
|
||||
msgstr "Avvertenze ({warningCount})"
|
||||
|
||||
|
||||
+19
-32
@@ -22,8 +22,7 @@ msgstr ""
|
||||
#~ msgid "...and {0} more"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "...and {remainingErrors} more"
|
||||
msgstr "...他{remainingErrors}件"
|
||||
|
||||
@@ -1043,8 +1042,7 @@ msgstr "サーバーをお持ちでないですか?"
|
||||
msgid "Don't have an account?"
|
||||
msgstr "アカウントをお持ちでないですか?"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Done"
|
||||
msgstr "完了"
|
||||
|
||||
@@ -1210,8 +1208,7 @@ msgstr "エラー"
|
||||
#~ msgid "Errors ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Errors ({errorCount})"
|
||||
msgstr "エラー ({errorCount})"
|
||||
|
||||
@@ -1236,8 +1233,7 @@ msgstr "作品を探索"
|
||||
msgid "Export"
|
||||
msgstr "エクスポート"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Failed"
|
||||
msgstr "失敗"
|
||||
|
||||
@@ -1450,13 +1446,13 @@ msgstr "フィルモグラフィ"
|
||||
msgid "Filters"
|
||||
msgstr "フィルター"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Finished importing from {source}."
|
||||
msgstr "{source}からのインポートが完了しました。"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Finished importing your Sofa export."
|
||||
msgstr "Sofaのエクスポートのインポートが完了しました。"
|
||||
#~ msgid "Finished importing your Sofa export."
|
||||
#~ msgstr "Sofaのエクスポートのインポートが完了しました。"
|
||||
|
||||
#: apps/web/src/components/titles/title-availability.tsx
|
||||
#~ msgid "Free"
|
||||
@@ -1605,13 +1601,11 @@ msgstr "インポート"
|
||||
msgid "Import {totalItems} items"
|
||||
msgstr "{totalItems}件をインポート"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Import complete"
|
||||
msgstr "インポート完了"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import failed"
|
||||
msgstr "インポート失敗"
|
||||
|
||||
@@ -1627,8 +1621,7 @@ msgstr "{source}からインポート"
|
||||
msgid "Import from {sourceLabel}"
|
||||
msgstr "{sourceLabel}からインポート"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import is still running in the background. Check back later."
|
||||
msgstr "インポートはバックグラウンドで実行中です。後で確認してください。"
|
||||
|
||||
@@ -1646,8 +1639,7 @@ msgstr ""
|
||||
msgid "Import Sofa data"
|
||||
msgstr "Sofaデータをインポート"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Imported"
|
||||
msgstr "インポート済み"
|
||||
|
||||
@@ -1664,10 +1656,10 @@ msgid "Imported {importedCount} items from Sofa export"
|
||||
msgstr "Sofaエクスポートから{importedCount}件をインポートしました"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Importing data"
|
||||
msgstr "データをインポート中"
|
||||
#~ msgid "Importing data"
|
||||
#~ msgstr "データをインポート中"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Importing from {source}"
|
||||
msgstr "{source}からインポート中"
|
||||
|
||||
@@ -1843,8 +1835,7 @@ msgstr "読み込み中…"
|
||||
msgid "Login failed"
|
||||
msgstr "ログインに失敗しました"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Lost connection to import. Check status in settings."
|
||||
msgstr "インポートへの接続が切断されました。設定でステータスを確認してください。"
|
||||
|
||||
@@ -3062,8 +3053,7 @@ msgstr "他のセッションからサインアウト"
|
||||
msgid "Skip for now"
|
||||
msgstr "今はスキップ"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Skipped"
|
||||
msgstr "スキップ済み"
|
||||
|
||||
@@ -3148,8 +3138,7 @@ msgstr "視聴のトラッキングを開始する"
|
||||
msgid "Starting at"
|
||||
msgstr "開始時刻"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Starting import..."
|
||||
msgstr "インポートを開始中..."
|
||||
|
||||
@@ -3233,8 +3222,7 @@ msgstr ""
|
||||
msgid "This import cannot be cancelled"
|
||||
msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "This may take a few minutes for large libraries. Please don't close this tab."
|
||||
msgstr "大規模なライブラリでは数分かかる場合があります。このタブを閉じないでください。"
|
||||
|
||||
@@ -3646,8 +3634,7 @@ msgstr "警告"
|
||||
#~ msgid "Warnings ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Warnings ({warningCount})"
|
||||
msgstr "警告({warningCount})"
|
||||
|
||||
|
||||
+19
-32
@@ -22,8 +22,7 @@ msgstr ""
|
||||
#~ msgid "...and {0} more"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "...and {remainingErrors} more"
|
||||
msgstr "...그 외 {remainingErrors}개 더"
|
||||
|
||||
@@ -1043,8 +1042,7 @@ msgstr "서버가 없으신가요?"
|
||||
msgid "Don't have an account?"
|
||||
msgstr "계정이 없으신가요?"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Done"
|
||||
msgstr "완료"
|
||||
|
||||
@@ -1210,8 +1208,7 @@ msgstr "오류"
|
||||
#~ msgid "Errors ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Errors ({errorCount})"
|
||||
msgstr "오류 ({errorCount})"
|
||||
|
||||
@@ -1236,8 +1233,7 @@ msgstr "작품 탐색"
|
||||
msgid "Export"
|
||||
msgstr "내보내기"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Failed"
|
||||
msgstr "실패"
|
||||
|
||||
@@ -1450,13 +1446,13 @@ msgstr "필모그래피"
|
||||
msgid "Filters"
|
||||
msgstr "필터"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Finished importing from {source}."
|
||||
msgstr "{source}에서 가져오기가 완료되었습니다."
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Finished importing your Sofa export."
|
||||
msgstr "Sofa 내보내기 가져오기가 완료되었습니다."
|
||||
#~ msgid "Finished importing your Sofa export."
|
||||
#~ msgstr "Sofa 내보내기 가져오기가 완료되었습니다."
|
||||
|
||||
#: apps/web/src/components/titles/title-availability.tsx
|
||||
#~ msgid "Free"
|
||||
@@ -1605,13 +1601,11 @@ msgstr "가져오기"
|
||||
msgid "Import {totalItems} items"
|
||||
msgstr "항목 {totalItems}개 가져오기"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Import complete"
|
||||
msgstr "가져오기 완료"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import failed"
|
||||
msgstr "가져오기 실패"
|
||||
|
||||
@@ -1627,8 +1621,7 @@ msgstr "{source}에서 가져오기"
|
||||
msgid "Import from {sourceLabel}"
|
||||
msgstr "{sourceLabel}에서 가져오기"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import is still running in the background. Check back later."
|
||||
msgstr "가져오기가 백그라운드에서 계속 실행 중입니다. 나중에 다시 확인하세요."
|
||||
|
||||
@@ -1646,8 +1639,7 @@ msgstr ""
|
||||
msgid "Import Sofa data"
|
||||
msgstr "Sofa 데이터 가져오기"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Imported"
|
||||
msgstr "가져옴"
|
||||
|
||||
@@ -1664,10 +1656,10 @@ msgid "Imported {importedCount} items from Sofa export"
|
||||
msgstr "Sofa 내보내기에서 {importedCount}개의 항목을 가져왔습니다"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Importing data"
|
||||
msgstr "데이터 가져오는 중"
|
||||
#~ msgid "Importing data"
|
||||
#~ msgstr "데이터 가져오는 중"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Importing from {source}"
|
||||
msgstr "{source}에서 가져오는 중"
|
||||
|
||||
@@ -1843,8 +1835,7 @@ msgstr "로딩 중…"
|
||||
msgid "Login failed"
|
||||
msgstr "로그인 실패"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Lost connection to import. Check status in settings."
|
||||
msgstr "가져오기 연결이 끊어졌습니다. 설정에서 상태를 확인하세요."
|
||||
|
||||
@@ -3062,8 +3053,7 @@ msgstr "다른 세션에서 로그아웃"
|
||||
msgid "Skip for now"
|
||||
msgstr "지금은 건너뛰기"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Skipped"
|
||||
msgstr "건너뜀"
|
||||
|
||||
@@ -3148,8 +3138,7 @@ msgstr "시청 추적을 시작하세요"
|
||||
msgid "Starting at"
|
||||
msgstr "시작 시간"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Starting import..."
|
||||
msgstr "가져오기 시작 중..."
|
||||
|
||||
@@ -3233,8 +3222,7 @@ msgstr ""
|
||||
msgid "This import cannot be cancelled"
|
||||
msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "This may take a few minutes for large libraries. Please don't close this tab."
|
||||
msgstr "라이브러리가 크면 몇 분 정도 걸릴 수 있습니다. 이 탭을 닫지 마세요."
|
||||
|
||||
@@ -3646,8 +3634,7 @@ msgstr "경고"
|
||||
#~ msgid "Warnings ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Warnings ({warningCount})"
|
||||
msgstr "경고 ({warningCount})"
|
||||
|
||||
|
||||
+19
-32
@@ -22,8 +22,7 @@ msgstr ""
|
||||
#~ msgid "...and {0} more"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "...and {remainingErrors} more"
|
||||
msgstr "...en {remainingErrors} meer"
|
||||
|
||||
@@ -1043,8 +1042,7 @@ msgstr "Geen server?"
|
||||
msgid "Don't have an account?"
|
||||
msgstr "Nog geen account?"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Done"
|
||||
msgstr "Klaar"
|
||||
|
||||
@@ -1210,8 +1208,7 @@ msgstr "Fout"
|
||||
#~ msgid "Errors ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Errors ({errorCount})"
|
||||
msgstr "Fouten ({errorCount})"
|
||||
|
||||
@@ -1236,8 +1233,7 @@ msgstr "Titels verkennen"
|
||||
msgid "Export"
|
||||
msgstr "Exporteren"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Failed"
|
||||
msgstr "Mislukt"
|
||||
|
||||
@@ -1450,13 +1446,13 @@ msgstr "Filmografie"
|
||||
msgid "Filters"
|
||||
msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Finished importing from {source}."
|
||||
msgstr "Importeren vanuit {source} voltooid."
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Finished importing your Sofa export."
|
||||
msgstr "Importeren van je Sofa-export voltooid."
|
||||
#~ msgid "Finished importing your Sofa export."
|
||||
#~ msgstr "Importeren van je Sofa-export voltooid."
|
||||
|
||||
#: apps/web/src/components/titles/title-availability.tsx
|
||||
#~ msgid "Free"
|
||||
@@ -1605,13 +1601,11 @@ msgstr "Importeren"
|
||||
msgid "Import {totalItems} items"
|
||||
msgstr "{totalItems} items importeren"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Import complete"
|
||||
msgstr "Importeren voltooid"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import failed"
|
||||
msgstr "Importeren mislukt"
|
||||
|
||||
@@ -1627,8 +1621,7 @@ msgstr "Importeren vanuit {source}"
|
||||
msgid "Import from {sourceLabel}"
|
||||
msgstr "Importeren vanuit {sourceLabel}"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import is still running in the background. Check back later."
|
||||
msgstr "Importeren loopt nog op de achtergrond. Controleer het later."
|
||||
|
||||
@@ -1646,8 +1639,7 @@ msgstr ""
|
||||
msgid "Import Sofa data"
|
||||
msgstr "Sofa-gegevens importeren"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Imported"
|
||||
msgstr "Geïmporteerd"
|
||||
|
||||
@@ -1664,10 +1656,10 @@ msgid "Imported {importedCount} items from Sofa export"
|
||||
msgstr "{importedCount} items geïmporteerd vanuit Sofa-export"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Importing data"
|
||||
msgstr "Gegevens importeren"
|
||||
#~ msgid "Importing data"
|
||||
#~ msgstr "Gegevens importeren"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Importing from {source}"
|
||||
msgstr "Importeren vanuit {source}"
|
||||
|
||||
@@ -1843,8 +1835,7 @@ msgstr "Laden…"
|
||||
msgid "Login failed"
|
||||
msgstr "Aanmelden mislukt"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Lost connection to import. Check status in settings."
|
||||
msgstr "Verbinding met de import verbroken. Controleer de status in de instellingen."
|
||||
|
||||
@@ -3062,8 +3053,7 @@ msgstr "Andere sessies afmelden"
|
||||
msgid "Skip for now"
|
||||
msgstr "Nu overslaan"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Skipped"
|
||||
msgstr "Overgeslagen"
|
||||
|
||||
@@ -3148,8 +3138,7 @@ msgstr "Begin met het bijhouden van je kijkgeschiedenis"
|
||||
msgid "Starting at"
|
||||
msgstr "Starten om"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Starting import..."
|
||||
msgstr "Import starten..."
|
||||
|
||||
@@ -3233,8 +3222,7 @@ msgstr ""
|
||||
msgid "This import cannot be cancelled"
|
||||
msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "This may take a few minutes for large libraries. Please don't close this tab."
|
||||
msgstr "Dit kan een paar minuten duren voor grote bibliotheken. Sluit dit tabblad niet."
|
||||
|
||||
@@ -3646,8 +3634,7 @@ msgstr "Waarschuwingen"
|
||||
#~ msgid "Warnings ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Warnings ({warningCount})"
|
||||
msgstr "Waarschuwingen ({warningCount})"
|
||||
|
||||
|
||||
+19
-32
@@ -22,8 +22,7 @@ msgstr ""
|
||||
#~ msgid "...and {0} more"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "...and {remainingErrors} more"
|
||||
msgstr "...e mais {remainingErrors}"
|
||||
|
||||
@@ -1043,8 +1042,7 @@ msgstr "Não tem um servidor?"
|
||||
msgid "Don't have an account?"
|
||||
msgstr "Não tem uma conta?"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Done"
|
||||
msgstr "Concluído"
|
||||
|
||||
@@ -1210,8 +1208,7 @@ msgstr "Erro"
|
||||
#~ msgid "Errors ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Errors ({errorCount})"
|
||||
msgstr "Erros ({errorCount})"
|
||||
|
||||
@@ -1236,8 +1233,7 @@ msgstr "Explorar títulos"
|
||||
msgid "Export"
|
||||
msgstr "Exportar"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Failed"
|
||||
msgstr "Falhou"
|
||||
|
||||
@@ -1450,13 +1446,13 @@ msgstr "Filmografia"
|
||||
msgid "Filters"
|
||||
msgstr "Filtros"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Finished importing from {source}."
|
||||
msgstr "Importação de {source} concluída."
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Finished importing your Sofa export."
|
||||
msgstr "Importação do seu ficheiro Sofa concluída."
|
||||
#~ msgid "Finished importing your Sofa export."
|
||||
#~ msgstr "Importação do seu ficheiro Sofa concluída."
|
||||
|
||||
#: apps/web/src/components/titles/title-availability.tsx
|
||||
#~ msgid "Free"
|
||||
@@ -1605,13 +1601,11 @@ msgstr "Importar"
|
||||
msgid "Import {totalItems} items"
|
||||
msgstr "Importar {totalItems} itens"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Import complete"
|
||||
msgstr "Importação concluída"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import failed"
|
||||
msgstr "Importação falhou"
|
||||
|
||||
@@ -1627,8 +1621,7 @@ msgstr "Importar de {source}"
|
||||
msgid "Import from {sourceLabel}"
|
||||
msgstr "Importar de {sourceLabel}"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import is still running in the background. Check back later."
|
||||
msgstr "A importação ainda está em andamento em segundo plano. Verifique mais tarde."
|
||||
|
||||
@@ -1646,8 +1639,7 @@ msgstr ""
|
||||
msgid "Import Sofa data"
|
||||
msgstr "Importar dados do Sofa"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Imported"
|
||||
msgstr "Importado"
|
||||
|
||||
@@ -1664,10 +1656,10 @@ msgid "Imported {importedCount} items from Sofa export"
|
||||
msgstr "Importados {importedCount} itens do ficheiro Sofa"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Importing data"
|
||||
msgstr "A importar dados"
|
||||
#~ msgid "Importing data"
|
||||
#~ msgstr "A importar dados"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Importing from {source}"
|
||||
msgstr "Importando de {source}"
|
||||
|
||||
@@ -1843,8 +1835,7 @@ msgstr "Carregando…"
|
||||
msgid "Login failed"
|
||||
msgstr "Falha no login"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Lost connection to import. Check status in settings."
|
||||
msgstr "Conexão de importação perdida. Verifique o status nas configurações."
|
||||
|
||||
@@ -3062,8 +3053,7 @@ msgstr "Sair de outras sessões"
|
||||
msgid "Skip for now"
|
||||
msgstr "Ignorar por agora"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Skipped"
|
||||
msgstr "Ignorado"
|
||||
|
||||
@@ -3148,8 +3138,7 @@ msgstr "Comece a registrar suas visualizações"
|
||||
msgid "Starting at"
|
||||
msgstr "Iniciando às"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Starting import..."
|
||||
msgstr "Iniciando importação..."
|
||||
|
||||
@@ -3233,8 +3222,7 @@ msgstr ""
|
||||
msgid "This import cannot be cancelled"
|
||||
msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "This may take a few minutes for large libraries. Please don't close this tab."
|
||||
msgstr "Isso pode levar alguns minutos para bibliotecas grandes. Por favor, não feche esta aba."
|
||||
|
||||
@@ -3646,8 +3634,7 @@ msgstr "Avisos"
|
||||
#~ msgid "Warnings ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Warnings ({warningCount})"
|
||||
msgstr "Avisos ({warningCount})"
|
||||
|
||||
|
||||
+19
-32
@@ -22,8 +22,7 @@ msgstr ""
|
||||
#~ msgid "...and {0} more"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "...and {remainingErrors} more"
|
||||
msgstr "...及另外 {remainingErrors} 个"
|
||||
|
||||
@@ -1043,8 +1042,7 @@ msgstr "没有服务器?"
|
||||
msgid "Don't have an account?"
|
||||
msgstr "没有账户?"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Done"
|
||||
msgstr "完成"
|
||||
|
||||
@@ -1210,8 +1208,7 @@ msgstr "错误"
|
||||
#~ msgid "Errors ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Errors ({errorCount})"
|
||||
msgstr "错误 ({errorCount})"
|
||||
|
||||
@@ -1236,8 +1233,7 @@ msgstr "浏览影视"
|
||||
msgid "Export"
|
||||
msgstr "导出"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Failed"
|
||||
msgstr "失败"
|
||||
|
||||
@@ -1450,13 +1446,13 @@ msgstr "作品列表"
|
||||
msgid "Filters"
|
||||
msgstr "筛选"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Finished importing from {source}."
|
||||
msgstr "已完成从 {source} 导入。"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Finished importing your Sofa export."
|
||||
msgstr "已完成导入您的 Sofa 导出文件。"
|
||||
#~ msgid "Finished importing your Sofa export."
|
||||
#~ msgstr "已完成导入您的 Sofa 导出文件。"
|
||||
|
||||
#: apps/web/src/components/titles/title-availability.tsx
|
||||
#~ msgid "Free"
|
||||
@@ -1605,13 +1601,11 @@ msgstr "导入"
|
||||
msgid "Import {totalItems} items"
|
||||
msgstr "导入 {totalItems} 个项目"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Import complete"
|
||||
msgstr "导入完成"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import failed"
|
||||
msgstr "导入失败"
|
||||
|
||||
@@ -1627,8 +1621,7 @@ msgstr "从 {source} 导入"
|
||||
msgid "Import from {sourceLabel}"
|
||||
msgstr "从 {sourceLabel} 导入"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Import is still running in the background. Check back later."
|
||||
msgstr "导入仍在后台运行,请稍后再来查看。"
|
||||
|
||||
@@ -1646,8 +1639,7 @@ msgstr ""
|
||||
msgid "Import Sofa data"
|
||||
msgstr "导入 Sofa 数据"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Imported"
|
||||
msgstr "已导入"
|
||||
|
||||
@@ -1664,10 +1656,10 @@ msgid "Imported {importedCount} items from Sofa export"
|
||||
msgstr "已从 Sofa 导出文件导入 {importedCount} 个条目"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
msgid "Importing data"
|
||||
msgstr "正在导入数据"
|
||||
#~ msgid "Importing data"
|
||||
#~ msgstr "正在导入数据"
|
||||
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Importing from {source}"
|
||||
msgstr "正在从 {source} 导入"
|
||||
|
||||
@@ -1843,8 +1835,7 @@ msgstr "加载中…"
|
||||
msgid "Login failed"
|
||||
msgstr "登录失败"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/use-import-job.ts
|
||||
msgid "Lost connection to import. Check status in settings."
|
||||
msgstr "导入连接已断开,请在设置中查看状态。"
|
||||
|
||||
@@ -3062,8 +3053,7 @@ msgstr "退出其他会话"
|
||||
msgid "Skip for now"
|
||||
msgstr "暂时跳过"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Skipped"
|
||||
msgstr "已跳过"
|
||||
|
||||
@@ -3148,8 +3138,7 @@ msgstr "开始记录您的观看历史"
|
||||
msgid "Starting at"
|
||||
msgstr "开始时间"
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Starting import..."
|
||||
msgstr "正在开始导入..."
|
||||
|
||||
@@ -3233,8 +3222,7 @@ msgstr ""
|
||||
msgid "This import cannot be cancelled"
|
||||
msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "This may take a few minutes for large libraries. Please don't close this tab."
|
||||
msgstr "大型媒体库可能需要几分钟。请勿关闭此标签页。"
|
||||
|
||||
@@ -3646,8 +3634,7 @@ msgstr "警告"
|
||||
#~ msgid "Warnings ({0})"
|
||||
#~ msgstr ""
|
||||
|
||||
#: apps/web/src/components/settings/account-section.tsx
|
||||
#: apps/web/src/components/settings/imports-section.tsx
|
||||
#: apps/web/src/components/settings/import-dialog-parts.tsx
|
||||
msgid "Warnings ({warningCount})"
|
||||
msgstr "警告({warningCount})"
|
||||
|
||||
|
||||
Reference in new issue
Block a user