mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-08-11 05:30:29 +02:00
refactor: profile imports
This commit is contained in:
@@ -53,10 +53,88 @@ import type {
|
||||
ImportProfileItem,
|
||||
ProfileImportBatchResult,
|
||||
ProfileImportProgress,
|
||||
ProfileImportReport,
|
||||
WayfernConfig,
|
||||
} from "@/types";
|
||||
import { RippleButton } from "./ui/ripple";
|
||||
|
||||
/**
|
||||
* What an import actually carried, and what it could not.
|
||||
*
|
||||
* The counts matter more than they look: an import that reports zero of
|
||||
* everything is the exact symptom of the bug where copied data landed where
|
||||
* the browser never reads it, and it used to be indistinguishable from success.
|
||||
*/
|
||||
function ImportReportSummary({ report }: { report: ProfileImportReport }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Label-then-value rather than "{{count}} cookies": it keeps the row scannable
|
||||
// and sidesteps needing correct plural forms in ten languages.
|
||||
const carried = (
|
||||
[
|
||||
["importProfile.reportCookies", report.cookies_migrated],
|
||||
["importProfile.reportPasswords", report.passwords_migrated],
|
||||
["importProfile.reportAutofill", report.payment_methods_migrated],
|
||||
["importProfile.reportExtensions", report.extensions_migrated],
|
||||
["importProfile.reportHistory", report.history_entries],
|
||||
["importProfile.reportBookmarks", report.bookmarks],
|
||||
["importProfile.reportLocalStorage", report.local_storage_origins],
|
||||
] as const
|
||||
)
|
||||
.filter(([, count]) => count > 0)
|
||||
.map(([key, count]) => `${t(key)} ${count.toLocaleString()}`);
|
||||
|
||||
const unrecoverable =
|
||||
report.cookies_unrecoverable +
|
||||
report.passwords_unrecoverable +
|
||||
report.payment_methods_unrecoverable;
|
||||
|
||||
return (
|
||||
<div className="mt-0.5 space-y-0.5 pl-1 text-xs text-muted-foreground">
|
||||
<p>
|
||||
{carried.length > 0
|
||||
? carried.join(" · ")
|
||||
: t("importProfile.reportNothingCarried")}
|
||||
</p>
|
||||
{unrecoverable > 0 && (
|
||||
<p>
|
||||
{t("importProfile.reportUnrecoverable", { count: unrecoverable })}
|
||||
</p>
|
||||
)}
|
||||
{report.warnings.map((code) => (
|
||||
<p key={code} className="text-warning-text">
|
||||
{t(`importProfile.warnings.${code}`)}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fold a retry's results back into the batch it came from.
|
||||
*
|
||||
* A retry only resubmits the items that failed, so the previous batch is still
|
||||
* authoritative for every other row. Replacing it wholesale would make the
|
||||
* successful imports disappear from the summary.
|
||||
*/
|
||||
function mergeImportResults(
|
||||
previous: ProfileImportBatchResult,
|
||||
retry: ProfileImportBatchResult,
|
||||
): ProfileImportBatchResult {
|
||||
const byPath = new Map(retry.results.map((item) => [item.source_path, item]));
|
||||
const results = previous.results.map(
|
||||
(item) => byPath.get(item.source_path) ?? item,
|
||||
);
|
||||
const count = (status: string) =>
|
||||
results.filter((item) => item.status === status).length;
|
||||
return {
|
||||
imported_count: count("imported"),
|
||||
skipped_count: count("skipped"),
|
||||
failed_count: count("failed"),
|
||||
results,
|
||||
};
|
||||
}
|
||||
|
||||
interface ImportProfileDialogProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
@@ -283,69 +361,99 @@ export function ImportProfileDialog({
|
||||
}
|
||||
};
|
||||
|
||||
const handleImport = useCallback(async () => {
|
||||
if (selectedProfiles.length === 0) {
|
||||
toast.error(t("importProfile.selectAtLeastOne"));
|
||||
return;
|
||||
}
|
||||
if (
|
||||
selectedProfiles.some((p) => !(profileNames[p.path] ?? p.name).trim())
|
||||
) {
|
||||
toast.error(t("importProfile.emptyNames"));
|
||||
return;
|
||||
}
|
||||
|
||||
const items: ImportProfileItem[] = selectedProfiles.map((p, index) => ({
|
||||
source_path: p.path,
|
||||
browser_type: p.browser,
|
||||
new_profile_name: (profileNames[p.path] ?? p.name).trim(),
|
||||
proxy_id: proxyIdForIndex(index),
|
||||
vpn_id: vpnAssignment === "none" ? null : vpnAssignment,
|
||||
}));
|
||||
|
||||
setCurrentStep("importing");
|
||||
setIsImporting(true);
|
||||
setProgress(null);
|
||||
setResult(null);
|
||||
try {
|
||||
const batchResult = await invoke<ProfileImportBatchResult>(
|
||||
"import_browser_profiles",
|
||||
{
|
||||
items,
|
||||
groupId: selectedGroupId === "none" ? null : selectedGroupId,
|
||||
duplicateStrategy: duplicateStrategy,
|
||||
wayfernConfig,
|
||||
},
|
||||
);
|
||||
setResult(batchResult);
|
||||
toast.success(
|
||||
t("importProfile.resultsSummary", {
|
||||
imported: batchResult.imported_count,
|
||||
skipped: batchResult.skipped_count,
|
||||
failed: batchResult.failed_count,
|
||||
}),
|
||||
);
|
||||
if (batchResult.imported_count > 0 && !reducedMotion) {
|
||||
fireSprinkleConfetti();
|
||||
const handleImport = useCallback(
|
||||
async (allowRunning = false, retryPaths?: ReadonlySet<string>) => {
|
||||
if (selectedProfiles.length === 0) {
|
||||
toast.error(t("importProfile.selectAtLeastOne"));
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to import profiles:", error);
|
||||
toast.error(translateBackendError(t, error));
|
||||
setCurrentStep("configure");
|
||||
} finally {
|
||||
setIsImporting(false);
|
||||
}
|
||||
}, [
|
||||
selectedProfiles,
|
||||
profileNames,
|
||||
proxyIdForIndex,
|
||||
vpnAssignment,
|
||||
selectedGroupId,
|
||||
duplicateStrategy,
|
||||
wayfernConfig,
|
||||
reducedMotion,
|
||||
t,
|
||||
]);
|
||||
if (
|
||||
selectedProfiles.some((p) => !(profileNames[p.path] ?? p.name).trim())
|
||||
) {
|
||||
toast.error(t("importProfile.emptyNames"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter AFTER the map, so a retry keeps the proxy each profile was
|
||||
// originally assigned by the index-based round-robin.
|
||||
const items: ImportProfileItem[] = selectedProfiles
|
||||
.map((p, index) => ({
|
||||
source_path: p.path,
|
||||
browser_type: p.browser,
|
||||
new_profile_name: (profileNames[p.path] ?? p.name).trim(),
|
||||
proxy_id: proxyIdForIndex(index),
|
||||
vpn_id: vpnAssignment === "none" ? null : vpnAssignment,
|
||||
allow_running: allowRunning,
|
||||
}))
|
||||
.filter((item) => !retryPaths || retryPaths.has(item.source_path));
|
||||
|
||||
if (items.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
setCurrentStep("importing");
|
||||
setIsImporting(true);
|
||||
setProgress(null);
|
||||
// A retry covers only the failed subset, so the earlier results are still
|
||||
// the truth for everything else and must not be thrown away.
|
||||
const previous = retryPaths ? result : null;
|
||||
setResult(null);
|
||||
try {
|
||||
const batchResult = await invoke<ProfileImportBatchResult>(
|
||||
"import_browser_profiles",
|
||||
{
|
||||
items,
|
||||
groupId: selectedGroupId === "none" ? null : selectedGroupId,
|
||||
duplicateStrategy: duplicateStrategy,
|
||||
wayfernConfig,
|
||||
},
|
||||
);
|
||||
setResult(
|
||||
previous ? mergeImportResults(previous, batchResult) : batchResult,
|
||||
);
|
||||
toast.success(
|
||||
t("importProfile.resultsSummary", {
|
||||
imported: batchResult.imported_count,
|
||||
skipped: batchResult.skipped_count,
|
||||
failed: batchResult.failed_count,
|
||||
}),
|
||||
);
|
||||
if (batchResult.imported_count > 0 && !reducedMotion) {
|
||||
fireSprinkleConfetti();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to import profiles:", error);
|
||||
toast.error(translateBackendError(t, error));
|
||||
setCurrentStep("configure");
|
||||
} finally {
|
||||
setIsImporting(false);
|
||||
}
|
||||
},
|
||||
[
|
||||
selectedProfiles,
|
||||
profileNames,
|
||||
proxyIdForIndex,
|
||||
vpnAssignment,
|
||||
selectedGroupId,
|
||||
duplicateStrategy,
|
||||
wayfernConfig,
|
||||
reducedMotion,
|
||||
result,
|
||||
t,
|
||||
],
|
||||
);
|
||||
|
||||
// A source browser that is still running is the one failure the user can fix
|
||||
// without starting over, so offer the override right where it happened.
|
||||
const hasRunningBrowserFailure = useMemo(
|
||||
() =>
|
||||
(result?.results ?? []).some(
|
||||
(item) =>
|
||||
item.status === "failed" &&
|
||||
item.error?.includes("IMPORT_SOURCE_BROWSER_RUNNING"),
|
||||
),
|
||||
[result],
|
||||
);
|
||||
|
||||
const handleClose = () => {
|
||||
void cleanupExtractedDir(extractedDir);
|
||||
@@ -840,38 +948,74 @@ export function ImportProfileDialog({
|
||||
</h3>
|
||||
<div className="max-h-64 space-y-1 overflow-y-auto rounded-lg border border-border p-2">
|
||||
{result.results.map((item) => (
|
||||
<div
|
||||
key={item.source_path}
|
||||
className="flex items-center gap-2 p-1 text-sm"
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"shrink-0 text-xs font-medium",
|
||||
item.status === "imported" && "text-success-text",
|
||||
item.status === "skipped" &&
|
||||
"text-muted-foreground",
|
||||
item.status === "failed" &&
|
||||
"text-destructive-text",
|
||||
)}
|
||||
>
|
||||
{item.status === "imported" &&
|
||||
t("importProfile.statusImported")}
|
||||
{item.status === "skipped" &&
|
||||
t("importProfile.statusSkipped")}
|
||||
{item.status === "failed" &&
|
||||
t("importProfile.statusFailed")}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate">
|
||||
{item.name || item.source_path}
|
||||
</span>
|
||||
{item.error && (
|
||||
<span className="min-w-0 flex-1 truncate text-xs text-destructive-text">
|
||||
{translateBackendError(t, new Error(item.error))}
|
||||
<div key={item.source_path} className="p-1 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
"shrink-0 text-xs font-medium",
|
||||
item.status === "imported" &&
|
||||
"text-success-text",
|
||||
item.status === "skipped" &&
|
||||
"text-muted-foreground",
|
||||
item.status === "failed" &&
|
||||
"text-destructive-text",
|
||||
)}
|
||||
>
|
||||
{item.status === "imported" &&
|
||||
t("importProfile.statusImported")}
|
||||
{item.status === "skipped" &&
|
||||
t("importProfile.statusSkipped")}
|
||||
{item.status === "failed" &&
|
||||
t("importProfile.statusFailed")}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate">
|
||||
{item.name || item.source_path}
|
||||
</span>
|
||||
{item.error && (
|
||||
<span className="min-w-0 flex-1 truncate text-xs text-destructive-text">
|
||||
{translateBackendError(
|
||||
t,
|
||||
new Error(item.error),
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{item.report && (
|
||||
<ImportReportSummary report={item.report} />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{hasRunningBrowserFailure && (
|
||||
<Alert>
|
||||
<AlertDescription className="space-y-2">
|
||||
<p>{t("importProfile.closeSourceBrowserHint")}</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
void handleImport(
|
||||
true,
|
||||
new Set(
|
||||
result.results
|
||||
.filter(
|
||||
(item) =>
|
||||
item.status === "failed" &&
|
||||
item.error?.includes(
|
||||
"IMPORT_SOURCE_BROWSER_RUNNING",
|
||||
),
|
||||
)
|
||||
.map((item) => item.source_path),
|
||||
),
|
||||
);
|
||||
}}
|
||||
>
|
||||
{t("importProfile.importAnyway")}
|
||||
</Button>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1855,10 +1855,18 @@
|
||||
"name": "ntapi",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "num",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "num-bigint",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "num-complex",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "num-conv",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
@@ -1871,6 +1879,10 @@
|
||||
"name": "num-integer",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "num-iter",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "num-rational",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
@@ -2407,6 +2419,10 @@
|
||||
"name": "sealed",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "secret-service",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "security-framework",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
|
||||
@@ -1467,7 +1467,28 @@
|
||||
"vpnOptional": "VPN (Optional)",
|
||||
"noVpn": "No VPN",
|
||||
"advancedOptions": "Advanced options",
|
||||
"configureFingerprint": "Configure fingerprint (optional)"
|
||||
"configureFingerprint": "Configure fingerprint (optional)",
|
||||
"reportCookies": "Cookies",
|
||||
"reportPasswords": "Passwords",
|
||||
"reportAutofill": "Payment methods",
|
||||
"reportExtensions": "Extensions",
|
||||
"reportHistory": "History",
|
||||
"reportBookmarks": "Bookmarks",
|
||||
"reportLocalStorage": "Site data",
|
||||
"reportNothingCarried": "No readable data was carried over",
|
||||
"reportUnrecoverable": "Could not be decrypted: {{count}}",
|
||||
"closeSourceBrowserHint": "Close the source browser and try again for a complete copy, or import now and accept that site data may be incomplete.",
|
||||
"importAnyway": "Import anyway",
|
||||
"warnings": {
|
||||
"secretsNotMigrated": "Cookies and passwords could not be unlocked, so you will need to sign in again.",
|
||||
"appBoundEncrypted": "Chrome 127+ on Windows locks cookies to the browser itself; those cookies cannot be migrated by any other app.",
|
||||
"storeTooOld": "A database was too old for this browser to open and was skipped.",
|
||||
"storeTooNew": "A database came from a newer browser than this one and was skipped.",
|
||||
"sourceBrowserRunning": "The source browser was running, so site data may be incomplete.",
|
||||
"securePreferencesReset": "Protected settings such as the homepage and search engine reset to defaults.",
|
||||
"extensionsPartial": "Some extensions belonged to the source browser and were not carried over.",
|
||||
"storeUnreadable": "A database could not be read and was skipped rather than copied damaged."
|
||||
}
|
||||
},
|
||||
"syncTooltips": {
|
||||
"syncing": "Syncing...",
|
||||
@@ -1914,7 +1935,10 @@
|
||||
"malformed": "The VLESS URI is invalid."
|
||||
},
|
||||
"camoufoxRemoved": "Camoufox is no longer supported. Recreate this profile with Wayfern.",
|
||||
"noE2ePasswordSet": "No end-to-end encryption password is set. Set one before syncing encrypted data."
|
||||
"noE2ePasswordSet": "No end-to-end encryption password is set. Set one before syncing encrypted data.",
|
||||
"importSourceNotChromium": "This folder is not a Chromium browser profile",
|
||||
"importSourceNotChromiumNamed": "{{family}} profiles cannot be imported; only Chromium-based browsers are supported",
|
||||
"importSourceBrowserRunning": "Close {{browser}} first, or choose to import anyway"
|
||||
},
|
||||
"rail": {
|
||||
"profiles": "Profiles",
|
||||
|
||||
@@ -1471,7 +1471,28 @@
|
||||
"vpnOptional": "VPN (opcional)",
|
||||
"noVpn": "Sin VPN",
|
||||
"advancedOptions": "Opciones avanzadas",
|
||||
"configureFingerprint": "Configurar huella digital (opcional)"
|
||||
"configureFingerprint": "Configurar huella digital (opcional)",
|
||||
"reportCookies": "Cookies",
|
||||
"reportPasswords": "Contraseñas",
|
||||
"reportAutofill": "Métodos de pago",
|
||||
"reportExtensions": "Extensiones",
|
||||
"reportHistory": "Historial",
|
||||
"reportBookmarks": "Marcadores",
|
||||
"reportLocalStorage": "Datos de sitios",
|
||||
"reportNothingCarried": "No se transfirió ningún dato legible",
|
||||
"reportUnrecoverable": "No se pudo descifrar: {{count}}",
|
||||
"closeSourceBrowserHint": "Cierra el navegador de origen y vuelve a intentarlo para obtener una copia completa, o importa ahora aceptando que los datos de sitios pueden quedar incompletos.",
|
||||
"importAnyway": "Importar de todos modos",
|
||||
"warnings": {
|
||||
"secretsNotMigrated": "No se pudieron desbloquear las cookies ni las contraseñas, así que tendrás que iniciar sesión de nuevo.",
|
||||
"appBoundEncrypted": "Chrome 127+ en Windows vincula las cookies al propio navegador; ninguna otra aplicación puede migrarlas.",
|
||||
"storeTooOld": "Una base de datos era demasiado antigua para este navegador y se omitió.",
|
||||
"storeTooNew": "Una base de datos procede de un navegador más reciente que este y se omitió.",
|
||||
"sourceBrowserRunning": "El navegador de origen estaba en ejecución, por lo que los datos de sitios pueden estar incompletos.",
|
||||
"securePreferencesReset": "Los ajustes protegidos, como la página de inicio y el buscador, volvieron a sus valores predeterminados.",
|
||||
"extensionsPartial": "Algunas extensiones pertenecían al navegador de origen y no se transfirieron.",
|
||||
"storeUnreadable": "No se pudo leer una base de datos y se omitió en lugar de copiarla dañada."
|
||||
}
|
||||
},
|
||||
"syncTooltips": {
|
||||
"syncing": "Sincronizando...",
|
||||
@@ -1921,7 +1942,10 @@
|
||||
"malformed": "La URI VLESS no es válida."
|
||||
},
|
||||
"camoufoxRemoved": "Camoufox ya no es compatible. Vuelve a crear este perfil con Wayfern.",
|
||||
"noE2ePasswordSet": "No hay contraseña de cifrado de extremo a extremo. Establece una antes de sincronizar datos cifrados."
|
||||
"noE2ePasswordSet": "No hay contraseña de cifrado de extremo a extremo. Establece una antes de sincronizar datos cifrados.",
|
||||
"importSourceNotChromium": "Esta carpeta no es un perfil de navegador Chromium",
|
||||
"importSourceNotChromiumNamed": "Los perfiles de {{family}} no se pueden importar; solo se admiten navegadores basados en Chromium",
|
||||
"importSourceBrowserRunning": "Cierra {{browser}} primero o elige importar de todos modos"
|
||||
},
|
||||
"rail": {
|
||||
"profiles": "Perfiles",
|
||||
|
||||
@@ -1471,7 +1471,28 @@
|
||||
"vpnOptional": "VPN (facultatif)",
|
||||
"noVpn": "Sans VPN",
|
||||
"advancedOptions": "Options avancées",
|
||||
"configureFingerprint": "Configurer l'empreinte (facultatif)"
|
||||
"configureFingerprint": "Configurer l'empreinte (facultatif)",
|
||||
"reportCookies": "Cookies",
|
||||
"reportPasswords": "Mots de passe",
|
||||
"reportAutofill": "Moyens de paiement",
|
||||
"reportExtensions": "Extensions",
|
||||
"reportHistory": "Historique",
|
||||
"reportBookmarks": "Favoris",
|
||||
"reportLocalStorage": "Données de sites",
|
||||
"reportNothingCarried": "Aucune donnée lisible n'a été transférée",
|
||||
"reportUnrecoverable": "Déchiffrement impossible : {{count}}",
|
||||
"closeSourceBrowserHint": "Fermez le navigateur source et réessayez pour obtenir une copie complète, ou importez maintenant en acceptant que les données de sites soient incomplètes.",
|
||||
"importAnyway": "Importer quand même",
|
||||
"warnings": {
|
||||
"secretsNotMigrated": "Les cookies et les mots de passe n'ont pas pu être déverrouillés : vous devrez vous reconnecter.",
|
||||
"appBoundEncrypted": "Chrome 127+ sous Windows lie les cookies au navigateur lui-même ; aucune autre application ne peut les migrer.",
|
||||
"storeTooOld": "Une base de données était trop ancienne pour ce navigateur et a été ignorée.",
|
||||
"storeTooNew": "Une base de données provient d'un navigateur plus récent que celui-ci et a été ignorée.",
|
||||
"sourceBrowserRunning": "Le navigateur source était en cours d'exécution, les données de sites peuvent donc être incomplètes.",
|
||||
"securePreferencesReset": "Les réglages protégés, comme la page d'accueil et le moteur de recherche, sont revenus aux valeurs par défaut.",
|
||||
"extensionsPartial": "Certaines extensions appartenaient au navigateur source et n'ont pas été transférées.",
|
||||
"storeUnreadable": "Une base de données n'a pas pu être lue et a été ignorée plutôt que copiée endommagée."
|
||||
}
|
||||
},
|
||||
"syncTooltips": {
|
||||
"syncing": "Synchronisation...",
|
||||
@@ -1921,7 +1942,10 @@
|
||||
"malformed": "L'URI VLESS n'est pas valide."
|
||||
},
|
||||
"camoufoxRemoved": "Camoufox n'est plus pris en charge. Recréez ce profil avec Wayfern.",
|
||||
"noE2ePasswordSet": "Aucun mot de passe de chiffrement de bout en bout n'est défini. Définissez-en un avant de synchroniser des données chiffrées."
|
||||
"noE2ePasswordSet": "Aucun mot de passe de chiffrement de bout en bout n'est défini. Définissez-en un avant de synchroniser des données chiffrées.",
|
||||
"importSourceNotChromium": "Ce dossier n'est pas un profil de navigateur Chromium",
|
||||
"importSourceNotChromiumNamed": "Les profils {{family}} ne peuvent pas être importés ; seuls les navigateurs basés sur Chromium sont pris en charge",
|
||||
"importSourceBrowserRunning": "Fermez d'abord {{browser}}, ou choisissez d'importer quand même"
|
||||
},
|
||||
"rail": {
|
||||
"profiles": "Profils",
|
||||
|
||||
@@ -1467,7 +1467,28 @@
|
||||
"vpnOptional": "VPN(任意)",
|
||||
"noVpn": "VPNなし",
|
||||
"advancedOptions": "詳細オプション",
|
||||
"configureFingerprint": "フィンガープリントを設定(任意)"
|
||||
"configureFingerprint": "フィンガープリントを設定(任意)",
|
||||
"reportCookies": "Cookie",
|
||||
"reportPasswords": "パスワード",
|
||||
"reportAutofill": "お支払い方法",
|
||||
"reportExtensions": "拡張機能",
|
||||
"reportHistory": "履歴",
|
||||
"reportBookmarks": "ブックマーク",
|
||||
"reportLocalStorage": "サイトデータ",
|
||||
"reportNothingCarried": "読み取り可能なデータは引き継がれませんでした",
|
||||
"reportUnrecoverable": "復号できませんでした: {{count}}",
|
||||
"closeSourceBrowserHint": "完全にコピーするには、元のブラウザーを閉じてからもう一度お試しください。サイトデータが不完全になることを承知のうえで、このままインポートすることもできます。",
|
||||
"importAnyway": "このままインポート",
|
||||
"warnings": {
|
||||
"secretsNotMigrated": "Cookie とパスワードのロックを解除できなかったため、再度サインインが必要です。",
|
||||
"appBoundEncrypted": "Windows の Chrome 127 以降は Cookie をブラウザー自体に紐付けるため、他のアプリからは移行できません。",
|
||||
"storeTooOld": "このブラウザーでは開けない古いデータベースがあったため、スキップしました。",
|
||||
"storeTooNew": "このブラウザーより新しいブラウザーのデータベースだったため、スキップしました。",
|
||||
"sourceBrowserRunning": "元のブラウザーが実行中だったため、サイトデータが不完全な可能性があります。",
|
||||
"securePreferencesReset": "ホームページや検索エンジンなど、保護された設定は既定値に戻りました。",
|
||||
"extensionsPartial": "一部の拡張機能は元のブラウザー付属のもので、引き継がれませんでした。",
|
||||
"storeUnreadable": "読み取れないデータベースがあったため、破損したままコピーせずスキップしました。"
|
||||
}
|
||||
},
|
||||
"syncTooltips": {
|
||||
"syncing": "同期中...",
|
||||
@@ -1914,7 +1935,10 @@
|
||||
"malformed": "VLESS URIが無効です。"
|
||||
},
|
||||
"camoufoxRemoved": "Camoufoxはサポートされなくなりました。Wayfernでこのプロファイルを作り直してください。",
|
||||
"noE2ePasswordSet": "エンドツーエンド暗号化のパスワードが設定されていません。暗号化データを同期する前に設定してください。"
|
||||
"noE2ePasswordSet": "エンドツーエンド暗号化のパスワードが設定されていません。暗号化データを同期する前に設定してください。",
|
||||
"importSourceNotChromium": "このフォルダーは Chromium ブラウザーのプロファイルではありません",
|
||||
"importSourceNotChromiumNamed": "{{family}} のプロファイルはインポートできません。Chromium 系ブラウザーのみ対応しています",
|
||||
"importSourceBrowserRunning": "先に {{browser}} を閉じるか、このままインポートを選択してください"
|
||||
},
|
||||
"rail": {
|
||||
"profiles": "プロファイル",
|
||||
|
||||
@@ -1467,7 +1467,28 @@
|
||||
"vpnOptional": "VPN (선택 사항)",
|
||||
"noVpn": "VPN 없음",
|
||||
"advancedOptions": "고급 옵션",
|
||||
"configureFingerprint": "핑거프린트 구성 (선택 사항)"
|
||||
"configureFingerprint": "핑거프린트 구성 (선택 사항)",
|
||||
"reportCookies": "쿠키",
|
||||
"reportPasswords": "비밀번호",
|
||||
"reportAutofill": "결제 수단",
|
||||
"reportExtensions": "확장 프로그램",
|
||||
"reportHistory": "방문 기록",
|
||||
"reportBookmarks": "북마크",
|
||||
"reportLocalStorage": "사이트 데이터",
|
||||
"reportNothingCarried": "읽을 수 있는 데이터가 이전되지 않았습니다",
|
||||
"reportUnrecoverable": "복호화할 수 없음: {{count}}",
|
||||
"closeSourceBrowserHint": "완전하게 복사하려면 원본 브라우저를 닫고 다시 시도하세요. 사이트 데이터가 불완전할 수 있음을 감수하고 지금 가져올 수도 있습니다.",
|
||||
"importAnyway": "그래도 가져오기",
|
||||
"warnings": {
|
||||
"secretsNotMigrated": "쿠키와 비밀번호를 잠금 해제하지 못해 다시 로그인해야 합니다.",
|
||||
"appBoundEncrypted": "Windows의 Chrome 127 이상은 쿠키를 브라우저 자체에 묶어 두므로 다른 앱에서는 이전할 수 없습니다.",
|
||||
"storeTooOld": "이 브라우저가 열 수 없을 만큼 오래된 데이터베이스가 있어 건너뛰었습니다.",
|
||||
"storeTooNew": "이 브라우저보다 최신 브라우저의 데이터베이스여서 건너뛰었습니다.",
|
||||
"sourceBrowserRunning": "원본 브라우저가 실행 중이어서 사이트 데이터가 불완전할 수 있습니다.",
|
||||
"securePreferencesReset": "홈페이지와 검색 엔진 같은 보호된 설정이 기본값으로 초기화되었습니다.",
|
||||
"extensionsPartial": "일부 확장 프로그램은 원본 브라우저의 것이어서 이전되지 않았습니다.",
|
||||
"storeUnreadable": "읽을 수 없는 데이터베이스가 있어 손상된 채로 복사하지 않고 건너뛰었습니다."
|
||||
}
|
||||
},
|
||||
"syncTooltips": {
|
||||
"syncing": "동기화 중...",
|
||||
@@ -1914,7 +1935,10 @@
|
||||
"malformed": "VLESS URI가 올바르지 않습니다."
|
||||
},
|
||||
"camoufoxRemoved": "Camoufox는 더 이상 지원되지 않습니다. Wayfern으로 이 프로필을 다시 만드세요.",
|
||||
"noE2ePasswordSet": "종단 간 암호화 비밀번호가 설정되지 않았습니다. 암호화된 데이터를 동기화하기 전에 설정하세요."
|
||||
"noE2ePasswordSet": "종단 간 암호화 비밀번호가 설정되지 않았습니다. 암호화된 데이터를 동기화하기 전에 설정하세요.",
|
||||
"importSourceNotChromium": "이 폴더는 Chromium 브라우저 프로필이 아닙니다",
|
||||
"importSourceNotChromiumNamed": "{{family}} 프로필은 가져올 수 없습니다. Chromium 기반 브라우저만 지원합니다",
|
||||
"importSourceBrowserRunning": "{{browser}}을(를) 먼저 닫거나 그래도 가져오기를 선택하세요"
|
||||
},
|
||||
"rail": {
|
||||
"profiles": "프로필",
|
||||
|
||||
@@ -1471,7 +1471,28 @@
|
||||
"vpnOptional": "VPN (opcional)",
|
||||
"noVpn": "Sem VPN",
|
||||
"advancedOptions": "Opções avançadas",
|
||||
"configureFingerprint": "Configurar impressão digital (opcional)"
|
||||
"configureFingerprint": "Configurar impressão digital (opcional)",
|
||||
"reportCookies": "Cookies",
|
||||
"reportPasswords": "Senhas",
|
||||
"reportAutofill": "Formas de pagamento",
|
||||
"reportExtensions": "Extensões",
|
||||
"reportHistory": "Histórico",
|
||||
"reportBookmarks": "Favoritos",
|
||||
"reportLocalStorage": "Dados de sites",
|
||||
"reportNothingCarried": "Nenhum dado legível foi transferido",
|
||||
"reportUnrecoverable": "Não foi possível descriptografar: {{count}}",
|
||||
"closeSourceBrowserHint": "Feche o navegador de origem e tente de novo para obter uma cópia completa, ou importe agora aceitando que os dados de sites podem ficar incompletos.",
|
||||
"importAnyway": "Importar mesmo assim",
|
||||
"warnings": {
|
||||
"secretsNotMigrated": "Não foi possível desbloquear cookies e senhas, então você precisará entrar novamente.",
|
||||
"appBoundEncrypted": "O Chrome 127+ no Windows vincula os cookies ao próprio navegador; nenhum outro aplicativo consegue migrá-los.",
|
||||
"storeTooOld": "Um banco de dados era antigo demais para este navegador e foi ignorado.",
|
||||
"storeTooNew": "Um banco de dados veio de um navegador mais novo que este e foi ignorado.",
|
||||
"sourceBrowserRunning": "O navegador de origem estava aberto, então os dados de sites podem estar incompletos.",
|
||||
"securePreferencesReset": "Configurações protegidas, como página inicial e mecanismo de busca, voltaram ao padrão.",
|
||||
"extensionsPartial": "Algumas extensões pertenciam ao navegador de origem e não foram transferidas.",
|
||||
"storeUnreadable": "Não foi possível ler um banco de dados, que foi ignorado em vez de copiado danificado."
|
||||
}
|
||||
},
|
||||
"syncTooltips": {
|
||||
"syncing": "Sincronizando...",
|
||||
@@ -1921,7 +1942,10 @@
|
||||
"malformed": "A URI VLESS é inválida."
|
||||
},
|
||||
"camoufoxRemoved": "O Camoufox não é mais compatível. Recrie este perfil com o Wayfern.",
|
||||
"noE2ePasswordSet": "Nenhuma senha de criptografia de ponta a ponta foi definida. Defina uma antes de sincronizar dados criptografados."
|
||||
"noE2ePasswordSet": "Nenhuma senha de criptografia de ponta a ponta foi definida. Defina uma antes de sincronizar dados criptografados.",
|
||||
"importSourceNotChromium": "Esta pasta não é um perfil de navegador Chromium",
|
||||
"importSourceNotChromiumNamed": "Perfis do {{family}} não podem ser importados; apenas navegadores baseados em Chromium são compatíveis",
|
||||
"importSourceBrowserRunning": "Feche o {{browser}} primeiro ou escolha importar mesmo assim"
|
||||
},
|
||||
"rail": {
|
||||
"profiles": "Perfis",
|
||||
|
||||
@@ -1475,7 +1475,28 @@
|
||||
"vpnOptional": "VPN (необязательно)",
|
||||
"noVpn": "Без VPN",
|
||||
"advancedOptions": "Дополнительные параметры",
|
||||
"configureFingerprint": "Настроить отпечаток (необязательно)"
|
||||
"configureFingerprint": "Настроить отпечаток (необязательно)",
|
||||
"reportCookies": "Файлы cookie",
|
||||
"reportPasswords": "Пароли",
|
||||
"reportAutofill": "Способы оплаты",
|
||||
"reportExtensions": "Расширения",
|
||||
"reportHistory": "История",
|
||||
"reportBookmarks": "Закладки",
|
||||
"reportLocalStorage": "Данные сайтов",
|
||||
"reportNothingCarried": "Читаемые данные не перенесены",
|
||||
"reportUnrecoverable": "Не удалось расшифровать: {{count}}",
|
||||
"closeSourceBrowserHint": "Закройте исходный браузер и повторите попытку, чтобы получить полную копию, либо импортируйте сейчас, приняв, что данные сайтов могут оказаться неполными.",
|
||||
"importAnyway": "Всё равно импортировать",
|
||||
"warnings": {
|
||||
"secretsNotMigrated": "Не удалось разблокировать файлы cookie и пароли, поэтому потребуется войти заново.",
|
||||
"appBoundEncrypted": "Chrome 127+ в Windows привязывает файлы cookie к самому браузеру, и другое приложение не может их перенести.",
|
||||
"storeTooOld": "База данных оказалась слишком старой для этого браузера и была пропущена.",
|
||||
"storeTooNew": "База данных создана более новым браузером и была пропущена.",
|
||||
"sourceBrowserRunning": "Исходный браузер был запущен, поэтому данные сайтов могут быть неполными.",
|
||||
"securePreferencesReset": "Защищённые настройки, например домашняя страница и поисковая система, сброшены до значений по умолчанию.",
|
||||
"extensionsPartial": "Некоторые расширения принадлежали исходному браузеру и не были перенесены.",
|
||||
"storeUnreadable": "База данных не читалась и была пропущена, а не скопирована повреждённой."
|
||||
}
|
||||
},
|
||||
"syncTooltips": {
|
||||
"syncing": "Синхронизация...",
|
||||
@@ -1928,7 +1949,10 @@
|
||||
"malformed": "VLESS URI недействителен."
|
||||
},
|
||||
"camoufoxRemoved": "Camoufox больше не поддерживается. Создайте этот профиль заново с Wayfern.",
|
||||
"noE2ePasswordSet": "Пароль сквозного шифрования не задан. Задайте его перед синхронизацией зашифрованных данных."
|
||||
"noE2ePasswordSet": "Пароль сквозного шифрования не задан. Задайте его перед синхронизацией зашифрованных данных.",
|
||||
"importSourceNotChromium": "Эта папка не является профилем браузера на Chromium",
|
||||
"importSourceNotChromiumNamed": "Профили {{family}} импортировать нельзя: поддерживаются только браузеры на Chromium",
|
||||
"importSourceBrowserRunning": "Сначала закройте {{browser}} или выберите импорт всё равно"
|
||||
},
|
||||
"rail": {
|
||||
"profiles": "Профили",
|
||||
|
||||
@@ -1467,7 +1467,28 @@
|
||||
"vpnOptional": "VPN (isteğe bağlı)",
|
||||
"noVpn": "VPN yok",
|
||||
"advancedOptions": "Gelişmiş seçenekler",
|
||||
"configureFingerprint": "Parmak izini yapılandır (isteğe bağlı)"
|
||||
"configureFingerprint": "Parmak izini yapılandır (isteğe bağlı)",
|
||||
"reportCookies": "Çerezler",
|
||||
"reportPasswords": "Parolalar",
|
||||
"reportAutofill": "Ödeme yöntemleri",
|
||||
"reportExtensions": "Uzantılar",
|
||||
"reportHistory": "Geçmiş",
|
||||
"reportBookmarks": "Yer imleri",
|
||||
"reportLocalStorage": "Site verileri",
|
||||
"reportNothingCarried": "Okunabilir hiçbir veri aktarılmadı",
|
||||
"reportUnrecoverable": "Şifresi çözülemedi: {{count}}",
|
||||
"closeSourceBrowserHint": "Tam bir kopya için kaynak tarayıcıyı kapatıp yeniden deneyin ya da site verilerinin eksik olabileceğini kabul ederek şimdi içe aktarın.",
|
||||
"importAnyway": "Yine de içe aktar",
|
||||
"warnings": {
|
||||
"secretsNotMigrated": "Çerezlerin ve parolaların kilidi açılamadı, bu yüzden yeniden oturum açmanız gerekecek.",
|
||||
"appBoundEncrypted": "Windows'ta Chrome 127+ çerezleri tarayıcının kendisine bağlar; başka hiçbir uygulama bunları taşıyamaz.",
|
||||
"storeTooOld": "Bir veritabanı bu tarayıcının açamayacağı kadar eskiydi ve atlandı.",
|
||||
"storeTooNew": "Bir veritabanı bundan daha yeni bir tarayıcıdan geldi ve atlandı.",
|
||||
"sourceBrowserRunning": "Kaynak tarayıcı çalışıyordu, bu yüzden site verileri eksik olabilir.",
|
||||
"securePreferencesReset": "Ana sayfa ve arama motoru gibi korumalı ayarlar varsayılana döndü.",
|
||||
"extensionsPartial": "Bazı uzantılar kaynak tarayıcıya aitti ve aktarılmadı.",
|
||||
"storeUnreadable": "Bir veritabanı okunamadı ve bozuk şekilde kopyalanmak yerine atlandı."
|
||||
}
|
||||
},
|
||||
"syncTooltips": {
|
||||
"syncing": "Eşitleniyor...",
|
||||
@@ -1914,7 +1935,10 @@
|
||||
"malformed": "VLESS URI'si geçersiz."
|
||||
},
|
||||
"camoufoxRemoved": "Camoufox artık desteklenmiyor. Bu profili Wayfern ile yeniden oluşturun.",
|
||||
"noE2ePasswordSet": "Uçtan uca şifreleme parolası ayarlanmamış. Şifreli veriyi eşitlemeden önce bir parola belirleyin."
|
||||
"noE2ePasswordSet": "Uçtan uca şifreleme parolası ayarlanmamış. Şifreli veriyi eşitlemeden önce bir parola belirleyin.",
|
||||
"importSourceNotChromium": "Bu klasör bir Chromium tarayıcı profili değil",
|
||||
"importSourceNotChromiumNamed": "{{family}} profilleri içe aktarılamaz; yalnızca Chromium tabanlı tarayıcılar desteklenir",
|
||||
"importSourceBrowserRunning": "Önce {{browser}} uygulamasını kapatın veya yine de içe aktarmayı seçin"
|
||||
},
|
||||
"rail": {
|
||||
"profiles": "Profiller",
|
||||
|
||||
@@ -1467,7 +1467,28 @@
|
||||
"vpnOptional": "VPN (tùy chọn)",
|
||||
"noVpn": "Không dùng VPN",
|
||||
"advancedOptions": "Tùy chọn nâng cao",
|
||||
"configureFingerprint": "Cấu hình vân tay (tùy chọn)"
|
||||
"configureFingerprint": "Cấu hình vân tay (tùy chọn)",
|
||||
"reportCookies": "Cookie",
|
||||
"reportPasswords": "Mật khẩu",
|
||||
"reportAutofill": "Phương thức thanh toán",
|
||||
"reportExtensions": "Tiện ích mở rộng",
|
||||
"reportHistory": "Lịch sử",
|
||||
"reportBookmarks": "Dấu trang",
|
||||
"reportLocalStorage": "Dữ liệu trang web",
|
||||
"reportNothingCarried": "Không có dữ liệu đọc được nào được chuyển sang",
|
||||
"reportUnrecoverable": "Không giải mã được: {{count}}",
|
||||
"closeSourceBrowserHint": "Hãy đóng trình duyệt nguồn rồi thử lại để có bản sao đầy đủ, hoặc nhập ngay và chấp nhận rằng dữ liệu trang web có thể chưa đầy đủ.",
|
||||
"importAnyway": "Vẫn nhập",
|
||||
"warnings": {
|
||||
"secretsNotMigrated": "Không mở khóa được cookie và mật khẩu, nên bạn sẽ phải đăng nhập lại.",
|
||||
"appBoundEncrypted": "Chrome 127 trở lên trên Windows gắn cookie với chính trình duyệt; không ứng dụng nào khác có thể chuyển được.",
|
||||
"storeTooOld": "Một cơ sở dữ liệu quá cũ để trình duyệt này mở nên đã bị bỏ qua.",
|
||||
"storeTooNew": "Một cơ sở dữ liệu đến từ trình duyệt mới hơn nên đã bị bỏ qua.",
|
||||
"sourceBrowserRunning": "Trình duyệt nguồn đang chạy nên dữ liệu trang web có thể chưa đầy đủ.",
|
||||
"securePreferencesReset": "Các cài đặt được bảo vệ như trang chủ và công cụ tìm kiếm đã trở về mặc định.",
|
||||
"extensionsPartial": "Một số tiện ích thuộc về trình duyệt nguồn nên không được chuyển sang.",
|
||||
"storeUnreadable": "Một cơ sở dữ liệu không đọc được nên đã bị bỏ qua thay vì sao chép hỏng."
|
||||
}
|
||||
},
|
||||
"syncTooltips": {
|
||||
"syncing": "Đang đồng bộ...",
|
||||
@@ -1914,7 +1935,10 @@
|
||||
"malformed": "URI VLESS không hợp lệ."
|
||||
},
|
||||
"camoufoxRemoved": "Camoufox không còn được hỗ trợ. Hãy tạo lại hồ sơ này bằng Wayfern.",
|
||||
"noE2ePasswordSet": "Chưa đặt mật khẩu mã hóa đầu cuối. Hãy đặt trước khi đồng bộ dữ liệu đã mã hóa."
|
||||
"noE2ePasswordSet": "Chưa đặt mật khẩu mã hóa đầu cuối. Hãy đặt trước khi đồng bộ dữ liệu đã mã hóa.",
|
||||
"importSourceNotChromium": "Thư mục này không phải hồ sơ trình duyệt Chromium",
|
||||
"importSourceNotChromiumNamed": "Không thể nhập hồ sơ {{family}}; chỉ hỗ trợ các trình duyệt nền Chromium",
|
||||
"importSourceBrowserRunning": "Hãy đóng {{browser}} trước, hoặc chọn vẫn nhập"
|
||||
},
|
||||
"rail": {
|
||||
"profiles": "Profile",
|
||||
|
||||
@@ -1467,7 +1467,28 @@
|
||||
"vpnOptional": "VPN(可选)",
|
||||
"noVpn": "不使用 VPN",
|
||||
"advancedOptions": "高级选项",
|
||||
"configureFingerprint": "配置指纹(可选)"
|
||||
"configureFingerprint": "配置指纹(可选)",
|
||||
"reportCookies": "Cookie",
|
||||
"reportPasswords": "密码",
|
||||
"reportAutofill": "付款方式",
|
||||
"reportExtensions": "扩展程序",
|
||||
"reportHistory": "历史记录",
|
||||
"reportBookmarks": "书签",
|
||||
"reportLocalStorage": "网站数据",
|
||||
"reportNothingCarried": "没有可读取的数据被迁移",
|
||||
"reportUnrecoverable": "无法解密:{{count}}",
|
||||
"closeSourceBrowserHint": "关闭源浏览器后重试可获得完整副本;也可以现在导入,但网站数据可能不完整。",
|
||||
"importAnyway": "仍要导入",
|
||||
"warnings": {
|
||||
"secretsNotMigrated": "无法解锁 Cookie 和密码,你需要重新登录。",
|
||||
"appBoundEncrypted": "Windows 上的 Chrome 127+ 会把 Cookie 绑定到浏览器本身,其他任何应用都无法迁移。",
|
||||
"storeTooOld": "某个数据库过旧,此浏览器无法打开,已跳过。",
|
||||
"storeTooNew": "某个数据库来自更新版本的浏览器,已跳过。",
|
||||
"sourceBrowserRunning": "源浏览器正在运行,网站数据可能不完整。",
|
||||
"securePreferencesReset": "主页、搜索引擎等受保护的设置已恢复为默认值。",
|
||||
"extensionsPartial": "部分扩展属于源浏览器,未被迁移。",
|
||||
"storeUnreadable": "某个数据库无法读取,已跳过而不是复制损坏的副本。"
|
||||
}
|
||||
},
|
||||
"syncTooltips": {
|
||||
"syncing": "同步中...",
|
||||
@@ -1914,7 +1935,10 @@
|
||||
"malformed": "VLESS URI 无效。"
|
||||
},
|
||||
"camoufoxRemoved": "Camoufox 已不再受支持。请使用 Wayfern 重新创建此配置文件。",
|
||||
"noE2ePasswordSet": "尚未设置端到端加密密码。请先设置后再同步加密数据。"
|
||||
"noE2ePasswordSet": "尚未设置端到端加密密码。请先设置后再同步加密数据。",
|
||||
"importSourceNotChromium": "该文件夹不是 Chromium 浏览器配置文件",
|
||||
"importSourceNotChromiumNamed": "无法导入 {{family}} 配置文件;仅支持基于 Chromium 的浏览器",
|
||||
"importSourceBrowserRunning": "请先关闭 {{browser}},或选择仍要导入"
|
||||
},
|
||||
"rail": {
|
||||
"profiles": "配置文件",
|
||||
|
||||
@@ -43,6 +43,8 @@ export type BackendErrorCode =
|
||||
| "UPDATE_PREPARATION_FAILED"
|
||||
| "PROFILE_NAME_EXISTS"
|
||||
| "IMPORT_SOURCE_NOT_FOUND"
|
||||
| "IMPORT_SOURCE_NOT_CHROMIUM"
|
||||
| "IMPORT_SOURCE_BROWSER_RUNNING"
|
||||
| "IMPORT_NO_ITEMS"
|
||||
| "BROWSER_NOT_DOWNLOADED"
|
||||
| "ARCHIVE_EXTRACTION_FAILED"
|
||||
@@ -253,6 +255,16 @@ export function translateBackendError(t: TFunction, err: unknown): string {
|
||||
});
|
||||
case "IMPORT_SOURCE_NOT_FOUND":
|
||||
return t("backendErrors.importSourceNotFound");
|
||||
case "IMPORT_SOURCE_NOT_CHROMIUM":
|
||||
return parsed.params?.family
|
||||
? t("backendErrors.importSourceNotChromiumNamed", {
|
||||
family: parsed.params.family,
|
||||
})
|
||||
: t("backendErrors.importSourceNotChromium");
|
||||
case "IMPORT_SOURCE_BROWSER_RUNNING":
|
||||
return t("backendErrors.importSourceBrowserRunning", {
|
||||
browser: parsed.params?.browser ?? "",
|
||||
});
|
||||
case "IMPORT_NO_ITEMS":
|
||||
return t("backendErrors.importNoItems");
|
||||
case "BROWSER_NOT_DOWNLOADED":
|
||||
|
||||
@@ -275,11 +275,44 @@ export interface DetectedProfile {
|
||||
|
||||
export interface ImportProfileItem {
|
||||
source_path: string;
|
||||
/**
|
||||
* Source browser family. Selects which OS keychain entry holds the key that
|
||||
* unlocks the source's cookies and passwords, so it decides whether secrets
|
||||
* survive the import.
|
||||
*/
|
||||
browser_type?: string;
|
||||
new_profile_name: string;
|
||||
/** Mutually exclusive with `vpn_id`; the importer rejects setting both. */
|
||||
proxy_id?: string | null;
|
||||
vpn_id?: string | null;
|
||||
/** Import even though the source browser is still running. */
|
||||
allow_running?: boolean;
|
||||
}
|
||||
|
||||
/** Stable warning codes; each maps to `importProfile.warnings.*`. */
|
||||
export type ProfileImportWarning =
|
||||
| "secretsNotMigrated"
|
||||
| "appBoundEncrypted"
|
||||
| "storeTooOld"
|
||||
| "storeTooNew"
|
||||
| "sourceBrowserRunning"
|
||||
| "securePreferencesReset"
|
||||
| "extensionsPartial"
|
||||
| "storeUnreadable";
|
||||
|
||||
export interface ProfileImportReport {
|
||||
cookies_migrated: number;
|
||||
cookies_unrecoverable: number;
|
||||
passwords_migrated: number;
|
||||
passwords_unrecoverable: number;
|
||||
payment_methods_migrated: number;
|
||||
payment_methods_unrecoverable: number;
|
||||
extensions_migrated: number;
|
||||
history_entries: number;
|
||||
bookmarks: number;
|
||||
local_storage_origins: number;
|
||||
bytes_copied: number;
|
||||
warnings: ProfileImportWarning[];
|
||||
}
|
||||
|
||||
export interface ProfileImportItemResult {
|
||||
@@ -288,6 +321,8 @@ export interface ProfileImportItemResult {
|
||||
status: "imported" | "skipped" | "failed";
|
||||
profile_id: string | null;
|
||||
error: string | null;
|
||||
/** What actually came across. Present when status is "imported". */
|
||||
report?: ProfileImportReport | null;
|
||||
}
|
||||
|
||||
export interface ProfileImportBatchResult {
|
||||
|
||||
Reference in New Issue
Block a user