mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-08-28 05:40:25 +02:00
refactor: better cookie import experience
This commit is contained in:
Generated
+613
-728
File diff suppressed because it is too large
Load Diff
@@ -129,7 +129,8 @@ export const commandCoverage = {
|
||||
"read_profile_cookies",
|
||||
"get_profile_cookie_stats",
|
||||
"copy_profile_cookies",
|
||||
"import_cookies_from_file",
|
||||
"analyze_pasted_cookies",
|
||||
"import_pasted_cookies",
|
||||
"export_profile_cookies",
|
||||
"set_profile_password",
|
||||
"change_profile_password",
|
||||
|
||||
@@ -766,11 +766,16 @@ test("cookie import/copy/export, profile encryption, and traffic-stat read/clear
|
||||
expirationDate: 2_000_000_000,
|
||||
},
|
||||
]);
|
||||
const imported = await app.invoke("import_cookies_from_file", {
|
||||
const imported = await app.invoke("import_pasted_cookies", {
|
||||
profileId: source.id,
|
||||
content: cookieJson,
|
||||
site: null,
|
||||
mode: "merge",
|
||||
includeExpired: false,
|
||||
});
|
||||
assert.equal(imported.cookies_imported, 1);
|
||||
assert.equal(imported.added, 1);
|
||||
assert.equal(imported.overwritten, 0);
|
||||
assert.equal(imported.deleted, 0);
|
||||
const cookies = await app.invoke("read_profile_cookies", {
|
||||
profileId: source.id,
|
||||
});
|
||||
@@ -803,6 +808,62 @@ test("cookie import/copy/export, profile encryption, and traffic-stat read/clear
|
||||
/fixture\.local/,
|
||||
);
|
||||
|
||||
const paste = [
|
||||
"# Netscape HTTP Cookie File",
|
||||
"#HttpOnly_.fixture.local\tTRUE\t/\tFALSE\t2000000000\tpasted\tpasted-value",
|
||||
].join("\n");
|
||||
const analysis = await app.invoke("analyze_pasted_cookies", {
|
||||
profileId: target.id,
|
||||
content: paste,
|
||||
site: null,
|
||||
});
|
||||
assert.equal(analysis.format, "netscape");
|
||||
assert.equal(analysis.cookies.length, 1);
|
||||
assert.equal(analysis.cookies[0].name, "pasted");
|
||||
assert.equal(analysis.cookies[0].isHttpOnly, true);
|
||||
assert.equal(
|
||||
analysis.cookies[0].value,
|
||||
undefined,
|
||||
"the preview must never carry the cookie value",
|
||||
);
|
||||
assert.equal(analysis.siteRequired, false);
|
||||
assert.equal(analysis.expiredCount, 0);
|
||||
assert.equal(analysis.blockedBy, null);
|
||||
// The copied fixture.local cookie is the one row replace mode would clear.
|
||||
assert.equal(analysis.replaceDeleteCount, 1);
|
||||
|
||||
const merged = await app.invoke("import_pasted_cookies", {
|
||||
profileId: target.id,
|
||||
content: paste,
|
||||
site: null,
|
||||
mode: "merge",
|
||||
includeExpired: false,
|
||||
});
|
||||
assert.equal(merged.added, 1);
|
||||
assert.equal(merged.deleted, 0);
|
||||
assert.equal(merged.skipped, 0);
|
||||
assert.equal(
|
||||
(await app.invoke("get_profile_cookie_stats", { profileId: target.id }))
|
||||
.total_count,
|
||||
2,
|
||||
);
|
||||
|
||||
// Both spellings of the pasted site go, and only they do.
|
||||
const replacedPaste = await app.invoke("import_pasted_cookies", {
|
||||
profileId: target.id,
|
||||
content: paste,
|
||||
site: null,
|
||||
mode: "replaceMatchingSites",
|
||||
includeExpired: false,
|
||||
});
|
||||
assert.equal(replacedPaste.deleted, 2);
|
||||
assert.equal(replacedPaste.added, 1);
|
||||
const afterReplace = await app.invoke("read_profile_cookies", {
|
||||
profileId: target.id,
|
||||
});
|
||||
assert.equal(afterReplace.total_count, 1);
|
||||
assert.equal(afterReplace.domains[0].cookies[0].name, "pasted");
|
||||
|
||||
await app.invoke("set_profile_password", {
|
||||
profileId: source.id,
|
||||
password: "correct horse battery staple",
|
||||
|
||||
@@ -148,6 +148,10 @@ windows = { version = "0.62", features = [
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_System_Registry",
|
||||
"Win32_UI_Shell",
|
||||
# SendMessageTimeoutW, for the association-change broadcast in
|
||||
# default_browser.rs. Going through the crate rather than a hand-written
|
||||
# `extern "system"` block is what keeps `lpdwResult` typed as DWORD_PTR.
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
# CryptUnprotectData, for unwrapping the source browser's os_crypt key from
|
||||
# `Local State` during profile import.
|
||||
"Win32_Security_Cryptography",
|
||||
|
||||
@@ -4253,7 +4253,7 @@ async fn import_profiles_api(
|
||||
(status = 400, description = "Invalid cookie file or unsupported browser"),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 404, description = "Profile not found"),
|
||||
(status = 409, description = "Browser is currently running"),
|
||||
(status = 409, description = "Browser is running, the profile is password-protected, or a remote session owns it"),
|
||||
(status = 500, description = "Internal server error")
|
||||
),
|
||||
security(
|
||||
@@ -4300,10 +4300,16 @@ async fn import_profile_cookies(
|
||||
}))
|
||||
}
|
||||
Err(e) => {
|
||||
let msg = e.to_lowercase();
|
||||
if msg.contains("running") {
|
||||
// The importer speaks in `{"code":…}` strings now; match those, and keep
|
||||
// the substring checks for the messages that are still plain text.
|
||||
if e.contains("COOKIE_IMPORT_BROWSER_RUNNING")
|
||||
|| e.contains("COOKIE_IMPORT_PROFILE_PROTECTED")
|
||||
|| e.contains("COOKIE_IMPORT_REMOTE_SESSION")
|
||||
{
|
||||
Err(StatusCode::CONFLICT)
|
||||
} else if msg.contains("no valid cookies") || msg.contains("unsupported browser") {
|
||||
} else if e.contains("COOKIE_IMPORT_NO_COOKIES")
|
||||
|| e.to_lowercase().contains("unsupported browser")
|
||||
{
|
||||
Err(StatusCode::BAD_REQUEST)
|
||||
} else {
|
||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
|
||||
+692
-356
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -358,38 +358,44 @@ mod windows {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tell the shell that the association it has cached is stale.
|
||||
///
|
||||
/// `SHChangeNotify` is the documented announcement for an association change,
|
||||
/// and the `WM_SETTINGCHANGE` broadcast is what the shell's own settings UI
|
||||
/// sends alongside it, so both go out.
|
||||
///
|
||||
/// This used to hand-declare `SendMessageTimeoutA` with `lpdwResult` typed as
|
||||
/// `*mut u32` and pass it a `u32`. The real parameter is `PDWORD_PTR`, eight
|
||||
/// bytes on x64, so every call wrote four bytes past a stack slot. The result
|
||||
/// was a corrupted stack at the exact moment a user set Donut as their default
|
||||
/// browser, and the process died with nothing in the log. Go through the
|
||||
/// `windows` crate instead, which types the out-parameter correctly and cannot
|
||||
/// drift from the real ABI.
|
||||
fn notify_system_of_changes() {
|
||||
// Use Windows API to notify the system of association changes
|
||||
// This helps refresh the system's understanding of the changes
|
||||
use windows::core::w;
|
||||
use windows::Win32::Foundation::{LPARAM, WPARAM};
|
||||
use windows::Win32::UI::Shell::{SHChangeNotify, SHCNE_ASSOCCHANGED, SHCNF_IDLIST};
|
||||
use windows::Win32::UI::WindowsAndMessaging::{
|
||||
SendMessageTimeoutW, HWND_BROADCAST, SMTO_ABORTIFHUNG, WM_SETTINGCHANGE,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
use std::ffi::c_void;
|
||||
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, None, None);
|
||||
|
||||
const HWND_BROADCAST: *mut c_void = 0xffff as *mut c_void;
|
||||
const WM_SETTINGCHANGE: u32 = 0x001A;
|
||||
const SMTO_ABORTIFHUNG: u32 = 0x0002;
|
||||
|
||||
extern "system" {
|
||||
fn SendMessageTimeoutA(
|
||||
hWnd: *mut c_void,
|
||||
Msg: u32,
|
||||
wParam: usize,
|
||||
lParam: isize,
|
||||
fuFlags: u32,
|
||||
uTimeout: u32,
|
||||
lpdwResult: *mut u32,
|
||||
) -> isize;
|
||||
}
|
||||
|
||||
let mut result: u32 = 0;
|
||||
|
||||
SendMessageTimeoutA(
|
||||
// The broadcast is best-effort: a hung top-level window elsewhere on the
|
||||
// desktop must not hold up the click that triggered this, hence the
|
||||
// timeout and SMTO_ABORTIFHUNG. `WM_SETTINGCHANGE`'s lParam string is
|
||||
// marshalled cross-process by the window manager, and this one is
|
||||
// 'static, so it stays valid for the whole call.
|
||||
let mut result: usize = 0;
|
||||
SendMessageTimeoutW(
|
||||
HWND_BROADCAST,
|
||||
WM_SETTINGCHANGE,
|
||||
0,
|
||||
c"Software\\Classes".as_ptr() as isize,
|
||||
WPARAM(0),
|
||||
LPARAM(w!("Software\\Classes").as_ptr() as isize),
|
||||
SMTO_ABORTIFHUNG,
|
||||
1000,
|
||||
&mut result,
|
||||
Some(&mut result),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+49
-19
@@ -105,6 +105,7 @@ mod cloud_errors;
|
||||
mod commercial_license;
|
||||
mod cookie_bot;
|
||||
mod cookie_manager;
|
||||
mod cookie_paste;
|
||||
pub mod events;
|
||||
mod mcp_integrations;
|
||||
mod mcp_server;
|
||||
@@ -476,29 +477,57 @@ async fn copy_profile_cookies(
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
/// Push a profile's freshly written cookies to the cloud, if it syncs at all.
|
||||
fn queue_profile_cookie_sync(profile_id: &str) {
|
||||
let Some(scheduler) = crate::sync::get_global_scheduler() else {
|
||||
return;
|
||||
};
|
||||
let Ok(profiles) = profile::manager::ProfileManager::instance().list_profiles() else {
|
||||
return;
|
||||
};
|
||||
let syncs = profiles
|
||||
.iter()
|
||||
.any(|p| p.id.to_string() == profile_id && p.is_sync_enabled());
|
||||
if !syncs {
|
||||
return;
|
||||
}
|
||||
let pid = profile_id.to_string();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
scheduler.queue_profile_sync(pid).await;
|
||||
});
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn import_cookies_from_file(
|
||||
async fn analyze_pasted_cookies(
|
||||
app_handle: tauri::AppHandle,
|
||||
profile_id: String,
|
||||
content: String,
|
||||
) -> Result<cookie_manager::CookieImportResult, String> {
|
||||
let result =
|
||||
cookie_manager::CookieManager::import_cookies(&app_handle, &profile_id, &content).await?;
|
||||
site: Option<String>,
|
||||
) -> Result<cookie_manager::CookiePasteAnalysis, String> {
|
||||
cookie_manager::CookieManager::analyze_paste(&app_handle, &profile_id, &content, site.as_deref())
|
||||
.await
|
||||
}
|
||||
|
||||
// Trigger sync for the profile if sync is enabled
|
||||
if let Some(scheduler) = crate::sync::get_global_scheduler() {
|
||||
let profile_manager = profile::manager::ProfileManager::instance();
|
||||
if let Ok(profiles) = profile_manager.list_profiles() {
|
||||
if let Some(profile) = profiles.iter().find(|p| p.id.to_string() == profile_id) {
|
||||
if profile.is_sync_enabled() {
|
||||
let pid = profile_id.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
scheduler.queue_profile_sync(pid).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[tauri::command]
|
||||
async fn import_pasted_cookies(
|
||||
app_handle: tauri::AppHandle,
|
||||
profile_id: String,
|
||||
content: String,
|
||||
site: Option<String>,
|
||||
mode: cookie_manager::CookieWriteMode,
|
||||
include_expired: bool,
|
||||
) -> Result<cookie_manager::CookiePasteImportResult, String> {
|
||||
let result = cookie_manager::CookieManager::import_paste(
|
||||
&app_handle,
|
||||
&profile_id,
|
||||
&content,
|
||||
site.as_deref(),
|
||||
mode,
|
||||
include_expired,
|
||||
)
|
||||
.await?;
|
||||
|
||||
queue_profile_cookie_sync(&profile_id);
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
@@ -2821,7 +2850,8 @@ pub fn run_with_builder(
|
||||
read_profile_cookies,
|
||||
get_profile_cookie_stats,
|
||||
copy_profile_cookies,
|
||||
import_cookies_from_file,
|
||||
analyze_pasted_cookies,
|
||||
import_pasted_cookies,
|
||||
export_profile_cookies,
|
||||
check_wayfern_terms_accepted,
|
||||
check_wayfern_downloaded,
|
||||
|
||||
@@ -5,9 +5,11 @@ import { save } from "@tauri-apps/plugin-dialog";
|
||||
import { writeTextFile } from "@tauri-apps/plugin-fs";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { LuChevronRight, LuUpload } from "react-icons/lu";
|
||||
import { LuChevronRight } from "react-icons/lu";
|
||||
import { toast } from "sonner";
|
||||
import { CookiePastePanel, IssueRow } from "@/components/cookie-paste-panel";
|
||||
import { LoadingButton } from "@/components/loading-button";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import {
|
||||
AnimatedDisclosureChevron,
|
||||
AnimatedDisclosureContent,
|
||||
@@ -31,19 +33,17 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { translateBackendError } from "@/lib/backend-errors";
|
||||
import type {
|
||||
BrowserProfile,
|
||||
CookieAnalysis,
|
||||
CookiePasteImportResult,
|
||||
CookieReadResult,
|
||||
CookieWriteMode,
|
||||
DomainCookies,
|
||||
UnifiedCookie,
|
||||
} from "@/types";
|
||||
|
||||
interface CookieImportResult {
|
||||
cookies_imported: number;
|
||||
cookies_replaced: number;
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
interface CookieManagementDialogProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
@@ -59,21 +59,8 @@ type SelectionState = Record<
|
||||
}
|
||||
>;
|
||||
|
||||
const countCookies = (content: string): number => {
|
||||
const trimmed = content.trim();
|
||||
if (trimmed.startsWith("[")) {
|
||||
try {
|
||||
const arr = JSON.parse(trimmed);
|
||||
if (Array.isArray(arr)) return arr.length;
|
||||
} catch {
|
||||
// Fall through to Netscape counting
|
||||
}
|
||||
}
|
||||
return content.split("\n").filter((line) => {
|
||||
const l = line.trim();
|
||||
return l && !l.startsWith("#");
|
||||
}).length;
|
||||
};
|
||||
/** Long enough that a paste is not re-parsed on every keystroke of a fix. */
|
||||
const ANALYZE_DEBOUNCE_MS = 250;
|
||||
|
||||
function formatJsonCookies(cookies: UnifiedCookie[]): string {
|
||||
const arr = cookies.map((c) => {
|
||||
@@ -130,13 +117,16 @@ export function CookieManagementDialog({
|
||||
}: CookieManagementDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
// Import state
|
||||
const [fileContent, setFileContent] = useState<string | null>(null);
|
||||
const [fileName, setFileName] = useState<string | null>(null);
|
||||
const [cookieCount, setCookieCount] = useState(0);
|
||||
const [pasteContent, setPasteContent] = useState("");
|
||||
const [pasteSite, setPasteSite] = useState("");
|
||||
const [writeMode, setWriteMode] = useState<CookieWriteMode>("merge");
|
||||
const [includeExpired, setIncludeExpired] = useState(false);
|
||||
const [analysis, setAnalysis] = useState<CookieAnalysis | null>(null);
|
||||
const [isAnalyzing, setIsAnalyzing] = useState(false);
|
||||
const [importError, setImportError] = useState<string | null>(null);
|
||||
const [isImporting, setIsImporting] = useState(false);
|
||||
const [importResult, setImportResult] = useState<CookieImportResult | null>(
|
||||
null,
|
||||
);
|
||||
const [importResult, setImportResult] =
|
||||
useState<CookiePasteImportResult | null>(null);
|
||||
|
||||
// Export state
|
||||
const [format, setFormat] = useState<"netscape" | "json">("json");
|
||||
@@ -179,7 +169,7 @@ export function CookieManagementDialog({
|
||||
} catch (err) {
|
||||
toast.error(
|
||||
t("cookies.management.loadFailed", {
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
error: translateBackendError(t, err),
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
@@ -196,9 +186,13 @@ export function CookieManagementDialog({
|
||||
}, [activeTab, profile, exportCookieData, loadExportCookies]);
|
||||
|
||||
const resetImportState = useCallback(() => {
|
||||
setFileContent(null);
|
||||
setFileName(null);
|
||||
setCookieCount(0);
|
||||
setPasteContent("");
|
||||
setPasteSite("");
|
||||
setWriteMode("merge");
|
||||
setIncludeExpired(false);
|
||||
setAnalysis(null);
|
||||
setIsAnalyzing(false);
|
||||
setImportError(null);
|
||||
setIsImporting(false);
|
||||
setImportResult(null);
|
||||
}, []);
|
||||
@@ -229,41 +223,82 @@ export function CookieManagementDialog({
|
||||
[resetImportState, resetExportState],
|
||||
);
|
||||
|
||||
const handleFileRead = useCallback(
|
||||
(file: File) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const content = e.target?.result as string;
|
||||
setFileContent(content);
|
||||
setFileName(file.name);
|
||||
setCookieCount(countCookies(content));
|
||||
};
|
||||
reader.onerror = () => {
|
||||
toast.error(t("cookies.management.fileReadError"));
|
||||
};
|
||||
reader.readAsText(file);
|
||||
},
|
||||
[t],
|
||||
);
|
||||
const profileId = profile?.id;
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen || !profileId || importResult) return;
|
||||
if (pasteContent.trim() === "") {
|
||||
setAnalysis(null);
|
||||
setIsAnalyzing(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Editing the paste is the user acting on the last failure, so retire it.
|
||||
setImportError(null);
|
||||
setIsAnalyzing(true);
|
||||
let cancelled = false;
|
||||
const timer = setTimeout(() => {
|
||||
void invoke<CookieAnalysis>("analyze_pasted_cookies", {
|
||||
profileId,
|
||||
content: pasteContent,
|
||||
site: pasteSite.trim() === "" ? null : pasteSite,
|
||||
})
|
||||
.then((result) => {
|
||||
if (!cancelled) setAnalysis(result);
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
if (cancelled) return;
|
||||
setAnalysis(null);
|
||||
setImportError(translateBackendError(t, error));
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setIsAnalyzing(false);
|
||||
});
|
||||
}, ANALYZE_DEBOUNCE_MS);
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, [isOpen, profileId, pasteContent, pasteSite, importResult, t]);
|
||||
|
||||
const handleImport = useCallback(async () => {
|
||||
if (!fileContent || !profile) return;
|
||||
if (!profileId) return;
|
||||
setIsImporting(true);
|
||||
setImportError(null);
|
||||
try {
|
||||
const result = await invoke<CookieImportResult>(
|
||||
"import_cookies_from_file",
|
||||
const result = await invoke<CookiePasteImportResult>(
|
||||
"import_pasted_cookies",
|
||||
{
|
||||
profileId: profile.id,
|
||||
content: fileContent,
|
||||
profileId,
|
||||
content: pasteContent,
|
||||
site: pasteSite.trim() === "" ? null : pasteSite,
|
||||
mode: writeMode,
|
||||
includeExpired,
|
||||
},
|
||||
);
|
||||
setImportResult(result);
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : String(error));
|
||||
// Kept inside the dialog rather than toasted: a toast would take the
|
||||
// failure away while leaving the user with a paste they cannot fix.
|
||||
setImportError(translateBackendError(t, error));
|
||||
} finally {
|
||||
setIsImporting(false);
|
||||
}
|
||||
}, [fileContent, profile]);
|
||||
}, [profileId, pasteContent, pasteSite, writeMode, includeExpired, t]);
|
||||
|
||||
const importBlockedReason = useMemo(() => {
|
||||
if (pasteContent.trim() === "") return t("cookies.paste.disabledEmpty");
|
||||
if (isAnalyzing || !analysis) return null;
|
||||
if (analysis.blockedBy) {
|
||||
return translateBackendError(t, analysis.blockedBy);
|
||||
}
|
||||
if (analysis.siteRequired) return t("cookies.paste.disabledSite");
|
||||
if (analysis.cookies.length === 0) {
|
||||
return t("cookies.paste.disabledNoCookies");
|
||||
}
|
||||
return null;
|
||||
}, [pasteContent, isAnalyzing, analysis, t]);
|
||||
|
||||
const getSelectedCookies = useCallback((): UnifiedCookie[] => {
|
||||
if (!exportCookieData) return [];
|
||||
@@ -312,7 +347,7 @@ export function CookieManagementDialog({
|
||||
toast.success(t("cookies.export.success"));
|
||||
handleClose();
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : String(error));
|
||||
toast.error(translateBackendError(t, error));
|
||||
} finally {
|
||||
setIsExporting(false);
|
||||
}
|
||||
@@ -415,92 +450,102 @@ export function CookieManagementDialog({
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="import" className="mt-4 space-y-4">
|
||||
{!fileContent && (
|
||||
<div className="space-y-4">
|
||||
{!importResult && (
|
||||
<>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("cookies.management.importDescription")}
|
||||
</p>
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed border-muted-foreground/25 p-8 transition-colors hover:border-muted-foreground/50"
|
||||
onClick={() =>
|
||||
document.getElementById("cookie-file-input")?.click()
|
||||
}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
document.getElementById("cookie-file-input")?.click();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LuUpload className="mb-4 size-10 text-muted-foreground" />
|
||||
<p className="text-center text-sm text-muted-foreground">
|
||||
{t("cookies.management.dropPrompt")}
|
||||
<br />
|
||||
<span className="text-xs">
|
||||
{t("cookies.management.fileFormats")}
|
||||
</span>
|
||||
</p>
|
||||
<input
|
||||
id="cookie-file-input"
|
||||
type="file"
|
||||
accept=".txt,.cookies,.json"
|
||||
className="hidden"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) handleFileRead(file);
|
||||
e.target.value = "";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{fileContent && !importResult && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3 rounded-lg bg-muted/30 p-4">
|
||||
<div>
|
||||
<div className="font-medium">{fileName}</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{t("cookies.management.cookiesFound", {
|
||||
count: cookieCount,
|
||||
})}
|
||||
</div>
|
||||
<CookiePastePanel
|
||||
content={pasteContent}
|
||||
onContentChange={setPasteContent}
|
||||
site={pasteSite}
|
||||
onSiteChange={setPasteSite}
|
||||
mode={writeMode}
|
||||
onModeChange={setWriteMode}
|
||||
includeExpired={includeExpired}
|
||||
onIncludeExpiredChange={setIncludeExpired}
|
||||
analysis={analysis}
|
||||
isAnalyzing={isAnalyzing}
|
||||
disabled={isImporting}
|
||||
/>
|
||||
|
||||
<div className="space-y-2">
|
||||
{/* Sits with the button, not at the top of the dialog: the
|
||||
panel is taller than the viewport, so a failure announced
|
||||
above the description is a failure nobody sees. */}
|
||||
{importError && (
|
||||
<Alert variant="destructive">
|
||||
<AlertDescription>{importError}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
<div className="flex justify-end gap-2">
|
||||
<RippleButton variant="outline" onClick={handleClose}>
|
||||
{t("common.buttons.cancel")}
|
||||
</RippleButton>
|
||||
<LoadingButton
|
||||
isLoading={isImporting}
|
||||
variant={
|
||||
writeMode === "replaceMatchingSites"
|
||||
? "destructive"
|
||||
: "default"
|
||||
}
|
||||
onClick={() => void handleImport()}
|
||||
disabled={
|
||||
isAnalyzing ||
|
||||
analysis === null ||
|
||||
importBlockedReason !== null
|
||||
}
|
||||
>
|
||||
{t("common.buttons.import")}
|
||||
</LoadingButton>
|
||||
</div>
|
||||
{importBlockedReason && (
|
||||
<p className="text-right text-xs text-muted-foreground">
|
||||
{importBlockedReason}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<RippleButton variant="outline" onClick={resetImportState}>
|
||||
{t("cookies.management.backButton")}
|
||||
</RippleButton>
|
||||
<LoadingButton
|
||||
isLoading={isImporting}
|
||||
onClick={() => void handleImport()}
|
||||
disabled={cookieCount === 0}
|
||||
>
|
||||
{t("cookies.management.importButton")}
|
||||
</LoadingButton>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{importResult && (
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-lg bg-success/10 p-4">
|
||||
<div className="font-medium text-success-text">
|
||||
{t("cookies.management.importedSuccess", {
|
||||
imported: importResult.cookies_imported,
|
||||
replaced: importResult.cookies_replaced,
|
||||
})}
|
||||
</div>
|
||||
{importResult.errors.length > 0 && (
|
||||
<div className="mt-2 text-sm text-muted-foreground">
|
||||
{t("cookies.management.linesSkipped", {
|
||||
count: importResult.errors.length,
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<div className="grid grid-cols-2 gap-x-4 gap-y-2 rounded-lg bg-muted/30 p-4 sm:grid-cols-4">
|
||||
<ResultCounter
|
||||
label={t("cookies.paste.resultAdded")}
|
||||
value={importResult.added}
|
||||
/>
|
||||
<ResultCounter
|
||||
label={t("cookies.paste.resultOverwritten")}
|
||||
value={importResult.overwritten}
|
||||
/>
|
||||
<ResultCounter
|
||||
label={t("cookies.paste.resultDeleted")}
|
||||
value={importResult.deleted}
|
||||
/>
|
||||
<ResultCounter
|
||||
label={t("cookies.paste.resultSkipped")}
|
||||
value={importResult.skipped}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{importResult.issues.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<Label>{t("cookies.paste.issuesTitle")}</Label>
|
||||
<FadingScrollArea className="max-h-[clamp(100px,24vh,300px)]">
|
||||
<div className="space-y-1 pr-3">
|
||||
{importResult.issues.map((issue, index) => (
|
||||
<IssueRow
|
||||
key={`${issue.code}-${issue.source ?? ""}-${index}`}
|
||||
issue={issue}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</FadingScrollArea>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end">
|
||||
<RippleButton onClick={handleClose}>
|
||||
{t("cookies.management.doneButton")}
|
||||
@@ -605,6 +650,16 @@ export function CookieManagementDialog({
|
||||
);
|
||||
}
|
||||
|
||||
/** Zeros are shown too: "deleted 0" is the reassurance replace mode needs. */
|
||||
function ResultCounter({ label, value }: { label: string; value: number }) {
|
||||
return (
|
||||
<div className="space-y-0.5">
|
||||
<div className="text-xs text-muted-foreground">{label}</div>
|
||||
<div className="text-lg font-medium tabular-nums">{value}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface ExportDomainRowProps {
|
||||
domain: DomainCookies;
|
||||
selection: SelectionState;
|
||||
|
||||
@@ -0,0 +1,512 @@
|
||||
"use client";
|
||||
|
||||
import type { TFunction } from "i18next";
|
||||
import { useCallback, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { LuTriangleAlert } from "react-icons/lu";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import type {
|
||||
CookieAnalysis,
|
||||
CookieIssue,
|
||||
CookiePasteFormat,
|
||||
CookieWriteMode,
|
||||
PastedCookiePreview,
|
||||
} from "@/types";
|
||||
|
||||
/** Issue codes `cookie_paste.rs` can emit, mapped to their translation key. */
|
||||
const ISSUE_KEYS: Record<string, string> = {
|
||||
EMPTY_INPUT: "emptyInput",
|
||||
SITE_INVALID: "siteInvalid",
|
||||
UNRECOGNIZED_FORMAT: "unrecognizedFormat",
|
||||
SITE_REQUIRED: "siteRequired",
|
||||
NO_COOKIES_FOUND: "noCookiesFound",
|
||||
NAME_EMPTY: "nameEmpty",
|
||||
NAME_INVALID: "nameInvalid",
|
||||
NAME_MISSING: "nameMissing",
|
||||
VALUE_INVALID: "valueInvalid",
|
||||
VALUE_COERCED: "valueCoerced",
|
||||
DOMAIN_FROM_SITE: "domainFromSite",
|
||||
DOMAIN_MISSING: "domainMissing",
|
||||
DOMAIN_INVALID: "domainInvalid",
|
||||
DOMAIN_ATTRIBUTE_IGNORED: "domainAttributeIgnored",
|
||||
HOST_ONLY_MISMATCH: "hostOnlyMismatch",
|
||||
PATH_REPAIRED: "pathRepaired",
|
||||
EXPIRY_MILLISECONDS: "expiryMilliseconds",
|
||||
EXPIRY_CLAMPED: "expiryClamped",
|
||||
EXPIRY_INVALID: "expiryInvalid",
|
||||
EXPIRES_INVALID: "expiresInvalid",
|
||||
MAX_AGE_INVALID: "maxAgeInvalid",
|
||||
MAX_AGE_DELETION: "maxAgeDeletion",
|
||||
SAME_SITE_NONE_INSECURE: "sameSiteNoneInsecure",
|
||||
SAME_SITE_UNRECOGNIZED: "sameSiteUnrecognized",
|
||||
DUPLICATE_COOKIE: "duplicateCookie",
|
||||
BOOL_COERCED_FROM_STRING: "boolCoercedFromString",
|
||||
BOOL_INVALID: "boolInvalid",
|
||||
QUOTED_VALUE: "quotedValue",
|
||||
JSON_PARSE_FAILED: "jsonParseFailed",
|
||||
JSON_NOT_COOKIE_LIST: "jsonNotCookieList",
|
||||
JSON_ENTRY_NOT_OBJECT: "jsonEntryNotObject",
|
||||
NETSCAPE_PATH_OMITTED: "netscapePathOmitted",
|
||||
NETSCAPE_FIELD_COUNT: "netscapeFieldCount",
|
||||
NETSCAPE_INCLUDE_SUBDOMAINS_INVALID: "netscapeIncludeSubdomainsInvalid",
|
||||
NETSCAPE_SECURE_INVALID: "netscapeSecureInvalid",
|
||||
NETSCAPE_EXPIRY_INVALID: "netscapeExpiryInvalid",
|
||||
NAME_VALUE_NO_PAIR: "nameValueNoPair",
|
||||
PAIR_TREATED_AS_ATTRIBUTE: "pairTreatedAsAttribute",
|
||||
};
|
||||
|
||||
const FORMAT_KEYS: Record<CookiePasteFormat, string> = {
|
||||
json: "cookies.paste.formatJson",
|
||||
netscape: "cookies.paste.formatNetscape",
|
||||
nameValue: "cookies.paste.formatNameValue",
|
||||
};
|
||||
|
||||
const VISIBLE_ISSUES = 5;
|
||||
|
||||
const RELATIVE_UNITS: [Intl.RelativeTimeFormatUnit, number][] = [
|
||||
["year", 31_536_000],
|
||||
["month", 2_592_000],
|
||||
["day", 86_400],
|
||||
["hour", 3600],
|
||||
["minute", 60],
|
||||
];
|
||||
|
||||
export interface CookiePastePanelProps {
|
||||
content: string;
|
||||
onContentChange: (content: string) => void;
|
||||
site: string;
|
||||
onSiteChange: (site: string) => void;
|
||||
mode: CookieWriteMode;
|
||||
onModeChange: (mode: CookieWriteMode) => void;
|
||||
includeExpired: boolean;
|
||||
onIncludeExpiredChange: (includeExpired: boolean) => void;
|
||||
analysis: CookieAnalysis | null;
|
||||
isAnalyzing: boolean;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
export function CookiePastePanel({
|
||||
content,
|
||||
onContentChange,
|
||||
site,
|
||||
onSiteChange,
|
||||
mode,
|
||||
onModeChange,
|
||||
includeExpired,
|
||||
onIncludeExpiredChange,
|
||||
analysis,
|
||||
isAnalyzing,
|
||||
disabled,
|
||||
}: CookiePastePanelProps) {
|
||||
const { t, i18n } = useTranslation();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [showAllIssues, setShowAllIssues] = useState(false);
|
||||
const [fileError, setFileError] = useState<string | null>(null);
|
||||
|
||||
const relativeFormatter = useMemo(
|
||||
() => new Intl.RelativeTimeFormat(i18n.language, { numeric: "auto" }),
|
||||
[i18n.language],
|
||||
);
|
||||
|
||||
const readFile = useCallback(
|
||||
(file: File) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
setFileError(null);
|
||||
onContentChange(String(event.target?.result ?? ""));
|
||||
};
|
||||
reader.onerror = () => {
|
||||
setFileError(t("cookies.management.fileReadError"));
|
||||
};
|
||||
reader.readAsText(file);
|
||||
},
|
||||
[onContentChange, t],
|
||||
);
|
||||
|
||||
const handleDrop = useCallback(
|
||||
(event: React.DragEvent<HTMLTextAreaElement>) => {
|
||||
const file = event.dataTransfer.files[0];
|
||||
if (!file) return;
|
||||
event.preventDefault();
|
||||
readFile(file);
|
||||
},
|
||||
[readFile],
|
||||
);
|
||||
|
||||
// A JSON or Netscape entry with no domain of its own needs the site as much
|
||||
// as a bare pair does: without the field, "carries no domain and no site was
|
||||
// given" is a dead end with no control anywhere that answers it. The last
|
||||
// clause keeps the field once anything has been typed into it, because every
|
||||
// other condition stops being true the moment the site is accepted, which
|
||||
// would yank the input out from under the cursor.
|
||||
const siteVisible =
|
||||
analysis !== null &&
|
||||
(analysis.siteRequired ||
|
||||
analysis.format === "nameValue" ||
|
||||
analysis.issues.some((issue) => issue.code === "DOMAIN_MISSING") ||
|
||||
site.trim() !== "");
|
||||
|
||||
const scopeDomains = useMemo(() => {
|
||||
if (!analysis) return [];
|
||||
return [...new Set(analysis.cookies.map((cookie) => cookie.domain))];
|
||||
}, [analysis]);
|
||||
|
||||
const issues = analysis?.issues ?? [];
|
||||
const shownIssues = showAllIssues ? issues : issues.slice(0, VISIBLE_ISSUES);
|
||||
|
||||
const formatExpiry = useCallback(
|
||||
(expires: number) => {
|
||||
if (expires === 0) return t("cookies.paste.session");
|
||||
const delta = expires - Math.floor(Date.now() / 1000);
|
||||
for (const [unit, seconds] of RELATIVE_UNITS) {
|
||||
if (Math.abs(delta) >= seconds) {
|
||||
return relativeFormatter.format(Math.trunc(delta / seconds), unit);
|
||||
}
|
||||
}
|
||||
return relativeFormatter.format(delta, "second");
|
||||
},
|
||||
[relativeFormatter, t],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cookie-paste-input">{t("cookies.paste.label")}</Label>
|
||||
<Textarea
|
||||
id="cookie-paste-input"
|
||||
rows={10}
|
||||
spellCheck={false}
|
||||
disabled={disabled}
|
||||
value={content}
|
||||
placeholder={t("cookies.paste.placeholder")}
|
||||
className="resize-y font-mono text-xs"
|
||||
onChange={(event) => {
|
||||
setFileError(null);
|
||||
onContentChange(event.target.value);
|
||||
}}
|
||||
onDrop={handleDrop}
|
||||
onDragOver={(event) => {
|
||||
if (event.dataTransfer.types.includes("Files")) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
className="text-xs text-muted-foreground underline-offset-2 transition-colors hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
>
|
||||
{t("cookies.paste.chooseFile")}
|
||||
</button>
|
||||
{isAnalyzing ? (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t("cookies.paste.analyzing")}
|
||||
</span>
|
||||
) : (
|
||||
analysis !== null &&
|
||||
content.trim() !== "" && (
|
||||
<Badge
|
||||
variant={analysis.format ? "secondary" : "destructive"}
|
||||
className="font-normal"
|
||||
>
|
||||
{analysis.format
|
||||
? t(FORMAT_KEYS[analysis.format])
|
||||
: t("cookies.paste.formatUnknown")}
|
||||
</Badge>
|
||||
)
|
||||
)}
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept=".txt,.cookies,.json"
|
||||
className="hidden"
|
||||
onChange={(event) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (file) readFile(file);
|
||||
event.target.value = "";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{fileError && (
|
||||
<p className="text-xs text-destructive-text">{fileError}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{siteVisible && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cookie-paste-site">
|
||||
{t("cookies.paste.siteLabel")}
|
||||
</Label>
|
||||
<Input
|
||||
id="cookie-paste-site"
|
||||
disabled={disabled}
|
||||
value={site}
|
||||
placeholder={t("cookies.paste.sitePlaceholder")}
|
||||
onChange={(event) => {
|
||||
onSiteChange(event.target.value);
|
||||
}}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("cookies.paste.siteHelp")}
|
||||
</p>
|
||||
{scopeDomains.map((domain) => (
|
||||
<p key={domain} className="text-xs text-foreground">
|
||||
{domain.startsWith(".")
|
||||
? t("cookies.paste.scopeSubdomains", { domain })
|
||||
: t("cookies.paste.scopeHostOnly", { domain })}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>{t("common.labels.mode")}</Label>
|
||||
<RadioGroup
|
||||
value={mode}
|
||||
disabled={disabled}
|
||||
onValueChange={(value) => {
|
||||
onModeChange(value as CookieWriteMode);
|
||||
}}
|
||||
>
|
||||
<label
|
||||
htmlFor="cookie-mode-merge"
|
||||
className="flex cursor-pointer items-start gap-2"
|
||||
>
|
||||
<RadioGroupItem
|
||||
id="cookie-mode-merge"
|
||||
value="merge"
|
||||
className="mt-0.5"
|
||||
/>
|
||||
<span className="space-y-0.5">
|
||||
<span className="block text-sm font-medium">
|
||||
{t("cookies.paste.modeMerge")}
|
||||
</span>
|
||||
<span className="block text-xs text-muted-foreground">
|
||||
{t("cookies.paste.modeMergeDesc")}
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
htmlFor="cookie-mode-replace"
|
||||
className="flex cursor-pointer items-start gap-2"
|
||||
>
|
||||
<RadioGroupItem
|
||||
id="cookie-mode-replace"
|
||||
value="replaceMatchingSites"
|
||||
className="mt-0.5"
|
||||
/>
|
||||
<span className="space-y-0.5">
|
||||
<span className="block text-sm font-medium">
|
||||
{t("cookies.paste.modeReplace")}
|
||||
</span>
|
||||
<span className="block text-xs text-muted-foreground">
|
||||
{t("cookies.paste.modeReplaceDesc")}
|
||||
</span>
|
||||
{analysis && (
|
||||
<span className="block text-xs text-warning-text">
|
||||
{t("cookies.paste.replaceDeleteCount", {
|
||||
n:
|
||||
analysis.replaceDeleteCount === null
|
||||
? t("cookies.paste.unknownCount")
|
||||
: String(analysis.replaceDeleteCount),
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</label>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
{analysis !== null && analysis.expiredCount > 0 && (
|
||||
<label
|
||||
htmlFor="cookie-include-expired"
|
||||
className="flex cursor-pointer items-start gap-2"
|
||||
>
|
||||
<Checkbox
|
||||
id="cookie-include-expired"
|
||||
disabled={disabled}
|
||||
checked={includeExpired}
|
||||
onCheckedChange={(checked) => {
|
||||
onIncludeExpiredChange(checked === true);
|
||||
}}
|
||||
className="mt-0.5"
|
||||
/>
|
||||
<span className="space-y-0.5">
|
||||
<span className="block text-sm">
|
||||
{t("cookies.paste.includeExpired")}
|
||||
</span>
|
||||
<span className="block text-xs text-muted-foreground">
|
||||
{t("cookies.paste.expiredNote", {
|
||||
n: analysis.expiredCount,
|
||||
})}
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
|
||||
{analysis?.clearsOnClose && (
|
||||
<Alert>
|
||||
<LuTriangleAlert className="text-warning-text" />
|
||||
<AlertDescription>
|
||||
{t("cookies.paste.clearsOnCloseWarning")}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{analysis !== null && analysis.cookies.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<Label>
|
||||
{t("cookies.paste.previewTitle", { n: analysis.cookies.length })}
|
||||
</Label>
|
||||
<Table
|
||||
containerClassName="max-h-[clamp(120px,28vh,340px)] overflow-y-auto rounded-md border"
|
||||
className="text-xs"
|
||||
>
|
||||
<TableHeader className="sticky top-0 bg-background">
|
||||
<TableRow>
|
||||
<TableHead>{t("cookies.paste.colSite")}</TableHead>
|
||||
<TableHead>{t("cookies.paste.colName")}</TableHead>
|
||||
<TableHead>{t("cookies.paste.colPath")}</TableHead>
|
||||
<TableHead>{t("cookies.paste.colExpires")}</TableHead>
|
||||
<TableHead>{t("cookies.paste.colSecure")}</TableHead>
|
||||
<TableHead>{t("cookies.paste.colHttpOnly")}</TableHead>
|
||||
<TableHead>{t("cookies.paste.colSameSite")}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{analysis.cookies.map((cookie) => (
|
||||
<CookiePreviewRow
|
||||
key={`${cookie.domain}|${cookie.path}|${cookie.name}`}
|
||||
cookie={cookie}
|
||||
expiresLabel={formatExpiry(cookie.expires)}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{issues.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<Label>{t("cookies.paste.issuesTitle")}</Label>
|
||||
<div className="space-y-1">
|
||||
{shownIssues.map((issue, index) => (
|
||||
<IssueRow
|
||||
key={`${issue.code}-${issue.source ?? ""}-${index}`}
|
||||
issue={issue}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{issues.length > VISIBLE_ISSUES && (
|
||||
<button
|
||||
type="button"
|
||||
className="text-xs text-muted-foreground transition-colors hover:text-foreground"
|
||||
onClick={() => {
|
||||
setShowAllIssues((previous) => !previous);
|
||||
}}
|
||||
>
|
||||
{showAllIssues
|
||||
? t("cookies.paste.showFewer")
|
||||
: t("cookies.paste.showAll", { n: issues.length })}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CookiePreviewRow({
|
||||
cookie,
|
||||
expiresLabel,
|
||||
}: {
|
||||
cookie: PastedCookiePreview;
|
||||
expiresLabel: string;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const sameSite =
|
||||
cookie.sameSite === 2
|
||||
? t("cookies.paste.sameSiteStrict")
|
||||
: cookie.sameSite === 1
|
||||
? t("cookies.paste.sameSiteLax")
|
||||
: cookie.sameSite === 0
|
||||
? t("cookies.paste.sameSiteNone")
|
||||
: t("cookies.paste.sameSiteUnspecified");
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell className="font-mono whitespace-nowrap">
|
||||
{cookie.domain}
|
||||
</TableCell>
|
||||
<TableCell className="font-mono whitespace-nowrap">
|
||||
{cookie.name}
|
||||
</TableCell>
|
||||
<TableCell className="font-mono whitespace-nowrap">
|
||||
{cookie.path}
|
||||
</TableCell>
|
||||
<TableCell className="whitespace-nowrap">{expiresLabel}</TableCell>
|
||||
<TableCell className="whitespace-nowrap">
|
||||
{cookie.isSecure ? t("cookies.paste.yes") : t("cookies.paste.no")}
|
||||
</TableCell>
|
||||
<TableCell className="whitespace-nowrap">
|
||||
{cookie.isHttpOnly ? t("cookies.paste.yes") : t("cookies.paste.no")}
|
||||
</TableCell>
|
||||
<TableCell className="whitespace-nowrap">{sameSite}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
export function IssueRow({ issue }: { issue: CookieIssue }) {
|
||||
const { t } = useTranslation();
|
||||
const key = ISSUE_KEYS[issue.code];
|
||||
const message = key
|
||||
? t(`cookies.paste.issues.${key}`, issue.params)
|
||||
: t("cookies.paste.issues.unknown", { code: issue.code });
|
||||
|
||||
const tone =
|
||||
issue.severity === "error"
|
||||
? "text-destructive-text"
|
||||
: issue.severity === "warning"
|
||||
? "text-warning-text"
|
||||
: "text-muted-foreground";
|
||||
|
||||
return (
|
||||
<p className={`text-xs ${tone}`}>
|
||||
{issue.source && (
|
||||
<span className="text-muted-foreground">
|
||||
{formatIssueSource(t, issue.source)}
|
||||
{": "}
|
||||
</span>
|
||||
)}
|
||||
{message}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* `source` arrives as `line 4` / `cookie 12`, built in Rust where there is no
|
||||
* translator. Recognise those two shapes so the prefix is localised too.
|
||||
*/
|
||||
function formatIssueSource(t: TFunction, source: string): string {
|
||||
const match = /^(line|cookie) (\d+)$/.exec(source);
|
||||
if (!match) return source;
|
||||
return match[1] === "line"
|
||||
? t("cookies.paste.sourceLine", { n: match[2] })
|
||||
: t("cookies.paste.sourceCookie", { n: match[2] });
|
||||
}
|
||||
+34
-82
@@ -399,10 +399,6 @@
|
||||
"name": "aes-gcm",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "ahash",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "aho-corasick",
|
||||
"license": "Unlicense OR MIT"
|
||||
@@ -467,10 +463,6 @@
|
||||
"name": "aria-hidden",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "arrayref",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
{
|
||||
"name": "arrayvec",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
@@ -587,10 +579,6 @@
|
||||
"name": "bitstream-io",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "bitvec",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "blake2",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
@@ -619,14 +607,6 @@
|
||||
"name": "boringtun",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
{
|
||||
"name": "borsh",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "borsh-derive",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
{
|
||||
"name": "brotli",
|
||||
"license": "BSD-3-Clause AND MIT"
|
||||
@@ -647,26 +627,18 @@
|
||||
"name": "bumpalo",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "byte-unit",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "byte_string",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "bytecheck",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "bytecheck_derive",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "bytemuck",
|
||||
"license": "Zlib OR Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "bytemuck_derive",
|
||||
"license": "Zlib OR Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "byteorder",
|
||||
"license": "Unlicense OR MIT"
|
||||
@@ -1275,10 +1247,6 @@
|
||||
"name": "framer-motion",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "funty",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "futures",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
@@ -1647,6 +1615,18 @@
|
||||
"name": "jiff",
|
||||
"license": "Unlicense OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "jiff-core",
|
||||
"license": "Unlicense OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "jiff-tzdb",
|
||||
"license": "Unlicense OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "jiff-tzdb-platform",
|
||||
"license": "Unlicense OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "js-cookie",
|
||||
"license": "MIT"
|
||||
@@ -1695,6 +1675,10 @@
|
||||
"name": "libloading",
|
||||
"license": "ISC"
|
||||
},
|
||||
{
|
||||
"name": "libm",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "libsqlite3-sys",
|
||||
"license": "MIT"
|
||||
@@ -2164,11 +2148,11 @@
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "ptr_meta",
|
||||
"name": "pulp",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "ptr_meta_derive",
|
||||
"name": "pulp-wasm-simd-flag",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
@@ -2191,10 +2175,6 @@
|
||||
"name": "quote",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "radium",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "radix-ui",
|
||||
"license": "MIT"
|
||||
@@ -2219,6 +2199,10 @@
|
||||
"name": "ravif",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
{
|
||||
"name": "raw-cpuid",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "raw-window-handle",
|
||||
"license": "MIT OR Apache-2.0 OR Zlib"
|
||||
@@ -2271,6 +2255,10 @@
|
||||
"name": "react-style-singleton",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "reborrow",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "recharts",
|
||||
"license": "MIT"
|
||||
@@ -2307,10 +2295,6 @@
|
||||
"name": "regex-syntax",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "rend",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "reqwest",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
@@ -2339,14 +2323,6 @@
|
||||
"name": "ring-compat",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "rkyv",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "rkyv_derive",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "rusqlite",
|
||||
"license": "MIT"
|
||||
@@ -2355,10 +2331,6 @@
|
||||
"name": "rust-ini",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "rust_decimal",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "rustc-hash",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
@@ -2411,10 +2383,6 @@
|
||||
"name": "screenfull",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "seahash",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "sealed",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
@@ -2559,10 +2527,6 @@
|
||||
"name": "simd_helpers",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "simdutf8",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "siphasher",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
@@ -2671,10 +2635,6 @@
|
||||
"name": "tao",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
{
|
||||
"name": "tap",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "tar",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
@@ -2987,10 +2947,6 @@
|
||||
"name": "use-sync-external-store",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "utf8-width",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "utf8_iter",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
@@ -3019,10 +2975,6 @@
|
||||
"name": "v_frame",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
{
|
||||
"name": "value-bag",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "victory-vendor",
|
||||
"license": "MIT AND ISC"
|
||||
@@ -3203,10 +3155,6 @@
|
||||
"name": "wry",
|
||||
"license": "Apache-2.0 OR MIT"
|
||||
},
|
||||
{
|
||||
"name": "wyz",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "x11",
|
||||
"license": "MIT"
|
||||
@@ -3259,6 +3207,10 @@
|
||||
"name": "zbus_names",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "zcheapstr",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "zerocopy",
|
||||
"license": "BSD-2-Clause OR Apache-2.0 OR MIT"
|
||||
|
||||
@@ -895,12 +895,7 @@
|
||||
"menuItem": "Cookie Management",
|
||||
"tabImport": "Import",
|
||||
"tabExport": "Export",
|
||||
"importDescription": "Import cookies from a Netscape or JSON format file.",
|
||||
"dropPrompt": "Click to choose a cookie file",
|
||||
"fileFormats": "(.txt, .cookies, or .json)",
|
||||
"cookiesFound": "{{count}} cookies found",
|
||||
"importedSuccess": "Successfully imported {{imported}} cookies ({{replaced}} replaced)",
|
||||
"linesSkipped": "{{count}} line(s) skipped",
|
||||
"importDescription": "Paste cookies copied from another browser or tool, or choose a file.",
|
||||
"fileReadError": "Failed to read file",
|
||||
"loadFailed": "Failed to load cookies: {{error}}",
|
||||
"cookiesLabel": "Cookies",
|
||||
@@ -909,9 +904,7 @@
|
||||
"deselectAll": "Deselect all",
|
||||
"noCookies": "No cookies found in this profile",
|
||||
"doneButton": "Done",
|
||||
"importButton": "Import",
|
||||
"exportButton": "Export",
|
||||
"backButton": "Back"
|
||||
"exportButton": "Export"
|
||||
},
|
||||
"import": {
|
||||
"title": "Import Cookies",
|
||||
@@ -930,6 +923,98 @@
|
||||
"json": "JSON",
|
||||
"success": "Cookies exported successfully",
|
||||
"error": "Failed to export cookies"
|
||||
},
|
||||
"paste": {
|
||||
"label": "Cookies",
|
||||
"placeholder": "Paste cookies here. JSON (an array or a {cookies: [...]} object), a Netscape cookies.txt, or name=value; name2=value2",
|
||||
"chooseFile": "or choose a file",
|
||||
"analyzing": "Checking…",
|
||||
"formatJson": "JSON",
|
||||
"formatNetscape": "Netscape",
|
||||
"formatNameValue": "Name=Value",
|
||||
"formatUnknown": "Format not recognised",
|
||||
"siteLabel": "Site",
|
||||
"sitePlaceholder": "example.com or https://example.com",
|
||||
"siteHelp": "A name=value list carries no domain of its own, so name the site these cookies belong to.",
|
||||
"scopeSubdomains": "{{domain}}: this domain and all of its subdomains",
|
||||
"scopeHostOnly": "{{domain}}: this exact host only, no subdomains",
|
||||
"modeMerge": "Merge",
|
||||
"modeMergeDesc": "Update the stored cookies that a pasted one matches, add the rest, and delete nothing.",
|
||||
"modeReplace": "Replace matching sites",
|
||||
"modeReplaceDesc": "Delete this profile's stored cookies for the sites named in this paste, in both their dotted and undotted form, then write the paste. Cookies for every other site are kept.",
|
||||
"replaceDeleteCount": "Stored cookies this would delete: {{n}}",
|
||||
"unknownCount": "unknown",
|
||||
"includeExpired": "Also import cookies that have already expired",
|
||||
"expiredNote": "Already expired in this paste: {{n}}",
|
||||
"clearsOnCloseWarning": "This profile erases its browsing data when the browser closes, so these cookies will be deleted at the end of the next session.",
|
||||
"previewTitle": "Cookies to import: {{n}}",
|
||||
"colSite": "Site",
|
||||
"colName": "Name",
|
||||
"colPath": "Path",
|
||||
"colExpires": "Expires",
|
||||
"colSecure": "Secure",
|
||||
"colHttpOnly": "HttpOnly",
|
||||
"colSameSite": "SameSite",
|
||||
"session": "Session",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"sameSiteUnspecified": "Unspecified",
|
||||
"sameSiteNone": "None",
|
||||
"sameSiteLax": "Lax",
|
||||
"sameSiteStrict": "Strict",
|
||||
"issuesTitle": "Issues",
|
||||
"showAll": "Show all {{n}}",
|
||||
"showFewer": "Show fewer",
|
||||
"sourceLine": "Line {{n}}",
|
||||
"sourceCookie": "Cookie {{n}}",
|
||||
"disabledEmpty": "Paste cookies above to import them.",
|
||||
"disabledSite": "Name the site these cookies belong to.",
|
||||
"disabledNoCookies": "No cookies could be read from this paste.",
|
||||
"resultAdded": "Added",
|
||||
"resultOverwritten": "Overwritten",
|
||||
"resultDeleted": "Deleted",
|
||||
"resultSkipped": "Skipped",
|
||||
"issues": {
|
||||
"emptyInput": "Nothing has been pasted yet.",
|
||||
"siteInvalid": "\"{{site}}\" is not a usable site and was ignored.",
|
||||
"unrecognizedFormat": "This is not JSON, a Netscape cookies.txt, or a name=value list.",
|
||||
"siteRequired": "A name=value list carries no domain. Name the site these cookies belong to.",
|
||||
"noCookiesFound": "No cookies could be read from this paste.",
|
||||
"nameEmpty": "The cookie name is empty.",
|
||||
"nameInvalid": "\"{{name}}\" is not a usable cookie name.",
|
||||
"nameMissing": "This entry has no name.",
|
||||
"valueInvalid": "The value of \"{{name}}\" holds characters a cookie cannot carry.",
|
||||
"valueCoerced": "The value of \"{{name}}\" was not text, so it was converted to text.",
|
||||
"domainFromSite": "\"{{name}}\" carried no domain and was attached to {{domain}}.",
|
||||
"domainMissing": "\"{{name}}\" carries no domain and no site was given.",
|
||||
"domainInvalid": "\"{{name}}\" names a domain that cannot be used: {{domain}}.",
|
||||
"domainAttributeIgnored": "The Domain={{domain}} attribute was ignored in favour of the site you named, {{site}}.",
|
||||
"hostOnlyMismatch": "\"{{name}}\" says hostOnly={{hostOnly}} but its domain was {{domain}}. The flag was applied.",
|
||||
"pathRepaired": "The path of \"{{name}}\" was repaired from {{path}}.",
|
||||
"expiryMilliseconds": "The expiry of \"{{name}}\" ({{expires}}) was in milliseconds and was converted to seconds.",
|
||||
"expiryClamped": "An expiry was too far in the future to be real and was clamped to the maximum.",
|
||||
"expiryInvalid": "{{field}} is not a usable timestamp: {{value}}.",
|
||||
"expiresInvalid": "Expires is not a date that can be read: {{value}}.",
|
||||
"maxAgeInvalid": "Max-Age is not a number: {{value}}.",
|
||||
"maxAgeDeletion": "The Max-Age on \"{{name}}\" deletes it immediately.",
|
||||
"sameSiteNoneInsecure": "\"{{name}}\" on {{domain}} is SameSite=None but not Secure, so the browser will refuse to send it.",
|
||||
"sameSiteUnrecognized": "SameSite \"{{value}}\" was not recognised and was left unspecified.",
|
||||
"duplicateCookie": "\"{{name}}\" for {{domain}}{{path}} appears again later in the paste. The later copy wins.",
|
||||
"boolCoercedFromString": "{{field}} was the text \"{{value}}\" instead of true or false, and was read as a boolean.",
|
||||
"boolInvalid": "{{field}} is neither true nor false: {{value}}.",
|
||||
"quotedValue": "The quotes around the value of \"{{name}}\" were removed.",
|
||||
"jsonParseFailed": "The JSON could not be read: {{message}}",
|
||||
"jsonNotCookieList": "The JSON is neither an array of cookies nor an object holding a cookies array.",
|
||||
"jsonEntryNotObject": "This entry is not a JSON object.",
|
||||
"netscapePathOmitted": "This line has no path column, so / was used.",
|
||||
"netscapeFieldCount": "This line has {{actual}} columns; a Netscape cookie line has {{expected}}.",
|
||||
"netscapeIncludeSubdomainsInvalid": "The include-subdomains column is neither TRUE nor FALSE: {{value}}.",
|
||||
"netscapeSecureInvalid": "The secure column is neither TRUE nor FALSE: {{value}}.",
|
||||
"netscapeExpiryInvalid": "The expiry column is not a number: {{value}}. The line was dropped rather than turned into a live cookie.",
|
||||
"nameValueNoPair": "This part has no name=value pair and was ignored.",
|
||||
"pairTreatedAsAttribute": "\"{{name}}\" was read as a Set-Cookie attribute rather than a cookie, and its value was discarded.",
|
||||
"unknown": "{{code}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
@@ -1929,6 +2014,10 @@
|
||||
"invalidLaunchHookUrl": "Invalid launch hook URL. Use a full http:// or https:// URL.",
|
||||
"cookieDbLocked": "Could not read cookies — the database is locked. Close the browser and try again.",
|
||||
"cookieDbUnavailable": "Could not read cookies — the cookie store is unavailable.",
|
||||
"cookieImportBrowserRunning": "Cannot import cookies while the browser is running. Close it and try again.",
|
||||
"cookieImportProfileProtected": "Cannot import cookies into a password-protected profile. Remove the password first.",
|
||||
"cookieImportRemoteSession": "Cannot import cookies while a remote session owns this profile. Wait for it to finish syncing.",
|
||||
"cookieImportNoCookies": "No cookies were found in what you pasted.",
|
||||
"selfHostedRequiresLogout": "Sign out of your Donut account before configuring a self-hosted server.",
|
||||
"fingerprintRequiresPro": "Viewing or editing the fingerprint requires an active paid plan. Protection is included on all plans.",
|
||||
"proxyNotWorking": "The selected proxy isn't working, so the profile wasn't created.",
|
||||
|
||||
@@ -898,12 +898,7 @@
|
||||
"menuItem": "Gestión de Cookies",
|
||||
"tabImport": "Importar",
|
||||
"tabExport": "Exportar",
|
||||
"importDescription": "Importa cookies desde un archivo en formato Netscape o JSON.",
|
||||
"dropPrompt": "Haz clic para elegir un archivo de cookies",
|
||||
"fileFormats": "(.txt, .cookies o .json)",
|
||||
"cookiesFound": "{{count}} cookies encontradas",
|
||||
"importedSuccess": "{{imported}} cookies importadas correctamente ({{replaced}} reemplazadas)",
|
||||
"linesSkipped": "{{count}} línea(s) omitidas",
|
||||
"importDescription": "Pega las cookies copiadas de otro navegador o herramienta, o elige un archivo.",
|
||||
"fileReadError": "Error al leer el archivo",
|
||||
"loadFailed": "Error al cargar las cookies: {{error}}",
|
||||
"cookiesLabel": "Cookies",
|
||||
@@ -912,9 +907,7 @@
|
||||
"deselectAll": "Deseleccionar todo",
|
||||
"noCookies": "No se encontraron cookies en este perfil",
|
||||
"doneButton": "Hecho",
|
||||
"importButton": "Importar",
|
||||
"exportButton": "Exportar",
|
||||
"backButton": "Atrás"
|
||||
"exportButton": "Exportar"
|
||||
},
|
||||
"import": {
|
||||
"title": "Importar Cookies",
|
||||
@@ -933,6 +926,98 @@
|
||||
"json": "JSON",
|
||||
"success": "Cookies exportadas exitosamente",
|
||||
"error": "Error al exportar cookies"
|
||||
},
|
||||
"paste": {
|
||||
"label": "Cookies",
|
||||
"placeholder": "Pega las cookies aquí. JSON (un arreglo o un objeto {cookies: [...]}), un cookies.txt de Netscape, o nombre=valor; nombre2=valor2",
|
||||
"chooseFile": "o elige un archivo",
|
||||
"analyzing": "Comprobando…",
|
||||
"formatJson": "JSON",
|
||||
"formatNetscape": "Netscape",
|
||||
"formatNameValue": "Nombre=Valor",
|
||||
"formatUnknown": "Formato no reconocido",
|
||||
"siteLabel": "Sitio",
|
||||
"sitePlaceholder": "ejemplo.com o https://ejemplo.com",
|
||||
"siteHelp": "Una lista nombre=valor no lleva dominio propio, así que indica el sitio al que pertenecen estas cookies.",
|
||||
"scopeSubdomains": "{{domain}}: este dominio y todos sus subdominios",
|
||||
"scopeHostOnly": "{{domain}}: solo este host exacto, sin subdominios",
|
||||
"modeMerge": "Combinar",
|
||||
"modeMergeDesc": "Actualiza las cookies guardadas que coincidan con una pegada, añade las demás y no borra nada.",
|
||||
"modeReplace": "Reemplazar los sitios coincidentes",
|
||||
"modeReplaceDesc": "Borra las cookies guardadas de este perfil para los sitios indicados en este pegado, tanto en su forma con punto como sin punto, y luego escribe el pegado. Las cookies de los demás sitios se conservan.",
|
||||
"replaceDeleteCount": "Cookies guardadas que se borrarían: {{n}}",
|
||||
"unknownCount": "desconocido",
|
||||
"includeExpired": "Importar también las cookies ya caducadas",
|
||||
"expiredNote": "Ya caducadas en este pegado: {{n}}",
|
||||
"clearsOnCloseWarning": "Este perfil borra sus datos de navegación al cerrar el navegador, así que estas cookies se eliminarán al final de la próxima sesión.",
|
||||
"previewTitle": "Cookies por importar: {{n}}",
|
||||
"colSite": "Sitio",
|
||||
"colName": "Nombre",
|
||||
"colPath": "Ruta",
|
||||
"colExpires": "Caduca",
|
||||
"colSecure": "Secure",
|
||||
"colHttpOnly": "HttpOnly",
|
||||
"colSameSite": "SameSite",
|
||||
"session": "Sesión",
|
||||
"yes": "Sí",
|
||||
"no": "No",
|
||||
"sameSiteUnspecified": "Sin especificar",
|
||||
"sameSiteNone": "None",
|
||||
"sameSiteLax": "Lax",
|
||||
"sameSiteStrict": "Strict",
|
||||
"issuesTitle": "Incidencias",
|
||||
"showAll": "Mostrar las {{n}}",
|
||||
"showFewer": "Mostrar menos",
|
||||
"sourceLine": "Línea {{n}}",
|
||||
"sourceCookie": "Cookie {{n}}",
|
||||
"disabledEmpty": "Pega cookies arriba para importarlas.",
|
||||
"disabledSite": "Indica el sitio al que pertenecen estas cookies.",
|
||||
"disabledNoCookies": "No se pudo leer ninguna cookie de este pegado.",
|
||||
"resultAdded": "Añadidas",
|
||||
"resultOverwritten": "Sobrescritas",
|
||||
"resultDeleted": "Borradas",
|
||||
"resultSkipped": "Omitidas",
|
||||
"issues": {
|
||||
"emptyInput": "Todavía no se ha pegado nada.",
|
||||
"siteInvalid": "\"{{site}}\" no es un sitio válido y se ignoró.",
|
||||
"unrecognizedFormat": "Esto no es JSON, ni un cookies.txt de Netscape, ni una lista nombre=valor.",
|
||||
"siteRequired": "Una lista nombre=valor no lleva dominio. Indica el sitio al que pertenecen estas cookies.",
|
||||
"noCookiesFound": "No se pudo leer ninguna cookie de este pegado.",
|
||||
"nameEmpty": "El nombre de la cookie está vacío.",
|
||||
"nameInvalid": "\"{{name}}\" no es un nombre de cookie válido.",
|
||||
"nameMissing": "Esta entrada no tiene nombre.",
|
||||
"valueInvalid": "El valor de \"{{name}}\" contiene caracteres que una cookie no puede llevar.",
|
||||
"valueCoerced": "El valor de \"{{name}}\" no era texto, así que se convirtió a texto.",
|
||||
"domainFromSite": "\"{{name}}\" no llevaba dominio y se asoció a {{domain}}.",
|
||||
"domainMissing": "\"{{name}}\" no lleva dominio y no se indicó ningún sitio.",
|
||||
"domainInvalid": "\"{{name}}\" indica un dominio que no se puede usar: {{domain}}.",
|
||||
"domainAttributeIgnored": "El atributo Domain={{domain}} se ignoró en favor del sitio que indicaste, {{site}}.",
|
||||
"hostOnlyMismatch": "\"{{name}}\" declara hostOnly={{hostOnly}} pero su dominio era {{domain}}. Se aplicó la marca.",
|
||||
"pathRepaired": "La ruta de \"{{name}}\" se corrigió a partir de {{path}}.",
|
||||
"expiryMilliseconds": "La caducidad de \"{{name}}\" ({{expires}}) estaba en milisegundos y se convirtió a segundos.",
|
||||
"expiryClamped": "Una caducidad estaba demasiado lejos en el futuro para ser real y se limitó al máximo.",
|
||||
"expiryInvalid": "{{field}} no es una marca de tiempo válida: {{value}}.",
|
||||
"expiresInvalid": "Expires no es una fecha que se pueda leer: {{value}}.",
|
||||
"maxAgeInvalid": "Max-Age no es un número: {{value}}.",
|
||||
"maxAgeDeletion": "El Max-Age de \"{{name}}\" la borra de inmediato.",
|
||||
"sameSiteNoneInsecure": "\"{{name}}\" en {{domain}} es SameSite=None pero no Secure, así que el navegador se negará a enviarla.",
|
||||
"sameSiteUnrecognized": "No se reconoció el SameSite \"{{value}}\" y se dejó sin especificar.",
|
||||
"duplicateCookie": "\"{{name}}\" para {{domain}}{{path}} vuelve a aparecer más adelante en el pegado. Gana la copia posterior.",
|
||||
"boolCoercedFromString": "{{field}} era el texto \"{{value}}\" en lugar de true o false, y se leyó como booleano.",
|
||||
"boolInvalid": "{{field}} no es ni true ni false: {{value}}.",
|
||||
"quotedValue": "Se quitaron las comillas del valor de \"{{name}}\".",
|
||||
"jsonParseFailed": "No se pudo leer el JSON: {{message}}",
|
||||
"jsonNotCookieList": "El JSON no es ni un arreglo de cookies ni un objeto que contenga un arreglo cookies.",
|
||||
"jsonEntryNotObject": "Esta entrada no es un objeto JSON.",
|
||||
"netscapePathOmitted": "Esta línea no tiene columna de ruta, así que se usó /.",
|
||||
"netscapeFieldCount": "Esta línea tiene {{actual}} columnas; una línea de cookie Netscape tiene {{expected}}.",
|
||||
"netscapeIncludeSubdomainsInvalid": "La columna de incluir subdominios no es ni TRUE ni FALSE: {{value}}.",
|
||||
"netscapeSecureInvalid": "La columna secure no es ni TRUE ni FALSE: {{value}}.",
|
||||
"netscapeExpiryInvalid": "La columna de caducidad no es un número: {{value}}. Se descartó la línea en lugar de convertirla en una cookie activa.",
|
||||
"nameValueNoPair": "Esta parte no tiene un par nombre=valor y se ignoró.",
|
||||
"pairTreatedAsAttribute": "\"{{name}}\" se leyó como un atributo de Set-Cookie en lugar de una cookie, y su valor se descartó.",
|
||||
"unknown": "{{code}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
@@ -1935,6 +2020,10 @@
|
||||
"invalidLaunchHookUrl": "URL del hook de inicio no válida. Usa una URL completa http:// o https://.",
|
||||
"cookieDbLocked": "No se pudieron leer las cookies — la base de datos está bloqueada. Cierra el navegador e inténtalo de nuevo.",
|
||||
"cookieDbUnavailable": "No se pudieron leer las cookies — el almacén de cookies no está disponible.",
|
||||
"cookieImportBrowserRunning": "No se pueden importar cookies mientras el navegador está en ejecución. Ciérralo e inténtalo de nuevo.",
|
||||
"cookieImportProfileProtected": "No se pueden importar cookies en un perfil protegido con contraseña. Quita primero la contraseña.",
|
||||
"cookieImportRemoteSession": "No se pueden importar cookies mientras una sesión remota controla este perfil. Espera a que termine de sincronizarse.",
|
||||
"cookieImportNoCookies": "No se encontraron cookies en lo que pegaste.",
|
||||
"selfHostedRequiresLogout": "Cierra sesión en tu cuenta de Donut antes de configurar un servidor autoalojado.",
|
||||
"fingerprintRequiresPro": "Ver o editar la huella digital requiere un plan de pago activo. La protección está incluida en todos los planes.",
|
||||
"proxyNotWorking": "El proxy seleccionado no funciona, por lo que no se creó el perfil.",
|
||||
|
||||
@@ -898,12 +898,7 @@
|
||||
"menuItem": "Gestion des Cookies",
|
||||
"tabImport": "Importer",
|
||||
"tabExport": "Exporter",
|
||||
"importDescription": "Importer des cookies depuis un fichier au format Netscape ou JSON.",
|
||||
"dropPrompt": "Cliquez pour choisir un fichier de cookies",
|
||||
"fileFormats": "(.txt, .cookies ou .json)",
|
||||
"cookiesFound": "{{count}} cookies trouvés",
|
||||
"importedSuccess": "{{imported}} cookies importés avec succès ({{replaced}} remplacés)",
|
||||
"linesSkipped": "{{count}} ligne(s) ignorée(s)",
|
||||
"importDescription": "Collez les cookies copiés depuis un autre navigateur ou outil, ou choisissez un fichier.",
|
||||
"fileReadError": "Échec de la lecture du fichier",
|
||||
"loadFailed": "Échec du chargement des cookies : {{error}}",
|
||||
"cookiesLabel": "Cookies",
|
||||
@@ -912,9 +907,7 @@
|
||||
"deselectAll": "Tout désélectionner",
|
||||
"noCookies": "Aucun cookie trouvé dans ce profil",
|
||||
"doneButton": "Terminé",
|
||||
"importButton": "Importer",
|
||||
"exportButton": "Exporter",
|
||||
"backButton": "Retour"
|
||||
"exportButton": "Exporter"
|
||||
},
|
||||
"import": {
|
||||
"title": "Importer des Cookies",
|
||||
@@ -933,6 +926,98 @@
|
||||
"json": "JSON",
|
||||
"success": "Cookies exportés avec succès",
|
||||
"error": "Échec de l'exportation des cookies"
|
||||
},
|
||||
"paste": {
|
||||
"label": "Cookies",
|
||||
"placeholder": "Collez les cookies ici. JSON (un tableau ou un objet {cookies: [...]}), un cookies.txt Netscape, ou nom=valeur; nom2=valeur2",
|
||||
"chooseFile": "ou choisissez un fichier",
|
||||
"analyzing": "Vérification…",
|
||||
"formatJson": "JSON",
|
||||
"formatNetscape": "Netscape",
|
||||
"formatNameValue": "Nom=Valeur",
|
||||
"formatUnknown": "Format non reconnu",
|
||||
"siteLabel": "Site",
|
||||
"sitePlaceholder": "exemple.com ou https://exemple.com",
|
||||
"siteHelp": "Une liste nom=valeur ne porte aucun domaine, indiquez donc le site auquel ces cookies appartiennent.",
|
||||
"scopeSubdomains": "{{domain}} : ce domaine et tous ses sous-domaines",
|
||||
"scopeHostOnly": "{{domain}} : uniquement cet hôte exact, sans sous-domaines",
|
||||
"modeMerge": "Fusionner",
|
||||
"modeMergeDesc": "Met à jour les cookies enregistrés qu'un cookie collé fait correspondre, ajoute les autres et n'en supprime aucun.",
|
||||
"modeReplace": "Remplacer les sites correspondants",
|
||||
"modeReplaceDesc": "Supprime les cookies enregistrés de ce profil pour les sites nommés dans ce collage, sous leur forme avec point et sans point, puis écrit le collage. Les cookies de tous les autres sites sont conservés.",
|
||||
"replaceDeleteCount": "Cookies enregistrés qui seraient supprimés : {{n}}",
|
||||
"unknownCount": "inconnu",
|
||||
"includeExpired": "Importer aussi les cookies déjà expirés",
|
||||
"expiredNote": "Déjà expirés dans ce collage : {{n}}",
|
||||
"clearsOnCloseWarning": "Ce profil efface ses données de navigation à la fermeture du navigateur, donc ces cookies seront supprimés à la fin de la prochaine session.",
|
||||
"previewTitle": "Cookies à importer : {{n}}",
|
||||
"colSite": "Site",
|
||||
"colName": "Nom",
|
||||
"colPath": "Chemin",
|
||||
"colExpires": "Expiration",
|
||||
"colSecure": "Secure",
|
||||
"colHttpOnly": "HttpOnly",
|
||||
"colSameSite": "SameSite",
|
||||
"session": "Session",
|
||||
"yes": "Oui",
|
||||
"no": "Non",
|
||||
"sameSiteUnspecified": "Non précisé",
|
||||
"sameSiteNone": "None",
|
||||
"sameSiteLax": "Lax",
|
||||
"sameSiteStrict": "Strict",
|
||||
"issuesTitle": "Anomalies",
|
||||
"showAll": "Afficher les {{n}}",
|
||||
"showFewer": "Afficher moins",
|
||||
"sourceLine": "Ligne {{n}}",
|
||||
"sourceCookie": "Cookie {{n}}",
|
||||
"disabledEmpty": "Collez des cookies ci-dessus pour les importer.",
|
||||
"disabledSite": "Indiquez le site auquel ces cookies appartiennent.",
|
||||
"disabledNoCookies": "Aucun cookie n'a pu être lu dans ce collage.",
|
||||
"resultAdded": "Ajoutés",
|
||||
"resultOverwritten": "Écrasés",
|
||||
"resultDeleted": "Supprimés",
|
||||
"resultSkipped": "Ignorés",
|
||||
"issues": {
|
||||
"emptyInput": "Rien n'a encore été collé.",
|
||||
"siteInvalid": "« {{site}} » n'est pas un site exploitable et a été ignoré.",
|
||||
"unrecognizedFormat": "Ceci n'est ni du JSON, ni un cookies.txt Netscape, ni une liste nom=valeur.",
|
||||
"siteRequired": "Une liste nom=valeur ne porte aucun domaine. Indiquez le site auquel ces cookies appartiennent.",
|
||||
"noCookiesFound": "Aucun cookie n'a pu être lu dans ce collage.",
|
||||
"nameEmpty": "Le nom du cookie est vide.",
|
||||
"nameInvalid": "« {{name}} » n'est pas un nom de cookie exploitable.",
|
||||
"nameMissing": "Cette entrée n'a pas de nom.",
|
||||
"valueInvalid": "La valeur de « {{name}} » contient des caractères qu'un cookie ne peut pas porter.",
|
||||
"valueCoerced": "La valeur de « {{name}} » n'était pas du texte, elle a donc été convertie en texte.",
|
||||
"domainFromSite": "« {{name}} » ne portait aucun domaine et a été rattaché à {{domain}}.",
|
||||
"domainMissing": "« {{name}} » ne porte aucun domaine et aucun site n'a été indiqué.",
|
||||
"domainInvalid": "« {{name}} » désigne un domaine inutilisable : {{domain}}.",
|
||||
"domainAttributeIgnored": "L'attribut Domain={{domain}} a été ignoré au profit du site que vous avez indiqué, {{site}}.",
|
||||
"hostOnlyMismatch": "« {{name}} » annonce hostOnly={{hostOnly}} alors que son domaine était {{domain}}. L'indicateur a été appliqué.",
|
||||
"pathRepaired": "Le chemin de « {{name}} » a été corrigé à partir de {{path}}.",
|
||||
"expiryMilliseconds": "L'expiration de « {{name}} » ({{expires}}) était en millisecondes et a été convertie en secondes.",
|
||||
"expiryClamped": "Une expiration était trop lointaine pour être réelle et a été ramenée au maximum.",
|
||||
"expiryInvalid": "{{field}} n'est pas un horodatage exploitable : {{value}}.",
|
||||
"expiresInvalid": "Expires n'est pas une date lisible : {{value}}.",
|
||||
"maxAgeInvalid": "Max-Age n'est pas un nombre : {{value}}.",
|
||||
"maxAgeDeletion": "Le Max-Age de « {{name}} » le supprime immédiatement.",
|
||||
"sameSiteNoneInsecure": "« {{name}} » sur {{domain}} est SameSite=None mais pas Secure, le navigateur refusera donc de l'envoyer.",
|
||||
"sameSiteUnrecognized": "Le SameSite « {{value}} » n'a pas été reconnu et est resté non précisé.",
|
||||
"duplicateCookie": "« {{name}} » pour {{domain}}{{path}} réapparaît plus loin dans le collage. La dernière copie l'emporte.",
|
||||
"boolCoercedFromString": "{{field}} était le texte « {{value}} » au lieu de true ou false, et a été lu comme un booléen.",
|
||||
"boolInvalid": "{{field}} n'est ni true ni false : {{value}}.",
|
||||
"quotedValue": "Les guillemets autour de la valeur de « {{name}} » ont été retirés.",
|
||||
"jsonParseFailed": "Le JSON n'a pas pu être lu : {{message}}",
|
||||
"jsonNotCookieList": "Le JSON n'est ni un tableau de cookies ni un objet contenant un tableau cookies.",
|
||||
"jsonEntryNotObject": "Cette entrée n'est pas un objet JSON.",
|
||||
"netscapePathOmitted": "Cette ligne n'a pas de colonne de chemin, / a donc été utilisé.",
|
||||
"netscapeFieldCount": "Cette ligne a {{actual}} colonnes ; une ligne de cookie Netscape en a {{expected}}.",
|
||||
"netscapeIncludeSubdomainsInvalid": "La colonne d'inclusion des sous-domaines n'est ni TRUE ni FALSE : {{value}}.",
|
||||
"netscapeSecureInvalid": "La colonne secure n'est ni TRUE ni FALSE : {{value}}.",
|
||||
"netscapeExpiryInvalid": "La colonne d'expiration n'est pas un nombre : {{value}}. La ligne a été écartée plutôt que transformée en cookie actif.",
|
||||
"nameValueNoPair": "Cette partie ne contient pas de paire nom=valeur et a été ignorée.",
|
||||
"pairTreatedAsAttribute": "« {{name}} » a été lu comme un attribut Set-Cookie et non comme un cookie, et sa valeur a été ignorée.",
|
||||
"unknown": "{{code}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
@@ -1935,6 +2020,10 @@
|
||||
"invalidLaunchHookUrl": "URL du hook de lancement invalide. Utilisez une URL http:// ou https:// complète.",
|
||||
"cookieDbLocked": "Impossible de lire les cookies — la base de données est verrouillée. Fermez le navigateur et réessayez.",
|
||||
"cookieDbUnavailable": "Impossible de lire les cookies — le magasin de cookies est indisponible.",
|
||||
"cookieImportBrowserRunning": "Impossible d'importer des cookies pendant que le navigateur est ouvert. Fermez-le et réessayez.",
|
||||
"cookieImportProfileProtected": "Impossible d'importer des cookies dans un profil protégé par mot de passe. Retirez d'abord le mot de passe.",
|
||||
"cookieImportRemoteSession": "Impossible d'importer des cookies tant qu'une session distante détient ce profil. Attendez la fin de la synchronisation.",
|
||||
"cookieImportNoCookies": "Aucun cookie n'a été trouvé dans ce que vous avez collé.",
|
||||
"selfHostedRequiresLogout": "Déconnectez-vous de votre compte Donut avant de configurer un serveur auto-hébergé.",
|
||||
"fingerprintRequiresPro": "Afficher ou modifier l'empreinte nécessite un forfait payant actif. La protection est incluse dans tous les forfaits.",
|
||||
"proxyNotWorking": "Le proxy sélectionné ne fonctionne pas, le profil n'a donc pas été créé.",
|
||||
|
||||
@@ -895,12 +895,7 @@
|
||||
"menuItem": "Cookie管理",
|
||||
"tabImport": "インポート",
|
||||
"tabExport": "エクスポート",
|
||||
"importDescription": "Netscape または JSON 形式のファイルから Cookie をインポートします。",
|
||||
"dropPrompt": "クリックして Cookie ファイルを選択",
|
||||
"fileFormats": "(.txt, .cookies, または .json)",
|
||||
"cookiesFound": "{{count}} 件の Cookie が見つかりました",
|
||||
"importedSuccess": "{{imported}} 件の Cookie をインポートしました ({{replaced}} 件置換)",
|
||||
"linesSkipped": "{{count}} 行をスキップ",
|
||||
"importDescription": "他のブラウザやツールからコピーした Cookie を貼り付けるか、ファイルを選んでください。",
|
||||
"fileReadError": "ファイルの読み込みに失敗しました",
|
||||
"loadFailed": "Cookie の読み込みに失敗しました: {{error}}",
|
||||
"cookiesLabel": "Cookies",
|
||||
@@ -909,9 +904,7 @@
|
||||
"deselectAll": "すべて解除",
|
||||
"noCookies": "このプロファイルに Cookie はありません",
|
||||
"doneButton": "完了",
|
||||
"importButton": "インポート",
|
||||
"exportButton": "エクスポート",
|
||||
"backButton": "戻る"
|
||||
"exportButton": "エクスポート"
|
||||
},
|
||||
"import": {
|
||||
"title": "Cookieのインポート",
|
||||
@@ -930,6 +923,98 @@
|
||||
"json": "JSON",
|
||||
"success": "Cookieのエクスポートに成功しました",
|
||||
"error": "Cookieのエクスポートに失敗しました"
|
||||
},
|
||||
"paste": {
|
||||
"label": "Cookie",
|
||||
"placeholder": "ここに Cookie を貼り付けてください。JSON(配列または {cookies: [...]} オブジェクト)、Netscape 形式の cookies.txt、または name=value; name2=value2",
|
||||
"chooseFile": "またはファイルを選択",
|
||||
"analyzing": "確認中…",
|
||||
"formatJson": "JSON",
|
||||
"formatNetscape": "Netscape",
|
||||
"formatNameValue": "Name=Value",
|
||||
"formatUnknown": "形式を認識できません",
|
||||
"siteLabel": "サイト",
|
||||
"sitePlaceholder": "example.com または https://example.com",
|
||||
"siteHelp": "name=value の一覧にはドメインが含まれないため、これらの Cookie が属するサイトを指定してください。",
|
||||
"scopeSubdomains": "{{domain}}:このドメインとすべてのサブドメイン",
|
||||
"scopeHostOnly": "{{domain}}:このホストのみ、サブドメインは含みません",
|
||||
"modeMerge": "マージ",
|
||||
"modeMergeDesc": "貼り付けた Cookie と一致する保存済み Cookie を更新し、残りを追加します。削除は行いません。",
|
||||
"modeReplace": "一致するサイトを置き換え",
|
||||
"modeReplaceDesc": "この貼り付けに含まれるサイトについて、ドット付きとドットなしの両方の形式でこのプロファイルの保存済み Cookie を削除してから、貼り付け内容を書き込みます。他のサイトの Cookie はすべて保持されます。",
|
||||
"replaceDeleteCount": "削除される保存済み Cookie:{{n}}",
|
||||
"unknownCount": "不明",
|
||||
"includeExpired": "期限切れの Cookie もインポートする",
|
||||
"expiredNote": "この貼り付け中の期限切れ:{{n}}",
|
||||
"clearsOnCloseWarning": "このプロファイルはブラウザを閉じると閲覧データを消去するため、これらの Cookie は次のセッション終了時に削除されます。",
|
||||
"previewTitle": "インポートする Cookie:{{n}}",
|
||||
"colSite": "サイト",
|
||||
"colName": "名前",
|
||||
"colPath": "パス",
|
||||
"colExpires": "有効期限",
|
||||
"colSecure": "Secure",
|
||||
"colHttpOnly": "HttpOnly",
|
||||
"colSameSite": "SameSite",
|
||||
"session": "セッション",
|
||||
"yes": "はい",
|
||||
"no": "いいえ",
|
||||
"sameSiteUnspecified": "未指定",
|
||||
"sameSiteNone": "None",
|
||||
"sameSiteLax": "Lax",
|
||||
"sameSiteStrict": "Strict",
|
||||
"issuesTitle": "問題",
|
||||
"showAll": "{{n}} 件すべてを表示",
|
||||
"showFewer": "表示を減らす",
|
||||
"sourceLine": "{{n}} 行目",
|
||||
"sourceCookie": "Cookie {{n}} 番目",
|
||||
"disabledEmpty": "上に Cookie を貼り付けるとインポートできます。",
|
||||
"disabledSite": "これらの Cookie が属するサイトを指定してください。",
|
||||
"disabledNoCookies": "この貼り付けから Cookie を読み取れませんでした。",
|
||||
"resultAdded": "追加",
|
||||
"resultOverwritten": "上書き",
|
||||
"resultDeleted": "削除",
|
||||
"resultSkipped": "スキップ",
|
||||
"issues": {
|
||||
"emptyInput": "まだ何も貼り付けられていません。",
|
||||
"siteInvalid": "「{{site}}」は使用できるサイトではないため無視されました。",
|
||||
"unrecognizedFormat": "これは JSON、Netscape 形式の cookies.txt、name=value の一覧のいずれでもありません。",
|
||||
"siteRequired": "name=value の一覧にはドメインが含まれません。これらの Cookie が属するサイトを指定してください。",
|
||||
"noCookiesFound": "この貼り付けから Cookie を読み取れませんでした。",
|
||||
"nameEmpty": "Cookie 名が空です。",
|
||||
"nameInvalid": "「{{name}}」は使用できる Cookie 名ではありません。",
|
||||
"nameMissing": "このエントリには名前がありません。",
|
||||
"valueInvalid": "「{{name}}」の値には Cookie が保持できない文字が含まれています。",
|
||||
"valueCoerced": "「{{name}}」の値はテキストではなかったため、テキストに変換されました。",
|
||||
"domainFromSite": "「{{name}}」にはドメインがなく、{{domain}} に紐づけられました。",
|
||||
"domainMissing": "「{{name}}」にはドメインがなく、サイトも指定されていません。",
|
||||
"domainInvalid": "「{{name}}」は使用できないドメインを指定しています:{{domain}}。",
|
||||
"domainAttributeIgnored": "Domain={{domain}} 属性は無視され、指定されたサイト {{site}} が使われました。",
|
||||
"hostOnlyMismatch": "「{{name}}」は hostOnly={{hostOnly}} ですが、ドメインは {{domain}} でした。フラグを適用しました。",
|
||||
"pathRepaired": "「{{name}}」のパスを {{path}} から修正しました。",
|
||||
"expiryMilliseconds": "「{{name}}」の有効期限({{expires}})はミリ秒単位だったため、秒に変換しました。",
|
||||
"expiryClamped": "有効期限が現実的でないほど先だったため、最大値に制限しました。",
|
||||
"expiryInvalid": "{{field}} は使用できるタイムスタンプではありません:{{value}}。",
|
||||
"expiresInvalid": "Expires は読み取れる日付ではありません:{{value}}。",
|
||||
"maxAgeInvalid": "Max-Age が数値ではありません:{{value}}。",
|
||||
"maxAgeDeletion": "「{{name}}」の Max-Age はこれを即座に削除します。",
|
||||
"sameSiteNoneInsecure": "{{domain}} の「{{name}}」は SameSite=None ですが Secure ではないため、ブラウザは送信を拒否します。",
|
||||
"sameSiteUnrecognized": "SameSite「{{value}}」を認識できず、未指定のままにしました。",
|
||||
"duplicateCookie": "{{domain}}{{path}} の「{{name}}」は貼り付けの後方にもあります。後のものが優先されます。",
|
||||
"boolCoercedFromString": "{{field}} は true や false ではなく文字列「{{value}}」だったため、真偽値として読み取りました。",
|
||||
"boolInvalid": "{{field}} は true でも false でもありません:{{value}}。",
|
||||
"quotedValue": "「{{name}}」の値を囲む引用符を削除しました。",
|
||||
"jsonParseFailed": "JSON を読み取れませんでした:{{message}}",
|
||||
"jsonNotCookieList": "この JSON は Cookie の配列でも、cookies 配列を持つオブジェクトでもありません。",
|
||||
"jsonEntryNotObject": "このエントリは JSON オブジェクトではありません。",
|
||||
"netscapePathOmitted": "この行にパス列がないため、/ を使いました。",
|
||||
"netscapeFieldCount": "この行は {{actual}} 列です。Netscape の Cookie 行は {{expected}} 列です。",
|
||||
"netscapeIncludeSubdomainsInvalid": "サブドメインを含む列が TRUE でも FALSE でもありません:{{value}}。",
|
||||
"netscapeSecureInvalid": "secure 列が TRUE でも FALSE でもありません:{{value}}。",
|
||||
"netscapeExpiryInvalid": "有効期限の列が数値ではありません:{{value}}。有効な Cookie にするのではなく、この行を破棄しました。",
|
||||
"nameValueNoPair": "この部分に name=value の組がないため無視されました。",
|
||||
"pairTreatedAsAttribute": "「{{name}}」は Cookie ではなく Set-Cookie の属性として読み取られ、その値は破棄されました。",
|
||||
"unknown": "{{code}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
@@ -1928,6 +2013,10 @@
|
||||
"invalidLaunchHookUrl": "起動フックURLが無効です。完全な http:// または https:// URL を使用してください。",
|
||||
"cookieDbLocked": "Cookie を読み取れません — データベースがロックされています。ブラウザを閉じてから再試行してください。",
|
||||
"cookieDbUnavailable": "Cookie を読み取れません — Cookie ストアを利用できません。",
|
||||
"cookieImportBrowserRunning": "ブラウザの実行中は Cookie をインポートできません。ブラウザを閉じてから再試行してください。",
|
||||
"cookieImportProfileProtected": "パスワード保護されたプロファイルには Cookie をインポートできません。先にパスワードを解除してください。",
|
||||
"cookieImportRemoteSession": "リモートセッションがこのプロファイルを使用している間は Cookie をインポートできません。同期の完了をお待ちください。",
|
||||
"cookieImportNoCookies": "貼り付けた内容から Cookie が見つかりませんでした。",
|
||||
"selfHostedRequiresLogout": "セルフホストサーバーを設定する前に Donut アカウントからサインアウトしてください。",
|
||||
"fingerprintRequiresPro": "フィンガープリントの表示または編集には有効な有料プランが必要です。保護機能はすべてのプランに含まれています。",
|
||||
"proxyNotWorking": "選択したプロキシが機能していないため、プロファイルは作成されませんでした。",
|
||||
|
||||
@@ -895,12 +895,7 @@
|
||||
"menuItem": "쿠키 관리",
|
||||
"tabImport": "가져오기",
|
||||
"tabExport": "내보내기",
|
||||
"importDescription": "Netscape 또는 JSON 형식 파일에서 쿠키를 가져옵니다.",
|
||||
"dropPrompt": "쿠키 파일을 선택하려면 클릭하세요",
|
||||
"fileFormats": "(.txt, .cookies 또는 .json)",
|
||||
"cookiesFound": "{{count}}개 쿠키 발견",
|
||||
"importedSuccess": "{{imported}}개 쿠키를 가져왔습니다 ({{replaced}}개 교체됨)",
|
||||
"linesSkipped": "{{count}}개 줄 건너뜀",
|
||||
"importDescription": "다른 브라우저나 도구에서 복사한 쿠키를 붙여넣거나 파일을 선택하세요.",
|
||||
"fileReadError": "파일 읽기 실패",
|
||||
"loadFailed": "쿠키 불러오기 실패: {{error}}",
|
||||
"cookiesLabel": "쿠키",
|
||||
@@ -909,9 +904,7 @@
|
||||
"deselectAll": "모두 선택 해제",
|
||||
"noCookies": "이 프로필에 쿠키가 없습니다",
|
||||
"doneButton": "완료",
|
||||
"importButton": "가져오기",
|
||||
"exportButton": "내보내기",
|
||||
"backButton": "뒤로"
|
||||
"exportButton": "내보내기"
|
||||
},
|
||||
"import": {
|
||||
"title": "쿠키 가져오기",
|
||||
@@ -930,6 +923,98 @@
|
||||
"json": "JSON",
|
||||
"success": "쿠키를 내보냈습니다",
|
||||
"error": "쿠키 내보내기 실패"
|
||||
},
|
||||
"paste": {
|
||||
"label": "쿠키",
|
||||
"placeholder": "여기에 쿠키를 붙여넣으세요. JSON(배열 또는 {cookies: [...]} 객체), Netscape cookies.txt, 또는 name=value; name2=value2",
|
||||
"chooseFile": "또는 파일 선택",
|
||||
"analyzing": "확인 중…",
|
||||
"formatJson": "JSON",
|
||||
"formatNetscape": "Netscape",
|
||||
"formatNameValue": "Name=Value",
|
||||
"formatUnknown": "형식을 인식할 수 없음",
|
||||
"siteLabel": "사이트",
|
||||
"sitePlaceholder": "example.com 또는 https://example.com",
|
||||
"siteHelp": "name=value 목록에는 도메인이 없으므로 이 쿠키가 속한 사이트를 지정하세요.",
|
||||
"scopeSubdomains": "{{domain}}: 이 도메인과 모든 하위 도메인",
|
||||
"scopeHostOnly": "{{domain}}: 이 호스트만, 하위 도메인 제외",
|
||||
"modeMerge": "병합",
|
||||
"modeMergeDesc": "붙여넣은 쿠키와 일치하는 저장된 쿠키를 갱신하고 나머지는 추가하며, 아무것도 삭제하지 않습니다.",
|
||||
"modeReplace": "일치하는 사이트 교체",
|
||||
"modeReplaceDesc": "이번 붙여넣기에 포함된 사이트에 대해 점이 있는 형태와 없는 형태 모두로 이 프로필의 저장된 쿠키를 삭제한 뒤 붙여넣은 내용을 기록합니다. 다른 모든 사이트의 쿠키는 유지됩니다.",
|
||||
"replaceDeleteCount": "삭제될 저장된 쿠키: {{n}}",
|
||||
"unknownCount": "알 수 없음",
|
||||
"includeExpired": "이미 만료된 쿠키도 가져오기",
|
||||
"expiredNote": "이번 붙여넣기에서 만료됨: {{n}}",
|
||||
"clearsOnCloseWarning": "이 프로필은 브라우저를 닫을 때 인터넷 사용 기록을 지우므로, 이 쿠키들은 다음 세션이 끝나면 삭제됩니다.",
|
||||
"previewTitle": "가져올 쿠키: {{n}}",
|
||||
"colSite": "사이트",
|
||||
"colName": "이름",
|
||||
"colPath": "경로",
|
||||
"colExpires": "만료",
|
||||
"colSecure": "Secure",
|
||||
"colHttpOnly": "HttpOnly",
|
||||
"colSameSite": "SameSite",
|
||||
"session": "세션",
|
||||
"yes": "예",
|
||||
"no": "아니오",
|
||||
"sameSiteUnspecified": "지정 안 함",
|
||||
"sameSiteNone": "None",
|
||||
"sameSiteLax": "Lax",
|
||||
"sameSiteStrict": "Strict",
|
||||
"issuesTitle": "문제",
|
||||
"showAll": "{{n}}개 모두 보기",
|
||||
"showFewer": "접기",
|
||||
"sourceLine": "{{n}}번째 줄",
|
||||
"sourceCookie": "{{n}}번째 쿠키",
|
||||
"disabledEmpty": "위에 쿠키를 붙여넣으면 가져올 수 있습니다.",
|
||||
"disabledSite": "이 쿠키가 속한 사이트를 지정하세요.",
|
||||
"disabledNoCookies": "이번 붙여넣기에서 쿠키를 읽을 수 없었습니다.",
|
||||
"resultAdded": "추가됨",
|
||||
"resultOverwritten": "덮어쓰기됨",
|
||||
"resultDeleted": "삭제됨",
|
||||
"resultSkipped": "건너뜀",
|
||||
"issues": {
|
||||
"emptyInput": "아직 붙여넣은 내용이 없습니다.",
|
||||
"siteInvalid": "\"{{site}}\"은(는) 사용할 수 없는 사이트라 무시되었습니다.",
|
||||
"unrecognizedFormat": "이것은 JSON도, Netscape cookies.txt도, name=value 목록도 아닙니다.",
|
||||
"siteRequired": "name=value 목록에는 도메인이 없습니다. 이 쿠키가 속한 사이트를 지정하세요.",
|
||||
"noCookiesFound": "이번 붙여넣기에서 쿠키를 읽을 수 없었습니다.",
|
||||
"nameEmpty": "쿠키 이름이 비어 있습니다.",
|
||||
"nameInvalid": "\"{{name}}\"은(는) 사용할 수 없는 쿠키 이름입니다.",
|
||||
"nameMissing": "이 항목에는 이름이 없습니다.",
|
||||
"valueInvalid": "\"{{name}}\"의 값에 쿠키가 담을 수 없는 문자가 있습니다.",
|
||||
"valueCoerced": "\"{{name}}\"의 값이 텍스트가 아니어서 텍스트로 변환했습니다.",
|
||||
"domainFromSite": "\"{{name}}\"에 도메인이 없어 {{domain}}에 연결했습니다.",
|
||||
"domainMissing": "\"{{name}}\"에 도메인이 없고 사이트도 지정되지 않았습니다.",
|
||||
"domainInvalid": "\"{{name}}\"이(가) 사용할 수 없는 도메인을 가리킵니다: {{domain}}.",
|
||||
"domainAttributeIgnored": "Domain={{domain}} 속성을 무시하고 지정한 사이트 {{site}}을(를) 사용했습니다.",
|
||||
"hostOnlyMismatch": "\"{{name}}\"은(는) hostOnly={{hostOnly}}로 되어 있지만 도메인은 {{domain}}이었습니다. 플래그를 적용했습니다.",
|
||||
"pathRepaired": "\"{{name}}\"의 경로를 {{path}}에서 보정했습니다.",
|
||||
"expiryMilliseconds": "\"{{name}}\"의 만료 시각({{expires}})이 밀리초 단위여서 초 단위로 변환했습니다.",
|
||||
"expiryClamped": "만료 시각이 현실적이지 않을 만큼 멀어서 최대값으로 제한했습니다.",
|
||||
"expiryInvalid": "{{field}}은(는) 사용할 수 있는 타임스탬프가 아닙니다: {{value}}.",
|
||||
"expiresInvalid": "Expires는 읽을 수 있는 날짜가 아닙니다: {{value}}.",
|
||||
"maxAgeInvalid": "Max-Age가 숫자가 아닙니다: {{value}}.",
|
||||
"maxAgeDeletion": "\"{{name}}\"의 Max-Age가 이를 즉시 삭제합니다.",
|
||||
"sameSiteNoneInsecure": "{{domain}}의 \"{{name}}\"은(는) SameSite=None이지만 Secure가 아니므로 브라우저가 전송을 거부합니다.",
|
||||
"sameSiteUnrecognized": "SameSite \"{{value}}\"을(를) 인식하지 못해 지정하지 않은 상태로 두었습니다.",
|
||||
"duplicateCookie": "{{domain}}{{path}}의 \"{{name}}\"이(가) 붙여넣기 뒷부분에 다시 나타납니다. 나중 것이 적용됩니다.",
|
||||
"boolCoercedFromString": "{{field}}이(가) true나 false가 아닌 텍스트 \"{{value}}\"였으므로 불리언으로 읽었습니다.",
|
||||
"boolInvalid": "{{field}}은(는) true도 false도 아닙니다: {{value}}.",
|
||||
"quotedValue": "\"{{name}}\" 값을 감싼 따옴표를 제거했습니다.",
|
||||
"jsonParseFailed": "JSON을 읽을 수 없었습니다: {{message}}",
|
||||
"jsonNotCookieList": "이 JSON은 쿠키 배열도, cookies 배열을 담은 객체도 아닙니다.",
|
||||
"jsonEntryNotObject": "이 항목은 JSON 객체가 아닙니다.",
|
||||
"netscapePathOmitted": "이 줄에 경로 열이 없어 /를 사용했습니다.",
|
||||
"netscapeFieldCount": "이 줄은 {{actual}}개 열입니다. Netscape 쿠키 줄은 {{expected}}개입니다.",
|
||||
"netscapeIncludeSubdomainsInvalid": "하위 도메인 포함 열이 TRUE도 FALSE도 아닙니다: {{value}}.",
|
||||
"netscapeSecureInvalid": "secure 열이 TRUE도 FALSE도 아닙니다: {{value}}.",
|
||||
"netscapeExpiryInvalid": "만료 열이 숫자가 아닙니다: {{value}}. 이 줄을 유효한 쿠키로 만드는 대신 버렸습니다.",
|
||||
"nameValueNoPair": "이 부분에는 name=value 쌍이 없어 무시되었습니다.",
|
||||
"pairTreatedAsAttribute": "\"{{name}}\"을(를) 쿠키가 아니라 Set-Cookie 속성으로 읽었으며, 그 값은 버렸습니다.",
|
||||
"unknown": "{{code}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
@@ -1928,6 +2013,10 @@
|
||||
"invalidLaunchHookUrl": "잘못된 실행 후크 URL입니다. 전체 http:// 또는 https:// URL을 사용하세요.",
|
||||
"cookieDbLocked": "쿠키를 읽을 수 없습니다 — 데이터베이스가 잠겨 있습니다. 브라우저를 닫고 다시 시도하세요.",
|
||||
"cookieDbUnavailable": "쿠키를 읽을 수 없습니다 — 쿠키 저장소를 사용할 수 없습니다.",
|
||||
"cookieImportBrowserRunning": "브라우저가 실행 중일 때는 쿠키를 가져올 수 없습니다. 브라우저를 닫고 다시 시도하세요.",
|
||||
"cookieImportProfileProtected": "비밀번호로 보호된 프로필에는 쿠키를 가져올 수 없습니다. 먼저 비밀번호를 해제하세요.",
|
||||
"cookieImportRemoteSession": "원격 세션이 이 프로필을 사용하는 동안에는 쿠키를 가져올 수 없습니다. 동기화가 끝날 때까지 기다리세요.",
|
||||
"cookieImportNoCookies": "붙여넣은 내용에서 쿠키를 찾지 못했습니다.",
|
||||
"selfHostedRequiresLogout": "자체 호스팅 서버를 구성하기 전에 Donut 계정에서 로그아웃하세요.",
|
||||
"fingerprintRequiresPro": "핑거프린트를 보거나 편집하려면 활성 유료 요금제가 필요합니다. 보호 기능은 모든 요금제에 포함되어 있습니다.",
|
||||
"proxyNotWorking": "선택한 프록시가 작동하지 않아 프로필이 생성되지 않았습니다.",
|
||||
|
||||
@@ -898,12 +898,7 @@
|
||||
"menuItem": "Gerenciamento de Cookies",
|
||||
"tabImport": "Importar",
|
||||
"tabExport": "Exportar",
|
||||
"importDescription": "Importe cookies de um arquivo no formato Netscape ou JSON.",
|
||||
"dropPrompt": "Clique para escolher um arquivo de cookies",
|
||||
"fileFormats": "(.txt, .cookies ou .json)",
|
||||
"cookiesFound": "{{count}} cookies encontrados",
|
||||
"importedSuccess": "{{imported}} cookies importados com sucesso ({{replaced}} substituídos)",
|
||||
"linesSkipped": "{{count}} linha(s) ignoradas",
|
||||
"importDescription": "Cole os cookies copiados de outro navegador ou ferramenta, ou escolha um arquivo.",
|
||||
"fileReadError": "Falha ao ler o arquivo",
|
||||
"loadFailed": "Falha ao carregar cookies: {{error}}",
|
||||
"cookiesLabel": "Cookies",
|
||||
@@ -912,9 +907,7 @@
|
||||
"deselectAll": "Desmarcar tudo",
|
||||
"noCookies": "Nenhum cookie encontrado neste perfil",
|
||||
"doneButton": "Concluído",
|
||||
"importButton": "Importar",
|
||||
"exportButton": "Exportar",
|
||||
"backButton": "Voltar"
|
||||
"exportButton": "Exportar"
|
||||
},
|
||||
"import": {
|
||||
"title": "Importar Cookies",
|
||||
@@ -933,6 +926,98 @@
|
||||
"json": "JSON",
|
||||
"success": "Cookies exportados com sucesso",
|
||||
"error": "Falha ao exportar cookies"
|
||||
},
|
||||
"paste": {
|
||||
"label": "Cookies",
|
||||
"placeholder": "Cole os cookies aqui. JSON (uma matriz ou um objeto {cookies: [...]}), um cookies.txt do Netscape, ou nome=valor; nome2=valor2",
|
||||
"chooseFile": "ou escolha um arquivo",
|
||||
"analyzing": "Verificando…",
|
||||
"formatJson": "JSON",
|
||||
"formatNetscape": "Netscape",
|
||||
"formatNameValue": "Nome=Valor",
|
||||
"formatUnknown": "Formato não reconhecido",
|
||||
"siteLabel": "Site",
|
||||
"sitePlaceholder": "exemplo.com ou https://exemplo.com",
|
||||
"siteHelp": "Uma lista nome=valor não carrega domínio próprio, então informe o site ao qual esses cookies pertencem.",
|
||||
"scopeSubdomains": "{{domain}}: este domínio e todos os seus subdomínios",
|
||||
"scopeHostOnly": "{{domain}}: apenas este host exato, sem subdomínios",
|
||||
"modeMerge": "Mesclar",
|
||||
"modeMergeDesc": "Atualiza os cookies armazenados que um cookie colado corresponde, adiciona os demais e não exclui nada.",
|
||||
"modeReplace": "Substituir os sites correspondentes",
|
||||
"modeReplaceDesc": "Exclui os cookies armazenados deste perfil para os sites citados nesta colagem, tanto na forma com ponto quanto sem ponto, e depois grava a colagem. Os cookies de todos os outros sites são mantidos.",
|
||||
"replaceDeleteCount": "Cookies armazenados que seriam excluídos: {{n}}",
|
||||
"unknownCount": "desconhecido",
|
||||
"includeExpired": "Importar também os cookies já expirados",
|
||||
"expiredNote": "Já expirados nesta colagem: {{n}}",
|
||||
"clearsOnCloseWarning": "Este perfil apaga os dados de navegação quando o navegador fecha, então esses cookies serão excluídos ao final da próxima sessão.",
|
||||
"previewTitle": "Cookies a importar: {{n}}",
|
||||
"colSite": "Site",
|
||||
"colName": "Nome",
|
||||
"colPath": "Caminho",
|
||||
"colExpires": "Expira",
|
||||
"colSecure": "Secure",
|
||||
"colHttpOnly": "HttpOnly",
|
||||
"colSameSite": "SameSite",
|
||||
"session": "Sessão",
|
||||
"yes": "Sim",
|
||||
"no": "Não",
|
||||
"sameSiteUnspecified": "Não especificado",
|
||||
"sameSiteNone": "None",
|
||||
"sameSiteLax": "Lax",
|
||||
"sameSiteStrict": "Strict",
|
||||
"issuesTitle": "Ocorrências",
|
||||
"showAll": "Mostrar todas as {{n}}",
|
||||
"showFewer": "Mostrar menos",
|
||||
"sourceLine": "Linha {{n}}",
|
||||
"sourceCookie": "Cookie {{n}}",
|
||||
"disabledEmpty": "Cole cookies acima para importá-los.",
|
||||
"disabledSite": "Informe o site ao qual esses cookies pertencem.",
|
||||
"disabledNoCookies": "Nenhum cookie pôde ser lido desta colagem.",
|
||||
"resultAdded": "Adicionados",
|
||||
"resultOverwritten": "Sobrescritos",
|
||||
"resultDeleted": "Excluídos",
|
||||
"resultSkipped": "Ignorados",
|
||||
"issues": {
|
||||
"emptyInput": "Nada foi colado ainda.",
|
||||
"siteInvalid": "\"{{site}}\" não é um site utilizável e foi ignorado.",
|
||||
"unrecognizedFormat": "Isto não é JSON, nem um cookies.txt do Netscape, nem uma lista nome=valor.",
|
||||
"siteRequired": "Uma lista nome=valor não carrega domínio. Informe o site ao qual esses cookies pertencem.",
|
||||
"noCookiesFound": "Nenhum cookie pôde ser lido desta colagem.",
|
||||
"nameEmpty": "O nome do cookie está vazio.",
|
||||
"nameInvalid": "\"{{name}}\" não é um nome de cookie utilizável.",
|
||||
"nameMissing": "Esta entrada não tem nome.",
|
||||
"valueInvalid": "O valor de \"{{name}}\" contém caracteres que um cookie não pode carregar.",
|
||||
"valueCoerced": "O valor de \"{{name}}\" não era texto, por isso foi convertido para texto.",
|
||||
"domainFromSite": "\"{{name}}\" não trazia domínio e foi associado a {{domain}}.",
|
||||
"domainMissing": "\"{{name}}\" não traz domínio e nenhum site foi informado.",
|
||||
"domainInvalid": "\"{{name}}\" indica um domínio que não pode ser usado: {{domain}}.",
|
||||
"domainAttributeIgnored": "O atributo Domain={{domain}} foi ignorado em favor do site que você informou, {{site}}.",
|
||||
"hostOnlyMismatch": "\"{{name}}\" declara hostOnly={{hostOnly}} mas seu domínio era {{domain}}. A marca foi aplicada.",
|
||||
"pathRepaired": "O caminho de \"{{name}}\" foi corrigido a partir de {{path}}.",
|
||||
"expiryMilliseconds": "A expiração de \"{{name}}\" ({{expires}}) estava em milissegundos e foi convertida para segundos.",
|
||||
"expiryClamped": "Uma expiração estava longe demais no futuro para ser real e foi limitada ao máximo.",
|
||||
"expiryInvalid": "{{field}} não é um carimbo de tempo utilizável: {{value}}.",
|
||||
"expiresInvalid": "Expires não é uma data que possa ser lida: {{value}}.",
|
||||
"maxAgeInvalid": "Max-Age não é um número: {{value}}.",
|
||||
"maxAgeDeletion": "O Max-Age de \"{{name}}\" o exclui imediatamente.",
|
||||
"sameSiteNoneInsecure": "\"{{name}}\" em {{domain}} é SameSite=None mas não Secure, então o navegador se recusará a enviá-lo.",
|
||||
"sameSiteUnrecognized": "O SameSite \"{{value}}\" não foi reconhecido e ficou não especificado.",
|
||||
"duplicateCookie": "\"{{name}}\" para {{domain}}{{path}} aparece novamente mais adiante na colagem. A cópia posterior vence.",
|
||||
"boolCoercedFromString": "{{field}} era o texto \"{{value}}\" em vez de true ou false, e foi lido como booleano.",
|
||||
"boolInvalid": "{{field}} não é nem true nem false: {{value}}.",
|
||||
"quotedValue": "As aspas em torno do valor de \"{{name}}\" foram removidas.",
|
||||
"jsonParseFailed": "Não foi possível ler o JSON: {{message}}",
|
||||
"jsonNotCookieList": "O JSON não é nem uma matriz de cookies nem um objeto que contenha uma matriz cookies.",
|
||||
"jsonEntryNotObject": "Esta entrada não é um objeto JSON.",
|
||||
"netscapePathOmitted": "Esta linha não tem coluna de caminho, então / foi usado.",
|
||||
"netscapeFieldCount": "Esta linha tem {{actual}} colunas; uma linha de cookie Netscape tem {{expected}}.",
|
||||
"netscapeIncludeSubdomainsInvalid": "A coluna de incluir subdomínios não é nem TRUE nem FALSE: {{value}}.",
|
||||
"netscapeSecureInvalid": "A coluna secure não é nem TRUE nem FALSE: {{value}}.",
|
||||
"netscapeExpiryInvalid": "A coluna de expiração não é um número: {{value}}. A linha foi descartada em vez de virar um cookie ativo.",
|
||||
"nameValueNoPair": "Esta parte não tem um par nome=valor e foi ignorada.",
|
||||
"pairTreatedAsAttribute": "\"{{name}}\" foi lido como um atributo Set-Cookie em vez de um cookie, e o seu valor foi descartado.",
|
||||
"unknown": "{{code}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
@@ -1935,6 +2020,10 @@
|
||||
"invalidLaunchHookUrl": "URL do hook de inicialização inválida. Use uma URL completa http:// ou https://.",
|
||||
"cookieDbLocked": "Não foi possível ler os cookies — o banco de dados está bloqueado. Feche o navegador e tente novamente.",
|
||||
"cookieDbUnavailable": "Não foi possível ler os cookies — o repositório de cookies está indisponível.",
|
||||
"cookieImportBrowserRunning": "Não é possível importar cookies enquanto o navegador está em execução. Feche-o e tente novamente.",
|
||||
"cookieImportProfileProtected": "Não é possível importar cookies para um perfil protegido por senha. Remova a senha primeiro.",
|
||||
"cookieImportRemoteSession": "Não é possível importar cookies enquanto uma sessão remota controla este perfil. Aguarde a sincronização terminar.",
|
||||
"cookieImportNoCookies": "Nenhum cookie foi encontrado no que você colou.",
|
||||
"selfHostedRequiresLogout": "Saia da sua conta Donut antes de configurar um servidor auto-hospedado.",
|
||||
"fingerprintRequiresPro": "Visualizar ou editar a impressão digital requer um plano pago ativo. A proteção está incluída em todos os planos.",
|
||||
"proxyNotWorking": "O proxy selecionado não está funcionando, então o perfil não foi criado.",
|
||||
|
||||
@@ -901,12 +901,7 @@
|
||||
"menuItem": "Управление Cookies",
|
||||
"tabImport": "Импорт",
|
||||
"tabExport": "Экспорт",
|
||||
"importDescription": "Импортируйте cookies из файла в формате Netscape или JSON.",
|
||||
"dropPrompt": "Нажмите, чтобы выбрать файл cookies",
|
||||
"fileFormats": "(.txt, .cookies или .json)",
|
||||
"cookiesFound": "Найдено cookies: {{count}}",
|
||||
"importedSuccess": "Импортировано {{imported}} cookies ({{replaced}} заменено)",
|
||||
"linesSkipped": "Пропущено строк: {{count}}",
|
||||
"importDescription": "Вставьте cookie, скопированные из другого браузера или инструмента, либо выберите файл.",
|
||||
"fileReadError": "Не удалось прочитать файл",
|
||||
"loadFailed": "Не удалось загрузить cookies: {{error}}",
|
||||
"cookiesLabel": "Cookies",
|
||||
@@ -915,9 +910,7 @@
|
||||
"deselectAll": "Снять выбор",
|
||||
"noCookies": "Cookies в этом профиле не найдены",
|
||||
"doneButton": "Готово",
|
||||
"importButton": "Импорт",
|
||||
"exportButton": "Экспорт",
|
||||
"backButton": "Назад"
|
||||
"exportButton": "Экспорт"
|
||||
},
|
||||
"import": {
|
||||
"title": "Импорт Cookies",
|
||||
@@ -936,6 +929,98 @@
|
||||
"json": "JSON",
|
||||
"success": "Cookies успешно экспортированы",
|
||||
"error": "Ошибка экспорта cookies"
|
||||
},
|
||||
"paste": {
|
||||
"label": "Cookie",
|
||||
"placeholder": "Вставьте cookie сюда. JSON (массив или объект {cookies: [...]}), cookies.txt в формате Netscape или name=value; name2=value2",
|
||||
"chooseFile": "или выберите файл",
|
||||
"analyzing": "Проверка…",
|
||||
"formatJson": "JSON",
|
||||
"formatNetscape": "Netscape",
|
||||
"formatNameValue": "Name=Value",
|
||||
"formatUnknown": "Формат не распознан",
|
||||
"siteLabel": "Сайт",
|
||||
"sitePlaceholder": "example.com или https://example.com",
|
||||
"siteHelp": "Список name=value не содержит домена, поэтому укажите сайт, к которому относятся эти cookie.",
|
||||
"scopeSubdomains": "{{domain}}: этот домен и все его поддомены",
|
||||
"scopeHostOnly": "{{domain}}: только этот хост, без поддоменов",
|
||||
"modeMerge": "Объединить",
|
||||
"modeMergeDesc": "Обновляет сохранённые cookie, совпавшие с вставленными, добавляет остальные и ничего не удаляет.",
|
||||
"modeReplace": "Заменить совпадающие сайты",
|
||||
"modeReplaceDesc": "Удаляет сохранённые cookie этого профиля для сайтов из этой вставки, как с точкой в начале, так и без неё, а затем записывает вставленное. Cookie всех остальных сайтов сохраняются.",
|
||||
"replaceDeleteCount": "Сохранённых cookie будет удалено: {{n}}",
|
||||
"unknownCount": "неизвестно",
|
||||
"includeExpired": "Импортировать также уже истёкшие cookie",
|
||||
"expiredNote": "Уже истекли в этой вставке: {{n}}",
|
||||
"clearsOnCloseWarning": "Этот профиль стирает данные просмотра при закрытии браузера, поэтому эти cookie будут удалены в конце следующего сеанса.",
|
||||
"previewTitle": "Cookie к импорту: {{n}}",
|
||||
"colSite": "Сайт",
|
||||
"colName": "Имя",
|
||||
"colPath": "Путь",
|
||||
"colExpires": "Истекает",
|
||||
"colSecure": "Secure",
|
||||
"colHttpOnly": "HttpOnly",
|
||||
"colSameSite": "SameSite",
|
||||
"session": "Сеанс",
|
||||
"yes": "Да",
|
||||
"no": "Нет",
|
||||
"sameSiteUnspecified": "Не указано",
|
||||
"sameSiteNone": "None",
|
||||
"sameSiteLax": "Lax",
|
||||
"sameSiteStrict": "Strict",
|
||||
"issuesTitle": "Замечания",
|
||||
"showAll": "Показать все: {{n}}",
|
||||
"showFewer": "Свернуть",
|
||||
"sourceLine": "Строка {{n}}",
|
||||
"sourceCookie": "Cookie {{n}}",
|
||||
"disabledEmpty": "Вставьте cookie выше, чтобы их импортировать.",
|
||||
"disabledSite": "Укажите сайт, к которому относятся эти cookie.",
|
||||
"disabledNoCookies": "Из этой вставки не удалось прочитать ни одной cookie.",
|
||||
"resultAdded": "Добавлено",
|
||||
"resultOverwritten": "Перезаписано",
|
||||
"resultDeleted": "Удалено",
|
||||
"resultSkipped": "Пропущено",
|
||||
"issues": {
|
||||
"emptyInput": "Пока ничего не вставлено.",
|
||||
"siteInvalid": "«{{site}}» не является пригодным сайтом и был проигнорирован.",
|
||||
"unrecognizedFormat": "Это не JSON, не cookies.txt в формате Netscape и не список name=value.",
|
||||
"siteRequired": "Список name=value не содержит домена. Укажите сайт, к которому относятся эти cookie.",
|
||||
"noCookiesFound": "Из этой вставки не удалось прочитать ни одной cookie.",
|
||||
"nameEmpty": "Имя cookie пустое.",
|
||||
"nameInvalid": "«{{name}}» не является пригодным именем cookie.",
|
||||
"nameMissing": "У этой записи нет имени.",
|
||||
"valueInvalid": "Значение «{{name}}» содержит символы, недопустимые в cookie.",
|
||||
"valueCoerced": "Значение «{{name}}» не было текстом, поэтому оно преобразовано в текст.",
|
||||
"domainFromSite": "У «{{name}}» не было домена, она привязана к {{domain}}.",
|
||||
"domainMissing": "У «{{name}}» нет домена, и сайт не указан.",
|
||||
"domainInvalid": "«{{name}}» указывает непригодный домен: {{domain}}.",
|
||||
"domainAttributeIgnored": "Атрибут Domain={{domain}} проигнорирован в пользу указанного вами сайта {{site}}.",
|
||||
"hostOnlyMismatch": "У «{{name}}» указано hostOnly={{hostOnly}}, но домен был {{domain}}. Применён флаг.",
|
||||
"pathRepaired": "Путь «{{name}}» исправлен из {{path}}.",
|
||||
"expiryMilliseconds": "Срок действия «{{name}}» ({{expires}}) был в миллисекундах и переведён в секунды.",
|
||||
"expiryClamped": "Срок действия был слишком далёким, чтобы быть настоящим, и ограничен максимумом.",
|
||||
"expiryInvalid": "{{field}} не является пригодной меткой времени: {{value}}.",
|
||||
"expiresInvalid": "Expires не является читаемой датой: {{value}}.",
|
||||
"maxAgeInvalid": "Max-Age не является числом: {{value}}.",
|
||||
"maxAgeDeletion": "Max-Age у «{{name}}» удаляет её немедленно.",
|
||||
"sameSiteNoneInsecure": "«{{name}}» на {{domain}} имеет SameSite=None без Secure, поэтому браузер откажется её отправлять.",
|
||||
"sameSiteUnrecognized": "Значение SameSite «{{value}}» не распознано и оставлено неуказанным.",
|
||||
"duplicateCookie": "«{{name}}» для {{domain}}{{path}} встречается дальше в вставке ещё раз. Побеждает последняя копия.",
|
||||
"boolCoercedFromString": "{{field}} было текстом «{{value}}» вместо true или false и было прочитано как логическое значение.",
|
||||
"boolInvalid": "{{field}} не равно ни true, ни false: {{value}}.",
|
||||
"quotedValue": "Кавычки вокруг значения «{{name}}» удалены.",
|
||||
"jsonParseFailed": "Не удалось прочитать JSON: {{message}}",
|
||||
"jsonNotCookieList": "Этот JSON не является ни массивом cookie, ни объектом с массивом cookies.",
|
||||
"jsonEntryNotObject": "Эта запись не является объектом JSON.",
|
||||
"netscapePathOmitted": "В этой строке нет столбца пути, поэтому использован /.",
|
||||
"netscapeFieldCount": "В этой строке {{actual}} столбцов; в строке cookie Netscape их {{expected}}.",
|
||||
"netscapeIncludeSubdomainsInvalid": "Столбец включения поддоменов не равен ни TRUE, ни FALSE: {{value}}.",
|
||||
"netscapeSecureInvalid": "Столбец secure не равен ни TRUE, ни FALSE: {{value}}.",
|
||||
"netscapeExpiryInvalid": "Столбец срока действия не является числом: {{value}}. Строка отброшена, а не превращена в действующую cookie.",
|
||||
"nameValueNoPair": "В этой части нет пары name=value, она проигнорирована.",
|
||||
"pairTreatedAsAttribute": "«{{name}}» прочитано как атрибут Set-Cookie, а не как куки, и его значение отброшено.",
|
||||
"unknown": "{{code}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
@@ -1942,6 +2027,10 @@
|
||||
"invalidLaunchHookUrl": "Неверный URL хука запуска. Используйте полный URL http:// или https://.",
|
||||
"cookieDbLocked": "Не удалось прочитать куки — база данных заблокирована. Закройте браузер и попробуйте снова.",
|
||||
"cookieDbUnavailable": "Не удалось прочитать куки — хранилище куки недоступно.",
|
||||
"cookieImportBrowserRunning": "Нельзя импортировать куки, пока браузер запущен. Закройте его и попробуйте снова.",
|
||||
"cookieImportProfileProtected": "Нельзя импортировать куки в профиль, защищённый паролем. Сначала снимите пароль.",
|
||||
"cookieImportRemoteSession": "Нельзя импортировать куки, пока профиль занят удалённой сессией. Дождитесь окончания синхронизации.",
|
||||
"cookieImportNoCookies": "В том, что вы вставили, куки не найдены.",
|
||||
"selfHostedRequiresLogout": "Выйдите из аккаунта Donut, прежде чем настраивать собственный сервер.",
|
||||
"fingerprintRequiresPro": "Для просмотра или редактирования отпечатка требуется активный платный план. Защита включена во все планы.",
|
||||
"proxyNotWorking": "Выбранный прокси не работает, поэтому профиль не создан.",
|
||||
|
||||
@@ -895,12 +895,7 @@
|
||||
"menuItem": "Çerez Yönetimi",
|
||||
"tabImport": "İçe Aktar",
|
||||
"tabExport": "Dışa Aktar",
|
||||
"importDescription": "Netscape veya JSON biçimindeki bir dosyadan çerez içe aktarın.",
|
||||
"dropPrompt": "Bir çerez dosyası seçmek için tıklayın",
|
||||
"fileFormats": "(.txt, .cookies veya .json)",
|
||||
"cookiesFound": "{{count}} çerez bulundu",
|
||||
"importedSuccess": "{{imported}} çerez başarıyla içe aktarıldı ({{replaced}} değiştirildi)",
|
||||
"linesSkipped": "{{count}} satır atlandı",
|
||||
"importDescription": "Başka bir tarayıcıdan veya araçtan kopyalanan çerezleri yapıştırın ya da bir dosya seçin.",
|
||||
"fileReadError": "Dosya okunamadı",
|
||||
"loadFailed": "Çerezler yüklenemedi: {{error}}",
|
||||
"cookiesLabel": "Çerezler",
|
||||
@@ -909,9 +904,7 @@
|
||||
"deselectAll": "Tüm seçimleri kaldır",
|
||||
"noCookies": "Bu profilde çerez bulunamadı",
|
||||
"doneButton": "Bitti",
|
||||
"importButton": "İçe Aktar",
|
||||
"exportButton": "Dışa Aktar",
|
||||
"backButton": "Geri"
|
||||
"exportButton": "Dışa Aktar"
|
||||
},
|
||||
"import": {
|
||||
"title": "Çerezleri İçe Aktar",
|
||||
@@ -930,6 +923,98 @@
|
||||
"json": "JSON",
|
||||
"success": "Çerezler başarıyla dışa aktarıldı",
|
||||
"error": "Çerezler dışa aktarılamadı"
|
||||
},
|
||||
"paste": {
|
||||
"label": "Çerezler",
|
||||
"placeholder": "Çerezleri buraya yapıştırın. JSON (bir dizi veya {cookies: [...]} nesnesi), Netscape cookies.txt ya da ad=değer; ad2=değer2",
|
||||
"chooseFile": "veya bir dosya seçin",
|
||||
"analyzing": "Denetleniyor…",
|
||||
"formatJson": "JSON",
|
||||
"formatNetscape": "Netscape",
|
||||
"formatNameValue": "Ad=Değer",
|
||||
"formatUnknown": "Biçim tanınamadı",
|
||||
"siteLabel": "Site",
|
||||
"sitePlaceholder": "ornek.com veya https://ornek.com",
|
||||
"siteHelp": "ad=değer listesi kendi alan adını taşımaz, bu nedenle bu çerezlerin ait olduğu siteyi belirtin.",
|
||||
"scopeSubdomains": "{{domain}}: bu alan adı ve tüm alt alan adları",
|
||||
"scopeHostOnly": "{{domain}}: yalnızca bu ana bilgisayar, alt alan adları hariç",
|
||||
"modeMerge": "Birleştir",
|
||||
"modeMergeDesc": "Yapıştırılan bir çerezle eşleşen kayıtlı çerezleri günceller, kalanları ekler ve hiçbir şeyi silmez.",
|
||||
"modeReplace": "Eşleşen siteleri değiştir",
|
||||
"modeReplaceDesc": "Bu yapıştırmada geçen siteler için bu profilin kayıtlı çerezlerini hem noktalı hem noktasız biçimiyle siler, ardından yapıştırılanı yazar. Diğer tüm sitelerin çerezleri korunur.",
|
||||
"replaceDeleteCount": "Silinecek kayıtlı çerez sayısı: {{n}}",
|
||||
"unknownCount": "bilinmiyor",
|
||||
"includeExpired": "Süresi dolmuş çerezleri de içe aktar",
|
||||
"expiredNote": "Bu yapıştırmada süresi dolmuş olan: {{n}}",
|
||||
"clearsOnCloseWarning": "Bu profil, tarayıcı kapanınca gezinme verilerini siler; bu nedenle bu çerezler bir sonraki oturumun sonunda silinecek.",
|
||||
"previewTitle": "İçe aktarılacak çerezler: {{n}}",
|
||||
"colSite": "Site",
|
||||
"colName": "Ad",
|
||||
"colPath": "Yol",
|
||||
"colExpires": "Bitiş",
|
||||
"colSecure": "Secure",
|
||||
"colHttpOnly": "HttpOnly",
|
||||
"colSameSite": "SameSite",
|
||||
"session": "Oturum",
|
||||
"yes": "Evet",
|
||||
"no": "Hayır",
|
||||
"sameSiteUnspecified": "Belirtilmemiş",
|
||||
"sameSiteNone": "None",
|
||||
"sameSiteLax": "Lax",
|
||||
"sameSiteStrict": "Strict",
|
||||
"issuesTitle": "Sorunlar",
|
||||
"showAll": "{{n}} tanının tümünü göster",
|
||||
"showFewer": "Daha az göster",
|
||||
"sourceLine": "Satır {{n}}",
|
||||
"sourceCookie": "Çerez {{n}}",
|
||||
"disabledEmpty": "İçe aktarmak için yukarıya çerez yapıştırın.",
|
||||
"disabledSite": "Bu çerezlerin ait olduğu siteyi belirtin.",
|
||||
"disabledNoCookies": "Bu yapıştırmadan hiçbir çerez okunamadı.",
|
||||
"resultAdded": "Eklendi",
|
||||
"resultOverwritten": "Üzerine yazıldı",
|
||||
"resultDeleted": "Silindi",
|
||||
"resultSkipped": "Atlandı",
|
||||
"issues": {
|
||||
"emptyInput": "Henüz hiçbir şey yapıştırılmadı.",
|
||||
"siteInvalid": "\"{{site}}\" kullanılabilir bir site değil ve yok sayıldı.",
|
||||
"unrecognizedFormat": "Bu ne JSON, ne Netscape cookies.txt, ne de ad=değer listesi.",
|
||||
"siteRequired": "ad=değer listesi alan adı taşımaz. Bu çerezlerin ait olduğu siteyi belirtin.",
|
||||
"noCookiesFound": "Bu yapıştırmadan hiçbir çerez okunamadı.",
|
||||
"nameEmpty": "Çerez adı boş.",
|
||||
"nameInvalid": "\"{{name}}\" kullanılabilir bir çerez adı değil.",
|
||||
"nameMissing": "Bu kaydın adı yok.",
|
||||
"valueInvalid": "\"{{name}}\" değeri, bir çerezin taşıyamayacağı karakterler içeriyor.",
|
||||
"valueCoerced": "\"{{name}}\" değeri metin değildi, bu yüzden metne dönüştürüldü.",
|
||||
"domainFromSite": "\"{{name}}\" alan adı taşımıyordu ve {{domain}} ile ilişkilendirildi.",
|
||||
"domainMissing": "\"{{name}}\" alan adı taşımıyor ve site de belirtilmedi.",
|
||||
"domainInvalid": "\"{{name}}\" kullanılamayan bir alan adı belirtiyor: {{domain}}.",
|
||||
"domainAttributeIgnored": "Domain={{domain}} özelliği yok sayıldı; bunun yerine belirttiğiniz site {{site}} kullanıldı.",
|
||||
"hostOnlyMismatch": "\"{{name}}\" hostOnly={{hostOnly}} diyor ancak alan adı {{domain}} idi. Bayrak uygulandı.",
|
||||
"pathRepaired": "\"{{name}}\" yolu {{path}} değerinden düzeltildi.",
|
||||
"expiryMilliseconds": "\"{{name}}\" bitiş zamanı ({{expires}}) milisaniye cinsindendi ve saniyeye çevrildi.",
|
||||
"expiryClamped": "Bir bitiş zamanı gerçek olamayacak kadar uzaktaydı ve azami değere sınırlandı.",
|
||||
"expiryInvalid": "{{field}} kullanılabilir bir zaman damgası değil: {{value}}.",
|
||||
"expiresInvalid": "Expires okunabilir bir tarih değil: {{value}}.",
|
||||
"maxAgeInvalid": "Max-Age bir sayı değil: {{value}}.",
|
||||
"maxAgeDeletion": "\"{{name}}\" üzerindeki Max-Age onu hemen siler.",
|
||||
"sameSiteNoneInsecure": "{{domain}} üzerindeki \"{{name}}\" SameSite=None ancak Secure değil, bu yüzden tarayıcı onu göndermeyi reddedecek.",
|
||||
"sameSiteUnrecognized": "SameSite \"{{value}}\" tanınamadı ve belirtilmemiş bırakıldı.",
|
||||
"duplicateCookie": "{{domain}}{{path}} için \"{{name}}\" yapıştırmanın ilerisinde yeniden geçiyor. Sonraki kopya geçerli olur.",
|
||||
"boolCoercedFromString": "{{field}} true veya false yerine \"{{value}}\" metniydi ve mantıksal değer olarak okundu.",
|
||||
"boolInvalid": "{{field}} ne true ne de false: {{value}}.",
|
||||
"quotedValue": "\"{{name}}\" değerinin çevresindeki tırnaklar kaldırıldı.",
|
||||
"jsonParseFailed": "JSON okunamadı: {{message}}",
|
||||
"jsonNotCookieList": "Bu JSON ne bir çerez dizisi ne de cookies dizisi içeren bir nesne.",
|
||||
"jsonEntryNotObject": "Bu kayıt bir JSON nesnesi değil.",
|
||||
"netscapePathOmitted": "Bu satırda yol sütunu yok, bu yüzden / kullanıldı.",
|
||||
"netscapeFieldCount": "Bu satırda {{actual}} sütun var; bir Netscape çerez satırında {{expected}} sütun bulunur.",
|
||||
"netscapeIncludeSubdomainsInvalid": "Alt alan adlarını dahil etme sütunu ne TRUE ne de FALSE: {{value}}.",
|
||||
"netscapeSecureInvalid": "secure sütunu ne TRUE ne de FALSE: {{value}}.",
|
||||
"netscapeExpiryInvalid": "Bitiş sütunu bir sayı değil: {{value}}. Satır, geçerli bir çereze dönüştürülmek yerine atıldı.",
|
||||
"nameValueNoPair": "Bu parçada ad=değer çifti yok ve yok sayıldı.",
|
||||
"pairTreatedAsAttribute": "\"{{name}}\" bir çerez yerine Set-Cookie özelliği olarak okundu ve değeri atıldı.",
|
||||
"unknown": "{{code}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
@@ -1928,6 +2013,10 @@
|
||||
"invalidLaunchHookUrl": "Geçersiz başlatma kancası URL'si. Tam bir http:// veya https:// URL'si kullanın.",
|
||||
"cookieDbLocked": "Çerezler okunamadı — veritabanı kilitli. Tarayıcıyı kapatıp yeniden deneyin.",
|
||||
"cookieDbUnavailable": "Çerezler okunamadı — çerez deposu kullanılamıyor.",
|
||||
"cookieImportBrowserRunning": "Tarayıcı çalışırken çerezler içe aktarılamaz. Tarayıcıyı kapatıp yeniden deneyin.",
|
||||
"cookieImportProfileProtected": "Parola korumalı bir profile çerez içe aktarılamaz. Önce parolayı kaldırın.",
|
||||
"cookieImportRemoteSession": "Bu profili bir uzak oturum kullanırken çerezler içe aktarılamaz. Eşitlemenin bitmesini bekleyin.",
|
||||
"cookieImportNoCookies": "Yapıştırdığınız içerikte çerez bulunamadı.",
|
||||
"selfHostedRequiresLogout": "Kendi sunucunuzu yapılandırmadan önce Donut hesabınızdan çıkış yapın.",
|
||||
"fingerprintRequiresPro": "Parmak izini görüntülemek veya düzenlemek etkin bir ücretli plan gerektirir. Koruma tüm planlara dahildir.",
|
||||
"proxyNotWorking": "Seçilen proxy çalışmıyor, bu nedenle profil oluşturulmadı.",
|
||||
|
||||
@@ -895,12 +895,7 @@
|
||||
"menuItem": "Quản lý cookie",
|
||||
"tabImport": "Nhập",
|
||||
"tabExport": "Xuất",
|
||||
"importDescription": "Nhập cookie từ tệp định dạng Netscape hoặc JSON.",
|
||||
"dropPrompt": "Nhấn để chọn tệp cookie",
|
||||
"fileFormats": "(.txt, .cookies, hoặc .json)",
|
||||
"cookiesFound": "Tìm thấy {{count}} cookie",
|
||||
"importedSuccess": "Đã nhập thành công {{imported}} cookie (đã thay thế {{replaced}})",
|
||||
"linesSkipped": "Đã bỏ qua {{count}} dòng",
|
||||
"importDescription": "Dán cookie đã sao chép từ trình duyệt hoặc công cụ khác, hoặc chọn một tệp.",
|
||||
"fileReadError": "Đọc tệp thất bại",
|
||||
"loadFailed": "Tải cookie thất bại: {{error}}",
|
||||
"cookiesLabel": "Cookie",
|
||||
@@ -909,9 +904,7 @@
|
||||
"deselectAll": "Bỏ chọn tất cả",
|
||||
"noCookies": "Không tìm thấy cookie trong profile này",
|
||||
"doneButton": "Xong",
|
||||
"importButton": "Nhập",
|
||||
"exportButton": "Xuất",
|
||||
"backButton": "Quay lại"
|
||||
"exportButton": "Xuất"
|
||||
},
|
||||
"import": {
|
||||
"title": "Nhập cookie",
|
||||
@@ -930,6 +923,98 @@
|
||||
"json": "JSON",
|
||||
"success": "Xuất cookie thành công",
|
||||
"error": "Xuất cookie thất bại"
|
||||
},
|
||||
"paste": {
|
||||
"label": "Cookie",
|
||||
"placeholder": "Dán cookie vào đây. JSON (một mảng hoặc một đối tượng {cookies: [...]}), cookies.txt kiểu Netscape, hoặc name=value; name2=value2",
|
||||
"chooseFile": "hoặc chọn một tệp",
|
||||
"analyzing": "Đang kiểm tra…",
|
||||
"formatJson": "JSON",
|
||||
"formatNetscape": "Netscape",
|
||||
"formatNameValue": "Name=Value",
|
||||
"formatUnknown": "Không nhận ra định dạng",
|
||||
"siteLabel": "Trang",
|
||||
"sitePlaceholder": "example.com hoặc https://example.com",
|
||||
"siteHelp": "Danh sách name=value không mang tên miền riêng, vì vậy hãy chỉ rõ trang mà những cookie này thuộc về.",
|
||||
"scopeSubdomains": "{{domain}}: tên miền này và mọi tên miền phụ",
|
||||
"scopeHostOnly": "{{domain}}: chỉ đúng máy chủ này, không gồm tên miền phụ",
|
||||
"modeMerge": "Hợp nhất",
|
||||
"modeMergeDesc": "Cập nhật các cookie đã lưu trùng với cookie được dán, thêm phần còn lại và không xóa gì.",
|
||||
"modeReplace": "Thay thế các trang trùng",
|
||||
"modeReplaceDesc": "Xóa cookie đã lưu của hồ sơ này cho các trang có trong lần dán này, ở cả dạng có dấu chấm và không dấu chấm, rồi ghi nội dung đã dán. Cookie của mọi trang khác được giữ nguyên.",
|
||||
"replaceDeleteCount": "Số cookie đã lưu sẽ bị xóa: {{n}}",
|
||||
"unknownCount": "không rõ",
|
||||
"includeExpired": "Nhập cả cookie đã hết hạn",
|
||||
"expiredNote": "Đã hết hạn trong lần dán này: {{n}}",
|
||||
"clearsOnCloseWarning": "Hồ sơ này xóa dữ liệu duyệt web khi đóng trình duyệt, nên những cookie này sẽ bị xóa vào cuối phiên kế tiếp.",
|
||||
"previewTitle": "Cookie sẽ nhập: {{n}}",
|
||||
"colSite": "Trang",
|
||||
"colName": "Tên",
|
||||
"colPath": "Đường dẫn",
|
||||
"colExpires": "Hết hạn",
|
||||
"colSecure": "Secure",
|
||||
"colHttpOnly": "HttpOnly",
|
||||
"colSameSite": "SameSite",
|
||||
"session": "Phiên",
|
||||
"yes": "Có",
|
||||
"no": "Không",
|
||||
"sameSiteUnspecified": "Không xác định",
|
||||
"sameSiteNone": "None",
|
||||
"sameSiteLax": "Lax",
|
||||
"sameSiteStrict": "Strict",
|
||||
"issuesTitle": "Vấn đề",
|
||||
"showAll": "Hiển thị tất cả {{n}}",
|
||||
"showFewer": "Thu gọn",
|
||||
"sourceLine": "Dòng {{n}}",
|
||||
"sourceCookie": "Cookie {{n}}",
|
||||
"disabledEmpty": "Dán cookie ở trên để nhập.",
|
||||
"disabledSite": "Hãy chỉ rõ trang mà những cookie này thuộc về.",
|
||||
"disabledNoCookies": "Không đọc được cookie nào từ lần dán này.",
|
||||
"resultAdded": "Đã thêm",
|
||||
"resultOverwritten": "Đã ghi đè",
|
||||
"resultDeleted": "Đã xóa",
|
||||
"resultSkipped": "Đã bỏ qua",
|
||||
"issues": {
|
||||
"emptyInput": "Chưa dán nội dung nào.",
|
||||
"siteInvalid": "\"{{site}}\" không phải là trang dùng được nên đã bị bỏ qua.",
|
||||
"unrecognizedFormat": "Đây không phải JSON, cookies.txt kiểu Netscape hay danh sách name=value.",
|
||||
"siteRequired": "Danh sách name=value không mang tên miền. Hãy chỉ rõ trang mà những cookie này thuộc về.",
|
||||
"noCookiesFound": "Không đọc được cookie nào từ lần dán này.",
|
||||
"nameEmpty": "Tên cookie bỏ trống.",
|
||||
"nameInvalid": "\"{{name}}\" không phải là tên cookie dùng được.",
|
||||
"nameMissing": "Mục này không có tên.",
|
||||
"valueInvalid": "Giá trị của \"{{name}}\" chứa ký tự mà cookie không thể mang.",
|
||||
"valueCoerced": "Giá trị của \"{{name}}\" không phải văn bản nên đã được chuyển thành văn bản.",
|
||||
"domainFromSite": "\"{{name}}\" không có tên miền nên đã được gắn vào {{domain}}.",
|
||||
"domainMissing": "\"{{name}}\" không có tên miền và cũng không có trang nào được chỉ định.",
|
||||
"domainInvalid": "\"{{name}}\" chỉ định một tên miền không dùng được: {{domain}}.",
|
||||
"domainAttributeIgnored": "Thuộc tính Domain={{domain}} đã bị bỏ qua để dùng trang bạn chỉ định, {{site}}.",
|
||||
"hostOnlyMismatch": "\"{{name}}\" ghi hostOnly={{hostOnly}} nhưng tên miền lại là {{domain}}. Cờ đã được áp dụng.",
|
||||
"pathRepaired": "Đường dẫn của \"{{name}}\" đã được sửa từ {{path}}.",
|
||||
"expiryMilliseconds": "Hạn của \"{{name}}\" ({{expires}}) tính bằng mili giây và đã được đổi sang giây.",
|
||||
"expiryClamped": "Một thời hạn xa đến mức không thực tế nên đã bị giới hạn ở mức tối đa.",
|
||||
"expiryInvalid": "{{field}} không phải dấu thời gian dùng được: {{value}}.",
|
||||
"expiresInvalid": "Expires không phải ngày có thể đọc: {{value}}.",
|
||||
"maxAgeInvalid": "Max-Age không phải số: {{value}}.",
|
||||
"maxAgeDeletion": "Max-Age trên \"{{name}}\" xóa nó ngay lập tức.",
|
||||
"sameSiteNoneInsecure": "\"{{name}}\" trên {{domain}} có SameSite=None nhưng không có Secure, nên trình duyệt sẽ từ chối gửi nó.",
|
||||
"sameSiteUnrecognized": "Không nhận ra SameSite \"{{value}}\" nên để là không xác định.",
|
||||
"duplicateCookie": "\"{{name}}\" cho {{domain}}{{path}} xuất hiện lại ở phần sau của nội dung dán. Bản sau được dùng.",
|
||||
"boolCoercedFromString": "{{field}} là văn bản \"{{value}}\" thay vì true hoặc false, và đã được đọc như giá trị luận lý.",
|
||||
"boolInvalid": "{{field}} không phải true cũng không phải false: {{value}}.",
|
||||
"quotedValue": "Đã bỏ dấu nháy quanh giá trị của \"{{name}}\".",
|
||||
"jsonParseFailed": "Không đọc được JSON: {{message}}",
|
||||
"jsonNotCookieList": "JSON này không phải mảng cookie cũng không phải đối tượng chứa mảng cookies.",
|
||||
"jsonEntryNotObject": "Mục này không phải đối tượng JSON.",
|
||||
"netscapePathOmitted": "Dòng này không có cột đường dẫn nên đã dùng /.",
|
||||
"netscapeFieldCount": "Dòng này có {{actual}} cột; một dòng cookie Netscape có {{expected}} cột.",
|
||||
"netscapeIncludeSubdomainsInvalid": "Cột bao gồm tên miền phụ không phải TRUE cũng không phải FALSE: {{value}}.",
|
||||
"netscapeSecureInvalid": "Cột secure không phải TRUE cũng không phải FALSE: {{value}}.",
|
||||
"netscapeExpiryInvalid": "Cột hết hạn không phải số: {{value}}. Dòng này đã bị bỏ thay vì biến thành một cookie còn hiệu lực.",
|
||||
"nameValueNoPair": "Phần này không có cặp name=value nên đã bị bỏ qua.",
|
||||
"pairTreatedAsAttribute": "\"{{name}}\" được đọc là thuộc tính Set-Cookie chứ không phải cookie, và giá trị của nó đã bị bỏ.",
|
||||
"unknown": "{{code}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
@@ -1928,6 +2013,10 @@
|
||||
"invalidLaunchHookUrl": "URL hook khởi chạy không hợp lệ. Sử dụng URL http:// hoặc https:// đầy đủ.",
|
||||
"cookieDbLocked": "Không thể đọc cookie — cơ sở dữ liệu bị khóa. Đóng trình duyệt và thử lại.",
|
||||
"cookieDbUnavailable": "Không thể đọc cookie — kho cookie không khả dụng.",
|
||||
"cookieImportBrowserRunning": "Không thể nhập cookie khi trình duyệt đang chạy. Hãy đóng trình duyệt và thử lại.",
|
||||
"cookieImportProfileProtected": "Không thể nhập cookie vào hồ sơ được bảo vệ bằng mật khẩu. Hãy gỡ mật khẩu trước.",
|
||||
"cookieImportRemoteSession": "Không thể nhập cookie khi một phiên từ xa đang giữ hồ sơ này. Hãy đợi quá trình đồng bộ hoàn tất.",
|
||||
"cookieImportNoCookies": "Không tìm thấy cookie nào trong nội dung bạn đã dán.",
|
||||
"selfHostedRequiresLogout": "Đăng xuất khỏi tài khoản Donut trước khi cấu hình máy chủ tự lưu trữ.",
|
||||
"fingerprintRequiresPro": "Xem hoặc chỉnh sửa vân tay yêu cầu gói trả phí đang hoạt động. Tính năng bảo vệ được bao gồm trong mọi gói.",
|
||||
"proxyNotWorking": "Proxy đã chọn không hoạt động, nên profile chưa được tạo.",
|
||||
|
||||
@@ -895,12 +895,7 @@
|
||||
"menuItem": "Cookie 管理",
|
||||
"tabImport": "导入",
|
||||
"tabExport": "导出",
|
||||
"importDescription": "从 Netscape 或 JSON 格式的文件导入 Cookies。",
|
||||
"dropPrompt": "点击选择 Cookie 文件",
|
||||
"fileFormats": "(.txt、.cookies 或 .json)",
|
||||
"cookiesFound": "找到 {{count}} 个 Cookie",
|
||||
"importedSuccess": "已成功导入 {{imported}} 个 Cookie (替换 {{replaced}} 个)",
|
||||
"linesSkipped": "已跳过 {{count}} 行",
|
||||
"importDescription": "粘贴从其他浏览器或工具复制的 Cookie,或选择一个文件。",
|
||||
"fileReadError": "读取文件失败",
|
||||
"loadFailed": "加载 Cookie 失败: {{error}}",
|
||||
"cookiesLabel": "Cookies",
|
||||
@@ -909,9 +904,7 @@
|
||||
"deselectAll": "取消全选",
|
||||
"noCookies": "此配置文件中未找到 Cookie",
|
||||
"doneButton": "完成",
|
||||
"importButton": "导入",
|
||||
"exportButton": "导出",
|
||||
"backButton": "返回"
|
||||
"exportButton": "导出"
|
||||
},
|
||||
"import": {
|
||||
"title": "导入 Cookies",
|
||||
@@ -930,6 +923,98 @@
|
||||
"json": "JSON",
|
||||
"success": "Cookies 导出成功",
|
||||
"error": "导出 Cookies 失败"
|
||||
},
|
||||
"paste": {
|
||||
"label": "Cookie",
|
||||
"placeholder": "在此粘贴 Cookie。JSON(数组或 {cookies: [...]} 对象)、Netscape cookies.txt,或 name=value; name2=value2",
|
||||
"chooseFile": "或选择文件",
|
||||
"analyzing": "检查中…",
|
||||
"formatJson": "JSON",
|
||||
"formatNetscape": "Netscape",
|
||||
"formatNameValue": "Name=Value",
|
||||
"formatUnknown": "无法识别格式",
|
||||
"siteLabel": "站点",
|
||||
"sitePlaceholder": "example.com 或 https://example.com",
|
||||
"siteHelp": "name=value 列表自身不带域名,请指定这些 Cookie 所属的站点。",
|
||||
"scopeSubdomains": "{{domain}}:该域名及其全部子域名",
|
||||
"scopeHostOnly": "{{domain}}:仅限该主机,不包含子域名",
|
||||
"modeMerge": "合并",
|
||||
"modeMergeDesc": "更新与粘贴内容匹配的已存 Cookie,添加其余的,不删除任何内容。",
|
||||
"modeReplace": "替换匹配的站点",
|
||||
"modeReplaceDesc": "先删除本配置文件中属于本次粘贴所列站点的已存 Cookie(包括带点和不带点两种形式),然后写入粘贴内容。其他站点的 Cookie 均保留。",
|
||||
"replaceDeleteCount": "将被删除的已存 Cookie:{{n}}",
|
||||
"unknownCount": "未知",
|
||||
"includeExpired": "同时导入已过期的 Cookie",
|
||||
"expiredNote": "本次粘贴中已过期:{{n}}",
|
||||
"clearsOnCloseWarning": "本配置文件会在浏览器关闭时清除浏览数据,因此这些 Cookie 将在下一会话结束时被删除。",
|
||||
"previewTitle": "待导入 Cookie:{{n}}",
|
||||
"colSite": "站点",
|
||||
"colName": "名称",
|
||||
"colPath": "路径",
|
||||
"colExpires": "过期时间",
|
||||
"colSecure": "Secure",
|
||||
"colHttpOnly": "HttpOnly",
|
||||
"colSameSite": "SameSite",
|
||||
"session": "会话",
|
||||
"yes": "是",
|
||||
"no": "否",
|
||||
"sameSiteUnspecified": "未指定",
|
||||
"sameSiteNone": "None",
|
||||
"sameSiteLax": "Lax",
|
||||
"sameSiteStrict": "Strict",
|
||||
"issuesTitle": "问题",
|
||||
"showAll": "显示全部 {{n}} 条",
|
||||
"showFewer": "收起",
|
||||
"sourceLine": "第 {{n}} 行",
|
||||
"sourceCookie": "第 {{n}} 个 Cookie",
|
||||
"disabledEmpty": "请在上方粘贴 Cookie 后导入。",
|
||||
"disabledSite": "请指定这些 Cookie 所属的站点。",
|
||||
"disabledNoCookies": "无法从本次粘贴中读取任何 Cookie。",
|
||||
"resultAdded": "已添加",
|
||||
"resultOverwritten": "已覆盖",
|
||||
"resultDeleted": "已删除",
|
||||
"resultSkipped": "已跳过",
|
||||
"issues": {
|
||||
"emptyInput": "尚未粘贴任何内容。",
|
||||
"siteInvalid": "“{{site}}” 不是可用的站点,已忽略。",
|
||||
"unrecognizedFormat": "这既不是 JSON,也不是 Netscape cookies.txt 或 name=value 列表。",
|
||||
"siteRequired": "name=value 列表不带域名。请指定这些 Cookie 所属的站点。",
|
||||
"noCookiesFound": "无法从本次粘贴中读取任何 Cookie。",
|
||||
"nameEmpty": "Cookie 名称为空。",
|
||||
"nameInvalid": "“{{name}}” 不是可用的 Cookie 名称。",
|
||||
"nameMissing": "此条目没有名称。",
|
||||
"valueInvalid": "“{{name}}” 的值包含 Cookie 无法承载的字符。",
|
||||
"valueCoerced": "“{{name}}” 的值不是文本,已转换为文本。",
|
||||
"domainFromSite": "“{{name}}” 未带域名,已关联到 {{domain}}。",
|
||||
"domainMissing": "“{{name}}” 未带域名,也没有指定站点。",
|
||||
"domainInvalid": "“{{name}}” 指定了无法使用的域名:{{domain}}。",
|
||||
"domainAttributeIgnored": "Domain={{domain}} 属性已被忽略,改用你指定的站点 {{site}}。",
|
||||
"hostOnlyMismatch": "“{{name}}” 声明 hostOnly={{hostOnly}},但其域名为 {{domain}}。已按该标志处理。",
|
||||
"pathRepaired": "“{{name}}” 的路径已从 {{path}} 修正。",
|
||||
"expiryMilliseconds": "“{{name}}” 的过期时间({{expires}})以毫秒计,已转换为秒。",
|
||||
"expiryClamped": "某个过期时间远在未来,不可能真实,已限制为最大值。",
|
||||
"expiryInvalid": "{{field}} 不是可用的时间戳:{{value}}。",
|
||||
"expiresInvalid": "Expires 不是可读取的日期:{{value}}。",
|
||||
"maxAgeInvalid": "Max-Age 不是数字:{{value}}。",
|
||||
"maxAgeDeletion": "“{{name}}” 的 Max-Age 会立即删除它。",
|
||||
"sameSiteNoneInsecure": "{{domain}} 上的 “{{name}}” 为 SameSite=None 但未设 Secure,浏览器将拒绝发送它。",
|
||||
"sameSiteUnrecognized": "无法识别 SameSite “{{value}}”,已保留为未指定。",
|
||||
"duplicateCookie": "针对 {{domain}}{{path}} 的 “{{name}}” 在粘贴后面再次出现,以后一份为准。",
|
||||
"boolCoercedFromString": "{{field}} 是文本 “{{value}}” 而非 true 或 false,已按布尔值读取。",
|
||||
"boolInvalid": "{{field}} 既不是 true 也不是 false:{{value}}。",
|
||||
"quotedValue": "已去除 “{{name}}” 值两端的引号。",
|
||||
"jsonParseFailed": "无法读取 JSON:{{message}}",
|
||||
"jsonNotCookieList": "该 JSON 既不是 Cookie 数组,也不是包含 cookies 数组的对象。",
|
||||
"jsonEntryNotObject": "此条目不是 JSON 对象。",
|
||||
"netscapePathOmitted": "此行没有路径列,已使用 /。",
|
||||
"netscapeFieldCount": "此行有 {{actual}} 列;Netscape Cookie 行应为 {{expected}} 列。",
|
||||
"netscapeIncludeSubdomainsInvalid": "包含子域名列既不是 TRUE 也不是 FALSE:{{value}}。",
|
||||
"netscapeSecureInvalid": "secure 列既不是 TRUE 也不是 FALSE:{{value}}。",
|
||||
"netscapeExpiryInvalid": "过期列不是数字:{{value}}。该行已被丢弃,而不是转为有效 Cookie。",
|
||||
"nameValueNoPair": "此部分没有 name=value 对,已忽略。",
|
||||
"pairTreatedAsAttribute": "“{{name}}” 被读作 Set-Cookie 属性而非 Cookie,其值已被丢弃。",
|
||||
"unknown": "{{code}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
@@ -1928,6 +2013,10 @@
|
||||
"invalidLaunchHookUrl": "启动钩子 URL 无效。请使用完整的 http:// 或 https:// URL。",
|
||||
"cookieDbLocked": "无法读取 Cookie — 数据库已锁定。请关闭浏览器后重试。",
|
||||
"cookieDbUnavailable": "无法读取 Cookie — Cookie 存储不可用。",
|
||||
"cookieImportBrowserRunning": "浏览器运行时无法导入 Cookie。请关闭浏览器后重试。",
|
||||
"cookieImportProfileProtected": "无法将 Cookie 导入密码保护的配置文件。请先移除密码。",
|
||||
"cookieImportRemoteSession": "远程会话正在占用此配置文件,无法导入 Cookie。请等待同步完成。",
|
||||
"cookieImportNoCookies": "在你粘贴的内容中没有找到 Cookie。",
|
||||
"selfHostedRequiresLogout": "在配置自托管服务器之前请先退出 Donut 账户。",
|
||||
"fingerprintRequiresPro": "查看或编辑指纹需要有效的付费方案。所有方案均包含指纹保护。",
|
||||
"proxyNotWorking": "所选代理无法使用,因此未创建配置文件。",
|
||||
|
||||
@@ -19,6 +19,10 @@ export type BackendErrorCode =
|
||||
| "INVALID_LAUNCH_HOOK_URL"
|
||||
| "COOKIE_DB_LOCKED"
|
||||
| "COOKIE_DB_UNAVAILABLE"
|
||||
| "COOKIE_IMPORT_BROWSER_RUNNING"
|
||||
| "COOKIE_IMPORT_PROFILE_PROTECTED"
|
||||
| "COOKIE_IMPORT_REMOTE_SESSION"
|
||||
| "COOKIE_IMPORT_NO_COOKIES"
|
||||
| "SELF_HOSTED_REQUIRES_LOGOUT"
|
||||
| "PROXY_NOT_FOUND"
|
||||
| "GROUP_NOT_FOUND"
|
||||
@@ -224,6 +228,14 @@ export function translateBackendError(t: TFunction, err: unknown): string {
|
||||
return t("backendErrors.cookieDbLocked");
|
||||
case "COOKIE_DB_UNAVAILABLE":
|
||||
return t("backendErrors.cookieDbUnavailable");
|
||||
case "COOKIE_IMPORT_BROWSER_RUNNING":
|
||||
return t("backendErrors.cookieImportBrowserRunning");
|
||||
case "COOKIE_IMPORT_PROFILE_PROTECTED":
|
||||
return t("backendErrors.cookieImportProfileProtected");
|
||||
case "COOKIE_IMPORT_REMOTE_SESSION":
|
||||
return t("backendErrors.cookieImportRemoteSession");
|
||||
case "COOKIE_IMPORT_NO_COOKIES":
|
||||
return t("backendErrors.cookieImportNoCookies");
|
||||
case "SELF_HOSTED_REQUIRES_LOGOUT":
|
||||
return t("backendErrors.selfHostedRequiresLogout");
|
||||
case "PROXY_NOT_FOUND":
|
||||
|
||||
@@ -201,7 +201,7 @@ test("quotes hold a value together and keep separators literal", () => {
|
||||
assert.equal(hit('"-lead"', profile({ name: "-lead" })), true);
|
||||
});
|
||||
|
||||
test("several terms are ANDed", () => {
|
||||
test("several terms combine with AND", () => {
|
||||
const target = profile({ tags: ["prod"], group_id: "g1", note: "vat" });
|
||||
assert.equal(hit("tag:prod group:client", target), true);
|
||||
assert.equal(hit("tag:prod group:other", target), false);
|
||||
|
||||
@@ -650,6 +650,53 @@ export interface CookieCopyResult {
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
// Cookie paste types. Unlike the copy types above these are serialized with
|
||||
// `rename_all = "camelCase"`, so the field names differ from the Rust structs.
|
||||
export type CookieIssueSeverity = "error" | "warning" | "info";
|
||||
|
||||
export interface CookieIssue {
|
||||
code: string;
|
||||
severity: CookieIssueSeverity;
|
||||
source: string | null;
|
||||
params: Record<string, string>;
|
||||
}
|
||||
|
||||
export type CookiePasteFormat = "json" | "netscape" | "nameValue";
|
||||
|
||||
export type CookieWriteMode = "merge" | "replaceMatchingSites";
|
||||
|
||||
/** Carries no `value`: the value is the credential and never leaves Rust. */
|
||||
export interface PastedCookiePreview {
|
||||
name: string;
|
||||
domain: string;
|
||||
path: string;
|
||||
expires: number;
|
||||
isSecure: boolean;
|
||||
isHttpOnly: boolean;
|
||||
sameSite: number;
|
||||
}
|
||||
|
||||
export interface CookieAnalysis {
|
||||
format: CookiePasteFormat | null;
|
||||
cookies: PastedCookiePreview[];
|
||||
issues: CookieIssue[];
|
||||
siteRequired: boolean;
|
||||
expiredCount: number;
|
||||
/** `null` when the store cannot be read, which is not the same as zero. */
|
||||
replaceDeleteCount: number | null;
|
||||
clearsOnClose: boolean;
|
||||
/** A `{"code":…}` string for `translateBackendError`, or `null` to proceed. */
|
||||
blockedBy: string | null;
|
||||
}
|
||||
|
||||
export interface CookiePasteImportResult {
|
||||
added: number;
|
||||
overwritten: number;
|
||||
deleted: number;
|
||||
skipped: number;
|
||||
issues: CookieIssue[];
|
||||
}
|
||||
|
||||
// Proxy import/export types
|
||||
export interface ProxyExportData {
|
||||
version: string;
|
||||
|
||||
Reference in New Issue
Block a user