mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-06-06 23:13:58 +02:00
feat: daemon support, general improvement, and preparation for Windows release
This commit is contained in:
+8
-5
@@ -3,6 +3,7 @@ import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "@/styles/globals.css";
|
||||
import "flag-icons/css/flag-icons.min.css";
|
||||
import { useEffect } from "react";
|
||||
import { I18nProvider } from "@/components/i18n-provider";
|
||||
import { CustomThemeProvider } from "@/components/theme-provider";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
@@ -33,11 +34,13 @@ export default function RootLayout({
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased overflow-hidden bg-background`}
|
||||
>
|
||||
<CustomThemeProvider>
|
||||
<WindowDragArea />
|
||||
<TooltipProvider>{children}</TooltipProvider>
|
||||
<Toaster />
|
||||
</CustomThemeProvider>
|
||||
<I18nProvider>
|
||||
<CustomThemeProvider>
|
||||
<WindowDragArea />
|
||||
<TooltipProvider>{children}</TooltipProvider>
|
||||
<Toaster />
|
||||
</CustomThemeProvider>
|
||||
</I18nProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -786,6 +786,25 @@ export default function Home() {
|
||||
}
|
||||
}, [profiles]);
|
||||
|
||||
// Re-check Wayfern terms when a browser download completes
|
||||
useEffect(() => {
|
||||
let unlisten: (() => void) | null = null;
|
||||
const setup = async () => {
|
||||
unlisten = await listen<{ stage: string }>(
|
||||
"download-progress",
|
||||
(event) => {
|
||||
if (event.payload.stage === "completed") {
|
||||
void checkTerms();
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
void setup();
|
||||
return () => {
|
||||
if (unlisten) unlisten();
|
||||
};
|
||||
}, [checkTerms]);
|
||||
|
||||
// Check permissions when they are initialized
|
||||
useEffect(() => {
|
||||
if (isInitialized) {
|
||||
|
||||
@@ -58,13 +58,8 @@ export function CommercialTrialModal({
|
||||
<div className="space-y-4 py-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
If you are using Donut Browser for business purposes, you need to
|
||||
purchase a commercial license to continue.
|
||||
</p>
|
||||
<p className="text-sm font-medium">
|
||||
Personal use remains free and unrestricted.
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Visit our website to learn more about commercial licensing options.
|
||||
purchase a commercial license to continue. You can still use it for
|
||||
personal use for free.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FaDownload } from "react-icons/fa";
|
||||
import { FiWifi } from "react-icons/fi";
|
||||
import { GoGear, GoKebabHorizontal, GoPlus } from "react-icons/go";
|
||||
@@ -37,6 +38,7 @@ const HomeHeader = ({
|
||||
searchQuery,
|
||||
onSearchQueryChange,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const handleLogoClick = () => {
|
||||
// Trigger the same URL handling logic as if the URL came from the system
|
||||
const event = new CustomEvent("url-open-request", {
|
||||
@@ -61,7 +63,7 @@ const HomeHeader = ({
|
||||
<div className="relative">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search profiles..."
|
||||
placeholder={t("header.searchPlaceholder")}
|
||||
value={searchQuery}
|
||||
onChange={(e) => onSearchQueryChange(e.target.value)}
|
||||
className="pr-8 pl-10 w-48"
|
||||
@@ -72,7 +74,7 @@ const HomeHeader = ({
|
||||
type="button"
|
||||
onClick={() => onSearchQueryChange("")}
|
||||
className="absolute right-2 top-1/2 p-1 rounded-sm transition-colors transform -translate-y-1/2 hover:bg-accent"
|
||||
aria-label="Clear search"
|
||||
aria-label={t("header.clearSearch")}
|
||||
>
|
||||
<LuX className="w-4 h-4 text-muted-foreground hover:text-foreground" />
|
||||
</button>
|
||||
@@ -93,7 +95,7 @@ const HomeHeader = ({
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>More actions</TooltipContent>
|
||||
<TooltipContent>{t("header.moreActions")}</TooltipContent>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</DropdownMenuTrigger>
|
||||
@@ -104,7 +106,7 @@ const HomeHeader = ({
|
||||
}}
|
||||
>
|
||||
<GoGear className="mr-2 w-4 h-4" />
|
||||
Settings
|
||||
{t("header.menu.settings")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
@@ -112,7 +114,7 @@ const HomeHeader = ({
|
||||
}}
|
||||
>
|
||||
<FiWifi className="mr-2 w-4 h-4" />
|
||||
Proxies
|
||||
{t("header.menu.proxies")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
@@ -120,7 +122,7 @@ const HomeHeader = ({
|
||||
}}
|
||||
>
|
||||
<LuUsers className="mr-2 w-4 h-4" />
|
||||
Groups
|
||||
{t("header.menu.groups")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
@@ -128,7 +130,7 @@ const HomeHeader = ({
|
||||
}}
|
||||
>
|
||||
<LuCloud className="mr-2 w-4 h-4" />
|
||||
Sync Service
|
||||
{t("header.menu.syncService")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
@@ -136,7 +138,7 @@ const HomeHeader = ({
|
||||
}}
|
||||
>
|
||||
<LuPlug className="mr-2 w-4 h-4" />
|
||||
Integrations
|
||||
{t("header.menu.integrations")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
@@ -144,7 +146,7 @@ const HomeHeader = ({
|
||||
}}
|
||||
>
|
||||
<FaDownload className="mr-2 w-4 h-4" />
|
||||
Import Profile
|
||||
{t("header.menu.importProfile")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -166,7 +168,7 @@ const HomeHeader = ({
|
||||
arrowOffset={-8}
|
||||
style={{ transform: "translateX(-8px)" }}
|
||||
>
|
||||
Create a new profile
|
||||
{t("header.createProfile")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
"use client";
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useEffect, useState } from "react";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import i18n, { getLanguageWithFallback, SUPPORTED_LANGUAGES } from "@/i18n";
|
||||
|
||||
interface AppSettings {
|
||||
language?: string | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface I18nProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function I18nProvider({ children }: I18nProviderProps) {
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const initializeLanguage = async () => {
|
||||
try {
|
||||
const settings = await invoke<AppSettings>("get_app_settings");
|
||||
let language = settings.language;
|
||||
|
||||
if (!language) {
|
||||
const systemLanguage = await invoke<string>("get_system_language");
|
||||
language = getLanguageWithFallback(systemLanguage);
|
||||
}
|
||||
|
||||
if (
|
||||
language &&
|
||||
SUPPORTED_LANGUAGES.some((lang) => lang.code === language)
|
||||
) {
|
||||
await i18n.changeLanguage(language);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize language:", error);
|
||||
} finally {
|
||||
setIsReady(true);
|
||||
}
|
||||
};
|
||||
|
||||
void initializeLanguage();
|
||||
}, []);
|
||||
|
||||
if (!isReady) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>;
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
"use client";
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { LuCheck, LuCopy, LuDownload } from "react-icons/lu";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { RippleButton } from "./ui/ripple";
|
||||
|
||||
interface ProxyExportDialogProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function ProxyExportDialog({ isOpen, onClose }: ProxyExportDialogProps) {
|
||||
const [format, setFormat] = useState<"json" | "txt">("json");
|
||||
const [exportContent, setExportContent] = useState<string>("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const loadExportContent = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const content = await invoke<string>("export_proxies", { format });
|
||||
setExportContent(content);
|
||||
} catch (error) {
|
||||
console.error("Failed to export proxies:", error);
|
||||
toast.error("Failed to export proxies");
|
||||
setExportContent("");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [format]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
void loadExportContent();
|
||||
}
|
||||
}, [isOpen, loadExportContent]);
|
||||
|
||||
const handleCopyToClipboard = useCallback(async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(exportContent);
|
||||
setCopied(true);
|
||||
toast.success("Copied to clipboard");
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} catch (error) {
|
||||
console.error("Failed to copy to clipboard:", error);
|
||||
toast.error("Failed to copy to clipboard");
|
||||
}
|
||||
}, [exportContent]);
|
||||
|
||||
const handleDownload = useCallback(() => {
|
||||
const filename = format === "json" ? "proxies.json" : "proxies.txt";
|
||||
const mimeType = format === "json" ? "application/json" : "text/plain";
|
||||
|
||||
const blob = new Blob([exportContent], { type: mimeType });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
toast.success(`Downloaded ${filename}`);
|
||||
}, [format, exportContent]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setFormat("json");
|
||||
setExportContent("");
|
||||
setCopied(false);
|
||||
onClose();
|
||||
}, [onClose]);
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent className="max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Export Proxies</DialogTitle>
|
||||
<DialogDescription>
|
||||
Export your proxy configurations to a file
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Export Format</Label>
|
||||
<RadioGroup
|
||||
value={format}
|
||||
onValueChange={(value) => setFormat(value as "json" | "txt")}
|
||||
className="flex gap-4"
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="json" id="format-json" />
|
||||
<Label htmlFor="format-json" className="cursor-pointer">
|
||||
JSON
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="txt" id="format-txt" />
|
||||
<Label htmlFor="format-txt" className="cursor-pointer">
|
||||
TXT (URL format)
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Preview</Label>
|
||||
<ScrollArea className="h-[200px] border rounded-md bg-muted/30">
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center h-full p-4 text-sm text-muted-foreground">
|
||||
Loading...
|
||||
</div>
|
||||
) : exportContent ? (
|
||||
<pre className="p-3 text-xs font-mono whitespace-pre-wrap break-all">
|
||||
{exportContent}
|
||||
</pre>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full p-4 text-sm text-muted-foreground">
|
||||
No proxies to export
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter className="flex-col sm:flex-row gap-2">
|
||||
<RippleButton variant="outline" onClick={handleClose}>
|
||||
Close
|
||||
</RippleButton>
|
||||
<RippleButton
|
||||
variant="outline"
|
||||
onClick={() => void handleCopyToClipboard()}
|
||||
disabled={!exportContent || isLoading}
|
||||
className="flex gap-2 items-center"
|
||||
>
|
||||
{copied ? (
|
||||
<LuCheck className="w-4 h-4" />
|
||||
) : (
|
||||
<LuCopy className="w-4 h-4" />
|
||||
)}
|
||||
{copied ? "Copied" : "Copy"}
|
||||
</RippleButton>
|
||||
<RippleButton
|
||||
onClick={handleDownload}
|
||||
disabled={!exportContent || isLoading}
|
||||
className="flex gap-2 items-center"
|
||||
>
|
||||
<LuDownload className="w-4 h-4" />
|
||||
Download
|
||||
</RippleButton>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,727 @@
|
||||
"use client";
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { emit } from "@tauri-apps/api/event";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { LuShield, LuUpload } from "react-icons/lu";
|
||||
import { toast } from "sonner";
|
||||
import { LoadingButton } from "@/components/loading-button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { getCurrentOS } from "@/lib/browser-utils";
|
||||
import type {
|
||||
ParsedProxyLine,
|
||||
ProxyImportResult,
|
||||
ProxyParseResult,
|
||||
VpnImportResult,
|
||||
VpnType,
|
||||
} from "@/types";
|
||||
import { RippleButton } from "./ui/ripple";
|
||||
|
||||
interface ProxyImportDialogProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
type ImportStep =
|
||||
| "dropzone"
|
||||
| "preview"
|
||||
| "ambiguous"
|
||||
| "result"
|
||||
| "vpn-preview"
|
||||
| "vpn-result";
|
||||
|
||||
interface AmbiguousProxy {
|
||||
line: string;
|
||||
possible_formats: string[];
|
||||
selectedFormat?: string;
|
||||
}
|
||||
|
||||
interface VpnPreviewData {
|
||||
content: string;
|
||||
filename: string;
|
||||
detectedType: VpnType | null;
|
||||
endpoint: string | null;
|
||||
}
|
||||
|
||||
export function ProxyImportDialog({ isOpen, onClose }: ProxyImportDialogProps) {
|
||||
const [step, setStep] = useState<ImportStep>("dropzone");
|
||||
const [isDragOver, setIsDragOver] = useState(false);
|
||||
const [parsedProxies, setParsedProxies] = useState<ParsedProxyLine[]>([]);
|
||||
const [ambiguousProxies, setAmbiguousProxies] = useState<AmbiguousProxy[]>(
|
||||
[],
|
||||
);
|
||||
const [invalidProxies, setInvalidProxies] = useState<
|
||||
{ line: string; reason: string }[]
|
||||
>([]);
|
||||
const [importResult, setImportResult] = useState<ProxyImportResult | null>(
|
||||
null,
|
||||
);
|
||||
const [isImporting, setIsImporting] = useState(false);
|
||||
const [namePrefix, setNamePrefix] = useState("Imported");
|
||||
// VPN import state
|
||||
const [vpnPreview, setVpnPreview] = useState<VpnPreviewData | null>(null);
|
||||
const [vpnName, setVpnName] = useState("");
|
||||
const [vpnImportResult, setVpnImportResult] =
|
||||
useState<VpnImportResult | null>(null);
|
||||
|
||||
const os = getCurrentOS();
|
||||
const modKey = os === "macos" ? "⌘" : "Ctrl";
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
setStep("dropzone");
|
||||
setIsDragOver(false);
|
||||
setParsedProxies([]);
|
||||
setAmbiguousProxies([]);
|
||||
setInvalidProxies([]);
|
||||
setImportResult(null);
|
||||
setIsImporting(false);
|
||||
setNamePrefix("Imported");
|
||||
// Reset VPN state
|
||||
setVpnPreview(null);
|
||||
setVpnName("");
|
||||
setVpnImportResult(null);
|
||||
}, []);
|
||||
|
||||
// Detect VPN type from content
|
||||
const detectVpnType = useCallback(
|
||||
(
|
||||
content: string,
|
||||
filename: string,
|
||||
): { isVpn: boolean; type: VpnType | null; endpoint: string | null } => {
|
||||
const lowerFilename = filename.toLowerCase();
|
||||
|
||||
// Check for WireGuard config
|
||||
if (
|
||||
lowerFilename.endsWith(".conf") &&
|
||||
content.includes("[Interface]") &&
|
||||
content.includes("[Peer]")
|
||||
) {
|
||||
// Extract endpoint from WireGuard config
|
||||
const endpointMatch = content.match(/Endpoint\s*=\s*([^\s\n]+)/i);
|
||||
return {
|
||||
isVpn: true,
|
||||
type: "WireGuard",
|
||||
endpoint: endpointMatch ? endpointMatch[1] : null,
|
||||
};
|
||||
}
|
||||
|
||||
// Check for OpenVPN config
|
||||
if (
|
||||
lowerFilename.endsWith(".ovpn") ||
|
||||
(content.includes("remote ") &&
|
||||
(content.includes("client") || content.includes("dev tun")))
|
||||
) {
|
||||
// Extract remote from OpenVPN config
|
||||
const remoteMatch = content.match(/remote\s+(\S+)(?:\s+(\d+))?/i);
|
||||
const endpoint = remoteMatch
|
||||
? `${remoteMatch[1]}${remoteMatch[2] ? `:${remoteMatch[2]}` : ""}`
|
||||
: null;
|
||||
return { isVpn: true, type: "OpenVPN", endpoint };
|
||||
}
|
||||
|
||||
return { isVpn: false, type: null, endpoint: null };
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const processContent = useCallback(
|
||||
async (content: string, isJson: boolean, filename: string = "") => {
|
||||
try {
|
||||
// Check if it's a VPN config
|
||||
const vpnDetection = detectVpnType(content, filename);
|
||||
if (vpnDetection.isVpn) {
|
||||
setVpnPreview({
|
||||
content,
|
||||
filename,
|
||||
detectedType: vpnDetection.type,
|
||||
endpoint: vpnDetection.endpoint,
|
||||
});
|
||||
// Generate default name from filename
|
||||
const baseName = filename
|
||||
.replace(/\.(conf|ovpn)$/i, "")
|
||||
.replace(/_/g, " ")
|
||||
.replace(/-/g, " ");
|
||||
setVpnName(baseName || `${vpnDetection.type} VPN`);
|
||||
setStep("vpn-preview");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isJson) {
|
||||
setIsImporting(true);
|
||||
const result = await invoke<ProxyImportResult>(
|
||||
"import_proxies_json",
|
||||
{
|
||||
content,
|
||||
},
|
||||
);
|
||||
setImportResult(result);
|
||||
setStep("result");
|
||||
await emit("stored-proxies-changed");
|
||||
} else {
|
||||
const results = await invoke<ProxyParseResult[]>(
|
||||
"parse_txt_proxies",
|
||||
{
|
||||
content,
|
||||
},
|
||||
);
|
||||
|
||||
const parsed: ParsedProxyLine[] = [];
|
||||
const ambiguous: AmbiguousProxy[] = [];
|
||||
const invalid: { line: string; reason: string }[] = [];
|
||||
|
||||
for (const result of results) {
|
||||
if (result.status === "parsed") {
|
||||
parsed.push(result);
|
||||
} else if (result.status === "ambiguous") {
|
||||
ambiguous.push({
|
||||
line: result.line,
|
||||
possible_formats: result.possible_formats,
|
||||
});
|
||||
} else if (result.status === "invalid") {
|
||||
invalid.push({ line: result.line, reason: result.reason });
|
||||
}
|
||||
}
|
||||
|
||||
setParsedProxies(parsed);
|
||||
setAmbiguousProxies(ambiguous);
|
||||
setInvalidProxies(invalid);
|
||||
|
||||
if (ambiguous.length > 0) {
|
||||
setStep("ambiguous");
|
||||
} else if (parsed.length > 0) {
|
||||
setStep("preview");
|
||||
} else {
|
||||
toast.error("No valid proxies found in the file");
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to process content:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Failed to process file",
|
||||
);
|
||||
} finally {
|
||||
setIsImporting(false);
|
||||
}
|
||||
},
|
||||
[detectVpnType],
|
||||
);
|
||||
|
||||
const handleFileRead = useCallback(
|
||||
(file: File) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const content = e.target?.result as string;
|
||||
const isJson = file.name.endsWith(".json");
|
||||
void processContent(content, isJson, file.name);
|
||||
};
|
||||
reader.onerror = () => {
|
||||
toast.error("Failed to read file");
|
||||
};
|
||||
reader.readAsText(file);
|
||||
},
|
||||
[processContent],
|
||||
);
|
||||
|
||||
const handleDrop = useCallback(
|
||||
(e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
setIsDragOver(false);
|
||||
|
||||
const files = Array.from(e.dataTransfer.files);
|
||||
const validFile = files.find(
|
||||
(f) =>
|
||||
f.name.endsWith(".json") ||
|
||||
f.name.endsWith(".txt") ||
|
||||
f.name.endsWith(".conf") || // WireGuard
|
||||
f.name.endsWith(".ovpn"), // OpenVPN
|
||||
);
|
||||
|
||||
if (validFile) {
|
||||
handleFileRead(validFile);
|
||||
} else {
|
||||
toast.error("Please drop a .json, .txt, .conf, or .ovpn file");
|
||||
}
|
||||
},
|
||||
[handleFileRead],
|
||||
);
|
||||
|
||||
const handleDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
setIsDragOver(true);
|
||||
}, []);
|
||||
|
||||
const handleDragLeave = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
setIsDragOver(false);
|
||||
}, []);
|
||||
|
||||
// Handle paste from clipboard
|
||||
useEffect(() => {
|
||||
if (!isOpen || step !== "dropzone") return;
|
||||
|
||||
const handlePaste = async (e: ClipboardEvent) => {
|
||||
const text = e.clipboardData?.getData("text");
|
||||
if (text) {
|
||||
// Try to detect if it's JSON
|
||||
const trimmed = text.trim();
|
||||
const isJson =
|
||||
(trimmed.startsWith("{") && trimmed.endsWith("}")) ||
|
||||
(trimmed.startsWith("[") && trimmed.endsWith("]"));
|
||||
// Use "pasted.txt" as filename to trigger content-based detection
|
||||
await processContent(text, isJson, "pasted.txt");
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("paste", handlePaste);
|
||||
return () => {
|
||||
document.removeEventListener("paste", handlePaste);
|
||||
};
|
||||
}, [isOpen, step, processContent]);
|
||||
|
||||
const handleImport = useCallback(async () => {
|
||||
setIsImporting(true);
|
||||
try {
|
||||
const result = await invoke<ProxyImportResult>(
|
||||
"import_proxies_from_parsed",
|
||||
{
|
||||
parsedProxies,
|
||||
namePrefix: namePrefix.trim() || "Imported",
|
||||
},
|
||||
);
|
||||
setImportResult(result);
|
||||
setStep("result");
|
||||
await emit("stored-proxies-changed");
|
||||
} catch (error) {
|
||||
console.error("Failed to import proxies:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Failed to import proxies",
|
||||
);
|
||||
} finally {
|
||||
setIsImporting(false);
|
||||
}
|
||||
}, [parsedProxies, namePrefix]);
|
||||
|
||||
const handleVpnImport = useCallback(async () => {
|
||||
if (!vpnPreview) return;
|
||||
|
||||
setIsImporting(true);
|
||||
try {
|
||||
const result = await invoke<VpnImportResult>("import_vpn_config", {
|
||||
content: vpnPreview.content,
|
||||
filename: vpnPreview.filename,
|
||||
name: vpnName.trim() || null,
|
||||
});
|
||||
|
||||
setVpnImportResult(result);
|
||||
setStep("vpn-result");
|
||||
|
||||
if (result.success) {
|
||||
await emit("vpn-configs-changed");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to import VPN config:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Failed to import VPN config",
|
||||
);
|
||||
} finally {
|
||||
setIsImporting(false);
|
||||
}
|
||||
}, [vpnPreview, vpnName]);
|
||||
|
||||
const handleAmbiguousFormatSelect = useCallback(
|
||||
(index: number, format: string) => {
|
||||
setAmbiguousProxies((prev) =>
|
||||
prev.map((p, i) =>
|
||||
i === index ? { ...p, selectedFormat: format } : p,
|
||||
),
|
||||
);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleResolveAmbiguous = useCallback(() => {
|
||||
// Convert ambiguous proxies to parsed based on selected format
|
||||
const resolved: ParsedProxyLine[] = ambiguousProxies
|
||||
.filter((p) => p.selectedFormat)
|
||||
.map((p) => {
|
||||
const parts = p.line.split(":");
|
||||
if (p.selectedFormat === "host:port:username:password") {
|
||||
return {
|
||||
proxy_type: "http",
|
||||
host: parts[0],
|
||||
port: Number.parseInt(parts[1], 10),
|
||||
username: parts[2],
|
||||
password: parts[3],
|
||||
original_line: p.line,
|
||||
};
|
||||
}
|
||||
// username:password:host:port
|
||||
return {
|
||||
proxy_type: "http",
|
||||
host: parts[2],
|
||||
port: Number.parseInt(parts[3], 10),
|
||||
username: parts[0],
|
||||
password: parts[1],
|
||||
original_line: p.line,
|
||||
};
|
||||
});
|
||||
|
||||
setParsedProxies((prev) => [...prev, ...resolved]);
|
||||
setStep("preview");
|
||||
}, [ambiguousProxies]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
resetState();
|
||||
onClose();
|
||||
}, [resetState, onClose]);
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent className="max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{step === "vpn-preview" || step === "vpn-result"
|
||||
? "Import VPN Config"
|
||||
: "Import Proxies"}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{step === "dropzone" &&
|
||||
"Import proxies from a JSON or TXT file, or VPN configs (.conf/.ovpn)"}
|
||||
{step === "preview" && "Review the proxies to import"}
|
||||
{step === "ambiguous" &&
|
||||
"Some proxies have ambiguous formats. Please select the correct format."}
|
||||
{step === "result" && "Import completed"}
|
||||
{step === "vpn-preview" && "Review the VPN configuration to import"}
|
||||
{step === "vpn-result" && "VPN import completed"}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{step === "dropzone" && (
|
||||
<div className="space-y-4">
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className={`
|
||||
flex flex-col items-center justify-center
|
||||
border-2 border-dashed rounded-lg p-8
|
||||
transition-colors cursor-pointer
|
||||
${isDragOver ? "border-primary bg-primary/5" : "border-muted-foreground/25 hover:border-muted-foreground/50"}
|
||||
`}
|
||||
onDrop={handleDrop}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onClick={() =>
|
||||
document.getElementById("proxy-file-input")?.click()
|
||||
}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
document.getElementById("proxy-file-input")?.click();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LuUpload className="w-10 h-10 text-muted-foreground mb-4" />
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
Drop a proxy or VPN config file
|
||||
<br />
|
||||
<span className="text-xs">(.json, .txt, .conf, .ovpn)</span>
|
||||
</p>
|
||||
<input
|
||||
id="proxy-file-input"
|
||||
type="file"
|
||||
accept=".json,.txt,.conf,.ovpn"
|
||||
className="hidden"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) handleFileRead(file);
|
||||
e.target.value = "";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground text-center">
|
||||
Paste from clipboard with {modKey}+V
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === "preview" && (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name-prefix">Name Prefix</Label>
|
||||
<Input
|
||||
id="name-prefix"
|
||||
placeholder="Imported"
|
||||
value={namePrefix}
|
||||
onChange={(e) => setNamePrefix(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Proxies will be named "{namePrefix || "Imported"} Proxy
|
||||
1", "{namePrefix || "Imported"} Proxy 2", etc.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>
|
||||
Proxies to import ({parsedProxies.length})
|
||||
{invalidProxies.length > 0 && (
|
||||
<span className="text-muted-foreground ml-2">
|
||||
({invalidProxies.length} invalid)
|
||||
</span>
|
||||
)}
|
||||
</Label>
|
||||
<ScrollArea className="h-[200px] border rounded-md">
|
||||
<div className="p-2 space-y-1">
|
||||
{parsedProxies.map((proxy, i) => (
|
||||
<div
|
||||
key={`${proxy.original_line}-${i}`}
|
||||
className="text-xs font-mono p-2 bg-muted/30 rounded"
|
||||
>
|
||||
<span className="text-primary">
|
||||
{proxy.proxy_type}://
|
||||
</span>
|
||||
{proxy.username && (
|
||||
<span className="text-muted-foreground">
|
||||
{proxy.username}:***@
|
||||
</span>
|
||||
)}
|
||||
<span>
|
||||
{proxy.host}:{proxy.port}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === "ambiguous" && (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
The following proxies have an ambiguous format. Please select the
|
||||
correct interpretation for each.
|
||||
</p>
|
||||
<ScrollArea className="h-[250px] border rounded-md">
|
||||
<div className="p-3 space-y-4">
|
||||
{ambiguousProxies.map((proxy, i) => (
|
||||
<div
|
||||
key={`${proxy.line}-${i}`}
|
||||
className="space-y-2 pb-3 border-b last:border-0"
|
||||
>
|
||||
<code className="text-xs bg-muted px-2 py-1 rounded block">
|
||||
{proxy.line}
|
||||
</code>
|
||||
<div className="flex flex-col gap-2">
|
||||
{proxy.possible_formats.map((format) => (
|
||||
<label
|
||||
key={format}
|
||||
className="flex items-center gap-2 cursor-pointer"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name={`format-${i}`}
|
||||
checked={proxy.selectedFormat === format}
|
||||
onChange={() =>
|
||||
handleAmbiguousFormatSelect(i, format)
|
||||
}
|
||||
className="accent-primary"
|
||||
/>
|
||||
<span className="text-xs">{format}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === "result" && importResult && (
|
||||
<div className="space-y-4">
|
||||
<div className="p-4 bg-muted/30 rounded-lg space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm">Imported:</span>
|
||||
<span className="text-sm font-medium text-green-600 dark:text-green-400">
|
||||
{importResult.imported_count}
|
||||
</span>
|
||||
</div>
|
||||
{importResult.skipped_count > 0 && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm">Skipped (duplicates):</span>
|
||||
<span className="text-sm font-medium text-yellow-600 dark:text-yellow-400">
|
||||
{importResult.skipped_count}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{importResult.errors.length > 0 && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm">Errors:</span>
|
||||
<span className="text-sm font-medium text-red-600 dark:text-red-400">
|
||||
{importResult.errors.length}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{importResult.errors.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<Label>Errors</Label>
|
||||
<ScrollArea className="h-[100px] border rounded-md">
|
||||
<div className="p-2 space-y-1">
|
||||
{importResult.errors.map((error, i) => (
|
||||
<div
|
||||
key={`error-${i}`}
|
||||
className="text-xs text-red-600 dark:text-red-400"
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === "vpn-preview" && vpnPreview && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3 p-4 bg-muted/30 rounded-lg">
|
||||
<LuShield className="w-8 h-8 text-primary" />
|
||||
<div>
|
||||
<div className="font-medium">
|
||||
{vpnPreview.detectedType} Configuration
|
||||
</div>
|
||||
{vpnPreview.endpoint && (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Endpoint: {vpnPreview.endpoint}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="vpn-name">VPN Name</Label>
|
||||
<Input
|
||||
id="vpn-name"
|
||||
placeholder="My VPN"
|
||||
value={vpnName}
|
||||
onChange={(e) => setVpnName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Config Preview</Label>
|
||||
<ScrollArea className="h-[150px] border rounded-md">
|
||||
<pre className="p-2 text-xs font-mono whitespace-pre-wrap break-all">
|
||||
{vpnPreview.content.slice(0, 1000)}
|
||||
{vpnPreview.content.length > 1000 && "..."}
|
||||
</pre>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === "vpn-result" && vpnImportResult && (
|
||||
<div className="space-y-4">
|
||||
<div
|
||||
className={`p-4 rounded-lg ${vpnImportResult.success ? "bg-green-500/10" : "bg-red-500/10"}`}
|
||||
>
|
||||
{vpnImportResult.success ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<LuShield className="w-8 h-8 text-green-600 dark:text-green-400" />
|
||||
<div>
|
||||
<div className="font-medium text-green-600 dark:text-green-400">
|
||||
VPN Imported Successfully
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{vpnImportResult.name} ({vpnImportResult.vpn_type})
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<div className="font-medium text-red-600 dark:text-red-400">
|
||||
Import Failed
|
||||
</div>
|
||||
<div className="text-sm text-red-600 dark:text-red-400">
|
||||
{vpnImportResult.error}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DialogFooter>
|
||||
{step === "dropzone" && (
|
||||
<RippleButton variant="outline" onClick={handleClose}>
|
||||
Cancel
|
||||
</RippleButton>
|
||||
)}
|
||||
|
||||
{step === "preview" && (
|
||||
<>
|
||||
<RippleButton variant="outline" onClick={resetState}>
|
||||
Back
|
||||
</RippleButton>
|
||||
<LoadingButton
|
||||
isLoading={isImporting}
|
||||
onClick={() => void handleImport()}
|
||||
disabled={parsedProxies.length === 0}
|
||||
>
|
||||
Import {parsedProxies.length} Proxies
|
||||
</LoadingButton>
|
||||
</>
|
||||
)}
|
||||
|
||||
{step === "ambiguous" && (
|
||||
<>
|
||||
<RippleButton variant="outline" onClick={resetState}>
|
||||
Back
|
||||
</RippleButton>
|
||||
<RippleButton
|
||||
onClick={handleResolveAmbiguous}
|
||||
disabled={ambiguousProxies.some((p) => !p.selectedFormat)}
|
||||
>
|
||||
Continue
|
||||
</RippleButton>
|
||||
</>
|
||||
)}
|
||||
|
||||
{step === "result" && (
|
||||
<RippleButton onClick={handleClose}>Done</RippleButton>
|
||||
)}
|
||||
|
||||
{step === "vpn-preview" && (
|
||||
<>
|
||||
<RippleButton variant="outline" onClick={resetState}>
|
||||
Back
|
||||
</RippleButton>
|
||||
<LoadingButton
|
||||
isLoading={isImporting}
|
||||
onClick={() => void handleVpnImport()}
|
||||
>
|
||||
Import VPN
|
||||
</LoadingButton>
|
||||
</>
|
||||
)}
|
||||
|
||||
{step === "vpn-result" && (
|
||||
<RippleButton onClick={handleClose}>Done</RippleButton>
|
||||
)}
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -4,10 +4,12 @@ import { invoke } from "@tauri-apps/api/core";
|
||||
import { emit, listen } from "@tauri-apps/api/event";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { GoPlus } from "react-icons/go";
|
||||
import { LuPencil, LuTrash2 } from "react-icons/lu";
|
||||
import { LuDownload, LuPencil, LuTrash2, LuUpload } from "react-icons/lu";
|
||||
import { toast } from "sonner";
|
||||
import { DeleteConfirmationDialog } from "@/components/delete-confirmation-dialog";
|
||||
import { ProxyExportDialog } from "@/components/proxy-export-dialog";
|
||||
import { ProxyFormDialog } from "@/components/proxy-form-dialog";
|
||||
import { ProxyImportDialog } from "@/components/proxy-import-dialog";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
@@ -19,7 +21,6 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
Table,
|
||||
@@ -82,6 +83,8 @@ export function ProxyManagementDialog({
|
||||
onClose,
|
||||
}: ProxyManagementDialogProps) {
|
||||
const [showProxyForm, setShowProxyForm] = useState(false);
|
||||
const [showImportDialog, setShowImportDialog] = useState(false);
|
||||
const [showExportDialog, setShowExportDialog] = useState(false);
|
||||
const [editingProxy, setEditingProxy] = useState<StoredProxy | null>(null);
|
||||
const [proxyToDelete, setProxyToDelete] = useState<StoredProxy | null>(null);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
@@ -221,9 +224,29 @@ export function ProxyManagementDialog({
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Create new proxy button */}
|
||||
{/* Proxy actions */}
|
||||
<div className="flex justify-between items-center">
|
||||
<Label>Proxies</Label>
|
||||
<div className="flex gap-2">
|
||||
<RippleButton
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => setShowImportDialog(true)}
|
||||
className="flex gap-2 items-center"
|
||||
>
|
||||
<LuUpload className="w-4 h-4" />
|
||||
Import
|
||||
</RippleButton>
|
||||
<RippleButton
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => setShowExportDialog(true)}
|
||||
className="flex gap-2 items-center"
|
||||
disabled={storedProxies.length === 0}
|
||||
>
|
||||
<LuDownload className="w-4 h-4" />
|
||||
Export
|
||||
</RippleButton>
|
||||
</div>
|
||||
<RippleButton
|
||||
size="sm"
|
||||
onClick={handleCreateProxy}
|
||||
@@ -414,6 +437,14 @@ export function ProxyManagementDialog({
|
||||
confirmButtonText="Delete"
|
||||
isLoading={isDeleting}
|
||||
/>
|
||||
<ProxyImportDialog
|
||||
isOpen={showImportDialog}
|
||||
onClose={() => setShowImportDialog(false)}
|
||||
/>
|
||||
<ProxyExportDialog
|
||||
isOpen={showExportDialog}
|
||||
onClose={() => setShowExportDialog(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { invoke } from "@tauri-apps/api/core";
|
||||
import Color from "color";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { BsCamera, BsMic } from "react-icons/bs";
|
||||
import { LoadingButton } from "@/components/loading-button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@@ -37,6 +38,7 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { useCommercialTrial } from "@/hooks/use-commercial-trial";
|
||||
import { useLanguage } from "@/hooks/use-language";
|
||||
import type { PermissionType } from "@/hooks/use-permissions";
|
||||
import { usePermissions } from "@/hooks/use-permissions";
|
||||
import {
|
||||
@@ -112,6 +114,7 @@ export function SettingsDialog({
|
||||
useState<PermissionType | null>(null);
|
||||
const [isMacOS, setIsMacOS] = useState(false);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { setTheme } = useTheme();
|
||||
const {
|
||||
requestPermission,
|
||||
@@ -119,6 +122,14 @@ export function SettingsDialog({
|
||||
isCameraAccessGranted,
|
||||
} = usePermissions();
|
||||
const { trialStatus } = useCommercialTrial();
|
||||
const {
|
||||
currentLanguage,
|
||||
changeLanguage,
|
||||
supportedLanguages,
|
||||
isLoading: isLanguageLoading,
|
||||
} = useLanguage();
|
||||
const [selectedLanguage, setSelectedLanguage] = useState<string | null>(null);
|
||||
const [originalLanguage, setOriginalLanguage] = useState<string | null>(null);
|
||||
|
||||
const getPermissionIcon = useCallback((type: PermissionType) => {
|
||||
switch (type) {
|
||||
@@ -316,9 +327,26 @@ export function SettingsDialog({
|
||||
: settings.custom_theme,
|
||||
};
|
||||
|
||||
console.log("[settings-dialog] Saving settings:", {
|
||||
theme: settingsToSave.theme,
|
||||
hasCustomTheme: !!settingsToSave.custom_theme,
|
||||
customThemeKeys: settingsToSave.custom_theme
|
||||
? Object.keys(settingsToSave.custom_theme).length
|
||||
: 0,
|
||||
});
|
||||
|
||||
const savedSettings = await invoke<AppSettings>("save_app_settings", {
|
||||
settings: settingsToSave,
|
||||
});
|
||||
|
||||
console.log("[settings-dialog] Saved settings response:", {
|
||||
theme: savedSettings.theme,
|
||||
hasCustomTheme: !!savedSettings.custom_theme,
|
||||
customThemeKeys: savedSettings.custom_theme
|
||||
? Object.keys(savedSettings.custom_theme).length
|
||||
: 0,
|
||||
});
|
||||
|
||||
// Update settings with any generated tokens
|
||||
setSettings(savedSettings);
|
||||
settingsToSave = savedSettings;
|
||||
@@ -350,6 +378,23 @@ export function SettingsDialog({
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// Save language if changed
|
||||
if (selectedLanguage !== originalLanguage) {
|
||||
await changeLanguage(
|
||||
selectedLanguage === "system"
|
||||
? null
|
||||
: (selectedLanguage as
|
||||
| "en"
|
||||
| "es"
|
||||
| "pt"
|
||||
| "fr"
|
||||
| "zh"
|
||||
| "ja"
|
||||
| "ru"),
|
||||
);
|
||||
setOriginalLanguage(selectedLanguage);
|
||||
}
|
||||
|
||||
setOriginalSettings(settingsToSave);
|
||||
onClose();
|
||||
} catch (error) {
|
||||
@@ -357,7 +402,15 @@ export function SettingsDialog({
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
}, [onClose, setTheme, settings, customThemeState]);
|
||||
}, [
|
||||
onClose,
|
||||
setTheme,
|
||||
settings,
|
||||
customThemeState,
|
||||
selectedLanguage,
|
||||
originalLanguage,
|
||||
changeLanguage,
|
||||
]);
|
||||
|
||||
const updateSetting = useCallback(
|
||||
(
|
||||
@@ -428,6 +481,14 @@ export function SettingsDialog({
|
||||
}
|
||||
}, [isOpen, loadPermissions, checkDefaultBrowserStatus, loadSettings]);
|
||||
|
||||
// Initialize language selection when dialog opens or language loads
|
||||
useEffect(() => {
|
||||
if (isOpen && !isLanguageLoading) {
|
||||
setSelectedLanguage(currentLanguage);
|
||||
setOriginalLanguage(currentLanguage);
|
||||
}
|
||||
}, [isOpen, currentLanguage, isLanguageLoading]);
|
||||
|
||||
// Update permissions when the permission states change
|
||||
useEffect(() => {
|
||||
if (isMacOS) {
|
||||
@@ -458,6 +519,7 @@ export function SettingsDialog({
|
||||
const hasChanges =
|
||||
settings.theme !== originalSettings.theme ||
|
||||
settings.api_enabled !== originalSettings.api_enabled ||
|
||||
selectedLanguage !== originalLanguage ||
|
||||
(settings.theme === "custom" &&
|
||||
JSON.stringify(customThemeState.colors) !==
|
||||
JSON.stringify(originalSettings.custom_theme ?? {})) ||
|
||||
@@ -469,7 +531,7 @@ export function SettingsDialog({
|
||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent className="max-w-md max-h-[80vh] my-8 flex flex-col">
|
||||
<DialogHeader className="shrink-0">
|
||||
<DialogTitle>Settings</DialogTitle>
|
||||
<DialogTitle>{t("settings.title")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="grid overflow-y-auto flex-1 gap-6 py-4 min-h-0">
|
||||
@@ -625,6 +687,38 @@ export function SettingsDialog({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Language Section */}
|
||||
<div className="space-y-4">
|
||||
<Label className="text-base font-medium">Language</Label>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="language-select" className="text-sm">
|
||||
Interface Language
|
||||
</Label>
|
||||
<Select
|
||||
value={selectedLanguage || "system"}
|
||||
onValueChange={(value) => setSelectedLanguage(value)}
|
||||
disabled={isLanguageLoading}
|
||||
>
|
||||
<SelectTrigger id="language-select">
|
||||
<SelectValue placeholder="Select language" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="system">System Default</SelectItem>
|
||||
{supportedLanguages.map((lang) => (
|
||||
<SelectItem key={lang.code} value={lang.code}>
|
||||
{lang.nativeName} ({lang.name})
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Choose your preferred language for the application interface.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Default Browser Section */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
|
||||
@@ -31,6 +31,14 @@ export function CustomThemeProvider({ children }: CustomThemeProviderProps) {
|
||||
const settings = await invoke<AppSettings>("get_app_settings");
|
||||
const themeValue = settings?.theme ?? "system";
|
||||
|
||||
console.log("[theme-provider] Loaded settings:", {
|
||||
theme: themeValue,
|
||||
hasCustomTheme: !!settings?.custom_theme,
|
||||
customThemeKeys: settings?.custom_theme
|
||||
? Object.keys(settings.custom_theme).length
|
||||
: 0,
|
||||
});
|
||||
|
||||
if (
|
||||
themeValue === "light" ||
|
||||
themeValue === "dark" ||
|
||||
|
||||
@@ -295,13 +295,26 @@ export function useBrowserDownload() {
|
||||
eta: etaText,
|
||||
},
|
||||
{
|
||||
onCancel: () => dismissToast(toastId),
|
||||
onCancel: () => {
|
||||
invoke("cancel_download", {
|
||||
browserStr: progress.browser,
|
||||
version: progress.version,
|
||||
}).catch((err) =>
|
||||
console.error("Failed to cancel download:", err),
|
||||
);
|
||||
dismissToast(toastId);
|
||||
},
|
||||
},
|
||||
);
|
||||
} else if (progress.stage === "extracting") {
|
||||
showDownloadToast(browserName, progress.version, "extracting");
|
||||
} else if (progress.stage === "verifying") {
|
||||
showDownloadToast(browserName, progress.version, "verifying");
|
||||
} else if (progress.stage === "cancelled") {
|
||||
dismissToast(
|
||||
`download-${browserName.toLowerCase()}-${progress.version}`,
|
||||
);
|
||||
setDownloadProgress(null);
|
||||
} else if (progress.stage === "completed") {
|
||||
// On completion, refresh the downloaded versions for this browser and also refresh camoufox,
|
||||
// since the Create dialog implicitly uses camoufox on the anti-detect tab
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
getLanguageWithFallback,
|
||||
SUPPORTED_LANGUAGES,
|
||||
type SupportedLanguage,
|
||||
} from "@/i18n";
|
||||
|
||||
interface AppSettings {
|
||||
language?: string | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export function useLanguage() {
|
||||
const { i18n } = useTranslation();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [currentLanguage, setCurrentLanguage] = useState<string>("en");
|
||||
|
||||
const loadLanguage = useCallback(async () => {
|
||||
try {
|
||||
const settings = await invoke<AppSettings>("get_app_settings");
|
||||
let language = settings.language;
|
||||
|
||||
if (!language) {
|
||||
const systemLanguage = await invoke<string>("get_system_language");
|
||||
language = getLanguageWithFallback(systemLanguage);
|
||||
}
|
||||
|
||||
if (
|
||||
language &&
|
||||
SUPPORTED_LANGUAGES.some((lang) => lang.code === language)
|
||||
) {
|
||||
await i18n.changeLanguage(language);
|
||||
setCurrentLanguage(language);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load language setting:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [i18n]);
|
||||
|
||||
const changeLanguage = useCallback(
|
||||
async (language: SupportedLanguage | null) => {
|
||||
try {
|
||||
const settings = await invoke<AppSettings>("get_app_settings");
|
||||
const updatedSettings = {
|
||||
...settings,
|
||||
language,
|
||||
};
|
||||
await invoke("save_app_settings", { settings: updatedSettings });
|
||||
|
||||
if (language) {
|
||||
await i18n.changeLanguage(language);
|
||||
setCurrentLanguage(language);
|
||||
} else {
|
||||
const systemLanguage = await invoke<string>("get_system_language");
|
||||
const fallbackLanguage = getLanguageWithFallback(systemLanguage);
|
||||
await i18n.changeLanguage(fallbackLanguage);
|
||||
setCurrentLanguage(fallbackLanguage);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to change language:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[i18n],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
void loadLanguage();
|
||||
}, [loadLanguage]);
|
||||
|
||||
return {
|
||||
currentLanguage,
|
||||
changeLanguage,
|
||||
isLoading,
|
||||
supportedLanguages: SUPPORTED_LANGUAGES,
|
||||
};
|
||||
}
|
||||
@@ -13,8 +13,16 @@ export function useWayfernTerms(): UseWayfernTermsReturn {
|
||||
|
||||
const checkTerms = useCallback(async () => {
|
||||
try {
|
||||
const accepted = await invoke<boolean>("check_wayfern_terms_accepted");
|
||||
setTermsAccepted(accepted);
|
||||
const [accepted, downloaded] = await Promise.all([
|
||||
invoke<boolean>("check_wayfern_terms_accepted"),
|
||||
invoke<boolean>("check_wayfern_downloaded"),
|
||||
]);
|
||||
// Only require terms when Wayfern is downloaded and terms not accepted
|
||||
if (!downloaded) {
|
||||
setTermsAccepted(true);
|
||||
} else {
|
||||
setTermsAccepted(accepted);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to check terms acceptance:", error);
|
||||
setTermsAccepted(false);
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
|
||||
import en from "./locales/en.json";
|
||||
import es from "./locales/es.json";
|
||||
import fr from "./locales/fr.json";
|
||||
import ja from "./locales/ja.json";
|
||||
import pt from "./locales/pt.json";
|
||||
import ru from "./locales/ru.json";
|
||||
import zh from "./locales/zh.json";
|
||||
|
||||
export const SUPPORTED_LANGUAGES = [
|
||||
{ code: "en", name: "English", nativeName: "English" },
|
||||
{ code: "es", name: "Spanish", nativeName: "Español" },
|
||||
{ code: "pt", name: "Portuguese", nativeName: "Português" },
|
||||
{ code: "fr", name: "French", nativeName: "Français" },
|
||||
{ code: "zh", name: "Chinese", nativeName: "中文" },
|
||||
{ code: "ja", name: "Japanese", nativeName: "日本語" },
|
||||
{ code: "ru", name: "Russian", nativeName: "Русский" },
|
||||
] as const;
|
||||
|
||||
export type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number]["code"];
|
||||
|
||||
export const LANGUAGE_FALLBACKS: Record<string, string[]> = {
|
||||
uk: ["ru", "en"],
|
||||
be: ["ru", "en"],
|
||||
"zh-TW": ["zh", "en"],
|
||||
"zh-CN": ["zh", "en"],
|
||||
"zh-HK": ["zh", "en"],
|
||||
"pt-BR": ["pt", "en"],
|
||||
"pt-PT": ["pt", "en"],
|
||||
"es-MX": ["es", "en"],
|
||||
"es-AR": ["es", "en"],
|
||||
"es-ES": ["es", "en"],
|
||||
"fr-CA": ["fr", "en"],
|
||||
"fr-FR": ["fr", "en"],
|
||||
};
|
||||
|
||||
export function getLanguageWithFallback(systemLocale: string): string {
|
||||
const baseLanguage = systemLocale.split(/[-_]/)[0].toLowerCase();
|
||||
|
||||
if (SUPPORTED_LANGUAGES.some((lang) => lang.code === baseLanguage)) {
|
||||
return baseLanguage;
|
||||
}
|
||||
|
||||
if (LANGUAGE_FALLBACKS[systemLocale]) {
|
||||
return LANGUAGE_FALLBACKS[systemLocale][0];
|
||||
}
|
||||
|
||||
if (LANGUAGE_FALLBACKS[baseLanguage]) {
|
||||
return LANGUAGE_FALLBACKS[baseLanguage][0];
|
||||
}
|
||||
|
||||
return "en";
|
||||
}
|
||||
|
||||
const resources = {
|
||||
en: { translation: en },
|
||||
es: { translation: es },
|
||||
pt: { translation: pt },
|
||||
fr: { translation: fr },
|
||||
zh: { translation: zh },
|
||||
ja: { translation: ja },
|
||||
ru: { translation: ru },
|
||||
};
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
resources,
|
||||
lng: "en",
|
||||
fallbackLng: "en",
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
react: {
|
||||
useSuspense: false,
|
||||
},
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
@@ -0,0 +1,460 @@
|
||||
{
|
||||
"common": {
|
||||
"buttons": {
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"close": "Close",
|
||||
"delete": "Delete",
|
||||
"create": "Create",
|
||||
"back": "Back",
|
||||
"retry": "Retry",
|
||||
"download": "Download",
|
||||
"confirm": "Confirm",
|
||||
"apply": "Apply",
|
||||
"reset": "Reset",
|
||||
"add": "Add",
|
||||
"edit": "Edit",
|
||||
"copy": "Copy",
|
||||
"clear": "Clear",
|
||||
"search": "Search",
|
||||
"select": "Select",
|
||||
"grant": "Grant",
|
||||
"start": "Start",
|
||||
"stop": "Stop",
|
||||
"enable": "Enable",
|
||||
"disable": "Disable",
|
||||
"import": "Import",
|
||||
"export": "Export",
|
||||
"refresh": "Refresh",
|
||||
"loading": "Loading...",
|
||||
"saveSettings": "Save Settings"
|
||||
},
|
||||
"status": {
|
||||
"active": "Active",
|
||||
"inactive": "Inactive",
|
||||
"running": "Running",
|
||||
"stopped": "Stopped",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"granted": "Granted",
|
||||
"notGranted": "Not Granted",
|
||||
"connected": "Connected",
|
||||
"disconnected": "Disconnected",
|
||||
"synced": "Synced",
|
||||
"syncing": "Syncing",
|
||||
"pending": "Pending",
|
||||
"error": "Error"
|
||||
},
|
||||
"labels": {
|
||||
"name": "Name",
|
||||
"type": "Type",
|
||||
"status": "Status",
|
||||
"actions": "Actions",
|
||||
"description": "Description",
|
||||
"none": "None",
|
||||
"default": "Default",
|
||||
"custom": "Custom",
|
||||
"optional": "Optional",
|
||||
"required": "Required"
|
||||
},
|
||||
"time": {
|
||||
"days": "days",
|
||||
"hours": "hours",
|
||||
"minutes": "minutes",
|
||||
"seconds": "seconds",
|
||||
"remaining": "remaining"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
"appearance": {
|
||||
"title": "Appearance",
|
||||
"theme": "Theme",
|
||||
"themeDescription": "Choose your preferred theme or follow your system settings. Custom theme changes are applied only when you save.",
|
||||
"themePreset": "Theme Preset",
|
||||
"customColors": "Custom Colors",
|
||||
"selectTheme": "Select theme",
|
||||
"selectThemePreset": "Select a theme preset",
|
||||
"yourOwn": "Your Own",
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"system": "System"
|
||||
},
|
||||
"language": {
|
||||
"title": "Language",
|
||||
"description": "Choose your preferred language for the application interface.",
|
||||
"systemDefault": "System Default",
|
||||
"selectLanguage": "Select language"
|
||||
},
|
||||
"defaultBrowser": {
|
||||
"title": "Default Browser",
|
||||
"setAsDefault": "Set as Default Browser",
|
||||
"alreadyDefault": "Already Default Browser",
|
||||
"description": "When set as default, Donut Browser will handle web links and allow you to choose which profile to use."
|
||||
},
|
||||
"permissions": {
|
||||
"title": "System Permissions",
|
||||
"loading": "Loading permissions...",
|
||||
"description": "These permissions allow browsers launched from Donut Browser to access system resources. Each website will still ask for your permission individually.",
|
||||
"microphone": "Microphone",
|
||||
"microphoneDescription": "Access to microphone for browser applications",
|
||||
"camera": "Camera",
|
||||
"cameraDescription": "Access to camera for browser applications"
|
||||
},
|
||||
"integrations": {
|
||||
"title": "Integrations",
|
||||
"description": "Configure Local API and MCP (Model Context Protocol) for integrating with external tools and AI assistants.",
|
||||
"openSettings": "Open Integrations Settings"
|
||||
},
|
||||
"commercial": {
|
||||
"title": "Commercial License",
|
||||
"trialActive": "Trial: {{days}} days, {{hours}} hours remaining",
|
||||
"trialActiveDescription": "Commercial use is free during the trial period",
|
||||
"trialExpired": "Trial expired",
|
||||
"trialExpiredDescription": "Personal use remains free. Commercial use requires a license."
|
||||
},
|
||||
"advanced": {
|
||||
"title": "Advanced",
|
||||
"clearCache": "Clear All Version Cache",
|
||||
"clearCacheDescription": "Clear all cached browser version data and refresh all browser versions from their sources. This will force a fresh download of version information for all browsers."
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"searchPlaceholder": "Search profiles...",
|
||||
"clearSearch": "Clear search",
|
||||
"moreActions": "More actions",
|
||||
"createProfile": "Create a new profile",
|
||||
"menu": {
|
||||
"settings": "Settings",
|
||||
"proxies": "Proxies",
|
||||
"groups": "Groups",
|
||||
"syncService": "Sync Service",
|
||||
"integrations": "Integrations",
|
||||
"importProfile": "Import Profile"
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"title": "Profiles",
|
||||
"empty": "No profiles yet",
|
||||
"emptyDescription": "Create your first browser profile to get started.",
|
||||
"createFirst": "Create Profile",
|
||||
"noResults": "No profiles found",
|
||||
"noResultsDescription": "No profiles match your search criteria.",
|
||||
"table": {
|
||||
"name": "Name",
|
||||
"browser": "Browser",
|
||||
"status": "Status",
|
||||
"actions": "Actions",
|
||||
"note": "Note",
|
||||
"group": "Group",
|
||||
"proxy": "Proxy",
|
||||
"lastLaunch": "Last Launch"
|
||||
},
|
||||
"actions": {
|
||||
"launch": "Launch",
|
||||
"stop": "Stop",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"copyCookies": "Copy Cookies",
|
||||
"configure": "Configure"
|
||||
}
|
||||
},
|
||||
"createProfile": {
|
||||
"title": "Create New Profile",
|
||||
"configureTitle": "Configure Profile",
|
||||
"antiDetect": {
|
||||
"title": "Anti-Detect Browser",
|
||||
"description": "Choose a browser with anti-detection capabilities",
|
||||
"chromium": "Chromium (Wayfern)",
|
||||
"firefox": "Firefox (Camoufox)",
|
||||
"badge": "Anti-Detect Browser"
|
||||
},
|
||||
"regular": {
|
||||
"title": "Regular Browsers",
|
||||
"description": "Choose from supported regular browsers",
|
||||
"badge": "Regular Browser"
|
||||
},
|
||||
"profileName": "Profile Name",
|
||||
"profileNamePlaceholder": "Enter profile name",
|
||||
"proxy": {
|
||||
"title": "Proxy",
|
||||
"addProxy": "Add Proxy",
|
||||
"noProxy": "No proxy",
|
||||
"noProxiesAvailable": "No proxies available. Add one to route this profile's traffic."
|
||||
},
|
||||
"version": {
|
||||
"fetching": "Fetching available versions...",
|
||||
"fetchError": "Failed to fetch browser versions. Please check your internet connection and try again.",
|
||||
"needsDownload": "{{browser}} version ({{version}}) needs to be downloaded",
|
||||
"available": "{{browser}} version ({{version}}) is available",
|
||||
"downloading": "Downloading {{browser}} version ({{version}})...",
|
||||
"latestNeedsDownload": "Latest version ({{version}}) needs to be downloaded",
|
||||
"latestAvailable": "Latest version ({{version}}) is available",
|
||||
"latestDownloading": "Downloading version ({{version}})..."
|
||||
}
|
||||
},
|
||||
"deleteDialog": {
|
||||
"title": "Delete Profile",
|
||||
"description": "Are you sure you want to delete this profile? This action cannot be undone.",
|
||||
"profilesTitle": "Delete Profiles",
|
||||
"profilesDescription": "Are you sure you want to delete the selected profiles? This action cannot be undone.",
|
||||
"profilesToDelete": "Profiles to be deleted:"
|
||||
},
|
||||
"proxies": {
|
||||
"title": "Proxies",
|
||||
"management": "Proxy Management",
|
||||
"add": "Add Proxy",
|
||||
"edit": "Edit Proxy",
|
||||
"delete": "Delete Proxy",
|
||||
"import": "Import",
|
||||
"export": "Export",
|
||||
"noProxies": "No proxies configured",
|
||||
"noProxiesDescription": "Add a proxy to route browser traffic through it.",
|
||||
"form": {
|
||||
"name": "Name",
|
||||
"namePlaceholder": "Enter proxy name",
|
||||
"type": "Type",
|
||||
"host": "Host",
|
||||
"hostPlaceholder": "proxy.example.com",
|
||||
"port": "Port",
|
||||
"portPlaceholder": "8080",
|
||||
"username": "Username",
|
||||
"usernamePlaceholder": "Optional",
|
||||
"password": "Password",
|
||||
"passwordPlaceholder": "Optional"
|
||||
},
|
||||
"types": {
|
||||
"http": "HTTP",
|
||||
"https": "HTTPS",
|
||||
"socks4": "SOCKS4",
|
||||
"socks5": "SOCKS5"
|
||||
},
|
||||
"check": {
|
||||
"checking": "Checking proxy...",
|
||||
"valid": "Proxy is valid",
|
||||
"invalid": "Proxy is invalid",
|
||||
"lastChecked": "Last checked: {{time}}"
|
||||
},
|
||||
"sync": {
|
||||
"enabled": "Sync Enabled",
|
||||
"disabled": "Sync Disabled"
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"title": "Groups",
|
||||
"management": "Group Management",
|
||||
"add": "Add Group",
|
||||
"edit": "Edit Group",
|
||||
"delete": "Delete Group",
|
||||
"noGroups": "No groups created",
|
||||
"noGroupsDescription": "Create a group to organize your profiles.",
|
||||
"form": {
|
||||
"name": "Name",
|
||||
"namePlaceholder": "Enter group name"
|
||||
},
|
||||
"profileCount": "{{count}} profile",
|
||||
"profileCount_plural": "{{count}} profiles",
|
||||
"assignProfiles": "Assign Profiles",
|
||||
"sync": {
|
||||
"enabled": "Sync Enabled",
|
||||
"disabled": "Sync Disabled"
|
||||
}
|
||||
},
|
||||
"sync": {
|
||||
"title": "Sync Service",
|
||||
"config": "Sync Configuration",
|
||||
"serverUrl": "Server URL",
|
||||
"serverUrlPlaceholder": "https://sync.example.com",
|
||||
"token": "Sync Token",
|
||||
"tokenPlaceholder": "Enter your sync token",
|
||||
"status": {
|
||||
"connected": "Connected",
|
||||
"disconnected": "Disconnected",
|
||||
"syncing": "Syncing...",
|
||||
"error": "Sync Error"
|
||||
},
|
||||
"description": "Connect to a sync server to synchronize your profiles, proxies, and groups across devices."
|
||||
},
|
||||
"integrations": {
|
||||
"title": "Integrations",
|
||||
"api": {
|
||||
"title": "Local API",
|
||||
"description": "Enable the local API server for external integrations.",
|
||||
"enabled": "API Enabled",
|
||||
"disabled": "API Disabled",
|
||||
"port": "Port",
|
||||
"token": "API Token",
|
||||
"copyToken": "Copy Token",
|
||||
"regenerateToken": "Regenerate Token"
|
||||
},
|
||||
"mcp": {
|
||||
"title": "MCP Server",
|
||||
"description": "Enable the MCP (Model Context Protocol) server for AI assistant integrations.",
|
||||
"enabled": "MCP Enabled",
|
||||
"disabled": "MCP Disabled",
|
||||
"port": "Port",
|
||||
"token": "MCP Token",
|
||||
"config": "MCP Configuration",
|
||||
"copyConfig": "Copy Configuration"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"title": "Import Profile",
|
||||
"description": "Import an existing browser profile from your system.",
|
||||
"selectProfile": "Select a profile to import",
|
||||
"noProfiles": "No profiles detected",
|
||||
"noProfilesDescription": "No browser profiles were detected on your system.",
|
||||
"importing": "Importing profile...",
|
||||
"success": "Profile imported successfully",
|
||||
"error": "Failed to import profile"
|
||||
},
|
||||
"config": {
|
||||
"camoufox": {
|
||||
"title": "Camoufox Configuration",
|
||||
"fingerprint": {
|
||||
"title": "Fingerprint",
|
||||
"randomize": "Randomize on Launch",
|
||||
"randomizeDescription": "Generate a new fingerprint each time the browser is launched."
|
||||
},
|
||||
"os": {
|
||||
"title": "Operating System",
|
||||
"description": "The operating system to emulate for fingerprint generation.",
|
||||
"windows": "Windows",
|
||||
"macos": "macOS",
|
||||
"linux": "Linux"
|
||||
},
|
||||
"screen": {
|
||||
"title": "Screen Size",
|
||||
"minWidth": "Min Width",
|
||||
"maxWidth": "Max Width",
|
||||
"minHeight": "Min Height",
|
||||
"maxHeight": "Max Height"
|
||||
},
|
||||
"geoip": {
|
||||
"title": "GeoIP",
|
||||
"auto": "Automatic (based on proxy)",
|
||||
"manual": "Manual",
|
||||
"disabled": "Disabled"
|
||||
},
|
||||
"blocking": {
|
||||
"title": "Blocking",
|
||||
"images": "Block Images",
|
||||
"webrtc": "Block WebRTC",
|
||||
"webgl": "Block WebGL"
|
||||
}
|
||||
},
|
||||
"wayfern": {
|
||||
"title": "Wayfern Configuration",
|
||||
"fingerprint": {
|
||||
"title": "Fingerprint",
|
||||
"randomize": "Randomize on Launch",
|
||||
"randomizeDescription": "Generate a new fingerprint each time the browser is launched."
|
||||
},
|
||||
"os": {
|
||||
"title": "Operating System",
|
||||
"description": "The operating system to emulate for fingerprint generation.",
|
||||
"windows": "Windows",
|
||||
"macos": "macOS",
|
||||
"linux": "Linux",
|
||||
"android": "Android",
|
||||
"ios": "iOS"
|
||||
},
|
||||
"screen": {
|
||||
"title": "Screen Size",
|
||||
"minWidth": "Min Width",
|
||||
"maxWidth": "Max Width",
|
||||
"minHeight": "Min Height",
|
||||
"maxHeight": "Max Height"
|
||||
},
|
||||
"blocking": {
|
||||
"title": "Blocking",
|
||||
"webrtc": "Block WebRTC",
|
||||
"webgl": "Block WebGL"
|
||||
}
|
||||
}
|
||||
},
|
||||
"cookies": {
|
||||
"title": "Cookies",
|
||||
"copy": {
|
||||
"title": "Copy Cookies",
|
||||
"description": "Select cookies to copy to other profiles.",
|
||||
"selectSource": "Select Source Profile",
|
||||
"selectTarget": "Select Target Profiles",
|
||||
"selectCookies": "Select Cookies",
|
||||
"allDomains": "All Domains",
|
||||
"selectedCount": "{{count}} cookie selected",
|
||||
"selectedCount_plural": "{{count}} cookies selected"
|
||||
},
|
||||
"success": "Cookies copied successfully",
|
||||
"error": "Failed to copy cookies"
|
||||
},
|
||||
"toasts": {
|
||||
"success": {
|
||||
"profileCreated": "Profile created successfully",
|
||||
"profileDeleted": "Profile deleted successfully",
|
||||
"profileUpdated": "Profile updated successfully",
|
||||
"profileLaunched": "Profile launched successfully",
|
||||
"proxyCreated": "Proxy created successfully",
|
||||
"proxyDeleted": "Proxy deleted successfully",
|
||||
"proxyUpdated": "Proxy updated successfully",
|
||||
"groupCreated": "Group created successfully",
|
||||
"groupDeleted": "Group deleted successfully",
|
||||
"groupUpdated": "Group updated successfully",
|
||||
"settingsSaved": "Settings saved successfully",
|
||||
"copied": "Copied to clipboard",
|
||||
"permissionRequested": "{{permission}} access requested",
|
||||
"downloadComplete": "{{browser}} {{version}} downloaded successfully!",
|
||||
"importSuccess": "Successfully imported {{count}} items",
|
||||
"exportSuccess": "Successfully exported {{count}} items",
|
||||
"syncSuccess": "Sync completed successfully",
|
||||
"cacheCleared": "Cache cleared successfully"
|
||||
},
|
||||
"error": {
|
||||
"profileCreateFailed": "Failed to create profile",
|
||||
"profileDeleteFailed": "Failed to delete profile",
|
||||
"profileUpdateFailed": "Failed to update profile",
|
||||
"profileLaunchFailed": "Failed to launch profile",
|
||||
"proxyCreateFailed": "Failed to create proxy",
|
||||
"proxyDeleteFailed": "Failed to delete proxy",
|
||||
"proxyUpdateFailed": "Failed to update proxy",
|
||||
"groupCreateFailed": "Failed to create group",
|
||||
"groupDeleteFailed": "Failed to delete group",
|
||||
"groupUpdateFailed": "Failed to update group",
|
||||
"settingsSaveFailed": "Failed to save settings",
|
||||
"copyFailed": "Failed to copy to clipboard",
|
||||
"downloadFailed": "Failed to download {{browser}}",
|
||||
"importFailed": "Failed to import",
|
||||
"exportFailed": "Failed to export",
|
||||
"syncFailed": "Sync failed",
|
||||
"cacheClearFailed": "Failed to clear cache",
|
||||
"unknown": "An unknown error occurred"
|
||||
},
|
||||
"loading": {
|
||||
"downloading": "Downloading {{browser}} {{version}}",
|
||||
"extracting": "Extracting {{browser}} {{version}}",
|
||||
"verifying": "Verifying {{browser}} {{version}}",
|
||||
"syncing": "Syncing...",
|
||||
"updatingVersions": "Updating browser versions..."
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"required": "This field is required",
|
||||
"invalidUrl": "Please enter a valid URL",
|
||||
"invalidPort": "Please enter a valid port number (1-65535)",
|
||||
"invalidEmail": "Please enter a valid email address",
|
||||
"minLength": "Must be at least {{min}} characters",
|
||||
"maxLength": "Must be at most {{max}} characters",
|
||||
"networkError": "Network error. Please check your connection.",
|
||||
"serverError": "Server error. Please try again later.",
|
||||
"unknownError": "An unknown error occurred. Please try again."
|
||||
},
|
||||
"browser": {
|
||||
"firefox": "Firefox",
|
||||
"firefoxDeveloper": "Firefox Developer Edition",
|
||||
"chromium": "Chromium",
|
||||
"brave": "Brave",
|
||||
"zen": "Zen Browser",
|
||||
"camoufox": "Camoufox",
|
||||
"wayfern": "Wayfern"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
{
|
||||
"common": {
|
||||
"buttons": {
|
||||
"save": "Guardar",
|
||||
"cancel": "Cancelar",
|
||||
"close": "Cerrar",
|
||||
"delete": "Eliminar",
|
||||
"create": "Crear",
|
||||
"back": "Atrás",
|
||||
"retry": "Reintentar",
|
||||
"download": "Descargar",
|
||||
"confirm": "Confirmar",
|
||||
"apply": "Aplicar",
|
||||
"reset": "Restablecer",
|
||||
"add": "Agregar",
|
||||
"edit": "Editar",
|
||||
"copy": "Copiar",
|
||||
"clear": "Limpiar",
|
||||
"search": "Buscar",
|
||||
"select": "Seleccionar",
|
||||
"grant": "Otorgar",
|
||||
"start": "Iniciar",
|
||||
"stop": "Detener",
|
||||
"enable": "Habilitar",
|
||||
"disable": "Deshabilitar",
|
||||
"import": "Importar",
|
||||
"export": "Exportar",
|
||||
"refresh": "Actualizar",
|
||||
"loading": "Cargando...",
|
||||
"saveSettings": "Guardar Configuración"
|
||||
},
|
||||
"status": {
|
||||
"active": "Activo",
|
||||
"inactive": "Inactivo",
|
||||
"running": "Ejecutando",
|
||||
"stopped": "Detenido",
|
||||
"enabled": "Habilitado",
|
||||
"disabled": "Deshabilitado",
|
||||
"granted": "Otorgado",
|
||||
"notGranted": "No Otorgado",
|
||||
"connected": "Conectado",
|
||||
"disconnected": "Desconectado",
|
||||
"synced": "Sincronizado",
|
||||
"syncing": "Sincronizando",
|
||||
"pending": "Pendiente",
|
||||
"error": "Error"
|
||||
},
|
||||
"labels": {
|
||||
"name": "Nombre",
|
||||
"type": "Tipo",
|
||||
"status": "Estado",
|
||||
"actions": "Acciones",
|
||||
"description": "Descripción",
|
||||
"none": "Ninguno",
|
||||
"default": "Predeterminado",
|
||||
"custom": "Personalizado",
|
||||
"optional": "Opcional",
|
||||
"required": "Requerido"
|
||||
},
|
||||
"time": {
|
||||
"days": "días",
|
||||
"hours": "horas",
|
||||
"minutes": "minutos",
|
||||
"seconds": "segundos",
|
||||
"remaining": "restantes"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Configuración",
|
||||
"appearance": {
|
||||
"title": "Apariencia",
|
||||
"theme": "Tema",
|
||||
"themeDescription": "Elige tu tema preferido o sigue la configuración del sistema. Los cambios de tema personalizado se aplican solo al guardar.",
|
||||
"themePreset": "Tema Predefinido",
|
||||
"customColors": "Colores Personalizados",
|
||||
"selectTheme": "Seleccionar tema",
|
||||
"selectThemePreset": "Seleccionar tema predefinido",
|
||||
"yourOwn": "Tu Propio",
|
||||
"light": "Claro",
|
||||
"dark": "Oscuro",
|
||||
"system": "Sistema"
|
||||
},
|
||||
"language": {
|
||||
"title": "Idioma",
|
||||
"description": "Elige tu idioma preferido para la interfaz de la aplicación.",
|
||||
"systemDefault": "Predeterminado del Sistema",
|
||||
"selectLanguage": "Seleccionar idioma"
|
||||
},
|
||||
"defaultBrowser": {
|
||||
"title": "Navegador Predeterminado",
|
||||
"setAsDefault": "Establecer como Navegador Predeterminado",
|
||||
"alreadyDefault": "Ya es el Navegador Predeterminado",
|
||||
"description": "Cuando se establece como predeterminado, Donut Browser manejará los enlaces web y te permitirá elegir qué perfil usar."
|
||||
},
|
||||
"permissions": {
|
||||
"title": "Permisos del Sistema",
|
||||
"loading": "Cargando permisos...",
|
||||
"description": "Estos permisos permiten que los navegadores iniciados desde Donut Browser accedan a los recursos del sistema. Cada sitio web seguirá pidiendo tu permiso individualmente.",
|
||||
"microphone": "Micrófono",
|
||||
"microphoneDescription": "Acceso al micrófono para aplicaciones del navegador",
|
||||
"camera": "Cámara",
|
||||
"cameraDescription": "Acceso a la cámara para aplicaciones del navegador"
|
||||
},
|
||||
"integrations": {
|
||||
"title": "Integraciones",
|
||||
"description": "Configura la API Local y MCP (Protocolo de Contexto de Modelo) para integración con herramientas externas y asistentes de IA.",
|
||||
"openSettings": "Abrir Configuración de Integraciones"
|
||||
},
|
||||
"commercial": {
|
||||
"title": "Licencia Comercial",
|
||||
"trialActive": "Prueba: {{days}} días, {{hours}} horas restantes",
|
||||
"trialActiveDescription": "El uso comercial es gratuito durante el período de prueba",
|
||||
"trialExpired": "Prueba expirada",
|
||||
"trialExpiredDescription": "El uso personal sigue siendo gratuito. El uso comercial requiere una licencia."
|
||||
},
|
||||
"advanced": {
|
||||
"title": "Avanzado",
|
||||
"clearCache": "Limpiar Toda la Caché de Versiones",
|
||||
"clearCacheDescription": "Limpia todos los datos de versiones de navegadores en caché y actualiza todas las versiones desde sus fuentes. Esto forzará una descarga nueva de información de versiones para todos los navegadores."
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"searchPlaceholder": "Buscar perfiles...",
|
||||
"clearSearch": "Limpiar búsqueda",
|
||||
"moreActions": "Más acciones",
|
||||
"createProfile": "Crear un nuevo perfil",
|
||||
"menu": {
|
||||
"settings": "Configuración",
|
||||
"proxies": "Proxies",
|
||||
"groups": "Grupos",
|
||||
"syncService": "Servicio de Sincronización",
|
||||
"integrations": "Integraciones",
|
||||
"importProfile": "Importar Perfil"
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"title": "Perfiles",
|
||||
"empty": "Sin perfiles aún",
|
||||
"emptyDescription": "Crea tu primer perfil de navegador para comenzar.",
|
||||
"createFirst": "Crear Perfil",
|
||||
"noResults": "No se encontraron perfiles",
|
||||
"noResultsDescription": "Ningún perfil coincide con tus criterios de búsqueda.",
|
||||
"table": {
|
||||
"name": "Nombre",
|
||||
"browser": "Navegador",
|
||||
"status": "Estado",
|
||||
"actions": "Acciones",
|
||||
"note": "Nota",
|
||||
"group": "Grupo",
|
||||
"proxy": "Proxy",
|
||||
"lastLaunch": "Último Inicio"
|
||||
},
|
||||
"actions": {
|
||||
"launch": "Iniciar",
|
||||
"stop": "Detener",
|
||||
"edit": "Editar",
|
||||
"delete": "Eliminar",
|
||||
"copyCookies": "Copiar Cookies",
|
||||
"configure": "Configurar"
|
||||
}
|
||||
},
|
||||
"createProfile": {
|
||||
"title": "Crear Nuevo Perfil",
|
||||
"configureTitle": "Configurar Perfil",
|
||||
"antiDetect": {
|
||||
"title": "Navegador Anti-Detección",
|
||||
"description": "Elige un navegador con capacidades anti-detección",
|
||||
"chromium": "Chromium (Wayfern)",
|
||||
"firefox": "Firefox (Camoufox)",
|
||||
"badge": "Navegador Anti-Detección"
|
||||
},
|
||||
"regular": {
|
||||
"title": "Navegadores Regulares",
|
||||
"description": "Elige entre navegadores regulares soportados",
|
||||
"badge": "Navegador Regular"
|
||||
},
|
||||
"profileName": "Nombre del Perfil",
|
||||
"profileNamePlaceholder": "Ingresa el nombre del perfil",
|
||||
"proxy": {
|
||||
"title": "Proxy",
|
||||
"addProxy": "Agregar Proxy",
|
||||
"noProxy": "Sin proxy",
|
||||
"noProxiesAvailable": "No hay proxies disponibles. Agrega uno para enrutar el tráfico de este perfil."
|
||||
},
|
||||
"version": {
|
||||
"fetching": "Obteniendo versiones disponibles...",
|
||||
"fetchError": "Error al obtener versiones del navegador. Por favor verifica tu conexión a internet e intenta de nuevo.",
|
||||
"needsDownload": "La versión de {{browser}} ({{version}}) necesita ser descargada",
|
||||
"available": "La versión de {{browser}} ({{version}}) está disponible",
|
||||
"downloading": "Descargando versión de {{browser}} ({{version}})...",
|
||||
"latestNeedsDownload": "La última versión ({{version}}) necesita ser descargada",
|
||||
"latestAvailable": "La última versión ({{version}}) está disponible",
|
||||
"latestDownloading": "Descargando versión ({{version}})..."
|
||||
}
|
||||
},
|
||||
"deleteDialog": {
|
||||
"title": "Eliminar Perfil",
|
||||
"description": "¿Estás seguro de que deseas eliminar este perfil? Esta acción no se puede deshacer.",
|
||||
"profilesTitle": "Eliminar Perfiles",
|
||||
"profilesDescription": "¿Estás seguro de que deseas eliminar los perfiles seleccionados? Esta acción no se puede deshacer.",
|
||||
"profilesToDelete": "Perfiles a eliminar:"
|
||||
},
|
||||
"proxies": {
|
||||
"title": "Proxies",
|
||||
"management": "Gestión de Proxies",
|
||||
"add": "Agregar Proxy",
|
||||
"edit": "Editar Proxy",
|
||||
"delete": "Eliminar Proxy",
|
||||
"import": "Importar",
|
||||
"export": "Exportar",
|
||||
"noProxies": "No hay proxies configurados",
|
||||
"noProxiesDescription": "Agrega un proxy para enrutar el tráfico del navegador a través de él.",
|
||||
"form": {
|
||||
"name": "Nombre",
|
||||
"namePlaceholder": "Ingresa el nombre del proxy",
|
||||
"type": "Tipo",
|
||||
"host": "Host",
|
||||
"hostPlaceholder": "proxy.ejemplo.com",
|
||||
"port": "Puerto",
|
||||
"portPlaceholder": "8080",
|
||||
"username": "Usuario",
|
||||
"usernamePlaceholder": "Opcional",
|
||||
"password": "Contraseña",
|
||||
"passwordPlaceholder": "Opcional"
|
||||
},
|
||||
"types": {
|
||||
"http": "HTTP",
|
||||
"https": "HTTPS",
|
||||
"socks4": "SOCKS4",
|
||||
"socks5": "SOCKS5"
|
||||
},
|
||||
"check": {
|
||||
"checking": "Verificando proxy...",
|
||||
"valid": "El proxy es válido",
|
||||
"invalid": "El proxy es inválido",
|
||||
"lastChecked": "Última verificación: {{time}}"
|
||||
},
|
||||
"sync": {
|
||||
"enabled": "Sincronización Habilitada",
|
||||
"disabled": "Sincronización Deshabilitada"
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"title": "Grupos",
|
||||
"management": "Gestión de Grupos",
|
||||
"add": "Agregar Grupo",
|
||||
"edit": "Editar Grupo",
|
||||
"delete": "Eliminar Grupo",
|
||||
"noGroups": "No hay grupos creados",
|
||||
"noGroupsDescription": "Crea un grupo para organizar tus perfiles.",
|
||||
"form": {
|
||||
"name": "Nombre",
|
||||
"namePlaceholder": "Ingresa el nombre del grupo"
|
||||
},
|
||||
"profileCount": "{{count}} perfil",
|
||||
"profileCount_plural": "{{count}} perfiles",
|
||||
"assignProfiles": "Asignar Perfiles",
|
||||
"sync": {
|
||||
"enabled": "Sincronización Habilitada",
|
||||
"disabled": "Sincronización Deshabilitada"
|
||||
}
|
||||
},
|
||||
"sync": {
|
||||
"title": "Servicio de Sincronización",
|
||||
"config": "Configuración de Sincronización",
|
||||
"serverUrl": "URL del Servidor",
|
||||
"serverUrlPlaceholder": "https://sync.ejemplo.com",
|
||||
"token": "Token de Sincronización",
|
||||
"tokenPlaceholder": "Ingresa tu token de sincronización",
|
||||
"status": {
|
||||
"connected": "Conectado",
|
||||
"disconnected": "Desconectado",
|
||||
"syncing": "Sincronizando...",
|
||||
"error": "Error de Sincronización"
|
||||
},
|
||||
"description": "Conéctate a un servidor de sincronización para sincronizar tus perfiles, proxies y grupos entre dispositivos."
|
||||
},
|
||||
"integrations": {
|
||||
"title": "Integraciones",
|
||||
"api": {
|
||||
"title": "API Local",
|
||||
"description": "Habilita el servidor de API local para integraciones externas.",
|
||||
"enabled": "API Habilitada",
|
||||
"disabled": "API Deshabilitada",
|
||||
"port": "Puerto",
|
||||
"token": "Token de API",
|
||||
"copyToken": "Copiar Token",
|
||||
"regenerateToken": "Regenerar Token"
|
||||
},
|
||||
"mcp": {
|
||||
"title": "Servidor MCP",
|
||||
"description": "Habilita el servidor MCP (Protocolo de Contexto de Modelo) para integraciones con asistentes de IA.",
|
||||
"enabled": "MCP Habilitado",
|
||||
"disabled": "MCP Deshabilitado",
|
||||
"port": "Puerto",
|
||||
"token": "Token MCP",
|
||||
"config": "Configuración MCP",
|
||||
"copyConfig": "Copiar Configuración"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"title": "Importar Perfil",
|
||||
"description": "Importa un perfil de navegador existente de tu sistema.",
|
||||
"selectProfile": "Selecciona un perfil para importar",
|
||||
"noProfiles": "No se detectaron perfiles",
|
||||
"noProfilesDescription": "No se detectaron perfiles de navegador en tu sistema.",
|
||||
"importing": "Importando perfil...",
|
||||
"success": "Perfil importado exitosamente",
|
||||
"error": "Error al importar perfil"
|
||||
},
|
||||
"config": {
|
||||
"camoufox": {
|
||||
"title": "Configuración de Camoufox",
|
||||
"fingerprint": {
|
||||
"title": "Huella Digital",
|
||||
"randomize": "Aleatorizar al Iniciar",
|
||||
"randomizeDescription": "Genera una nueva huella digital cada vez que se inicia el navegador."
|
||||
},
|
||||
"os": {
|
||||
"title": "Sistema Operativo",
|
||||
"description": "El sistema operativo a emular para la generación de huellas digitales.",
|
||||
"windows": "Windows",
|
||||
"macos": "macOS",
|
||||
"linux": "Linux"
|
||||
},
|
||||
"screen": {
|
||||
"title": "Tamaño de Pantalla",
|
||||
"minWidth": "Ancho Mín",
|
||||
"maxWidth": "Ancho Máx",
|
||||
"minHeight": "Alto Mín",
|
||||
"maxHeight": "Alto Máx"
|
||||
},
|
||||
"geoip": {
|
||||
"title": "GeoIP",
|
||||
"auto": "Automático (basado en proxy)",
|
||||
"manual": "Manual",
|
||||
"disabled": "Deshabilitado"
|
||||
},
|
||||
"blocking": {
|
||||
"title": "Bloqueo",
|
||||
"images": "Bloquear Imágenes",
|
||||
"webrtc": "Bloquear WebRTC",
|
||||
"webgl": "Bloquear WebGL"
|
||||
}
|
||||
},
|
||||
"wayfern": {
|
||||
"title": "Configuración de Wayfern",
|
||||
"fingerprint": {
|
||||
"title": "Huella Digital",
|
||||
"randomize": "Aleatorizar al Iniciar",
|
||||
"randomizeDescription": "Genera una nueva huella digital cada vez que se inicia el navegador."
|
||||
},
|
||||
"os": {
|
||||
"title": "Sistema Operativo",
|
||||
"description": "El sistema operativo a emular para la generación de huellas digitales.",
|
||||
"windows": "Windows",
|
||||
"macos": "macOS",
|
||||
"linux": "Linux",
|
||||
"android": "Android",
|
||||
"ios": "iOS"
|
||||
},
|
||||
"screen": {
|
||||
"title": "Tamaño de Pantalla",
|
||||
"minWidth": "Ancho Mín",
|
||||
"maxWidth": "Ancho Máx",
|
||||
"minHeight": "Alto Mín",
|
||||
"maxHeight": "Alto Máx"
|
||||
},
|
||||
"blocking": {
|
||||
"title": "Bloqueo",
|
||||
"webrtc": "Bloquear WebRTC",
|
||||
"webgl": "Bloquear WebGL"
|
||||
}
|
||||
}
|
||||
},
|
||||
"cookies": {
|
||||
"title": "Cookies",
|
||||
"copy": {
|
||||
"title": "Copiar Cookies",
|
||||
"description": "Selecciona cookies para copiar a otros perfiles.",
|
||||
"selectSource": "Seleccionar Perfil de Origen",
|
||||
"selectTarget": "Seleccionar Perfiles de Destino",
|
||||
"selectCookies": "Seleccionar Cookies",
|
||||
"allDomains": "Todos los Dominios",
|
||||
"selectedCount": "{{count}} cookie seleccionada",
|
||||
"selectedCount_plural": "{{count}} cookies seleccionadas"
|
||||
},
|
||||
"success": "Cookies copiadas exitosamente",
|
||||
"error": "Error al copiar cookies"
|
||||
},
|
||||
"toasts": {
|
||||
"success": {
|
||||
"profileCreated": "Perfil creado exitosamente",
|
||||
"profileDeleted": "Perfil eliminado exitosamente",
|
||||
"profileUpdated": "Perfil actualizado exitosamente",
|
||||
"profileLaunched": "Perfil iniciado exitosamente",
|
||||
"proxyCreated": "Proxy creado exitosamente",
|
||||
"proxyDeleted": "Proxy eliminado exitosamente",
|
||||
"proxyUpdated": "Proxy actualizado exitosamente",
|
||||
"groupCreated": "Grupo creado exitosamente",
|
||||
"groupDeleted": "Grupo eliminado exitosamente",
|
||||
"groupUpdated": "Grupo actualizado exitosamente",
|
||||
"settingsSaved": "Configuración guardada exitosamente",
|
||||
"copied": "Copiado al portapapeles",
|
||||
"permissionRequested": "Acceso a {{permission}} solicitado",
|
||||
"downloadComplete": "¡{{browser}} {{version}} descargado exitosamente!",
|
||||
"importSuccess": "{{count}} elementos importados exitosamente",
|
||||
"exportSuccess": "{{count}} elementos exportados exitosamente",
|
||||
"syncSuccess": "Sincronización completada exitosamente",
|
||||
"cacheCleared": "Caché limpiada exitosamente"
|
||||
},
|
||||
"error": {
|
||||
"profileCreateFailed": "Error al crear perfil",
|
||||
"profileDeleteFailed": "Error al eliminar perfil",
|
||||
"profileUpdateFailed": "Error al actualizar perfil",
|
||||
"profileLaunchFailed": "Error al iniciar perfil",
|
||||
"proxyCreateFailed": "Error al crear proxy",
|
||||
"proxyDeleteFailed": "Error al eliminar proxy",
|
||||
"proxyUpdateFailed": "Error al actualizar proxy",
|
||||
"groupCreateFailed": "Error al crear grupo",
|
||||
"groupDeleteFailed": "Error al eliminar grupo",
|
||||
"groupUpdateFailed": "Error al actualizar grupo",
|
||||
"settingsSaveFailed": "Error al guardar configuración",
|
||||
"copyFailed": "Error al copiar al portapapeles",
|
||||
"downloadFailed": "Error al descargar {{browser}}",
|
||||
"importFailed": "Error al importar",
|
||||
"exportFailed": "Error al exportar",
|
||||
"syncFailed": "Error de sincronización",
|
||||
"cacheClearFailed": "Error al limpiar caché",
|
||||
"unknown": "Ocurrió un error desconocido"
|
||||
},
|
||||
"loading": {
|
||||
"downloading": "Descargando {{browser}} {{version}}",
|
||||
"extracting": "Extrayendo {{browser}} {{version}}",
|
||||
"verifying": "Verificando {{browser}} {{version}}",
|
||||
"syncing": "Sincronizando...",
|
||||
"updatingVersions": "Actualizando versiones de navegadores..."
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"required": "Este campo es requerido",
|
||||
"invalidUrl": "Por favor ingresa una URL válida",
|
||||
"invalidPort": "Por favor ingresa un número de puerto válido (1-65535)",
|
||||
"invalidEmail": "Por favor ingresa un correo electrónico válido",
|
||||
"minLength": "Debe tener al menos {{min}} caracteres",
|
||||
"maxLength": "Debe tener como máximo {{max}} caracteres",
|
||||
"networkError": "Error de red. Por favor verifica tu conexión.",
|
||||
"serverError": "Error del servidor. Por favor intenta de nuevo más tarde.",
|
||||
"unknownError": "Ocurrió un error desconocido. Por favor intenta de nuevo."
|
||||
},
|
||||
"browser": {
|
||||
"firefox": "Firefox",
|
||||
"firefoxDeveloper": "Firefox Developer Edition",
|
||||
"chromium": "Chromium",
|
||||
"brave": "Brave",
|
||||
"zen": "Zen Browser",
|
||||
"camoufox": "Camoufox",
|
||||
"wayfern": "Wayfern"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
{
|
||||
"common": {
|
||||
"buttons": {
|
||||
"save": "Enregistrer",
|
||||
"cancel": "Annuler",
|
||||
"close": "Fermer",
|
||||
"delete": "Supprimer",
|
||||
"create": "Créer",
|
||||
"back": "Retour",
|
||||
"retry": "Réessayer",
|
||||
"download": "Télécharger",
|
||||
"confirm": "Confirmer",
|
||||
"apply": "Appliquer",
|
||||
"reset": "Réinitialiser",
|
||||
"add": "Ajouter",
|
||||
"edit": "Modifier",
|
||||
"copy": "Copier",
|
||||
"clear": "Effacer",
|
||||
"search": "Rechercher",
|
||||
"select": "Sélectionner",
|
||||
"grant": "Accorder",
|
||||
"start": "Démarrer",
|
||||
"stop": "Arrêter",
|
||||
"enable": "Activer",
|
||||
"disable": "Désactiver",
|
||||
"import": "Importer",
|
||||
"export": "Exporter",
|
||||
"refresh": "Actualiser",
|
||||
"loading": "Chargement...",
|
||||
"saveSettings": "Enregistrer les paramètres"
|
||||
},
|
||||
"status": {
|
||||
"active": "Actif",
|
||||
"inactive": "Inactif",
|
||||
"running": "En cours",
|
||||
"stopped": "Arrêté",
|
||||
"enabled": "Activé",
|
||||
"disabled": "Désactivé",
|
||||
"granted": "Accordé",
|
||||
"notGranted": "Non accordé",
|
||||
"connected": "Connecté",
|
||||
"disconnected": "Déconnecté",
|
||||
"synced": "Synchronisé",
|
||||
"syncing": "Synchronisation",
|
||||
"pending": "En attente",
|
||||
"error": "Erreur"
|
||||
},
|
||||
"labels": {
|
||||
"name": "Nom",
|
||||
"type": "Type",
|
||||
"status": "Statut",
|
||||
"actions": "Actions",
|
||||
"description": "Description",
|
||||
"none": "Aucun",
|
||||
"default": "Par défaut",
|
||||
"custom": "Personnalisé",
|
||||
"optional": "Optionnel",
|
||||
"required": "Requis"
|
||||
},
|
||||
"time": {
|
||||
"days": "jours",
|
||||
"hours": "heures",
|
||||
"minutes": "minutes",
|
||||
"seconds": "secondes",
|
||||
"remaining": "restants"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Paramètres",
|
||||
"appearance": {
|
||||
"title": "Apparence",
|
||||
"theme": "Thème",
|
||||
"themeDescription": "Choisissez votre thème préféré ou suivez les paramètres du système. Les modifications de thème personnalisé ne sont appliquées qu'à l'enregistrement.",
|
||||
"themePreset": "Thème prédéfini",
|
||||
"customColors": "Couleurs personnalisées",
|
||||
"selectTheme": "Sélectionner un thème",
|
||||
"selectThemePreset": "Sélectionner un thème prédéfini",
|
||||
"yourOwn": "Le vôtre",
|
||||
"light": "Clair",
|
||||
"dark": "Sombre",
|
||||
"system": "Système"
|
||||
},
|
||||
"language": {
|
||||
"title": "Langue",
|
||||
"description": "Choisissez votre langue préférée pour l'interface de l'application.",
|
||||
"systemDefault": "Par défaut du système",
|
||||
"selectLanguage": "Sélectionner la langue"
|
||||
},
|
||||
"defaultBrowser": {
|
||||
"title": "Navigateur par défaut",
|
||||
"setAsDefault": "Définir comme navigateur par défaut",
|
||||
"alreadyDefault": "Déjà le navigateur par défaut",
|
||||
"description": "Lorsqu'il est défini par défaut, Donut Browser gérera les liens web et vous permettra de choisir quel profil utiliser."
|
||||
},
|
||||
"permissions": {
|
||||
"title": "Permissions système",
|
||||
"loading": "Chargement des permissions...",
|
||||
"description": "Ces permissions permettent aux navigateurs lancés depuis Donut Browser d'accéder aux ressources système. Chaque site web demandera toujours votre permission individuellement.",
|
||||
"microphone": "Microphone",
|
||||
"microphoneDescription": "Accès au microphone pour les applications du navigateur",
|
||||
"camera": "Caméra",
|
||||
"cameraDescription": "Accès à la caméra pour les applications du navigateur"
|
||||
},
|
||||
"integrations": {
|
||||
"title": "Intégrations",
|
||||
"description": "Configurez l'API locale et MCP (Model Context Protocol) pour l'intégration avec des outils externes et des assistants IA.",
|
||||
"openSettings": "Ouvrir les paramètres d'intégration"
|
||||
},
|
||||
"commercial": {
|
||||
"title": "Licence commerciale",
|
||||
"trialActive": "Essai: {{days}} jours, {{hours}} heures restantes",
|
||||
"trialActiveDescription": "L'utilisation commerciale est gratuite pendant la période d'essai",
|
||||
"trialExpired": "Essai expiré",
|
||||
"trialExpiredDescription": "L'utilisation personnelle reste gratuite. L'utilisation commerciale nécessite une licence."
|
||||
},
|
||||
"advanced": {
|
||||
"title": "Avancé",
|
||||
"clearCache": "Effacer tout le cache des versions",
|
||||
"clearCacheDescription": "Efface toutes les données de versions de navigateurs en cache et actualise toutes les versions depuis leurs sources. Cela forcera un nouveau téléchargement des informations de version pour tous les navigateurs."
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"searchPlaceholder": "Rechercher des profils...",
|
||||
"clearSearch": "Effacer la recherche",
|
||||
"moreActions": "Plus d'actions",
|
||||
"createProfile": "Créer un nouveau profil",
|
||||
"menu": {
|
||||
"settings": "Paramètres",
|
||||
"proxies": "Proxies",
|
||||
"groups": "Groupes",
|
||||
"syncService": "Service de synchronisation",
|
||||
"integrations": "Intégrations",
|
||||
"importProfile": "Importer un profil"
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"title": "Profils",
|
||||
"empty": "Aucun profil pour l'instant",
|
||||
"emptyDescription": "Créez votre premier profil de navigateur pour commencer.",
|
||||
"createFirst": "Créer un profil",
|
||||
"noResults": "Aucun profil trouvé",
|
||||
"noResultsDescription": "Aucun profil ne correspond à vos critères de recherche.",
|
||||
"table": {
|
||||
"name": "Nom",
|
||||
"browser": "Navigateur",
|
||||
"status": "Statut",
|
||||
"actions": "Actions",
|
||||
"note": "Note",
|
||||
"group": "Groupe",
|
||||
"proxy": "Proxy",
|
||||
"lastLaunch": "Dernier lancement"
|
||||
},
|
||||
"actions": {
|
||||
"launch": "Lancer",
|
||||
"stop": "Arrêter",
|
||||
"edit": "Modifier",
|
||||
"delete": "Supprimer",
|
||||
"copyCookies": "Copier les cookies",
|
||||
"configure": "Configurer"
|
||||
}
|
||||
},
|
||||
"createProfile": {
|
||||
"title": "Créer un nouveau profil",
|
||||
"configureTitle": "Configurer le profil",
|
||||
"antiDetect": {
|
||||
"title": "Navigateur anti-détection",
|
||||
"description": "Choisissez un navigateur avec des capacités anti-détection",
|
||||
"chromium": "Chromium (Wayfern)",
|
||||
"firefox": "Firefox (Camoufox)",
|
||||
"badge": "Navigateur anti-détection"
|
||||
},
|
||||
"regular": {
|
||||
"title": "Navigateurs réguliers",
|
||||
"description": "Choisissez parmi les navigateurs réguliers pris en charge",
|
||||
"badge": "Navigateur régulier"
|
||||
},
|
||||
"profileName": "Nom du profil",
|
||||
"profileNamePlaceholder": "Entrez le nom du profil",
|
||||
"proxy": {
|
||||
"title": "Proxy",
|
||||
"addProxy": "Ajouter un proxy",
|
||||
"noProxy": "Pas de proxy",
|
||||
"noProxiesAvailable": "Aucun proxy disponible. Ajoutez-en un pour acheminer le trafic de ce profil."
|
||||
},
|
||||
"version": {
|
||||
"fetching": "Récupération des versions disponibles...",
|
||||
"fetchError": "Échec de la récupération des versions du navigateur. Veuillez vérifier votre connexion Internet et réessayer.",
|
||||
"needsDownload": "La version de {{browser}} ({{version}}) doit être téléchargée",
|
||||
"available": "La version de {{browser}} ({{version}}) est disponible",
|
||||
"downloading": "Téléchargement de la version de {{browser}} ({{version}})...",
|
||||
"latestNeedsDownload": "La dernière version ({{version}}) doit être téléchargée",
|
||||
"latestAvailable": "La dernière version ({{version}}) est disponible",
|
||||
"latestDownloading": "Téléchargement de la version ({{version}})..."
|
||||
}
|
||||
},
|
||||
"deleteDialog": {
|
||||
"title": "Supprimer le profil",
|
||||
"description": "Êtes-vous sûr de vouloir supprimer ce profil ? Cette action ne peut pas être annulée.",
|
||||
"profilesTitle": "Supprimer les profils",
|
||||
"profilesDescription": "Êtes-vous sûr de vouloir supprimer les profils sélectionnés ? Cette action ne peut pas être annulée.",
|
||||
"profilesToDelete": "Profils à supprimer :"
|
||||
},
|
||||
"proxies": {
|
||||
"title": "Proxies",
|
||||
"management": "Gestion des proxies",
|
||||
"add": "Ajouter un proxy",
|
||||
"edit": "Modifier le proxy",
|
||||
"delete": "Supprimer le proxy",
|
||||
"import": "Importer",
|
||||
"export": "Exporter",
|
||||
"noProxies": "Aucun proxy configuré",
|
||||
"noProxiesDescription": "Ajoutez un proxy pour acheminer le trafic du navigateur à travers lui.",
|
||||
"form": {
|
||||
"name": "Nom",
|
||||
"namePlaceholder": "Entrez le nom du proxy",
|
||||
"type": "Type",
|
||||
"host": "Hôte",
|
||||
"hostPlaceholder": "proxy.exemple.com",
|
||||
"port": "Port",
|
||||
"portPlaceholder": "8080",
|
||||
"username": "Nom d'utilisateur",
|
||||
"usernamePlaceholder": "Optionnel",
|
||||
"password": "Mot de passe",
|
||||
"passwordPlaceholder": "Optionnel"
|
||||
},
|
||||
"types": {
|
||||
"http": "HTTP",
|
||||
"https": "HTTPS",
|
||||
"socks4": "SOCKS4",
|
||||
"socks5": "SOCKS5"
|
||||
},
|
||||
"check": {
|
||||
"checking": "Vérification du proxy...",
|
||||
"valid": "Le proxy est valide",
|
||||
"invalid": "Le proxy est invalide",
|
||||
"lastChecked": "Dernière vérification : {{time}}"
|
||||
},
|
||||
"sync": {
|
||||
"enabled": "Synchronisation activée",
|
||||
"disabled": "Synchronisation désactivée"
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"title": "Groupes",
|
||||
"management": "Gestion des groupes",
|
||||
"add": "Ajouter un groupe",
|
||||
"edit": "Modifier le groupe",
|
||||
"delete": "Supprimer le groupe",
|
||||
"noGroups": "Aucun groupe créé",
|
||||
"noGroupsDescription": "Créez un groupe pour organiser vos profils.",
|
||||
"form": {
|
||||
"name": "Nom",
|
||||
"namePlaceholder": "Entrez le nom du groupe"
|
||||
},
|
||||
"profileCount": "{{count}} profil",
|
||||
"profileCount_plural": "{{count}} profils",
|
||||
"assignProfiles": "Attribuer des profils",
|
||||
"sync": {
|
||||
"enabled": "Synchronisation activée",
|
||||
"disabled": "Synchronisation désactivée"
|
||||
}
|
||||
},
|
||||
"sync": {
|
||||
"title": "Service de synchronisation",
|
||||
"config": "Configuration de la synchronisation",
|
||||
"serverUrl": "URL du serveur",
|
||||
"serverUrlPlaceholder": "https://sync.exemple.com",
|
||||
"token": "Jeton de synchronisation",
|
||||
"tokenPlaceholder": "Entrez votre jeton de synchronisation",
|
||||
"status": {
|
||||
"connected": "Connecté",
|
||||
"disconnected": "Déconnecté",
|
||||
"syncing": "Synchronisation...",
|
||||
"error": "Erreur de synchronisation"
|
||||
},
|
||||
"description": "Connectez-vous à un serveur de synchronisation pour synchroniser vos profils, proxies et groupes entre appareils."
|
||||
},
|
||||
"integrations": {
|
||||
"title": "Intégrations",
|
||||
"api": {
|
||||
"title": "API locale",
|
||||
"description": "Activez le serveur d'API locale pour les intégrations externes.",
|
||||
"enabled": "API activée",
|
||||
"disabled": "API désactivée",
|
||||
"port": "Port",
|
||||
"token": "Jeton API",
|
||||
"copyToken": "Copier le jeton",
|
||||
"regenerateToken": "Régénérer le jeton"
|
||||
},
|
||||
"mcp": {
|
||||
"title": "Serveur MCP",
|
||||
"description": "Activez le serveur MCP (Model Context Protocol) pour les intégrations avec les assistants IA.",
|
||||
"enabled": "MCP activé",
|
||||
"disabled": "MCP désactivé",
|
||||
"port": "Port",
|
||||
"token": "Jeton MCP",
|
||||
"config": "Configuration MCP",
|
||||
"copyConfig": "Copier la configuration"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"title": "Importer un profil",
|
||||
"description": "Importez un profil de navigateur existant depuis votre système.",
|
||||
"selectProfile": "Sélectionnez un profil à importer",
|
||||
"noProfiles": "Aucun profil détecté",
|
||||
"noProfilesDescription": "Aucun profil de navigateur n'a été détecté sur votre système.",
|
||||
"importing": "Importation du profil...",
|
||||
"success": "Profil importé avec succès",
|
||||
"error": "Échec de l'importation du profil"
|
||||
},
|
||||
"config": {
|
||||
"camoufox": {
|
||||
"title": "Configuration Camoufox",
|
||||
"fingerprint": {
|
||||
"title": "Empreinte digitale",
|
||||
"randomize": "Randomiser au lancement",
|
||||
"randomizeDescription": "Génère une nouvelle empreinte digitale à chaque lancement du navigateur."
|
||||
},
|
||||
"os": {
|
||||
"title": "Système d'exploitation",
|
||||
"description": "Le système d'exploitation à émuler pour la génération d'empreinte digitale.",
|
||||
"windows": "Windows",
|
||||
"macos": "macOS",
|
||||
"linux": "Linux"
|
||||
},
|
||||
"screen": {
|
||||
"title": "Taille d'écran",
|
||||
"minWidth": "Largeur min",
|
||||
"maxWidth": "Largeur max",
|
||||
"minHeight": "Hauteur min",
|
||||
"maxHeight": "Hauteur max"
|
||||
},
|
||||
"geoip": {
|
||||
"title": "GeoIP",
|
||||
"auto": "Automatique (basé sur le proxy)",
|
||||
"manual": "Manuel",
|
||||
"disabled": "Désactivé"
|
||||
},
|
||||
"blocking": {
|
||||
"title": "Blocage",
|
||||
"images": "Bloquer les images",
|
||||
"webrtc": "Bloquer WebRTC",
|
||||
"webgl": "Bloquer WebGL"
|
||||
}
|
||||
},
|
||||
"wayfern": {
|
||||
"title": "Configuration Wayfern",
|
||||
"fingerprint": {
|
||||
"title": "Empreinte digitale",
|
||||
"randomize": "Randomiser au lancement",
|
||||
"randomizeDescription": "Génère une nouvelle empreinte digitale à chaque lancement du navigateur."
|
||||
},
|
||||
"os": {
|
||||
"title": "Système d'exploitation",
|
||||
"description": "Le système d'exploitation à émuler pour la génération d'empreinte digitale.",
|
||||
"windows": "Windows",
|
||||
"macos": "macOS",
|
||||
"linux": "Linux",
|
||||
"android": "Android",
|
||||
"ios": "iOS"
|
||||
},
|
||||
"screen": {
|
||||
"title": "Taille d'écran",
|
||||
"minWidth": "Largeur min",
|
||||
"maxWidth": "Largeur max",
|
||||
"minHeight": "Hauteur min",
|
||||
"maxHeight": "Hauteur max"
|
||||
},
|
||||
"blocking": {
|
||||
"title": "Blocage",
|
||||
"webrtc": "Bloquer WebRTC",
|
||||
"webgl": "Bloquer WebGL"
|
||||
}
|
||||
}
|
||||
},
|
||||
"cookies": {
|
||||
"title": "Cookies",
|
||||
"copy": {
|
||||
"title": "Copier les cookies",
|
||||
"description": "Sélectionnez les cookies à copier vers d'autres profils.",
|
||||
"selectSource": "Sélectionner le profil source",
|
||||
"selectTarget": "Sélectionner les profils cibles",
|
||||
"selectCookies": "Sélectionner les cookies",
|
||||
"allDomains": "Tous les domaines",
|
||||
"selectedCount": "{{count}} cookie sélectionné",
|
||||
"selectedCount_plural": "{{count}} cookies sélectionnés"
|
||||
},
|
||||
"success": "Cookies copiés avec succès",
|
||||
"error": "Échec de la copie des cookies"
|
||||
},
|
||||
"toasts": {
|
||||
"success": {
|
||||
"profileCreated": "Profil créé avec succès",
|
||||
"profileDeleted": "Profil supprimé avec succès",
|
||||
"profileUpdated": "Profil mis à jour avec succès",
|
||||
"profileLaunched": "Profil lancé avec succès",
|
||||
"proxyCreated": "Proxy créé avec succès",
|
||||
"proxyDeleted": "Proxy supprimé avec succès",
|
||||
"proxyUpdated": "Proxy mis à jour avec succès",
|
||||
"groupCreated": "Groupe créé avec succès",
|
||||
"groupDeleted": "Groupe supprimé avec succès",
|
||||
"groupUpdated": "Groupe mis à jour avec succès",
|
||||
"settingsSaved": "Paramètres enregistrés avec succès",
|
||||
"copied": "Copié dans le presse-papiers",
|
||||
"permissionRequested": "Accès au {{permission}} demandé",
|
||||
"downloadComplete": "{{browser}} {{version}} téléchargé avec succès !",
|
||||
"importSuccess": "{{count}} éléments importés avec succès",
|
||||
"exportSuccess": "{{count}} éléments exportés avec succès",
|
||||
"syncSuccess": "Synchronisation terminée avec succès",
|
||||
"cacheCleared": "Cache effacé avec succès"
|
||||
},
|
||||
"error": {
|
||||
"profileCreateFailed": "Échec de la création du profil",
|
||||
"profileDeleteFailed": "Échec de la suppression du profil",
|
||||
"profileUpdateFailed": "Échec de la mise à jour du profil",
|
||||
"profileLaunchFailed": "Échec du lancement du profil",
|
||||
"proxyCreateFailed": "Échec de la création du proxy",
|
||||
"proxyDeleteFailed": "Échec de la suppression du proxy",
|
||||
"proxyUpdateFailed": "Échec de la mise à jour du proxy",
|
||||
"groupCreateFailed": "Échec de la création du groupe",
|
||||
"groupDeleteFailed": "Échec de la suppression du groupe",
|
||||
"groupUpdateFailed": "Échec de la mise à jour du groupe",
|
||||
"settingsSaveFailed": "Échec de l'enregistrement des paramètres",
|
||||
"copyFailed": "Échec de la copie dans le presse-papiers",
|
||||
"downloadFailed": "Échec du téléchargement de {{browser}}",
|
||||
"importFailed": "Échec de l'importation",
|
||||
"exportFailed": "Échec de l'exportation",
|
||||
"syncFailed": "Échec de la synchronisation",
|
||||
"cacheClearFailed": "Échec de l'effacement du cache",
|
||||
"unknown": "Une erreur inconnue s'est produite"
|
||||
},
|
||||
"loading": {
|
||||
"downloading": "Téléchargement de {{browser}} {{version}}",
|
||||
"extracting": "Extraction de {{browser}} {{version}}",
|
||||
"verifying": "Vérification de {{browser}} {{version}}",
|
||||
"syncing": "Synchronisation...",
|
||||
"updatingVersions": "Mise à jour des versions de navigateurs..."
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"required": "Ce champ est requis",
|
||||
"invalidUrl": "Veuillez entrer une URL valide",
|
||||
"invalidPort": "Veuillez entrer un numéro de port valide (1-65535)",
|
||||
"invalidEmail": "Veuillez entrer une adresse e-mail valide",
|
||||
"minLength": "Doit contenir au moins {{min}} caractères",
|
||||
"maxLength": "Doit contenir au maximum {{max}} caractères",
|
||||
"networkError": "Erreur réseau. Veuillez vérifier votre connexion.",
|
||||
"serverError": "Erreur serveur. Veuillez réessayer plus tard.",
|
||||
"unknownError": "Une erreur inconnue s'est produite. Veuillez réessayer."
|
||||
},
|
||||
"browser": {
|
||||
"firefox": "Firefox",
|
||||
"firefoxDeveloper": "Firefox Developer Edition",
|
||||
"chromium": "Chromium",
|
||||
"brave": "Brave",
|
||||
"zen": "Zen Browser",
|
||||
"camoufox": "Camoufox",
|
||||
"wayfern": "Wayfern"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
{
|
||||
"common": {
|
||||
"buttons": {
|
||||
"save": "保存",
|
||||
"cancel": "キャンセル",
|
||||
"close": "閉じる",
|
||||
"delete": "削除",
|
||||
"create": "作成",
|
||||
"back": "戻る",
|
||||
"retry": "再試行",
|
||||
"download": "ダウンロード",
|
||||
"confirm": "確認",
|
||||
"apply": "適用",
|
||||
"reset": "リセット",
|
||||
"add": "追加",
|
||||
"edit": "編集",
|
||||
"copy": "コピー",
|
||||
"clear": "クリア",
|
||||
"search": "検索",
|
||||
"select": "選択",
|
||||
"grant": "許可",
|
||||
"start": "開始",
|
||||
"stop": "停止",
|
||||
"enable": "有効",
|
||||
"disable": "無効",
|
||||
"import": "インポート",
|
||||
"export": "エクスポート",
|
||||
"refresh": "更新",
|
||||
"loading": "読み込み中...",
|
||||
"saveSettings": "設定を保存"
|
||||
},
|
||||
"status": {
|
||||
"active": "アクティブ",
|
||||
"inactive": "非アクティブ",
|
||||
"running": "実行中",
|
||||
"stopped": "停止",
|
||||
"enabled": "有効",
|
||||
"disabled": "無効",
|
||||
"granted": "許可済み",
|
||||
"notGranted": "未許可",
|
||||
"connected": "接続済み",
|
||||
"disconnected": "切断",
|
||||
"synced": "同期済み",
|
||||
"syncing": "同期中",
|
||||
"pending": "保留中",
|
||||
"error": "エラー"
|
||||
},
|
||||
"labels": {
|
||||
"name": "名前",
|
||||
"type": "タイプ",
|
||||
"status": "ステータス",
|
||||
"actions": "アクション",
|
||||
"description": "説明",
|
||||
"none": "なし",
|
||||
"default": "デフォルト",
|
||||
"custom": "カスタム",
|
||||
"optional": "任意",
|
||||
"required": "必須"
|
||||
},
|
||||
"time": {
|
||||
"days": "日",
|
||||
"hours": "時間",
|
||||
"minutes": "分",
|
||||
"seconds": "秒",
|
||||
"remaining": "残り"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "設定",
|
||||
"appearance": {
|
||||
"title": "外観",
|
||||
"theme": "テーマ",
|
||||
"themeDescription": "お好みのテーマを選択するか、システム設定に従います。カスタムテーマの変更は保存時のみ適用されます。",
|
||||
"themePreset": "テーマプリセット",
|
||||
"customColors": "カスタムカラー",
|
||||
"selectTheme": "テーマを選択",
|
||||
"selectThemePreset": "テーマプリセットを選択",
|
||||
"yourOwn": "カスタム",
|
||||
"light": "ライト",
|
||||
"dark": "ダーク",
|
||||
"system": "システム"
|
||||
},
|
||||
"language": {
|
||||
"title": "言語",
|
||||
"description": "アプリケーションインターフェースの言語を選択します。",
|
||||
"systemDefault": "システムデフォルト",
|
||||
"selectLanguage": "言語を選択"
|
||||
},
|
||||
"defaultBrowser": {
|
||||
"title": "デフォルトブラウザ",
|
||||
"setAsDefault": "デフォルトブラウザに設定",
|
||||
"alreadyDefault": "既にデフォルトブラウザです",
|
||||
"description": "デフォルトに設定すると、Donut Browser がウェブリンクを処理し、使用するプロファイルを選択できます。"
|
||||
},
|
||||
"permissions": {
|
||||
"title": "システム権限",
|
||||
"loading": "権限を読み込み中...",
|
||||
"description": "これらの権限により、Donut Browser から起動したブラウザがシステムリソースにアクセスできます。各ウェブサイトは個別に許可を求めます。",
|
||||
"microphone": "マイク",
|
||||
"microphoneDescription": "ブラウザアプリケーションのマイクアクセス",
|
||||
"camera": "カメラ",
|
||||
"cameraDescription": "ブラウザアプリケーションのカメラアクセス"
|
||||
},
|
||||
"integrations": {
|
||||
"title": "統合",
|
||||
"description": "外部ツールやAIアシスタントと統合するためのローカルAPIとMCP(モデルコンテキストプロトコル)を設定します。",
|
||||
"openSettings": "統合設定を開く"
|
||||
},
|
||||
"commercial": {
|
||||
"title": "商用ライセンス",
|
||||
"trialActive": "トライアル: 残り {{days}} 日 {{hours}} 時間",
|
||||
"trialActiveDescription": "トライアル期間中は商用利用が無料です",
|
||||
"trialExpired": "トライアル期限切れ",
|
||||
"trialExpiredDescription": "個人利用は引き続き無料です。商用利用にはライセンスが必要です。"
|
||||
},
|
||||
"advanced": {
|
||||
"title": "詳細設定",
|
||||
"clearCache": "すべてのバージョンキャッシュをクリア",
|
||||
"clearCacheDescription": "キャッシュされたすべてのブラウザバージョンデータをクリアし、すべてのブラウザバージョンをソースから更新します。これにより、すべてのブラウザのバージョン情報が強制的に再ダウンロードされます。"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"searchPlaceholder": "プロファイルを検索...",
|
||||
"clearSearch": "検索をクリア",
|
||||
"moreActions": "その他のアクション",
|
||||
"createProfile": "新しいプロファイルを作成",
|
||||
"menu": {
|
||||
"settings": "設定",
|
||||
"proxies": "プロキシ",
|
||||
"groups": "グループ",
|
||||
"syncService": "同期サービス",
|
||||
"integrations": "統合",
|
||||
"importProfile": "プロファイルをインポート"
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"title": "プロファイル",
|
||||
"empty": "プロファイルがありません",
|
||||
"emptyDescription": "最初のブラウザプロファイルを作成して始めましょう。",
|
||||
"createFirst": "プロファイルを作成",
|
||||
"noResults": "プロファイルが見つかりません",
|
||||
"noResultsDescription": "検索条件に一致するプロファイルがありません。",
|
||||
"table": {
|
||||
"name": "名前",
|
||||
"browser": "ブラウザ",
|
||||
"status": "ステータス",
|
||||
"actions": "アクション",
|
||||
"note": "メモ",
|
||||
"group": "グループ",
|
||||
"proxy": "プロキシ",
|
||||
"lastLaunch": "最終起動"
|
||||
},
|
||||
"actions": {
|
||||
"launch": "起動",
|
||||
"stop": "停止",
|
||||
"edit": "編集",
|
||||
"delete": "削除",
|
||||
"copyCookies": "Cookieをコピー",
|
||||
"configure": "設定"
|
||||
}
|
||||
},
|
||||
"createProfile": {
|
||||
"title": "新しいプロファイルを作成",
|
||||
"configureTitle": "プロファイルを設定",
|
||||
"antiDetect": {
|
||||
"title": "アンチ検出ブラウザ",
|
||||
"description": "アンチ検出機能を持つブラウザを選択",
|
||||
"chromium": "Chromium (Wayfern)",
|
||||
"firefox": "Firefox (Camoufox)",
|
||||
"badge": "アンチ検出ブラウザ"
|
||||
},
|
||||
"regular": {
|
||||
"title": "通常ブラウザ",
|
||||
"description": "サポートされている通常ブラウザから選択",
|
||||
"badge": "通常ブラウザ"
|
||||
},
|
||||
"profileName": "プロファイル名",
|
||||
"profileNamePlaceholder": "プロファイル名を入力",
|
||||
"proxy": {
|
||||
"title": "プロキシ",
|
||||
"addProxy": "プロキシを追加",
|
||||
"noProxy": "プロキシなし",
|
||||
"noProxiesAvailable": "利用可能なプロキシがありません。このプロファイルのトラフィックをルーティングするためにプロキシを追加してください。"
|
||||
},
|
||||
"version": {
|
||||
"fetching": "利用可能なバージョンを取得中...",
|
||||
"fetchError": "ブラウザバージョンの取得に失敗しました。インターネット接続を確認して再試行してください。",
|
||||
"needsDownload": "{{browser}} バージョン ({{version}}) をダウンロードする必要があります",
|
||||
"available": "{{browser}} バージョン ({{version}}) は利用可能です",
|
||||
"downloading": "{{browser}} バージョン ({{version}}) をダウンロード中...",
|
||||
"latestNeedsDownload": "最新バージョン ({{version}}) をダウンロードする必要があります",
|
||||
"latestAvailable": "最新バージョン ({{version}}) は利用可能です",
|
||||
"latestDownloading": "バージョン ({{version}}) をダウンロード中..."
|
||||
}
|
||||
},
|
||||
"deleteDialog": {
|
||||
"title": "プロファイルを削除",
|
||||
"description": "このプロファイルを削除してもよろしいですか?この操作は取り消せません。",
|
||||
"profilesTitle": "プロファイルを削除",
|
||||
"profilesDescription": "選択したプロファイルを削除してもよろしいですか?この操作は取り消せません。",
|
||||
"profilesToDelete": "削除されるプロファイル:"
|
||||
},
|
||||
"proxies": {
|
||||
"title": "プロキシ",
|
||||
"management": "プロキシ管理",
|
||||
"add": "プロキシを追加",
|
||||
"edit": "プロキシを編集",
|
||||
"delete": "プロキシを削除",
|
||||
"import": "インポート",
|
||||
"export": "エクスポート",
|
||||
"noProxies": "プロキシが設定されていません",
|
||||
"noProxiesDescription": "ブラウザトラフィックをルーティングするためのプロキシを追加してください。",
|
||||
"form": {
|
||||
"name": "名前",
|
||||
"namePlaceholder": "プロキシ名を入力",
|
||||
"type": "タイプ",
|
||||
"host": "ホスト",
|
||||
"hostPlaceholder": "proxy.example.com",
|
||||
"port": "ポート",
|
||||
"portPlaceholder": "8080",
|
||||
"username": "ユーザー名",
|
||||
"usernamePlaceholder": "任意",
|
||||
"password": "パスワード",
|
||||
"passwordPlaceholder": "任意"
|
||||
},
|
||||
"types": {
|
||||
"http": "HTTP",
|
||||
"https": "HTTPS",
|
||||
"socks4": "SOCKS4",
|
||||
"socks5": "SOCKS5"
|
||||
},
|
||||
"check": {
|
||||
"checking": "プロキシを確認中...",
|
||||
"valid": "プロキシは有効です",
|
||||
"invalid": "プロキシは無効です",
|
||||
"lastChecked": "最終確認: {{time}}"
|
||||
},
|
||||
"sync": {
|
||||
"enabled": "同期有効",
|
||||
"disabled": "同期無効"
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"title": "グループ",
|
||||
"management": "グループ管理",
|
||||
"add": "グループを追加",
|
||||
"edit": "グループを編集",
|
||||
"delete": "グループを削除",
|
||||
"noGroups": "グループがありません",
|
||||
"noGroupsDescription": "プロファイルを整理するためのグループを作成してください。",
|
||||
"form": {
|
||||
"name": "名前",
|
||||
"namePlaceholder": "グループ名を入力"
|
||||
},
|
||||
"profileCount": "{{count}} プロファイル",
|
||||
"profileCount_plural": "{{count}} プロファイル",
|
||||
"assignProfiles": "プロファイルを割り当て",
|
||||
"sync": {
|
||||
"enabled": "同期有効",
|
||||
"disabled": "同期無効"
|
||||
}
|
||||
},
|
||||
"sync": {
|
||||
"title": "同期サービス",
|
||||
"config": "同期設定",
|
||||
"serverUrl": "サーバーURL",
|
||||
"serverUrlPlaceholder": "https://sync.example.com",
|
||||
"token": "同期トークン",
|
||||
"tokenPlaceholder": "同期トークンを入力",
|
||||
"status": {
|
||||
"connected": "接続済み",
|
||||
"disconnected": "切断",
|
||||
"syncing": "同期中...",
|
||||
"error": "同期エラー"
|
||||
},
|
||||
"description": "同期サーバーに接続して、デバイス間でプロファイル、プロキシ、グループを同期します。"
|
||||
},
|
||||
"integrations": {
|
||||
"title": "統合",
|
||||
"api": {
|
||||
"title": "ローカルAPI",
|
||||
"description": "外部統合用のローカルAPIサーバーを有効にします。",
|
||||
"enabled": "API有効",
|
||||
"disabled": "API無効",
|
||||
"port": "ポート",
|
||||
"token": "APIトークン",
|
||||
"copyToken": "トークンをコピー",
|
||||
"regenerateToken": "トークンを再生成"
|
||||
},
|
||||
"mcp": {
|
||||
"title": "MCPサーバー",
|
||||
"description": "AIアシスタント統合用のMCP(モデルコンテキストプロトコル)サーバーを有効にします。",
|
||||
"enabled": "MCP有効",
|
||||
"disabled": "MCP無効",
|
||||
"port": "ポート",
|
||||
"token": "MCPトークン",
|
||||
"config": "MCP設定",
|
||||
"copyConfig": "設定をコピー"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"title": "プロファイルをインポート",
|
||||
"description": "システムから既存のブラウザプロファイルをインポートします。",
|
||||
"selectProfile": "インポートするプロファイルを選択",
|
||||
"noProfiles": "プロファイルが検出されませんでした",
|
||||
"noProfilesDescription": "システム上にブラウザプロファイルが検出されませんでした。",
|
||||
"importing": "プロファイルをインポート中...",
|
||||
"success": "プロファイルが正常にインポートされました",
|
||||
"error": "プロファイルのインポートに失敗しました"
|
||||
},
|
||||
"config": {
|
||||
"camoufox": {
|
||||
"title": "Camoufox設定",
|
||||
"fingerprint": {
|
||||
"title": "フィンガープリント",
|
||||
"randomize": "起動時にランダム化",
|
||||
"randomizeDescription": "ブラウザ起動時に新しいフィンガープリントを生成します。"
|
||||
},
|
||||
"os": {
|
||||
"title": "オペレーティングシステム",
|
||||
"description": "フィンガープリント生成用にエミュレートするOS。",
|
||||
"windows": "Windows",
|
||||
"macos": "macOS",
|
||||
"linux": "Linux"
|
||||
},
|
||||
"screen": {
|
||||
"title": "画面サイズ",
|
||||
"minWidth": "最小幅",
|
||||
"maxWidth": "最大幅",
|
||||
"minHeight": "最小高さ",
|
||||
"maxHeight": "最大高さ"
|
||||
},
|
||||
"geoip": {
|
||||
"title": "GeoIP",
|
||||
"auto": "自動(プロキシに基づく)",
|
||||
"manual": "手動",
|
||||
"disabled": "無効"
|
||||
},
|
||||
"blocking": {
|
||||
"title": "ブロック",
|
||||
"images": "画像をブロック",
|
||||
"webrtc": "WebRTCをブロック",
|
||||
"webgl": "WebGLをブロック"
|
||||
}
|
||||
},
|
||||
"wayfern": {
|
||||
"title": "Wayfern設定",
|
||||
"fingerprint": {
|
||||
"title": "フィンガープリント",
|
||||
"randomize": "起動時にランダム化",
|
||||
"randomizeDescription": "ブラウザ起動時に新しいフィンガープリントを生成します。"
|
||||
},
|
||||
"os": {
|
||||
"title": "オペレーティングシステム",
|
||||
"description": "フィンガープリント生成用にエミュレートするOS。",
|
||||
"windows": "Windows",
|
||||
"macos": "macOS",
|
||||
"linux": "Linux",
|
||||
"android": "Android",
|
||||
"ios": "iOS"
|
||||
},
|
||||
"screen": {
|
||||
"title": "画面サイズ",
|
||||
"minWidth": "最小幅",
|
||||
"maxWidth": "最大幅",
|
||||
"minHeight": "最小高さ",
|
||||
"maxHeight": "最大高さ"
|
||||
},
|
||||
"blocking": {
|
||||
"title": "ブロック",
|
||||
"webrtc": "WebRTCをブロック",
|
||||
"webgl": "WebGLをブロック"
|
||||
}
|
||||
}
|
||||
},
|
||||
"cookies": {
|
||||
"title": "Cookie",
|
||||
"copy": {
|
||||
"title": "Cookieをコピー",
|
||||
"description": "他のプロファイルにコピーするCookieを選択します。",
|
||||
"selectSource": "ソースプロファイルを選択",
|
||||
"selectTarget": "ターゲットプロファイルを選択",
|
||||
"selectCookies": "Cookieを選択",
|
||||
"allDomains": "すべてのドメイン",
|
||||
"selectedCount": "{{count}} 個のCookieを選択",
|
||||
"selectedCount_plural": "{{count}} 個のCookieを選択"
|
||||
},
|
||||
"success": "Cookieが正常にコピーされました",
|
||||
"error": "Cookieのコピーに失敗しました"
|
||||
},
|
||||
"toasts": {
|
||||
"success": {
|
||||
"profileCreated": "プロファイルが正常に作成されました",
|
||||
"profileDeleted": "プロファイルが正常に削除されました",
|
||||
"profileUpdated": "プロファイルが正常に更新されました",
|
||||
"profileLaunched": "プロファイルが正常に起動しました",
|
||||
"proxyCreated": "プロキシが正常に作成されました",
|
||||
"proxyDeleted": "プロキシが正常に削除されました",
|
||||
"proxyUpdated": "プロキシが正常に更新されました",
|
||||
"groupCreated": "グループが正常に作成されました",
|
||||
"groupDeleted": "グループが正常に削除されました",
|
||||
"groupUpdated": "グループが正常に更新されました",
|
||||
"settingsSaved": "設定が正常に保存されました",
|
||||
"copied": "クリップボードにコピーしました",
|
||||
"permissionRequested": "{{permission}}へのアクセスをリクエストしました",
|
||||
"downloadComplete": "{{browser}} {{version}} が正常にダウンロードされました!",
|
||||
"importSuccess": "{{count}} 個のアイテムが正常にインポートされました",
|
||||
"exportSuccess": "{{count}} 個のアイテムが正常にエクスポートされました",
|
||||
"syncSuccess": "同期が正常に完了しました",
|
||||
"cacheCleared": "キャッシュが正常にクリアされました"
|
||||
},
|
||||
"error": {
|
||||
"profileCreateFailed": "プロファイルの作成に失敗しました",
|
||||
"profileDeleteFailed": "プロファイルの削除に失敗しました",
|
||||
"profileUpdateFailed": "プロファイルの更新に失敗しました",
|
||||
"profileLaunchFailed": "プロファイルの起動に失敗しました",
|
||||
"proxyCreateFailed": "プロキシの作成に失敗しました",
|
||||
"proxyDeleteFailed": "プロキシの削除に失敗しました",
|
||||
"proxyUpdateFailed": "プロキシの更新に失敗しました",
|
||||
"groupCreateFailed": "グループの作成に失敗しました",
|
||||
"groupDeleteFailed": "グループの削除に失敗しました",
|
||||
"groupUpdateFailed": "グループの更新に失敗しました",
|
||||
"settingsSaveFailed": "設定の保存に失敗しました",
|
||||
"copyFailed": "クリップボードへのコピーに失敗しました",
|
||||
"downloadFailed": "{{browser}} のダウンロードに失敗しました",
|
||||
"importFailed": "インポートに失敗しました",
|
||||
"exportFailed": "エクスポートに失敗しました",
|
||||
"syncFailed": "同期に失敗しました",
|
||||
"cacheClearFailed": "キャッシュのクリアに失敗しました",
|
||||
"unknown": "不明なエラーが発生しました"
|
||||
},
|
||||
"loading": {
|
||||
"downloading": "{{browser}} {{version}} をダウンロード中",
|
||||
"extracting": "{{browser}} {{version}} を展開中",
|
||||
"verifying": "{{browser}} {{version}} を確認中",
|
||||
"syncing": "同期中...",
|
||||
"updatingVersions": "ブラウザバージョンを更新中..."
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"required": "この項目は必須です",
|
||||
"invalidUrl": "有効なURLを入力してください",
|
||||
"invalidPort": "有効なポート番号を入力してください(1-65535)",
|
||||
"invalidEmail": "有効なメールアドレスを入力してください",
|
||||
"minLength": "{{min}} 文字以上で入力してください",
|
||||
"maxLength": "{{max}} 文字以内で入力してください",
|
||||
"networkError": "ネットワークエラー。接続を確認してください。",
|
||||
"serverError": "サーバーエラー。後でもう一度お試しください。",
|
||||
"unknownError": "不明なエラーが発生しました。もう一度お試しください。"
|
||||
},
|
||||
"browser": {
|
||||
"firefox": "Firefox",
|
||||
"firefoxDeveloper": "Firefox Developer Edition",
|
||||
"chromium": "Chromium",
|
||||
"brave": "Brave",
|
||||
"zen": "Zen Browser",
|
||||
"camoufox": "Camoufox",
|
||||
"wayfern": "Wayfern"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
{
|
||||
"common": {
|
||||
"buttons": {
|
||||
"save": "Salvar",
|
||||
"cancel": "Cancelar",
|
||||
"close": "Fechar",
|
||||
"delete": "Excluir",
|
||||
"create": "Criar",
|
||||
"back": "Voltar",
|
||||
"retry": "Tentar novamente",
|
||||
"download": "Baixar",
|
||||
"confirm": "Confirmar",
|
||||
"apply": "Aplicar",
|
||||
"reset": "Redefinir",
|
||||
"add": "Adicionar",
|
||||
"edit": "Editar",
|
||||
"copy": "Copiar",
|
||||
"clear": "Limpar",
|
||||
"search": "Pesquisar",
|
||||
"select": "Selecionar",
|
||||
"grant": "Conceder",
|
||||
"start": "Iniciar",
|
||||
"stop": "Parar",
|
||||
"enable": "Ativar",
|
||||
"disable": "Desativar",
|
||||
"import": "Importar",
|
||||
"export": "Exportar",
|
||||
"refresh": "Atualizar",
|
||||
"loading": "Carregando...",
|
||||
"saveSettings": "Salvar Configurações"
|
||||
},
|
||||
"status": {
|
||||
"active": "Ativo",
|
||||
"inactive": "Inativo",
|
||||
"running": "Executando",
|
||||
"stopped": "Parado",
|
||||
"enabled": "Ativado",
|
||||
"disabled": "Desativado",
|
||||
"granted": "Concedido",
|
||||
"notGranted": "Não Concedido",
|
||||
"connected": "Conectado",
|
||||
"disconnected": "Desconectado",
|
||||
"synced": "Sincronizado",
|
||||
"syncing": "Sincronizando",
|
||||
"pending": "Pendente",
|
||||
"error": "Erro"
|
||||
},
|
||||
"labels": {
|
||||
"name": "Nome",
|
||||
"type": "Tipo",
|
||||
"status": "Status",
|
||||
"actions": "Ações",
|
||||
"description": "Descrição",
|
||||
"none": "Nenhum",
|
||||
"default": "Padrão",
|
||||
"custom": "Personalizado",
|
||||
"optional": "Opcional",
|
||||
"required": "Obrigatório"
|
||||
},
|
||||
"time": {
|
||||
"days": "dias",
|
||||
"hours": "horas",
|
||||
"minutes": "minutos",
|
||||
"seconds": "segundos",
|
||||
"remaining": "restantes"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Configurações",
|
||||
"appearance": {
|
||||
"title": "Aparência",
|
||||
"theme": "Tema",
|
||||
"themeDescription": "Escolha seu tema preferido ou siga as configurações do sistema. Alterações de tema personalizado são aplicadas apenas ao salvar.",
|
||||
"themePreset": "Tema Predefinido",
|
||||
"customColors": "Cores Personalizadas",
|
||||
"selectTheme": "Selecionar tema",
|
||||
"selectThemePreset": "Selecionar tema predefinido",
|
||||
"yourOwn": "Seu Próprio",
|
||||
"light": "Claro",
|
||||
"dark": "Escuro",
|
||||
"system": "Sistema"
|
||||
},
|
||||
"language": {
|
||||
"title": "Idioma",
|
||||
"description": "Escolha seu idioma preferido para a interface do aplicativo.",
|
||||
"systemDefault": "Padrão do Sistema",
|
||||
"selectLanguage": "Selecionar idioma"
|
||||
},
|
||||
"defaultBrowser": {
|
||||
"title": "Navegador Padrão",
|
||||
"setAsDefault": "Definir como Navegador Padrão",
|
||||
"alreadyDefault": "Já é o Navegador Padrão",
|
||||
"description": "Quando definido como padrão, o Donut Browser lidará com links da web e permitirá que você escolha qual perfil usar."
|
||||
},
|
||||
"permissions": {
|
||||
"title": "Permissões do Sistema",
|
||||
"loading": "Carregando permissões...",
|
||||
"description": "Essas permissões permitem que navegadores iniciados pelo Donut Browser acessem recursos do sistema. Cada site ainda pedirá sua permissão individualmente.",
|
||||
"microphone": "Microfone",
|
||||
"microphoneDescription": "Acesso ao microfone para aplicativos do navegador",
|
||||
"camera": "Câmera",
|
||||
"cameraDescription": "Acesso à câmera para aplicativos do navegador"
|
||||
},
|
||||
"integrations": {
|
||||
"title": "Integrações",
|
||||
"description": "Configure a API Local e MCP (Protocolo de Contexto de Modelo) para integração com ferramentas externas e assistentes de IA.",
|
||||
"openSettings": "Abrir Configurações de Integrações"
|
||||
},
|
||||
"commercial": {
|
||||
"title": "Licença Comercial",
|
||||
"trialActive": "Teste: {{days}} dias, {{hours}} horas restantes",
|
||||
"trialActiveDescription": "O uso comercial é gratuito durante o período de teste",
|
||||
"trialExpired": "Teste expirado",
|
||||
"trialExpiredDescription": "O uso pessoal continua gratuito. O uso comercial requer uma licença."
|
||||
},
|
||||
"advanced": {
|
||||
"title": "Avançado",
|
||||
"clearCache": "Limpar Todo o Cache de Versões",
|
||||
"clearCacheDescription": "Limpa todos os dados de versões de navegadores em cache e atualiza todas as versões de suas fontes. Isso forçará um novo download das informações de versão para todos os navegadores."
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"searchPlaceholder": "Pesquisar perfis...",
|
||||
"clearSearch": "Limpar pesquisa",
|
||||
"moreActions": "Mais ações",
|
||||
"createProfile": "Criar um novo perfil",
|
||||
"menu": {
|
||||
"settings": "Configurações",
|
||||
"proxies": "Proxies",
|
||||
"groups": "Grupos",
|
||||
"syncService": "Serviço de Sincronização",
|
||||
"integrations": "Integrações",
|
||||
"importProfile": "Importar Perfil"
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"title": "Perfis",
|
||||
"empty": "Nenhum perfil ainda",
|
||||
"emptyDescription": "Crie seu primeiro perfil de navegador para começar.",
|
||||
"createFirst": "Criar Perfil",
|
||||
"noResults": "Nenhum perfil encontrado",
|
||||
"noResultsDescription": "Nenhum perfil corresponde aos seus critérios de pesquisa.",
|
||||
"table": {
|
||||
"name": "Nome",
|
||||
"browser": "Navegador",
|
||||
"status": "Status",
|
||||
"actions": "Ações",
|
||||
"note": "Nota",
|
||||
"group": "Grupo",
|
||||
"proxy": "Proxy",
|
||||
"lastLaunch": "Último Início"
|
||||
},
|
||||
"actions": {
|
||||
"launch": "Iniciar",
|
||||
"stop": "Parar",
|
||||
"edit": "Editar",
|
||||
"delete": "Excluir",
|
||||
"copyCookies": "Copiar Cookies",
|
||||
"configure": "Configurar"
|
||||
}
|
||||
},
|
||||
"createProfile": {
|
||||
"title": "Criar Novo Perfil",
|
||||
"configureTitle": "Configurar Perfil",
|
||||
"antiDetect": {
|
||||
"title": "Navegador Anti-Detecção",
|
||||
"description": "Escolha um navegador com capacidades anti-detecção",
|
||||
"chromium": "Chromium (Wayfern)",
|
||||
"firefox": "Firefox (Camoufox)",
|
||||
"badge": "Navegador Anti-Detecção"
|
||||
},
|
||||
"regular": {
|
||||
"title": "Navegadores Regulares",
|
||||
"description": "Escolha entre navegadores regulares suportados",
|
||||
"badge": "Navegador Regular"
|
||||
},
|
||||
"profileName": "Nome do Perfil",
|
||||
"profileNamePlaceholder": "Digite o nome do perfil",
|
||||
"proxy": {
|
||||
"title": "Proxy",
|
||||
"addProxy": "Adicionar Proxy",
|
||||
"noProxy": "Sem proxy",
|
||||
"noProxiesAvailable": "Nenhum proxy disponível. Adicione um para rotear o tráfego deste perfil."
|
||||
},
|
||||
"version": {
|
||||
"fetching": "Buscando versões disponíveis...",
|
||||
"fetchError": "Falha ao buscar versões do navegador. Por favor, verifique sua conexão com a internet e tente novamente.",
|
||||
"needsDownload": "A versão do {{browser}} ({{version}}) precisa ser baixada",
|
||||
"available": "A versão do {{browser}} ({{version}}) está disponível",
|
||||
"downloading": "Baixando versão do {{browser}} ({{version}})...",
|
||||
"latestNeedsDownload": "A versão mais recente ({{version}}) precisa ser baixada",
|
||||
"latestAvailable": "A versão mais recente ({{version}}) está disponível",
|
||||
"latestDownloading": "Baixando versão ({{version}})..."
|
||||
}
|
||||
},
|
||||
"deleteDialog": {
|
||||
"title": "Excluir Perfil",
|
||||
"description": "Tem certeza de que deseja excluir este perfil? Esta ação não pode ser desfeita.",
|
||||
"profilesTitle": "Excluir Perfis",
|
||||
"profilesDescription": "Tem certeza de que deseja excluir os perfis selecionados? Esta ação não pode ser desfeita.",
|
||||
"profilesToDelete": "Perfis a serem excluídos:"
|
||||
},
|
||||
"proxies": {
|
||||
"title": "Proxies",
|
||||
"management": "Gerenciamento de Proxies",
|
||||
"add": "Adicionar Proxy",
|
||||
"edit": "Editar Proxy",
|
||||
"delete": "Excluir Proxy",
|
||||
"import": "Importar",
|
||||
"export": "Exportar",
|
||||
"noProxies": "Nenhum proxy configurado",
|
||||
"noProxiesDescription": "Adicione um proxy para rotear o tráfego do navegador através dele.",
|
||||
"form": {
|
||||
"name": "Nome",
|
||||
"namePlaceholder": "Digite o nome do proxy",
|
||||
"type": "Tipo",
|
||||
"host": "Host",
|
||||
"hostPlaceholder": "proxy.exemplo.com",
|
||||
"port": "Porta",
|
||||
"portPlaceholder": "8080",
|
||||
"username": "Usuário",
|
||||
"usernamePlaceholder": "Opcional",
|
||||
"password": "Senha",
|
||||
"passwordPlaceholder": "Opcional"
|
||||
},
|
||||
"types": {
|
||||
"http": "HTTP",
|
||||
"https": "HTTPS",
|
||||
"socks4": "SOCKS4",
|
||||
"socks5": "SOCKS5"
|
||||
},
|
||||
"check": {
|
||||
"checking": "Verificando proxy...",
|
||||
"valid": "O proxy é válido",
|
||||
"invalid": "O proxy é inválido",
|
||||
"lastChecked": "Última verificação: {{time}}"
|
||||
},
|
||||
"sync": {
|
||||
"enabled": "Sincronização Ativada",
|
||||
"disabled": "Sincronização Desativada"
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"title": "Grupos",
|
||||
"management": "Gerenciamento de Grupos",
|
||||
"add": "Adicionar Grupo",
|
||||
"edit": "Editar Grupo",
|
||||
"delete": "Excluir Grupo",
|
||||
"noGroups": "Nenhum grupo criado",
|
||||
"noGroupsDescription": "Crie um grupo para organizar seus perfis.",
|
||||
"form": {
|
||||
"name": "Nome",
|
||||
"namePlaceholder": "Digite o nome do grupo"
|
||||
},
|
||||
"profileCount": "{{count}} perfil",
|
||||
"profileCount_plural": "{{count}} perfis",
|
||||
"assignProfiles": "Atribuir Perfis",
|
||||
"sync": {
|
||||
"enabled": "Sincronização Ativada",
|
||||
"disabled": "Sincronização Desativada"
|
||||
}
|
||||
},
|
||||
"sync": {
|
||||
"title": "Serviço de Sincronização",
|
||||
"config": "Configuração de Sincronização",
|
||||
"serverUrl": "URL do Servidor",
|
||||
"serverUrlPlaceholder": "https://sync.exemplo.com",
|
||||
"token": "Token de Sincronização",
|
||||
"tokenPlaceholder": "Digite seu token de sincronização",
|
||||
"status": {
|
||||
"connected": "Conectado",
|
||||
"disconnected": "Desconectado",
|
||||
"syncing": "Sincronizando...",
|
||||
"error": "Erro de Sincronização"
|
||||
},
|
||||
"description": "Conecte-se a um servidor de sincronização para sincronizar seus perfis, proxies e grupos entre dispositivos."
|
||||
},
|
||||
"integrations": {
|
||||
"title": "Integrações",
|
||||
"api": {
|
||||
"title": "API Local",
|
||||
"description": "Ative o servidor de API local para integrações externas.",
|
||||
"enabled": "API Ativada",
|
||||
"disabled": "API Desativada",
|
||||
"port": "Porta",
|
||||
"token": "Token da API",
|
||||
"copyToken": "Copiar Token",
|
||||
"regenerateToken": "Regenerar Token"
|
||||
},
|
||||
"mcp": {
|
||||
"title": "Servidor MCP",
|
||||
"description": "Ative o servidor MCP (Protocolo de Contexto de Modelo) para integrações com assistentes de IA.",
|
||||
"enabled": "MCP Ativado",
|
||||
"disabled": "MCP Desativado",
|
||||
"port": "Porta",
|
||||
"token": "Token MCP",
|
||||
"config": "Configuração MCP",
|
||||
"copyConfig": "Copiar Configuração"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"title": "Importar Perfil",
|
||||
"description": "Importe um perfil de navegador existente do seu sistema.",
|
||||
"selectProfile": "Selecione um perfil para importar",
|
||||
"noProfiles": "Nenhum perfil detectado",
|
||||
"noProfilesDescription": "Nenhum perfil de navegador foi detectado no seu sistema.",
|
||||
"importing": "Importando perfil...",
|
||||
"success": "Perfil importado com sucesso",
|
||||
"error": "Falha ao importar perfil"
|
||||
},
|
||||
"config": {
|
||||
"camoufox": {
|
||||
"title": "Configuração do Camoufox",
|
||||
"fingerprint": {
|
||||
"title": "Impressão Digital",
|
||||
"randomize": "Aleatorizar ao Iniciar",
|
||||
"randomizeDescription": "Gera uma nova impressão digital cada vez que o navegador é iniciado."
|
||||
},
|
||||
"os": {
|
||||
"title": "Sistema Operacional",
|
||||
"description": "O sistema operacional a emular para geração de impressão digital.",
|
||||
"windows": "Windows",
|
||||
"macos": "macOS",
|
||||
"linux": "Linux"
|
||||
},
|
||||
"screen": {
|
||||
"title": "Tamanho da Tela",
|
||||
"minWidth": "Largura Mín",
|
||||
"maxWidth": "Largura Máx",
|
||||
"minHeight": "Altura Mín",
|
||||
"maxHeight": "Altura Máx"
|
||||
},
|
||||
"geoip": {
|
||||
"title": "GeoIP",
|
||||
"auto": "Automático (baseado no proxy)",
|
||||
"manual": "Manual",
|
||||
"disabled": "Desativado"
|
||||
},
|
||||
"blocking": {
|
||||
"title": "Bloqueio",
|
||||
"images": "Bloquear Imagens",
|
||||
"webrtc": "Bloquear WebRTC",
|
||||
"webgl": "Bloquear WebGL"
|
||||
}
|
||||
},
|
||||
"wayfern": {
|
||||
"title": "Configuração do Wayfern",
|
||||
"fingerprint": {
|
||||
"title": "Impressão Digital",
|
||||
"randomize": "Aleatorizar ao Iniciar",
|
||||
"randomizeDescription": "Gera uma nova impressão digital cada vez que o navegador é iniciado."
|
||||
},
|
||||
"os": {
|
||||
"title": "Sistema Operacional",
|
||||
"description": "O sistema operacional a emular para geração de impressão digital.",
|
||||
"windows": "Windows",
|
||||
"macos": "macOS",
|
||||
"linux": "Linux",
|
||||
"android": "Android",
|
||||
"ios": "iOS"
|
||||
},
|
||||
"screen": {
|
||||
"title": "Tamanho da Tela",
|
||||
"minWidth": "Largura Mín",
|
||||
"maxWidth": "Largura Máx",
|
||||
"minHeight": "Altura Mín",
|
||||
"maxHeight": "Altura Máx"
|
||||
},
|
||||
"blocking": {
|
||||
"title": "Bloqueio",
|
||||
"webrtc": "Bloquear WebRTC",
|
||||
"webgl": "Bloquear WebGL"
|
||||
}
|
||||
}
|
||||
},
|
||||
"cookies": {
|
||||
"title": "Cookies",
|
||||
"copy": {
|
||||
"title": "Copiar Cookies",
|
||||
"description": "Selecione cookies para copiar para outros perfis.",
|
||||
"selectSource": "Selecionar Perfil de Origem",
|
||||
"selectTarget": "Selecionar Perfis de Destino",
|
||||
"selectCookies": "Selecionar Cookies",
|
||||
"allDomains": "Todos os Domínios",
|
||||
"selectedCount": "{{count}} cookie selecionado",
|
||||
"selectedCount_plural": "{{count}} cookies selecionados"
|
||||
},
|
||||
"success": "Cookies copiados com sucesso",
|
||||
"error": "Falha ao copiar cookies"
|
||||
},
|
||||
"toasts": {
|
||||
"success": {
|
||||
"profileCreated": "Perfil criado com sucesso",
|
||||
"profileDeleted": "Perfil excluído com sucesso",
|
||||
"profileUpdated": "Perfil atualizado com sucesso",
|
||||
"profileLaunched": "Perfil iniciado com sucesso",
|
||||
"proxyCreated": "Proxy criado com sucesso",
|
||||
"proxyDeleted": "Proxy excluído com sucesso",
|
||||
"proxyUpdated": "Proxy atualizado com sucesso",
|
||||
"groupCreated": "Grupo criado com sucesso",
|
||||
"groupDeleted": "Grupo excluído com sucesso",
|
||||
"groupUpdated": "Grupo atualizado com sucesso",
|
||||
"settingsSaved": "Configurações salvas com sucesso",
|
||||
"copied": "Copiado para a área de transferência",
|
||||
"permissionRequested": "Acesso ao {{permission}} solicitado",
|
||||
"downloadComplete": "{{browser}} {{version}} baixado com sucesso!",
|
||||
"importSuccess": "{{count}} itens importados com sucesso",
|
||||
"exportSuccess": "{{count}} itens exportados com sucesso",
|
||||
"syncSuccess": "Sincronização concluída com sucesso",
|
||||
"cacheCleared": "Cache limpo com sucesso"
|
||||
},
|
||||
"error": {
|
||||
"profileCreateFailed": "Falha ao criar perfil",
|
||||
"profileDeleteFailed": "Falha ao excluir perfil",
|
||||
"profileUpdateFailed": "Falha ao atualizar perfil",
|
||||
"profileLaunchFailed": "Falha ao iniciar perfil",
|
||||
"proxyCreateFailed": "Falha ao criar proxy",
|
||||
"proxyDeleteFailed": "Falha ao excluir proxy",
|
||||
"proxyUpdateFailed": "Falha ao atualizar proxy",
|
||||
"groupCreateFailed": "Falha ao criar grupo",
|
||||
"groupDeleteFailed": "Falha ao excluir grupo",
|
||||
"groupUpdateFailed": "Falha ao atualizar grupo",
|
||||
"settingsSaveFailed": "Falha ao salvar configurações",
|
||||
"copyFailed": "Falha ao copiar para a área de transferência",
|
||||
"downloadFailed": "Falha ao baixar {{browser}}",
|
||||
"importFailed": "Falha ao importar",
|
||||
"exportFailed": "Falha ao exportar",
|
||||
"syncFailed": "Falha na sincronização",
|
||||
"cacheClearFailed": "Falha ao limpar cache",
|
||||
"unknown": "Ocorreu um erro desconhecido"
|
||||
},
|
||||
"loading": {
|
||||
"downloading": "Baixando {{browser}} {{version}}",
|
||||
"extracting": "Extraindo {{browser}} {{version}}",
|
||||
"verifying": "Verificando {{browser}} {{version}}",
|
||||
"syncing": "Sincronizando...",
|
||||
"updatingVersions": "Atualizando versões de navegadores..."
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"required": "Este campo é obrigatório",
|
||||
"invalidUrl": "Por favor, insira uma URL válida",
|
||||
"invalidPort": "Por favor, insira um número de porta válido (1-65535)",
|
||||
"invalidEmail": "Por favor, insira um email válido",
|
||||
"minLength": "Deve ter pelo menos {{min}} caracteres",
|
||||
"maxLength": "Deve ter no máximo {{max}} caracteres",
|
||||
"networkError": "Erro de rede. Por favor, verifique sua conexão.",
|
||||
"serverError": "Erro do servidor. Por favor, tente novamente mais tarde.",
|
||||
"unknownError": "Ocorreu um erro desconhecido. Por favor, tente novamente."
|
||||
},
|
||||
"browser": {
|
||||
"firefox": "Firefox",
|
||||
"firefoxDeveloper": "Firefox Developer Edition",
|
||||
"chromium": "Chromium",
|
||||
"brave": "Brave",
|
||||
"zen": "Zen Browser",
|
||||
"camoufox": "Camoufox",
|
||||
"wayfern": "Wayfern"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
{
|
||||
"common": {
|
||||
"buttons": {
|
||||
"save": "Сохранить",
|
||||
"cancel": "Отмена",
|
||||
"close": "Закрыть",
|
||||
"delete": "Удалить",
|
||||
"create": "Создать",
|
||||
"back": "Назад",
|
||||
"retry": "Повторить",
|
||||
"download": "Скачать",
|
||||
"confirm": "Подтвердить",
|
||||
"apply": "Применить",
|
||||
"reset": "Сбросить",
|
||||
"add": "Добавить",
|
||||
"edit": "Редактировать",
|
||||
"copy": "Копировать",
|
||||
"clear": "Очистить",
|
||||
"search": "Поиск",
|
||||
"select": "Выбрать",
|
||||
"grant": "Разрешить",
|
||||
"start": "Запустить",
|
||||
"stop": "Остановить",
|
||||
"enable": "Включить",
|
||||
"disable": "Выключить",
|
||||
"import": "Импорт",
|
||||
"export": "Экспорт",
|
||||
"refresh": "Обновить",
|
||||
"loading": "Загрузка...",
|
||||
"saveSettings": "Сохранить настройки"
|
||||
},
|
||||
"status": {
|
||||
"active": "Активен",
|
||||
"inactive": "Неактивен",
|
||||
"running": "Запущен",
|
||||
"stopped": "Остановлен",
|
||||
"enabled": "Включено",
|
||||
"disabled": "Выключено",
|
||||
"granted": "Разрешено",
|
||||
"notGranted": "Не разрешено",
|
||||
"connected": "Подключено",
|
||||
"disconnected": "Отключено",
|
||||
"synced": "Синхронизировано",
|
||||
"syncing": "Синхронизация",
|
||||
"pending": "Ожидание",
|
||||
"error": "Ошибка"
|
||||
},
|
||||
"labels": {
|
||||
"name": "Название",
|
||||
"type": "Тип",
|
||||
"status": "Статус",
|
||||
"actions": "Действия",
|
||||
"description": "Описание",
|
||||
"none": "Нет",
|
||||
"default": "По умолчанию",
|
||||
"custom": "Пользовательский",
|
||||
"optional": "Необязательно",
|
||||
"required": "Обязательно"
|
||||
},
|
||||
"time": {
|
||||
"days": "дней",
|
||||
"hours": "часов",
|
||||
"minutes": "минут",
|
||||
"seconds": "секунд",
|
||||
"remaining": "осталось"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Настройки",
|
||||
"appearance": {
|
||||
"title": "Внешний вид",
|
||||
"theme": "Тема",
|
||||
"themeDescription": "Выберите предпочитаемую тему или следуйте системным настройкам. Изменения пользовательской темы применяются только при сохранении.",
|
||||
"themePreset": "Предустановка темы",
|
||||
"customColors": "Пользовательские цвета",
|
||||
"selectTheme": "Выберите тему",
|
||||
"selectThemePreset": "Выберите предустановку темы",
|
||||
"yourOwn": "Собственная",
|
||||
"light": "Светлая",
|
||||
"dark": "Тёмная",
|
||||
"system": "Системная"
|
||||
},
|
||||
"language": {
|
||||
"title": "Язык",
|
||||
"description": "Выберите предпочитаемый язык интерфейса приложения.",
|
||||
"systemDefault": "Системный по умолчанию",
|
||||
"selectLanguage": "Выберите язык"
|
||||
},
|
||||
"defaultBrowser": {
|
||||
"title": "Браузер по умолчанию",
|
||||
"setAsDefault": "Установить браузером по умолчанию",
|
||||
"alreadyDefault": "Уже браузер по умолчанию",
|
||||
"description": "При установке по умолчанию Donut Browser будет обрабатывать веб-ссылки и позволит выбрать профиль для использования."
|
||||
},
|
||||
"permissions": {
|
||||
"title": "Системные разрешения",
|
||||
"loading": "Загрузка разрешений...",
|
||||
"description": "Эти разрешения позволяют браузерам, запущенным из Donut Browser, получать доступ к системным ресурсам. Каждый сайт всё равно будет запрашивать разрешение отдельно.",
|
||||
"microphone": "Микрофон",
|
||||
"microphoneDescription": "Доступ к микрофону для браузерных приложений",
|
||||
"camera": "Камера",
|
||||
"cameraDescription": "Доступ к камере для браузерных приложений"
|
||||
},
|
||||
"integrations": {
|
||||
"title": "Интеграции",
|
||||
"description": "Настройте локальный API и MCP (Model Context Protocol) для интеграции с внешними инструментами и AI-ассистентами.",
|
||||
"openSettings": "Открыть настройки интеграций"
|
||||
},
|
||||
"commercial": {
|
||||
"title": "Коммерческая лицензия",
|
||||
"trialActive": "Пробный период: осталось {{days}} дней, {{hours}} часов",
|
||||
"trialActiveDescription": "Коммерческое использование бесплатно в течение пробного периода",
|
||||
"trialExpired": "Пробный период истёк",
|
||||
"trialExpiredDescription": "Личное использование остаётся бесплатным. Для коммерческого использования требуется лицензия."
|
||||
},
|
||||
"advanced": {
|
||||
"title": "Дополнительно",
|
||||
"clearCache": "Очистить весь кэш версий",
|
||||
"clearCacheDescription": "Очищает все кэшированные данные версий браузеров и обновляет все версии из источников. Это принудительно загрузит информацию о версиях для всех браузеров."
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"searchPlaceholder": "Поиск профилей...",
|
||||
"clearSearch": "Очистить поиск",
|
||||
"moreActions": "Дополнительные действия",
|
||||
"createProfile": "Создать новый профиль",
|
||||
"menu": {
|
||||
"settings": "Настройки",
|
||||
"proxies": "Прокси",
|
||||
"groups": "Группы",
|
||||
"syncService": "Служба синхронизации",
|
||||
"integrations": "Интеграции",
|
||||
"importProfile": "Импорт профиля"
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"title": "Профили",
|
||||
"empty": "Профилей пока нет",
|
||||
"emptyDescription": "Создайте свой первый профиль браузера для начала работы.",
|
||||
"createFirst": "Создать профиль",
|
||||
"noResults": "Профили не найдены",
|
||||
"noResultsDescription": "Нет профилей, соответствующих критериям поиска.",
|
||||
"table": {
|
||||
"name": "Название",
|
||||
"browser": "Браузер",
|
||||
"status": "Статус",
|
||||
"actions": "Действия",
|
||||
"note": "Заметка",
|
||||
"group": "Группа",
|
||||
"proxy": "Прокси",
|
||||
"lastLaunch": "Последний запуск"
|
||||
},
|
||||
"actions": {
|
||||
"launch": "Запустить",
|
||||
"stop": "Остановить",
|
||||
"edit": "Редактировать",
|
||||
"delete": "Удалить",
|
||||
"copyCookies": "Копировать Cookie",
|
||||
"configure": "Настроить"
|
||||
}
|
||||
},
|
||||
"createProfile": {
|
||||
"title": "Создать новый профиль",
|
||||
"configureTitle": "Настроить профиль",
|
||||
"antiDetect": {
|
||||
"title": "Антидетект браузер",
|
||||
"description": "Выберите браузер с возможностями защиты от обнаружения",
|
||||
"chromium": "Chromium (Wayfern)",
|
||||
"firefox": "Firefox (Camoufox)",
|
||||
"badge": "Антидетект браузер"
|
||||
},
|
||||
"regular": {
|
||||
"title": "Обычные браузеры",
|
||||
"description": "Выберите из поддерживаемых обычных браузеров",
|
||||
"badge": "Обычный браузер"
|
||||
},
|
||||
"profileName": "Название профиля",
|
||||
"profileNamePlaceholder": "Введите название профиля",
|
||||
"proxy": {
|
||||
"title": "Прокси",
|
||||
"addProxy": "Добавить прокси",
|
||||
"noProxy": "Без прокси",
|
||||
"noProxiesAvailable": "Нет доступных прокси. Добавьте один для маршрутизации трафика этого профиля."
|
||||
},
|
||||
"version": {
|
||||
"fetching": "Получение доступных версий...",
|
||||
"fetchError": "Не удалось получить версии браузера. Проверьте интернет-соединение и попробуйте снова.",
|
||||
"needsDownload": "Версию {{browser}} ({{version}}) необходимо скачать",
|
||||
"available": "Версия {{browser}} ({{version}}) доступна",
|
||||
"downloading": "Загрузка версии {{browser}} ({{version}})...",
|
||||
"latestNeedsDownload": "Последнюю версию ({{version}}) необходимо скачать",
|
||||
"latestAvailable": "Последняя версия ({{version}}) доступна",
|
||||
"latestDownloading": "Загрузка версии ({{version}})..."
|
||||
}
|
||||
},
|
||||
"deleteDialog": {
|
||||
"title": "Удалить профиль",
|
||||
"description": "Вы уверены, что хотите удалить этот профиль? Это действие нельзя отменить.",
|
||||
"profilesTitle": "Удалить профили",
|
||||
"profilesDescription": "Вы уверены, что хотите удалить выбранные профили? Это действие нельзя отменить.",
|
||||
"profilesToDelete": "Профили для удаления:"
|
||||
},
|
||||
"proxies": {
|
||||
"title": "Прокси",
|
||||
"management": "Управление прокси",
|
||||
"add": "Добавить прокси",
|
||||
"edit": "Редактировать прокси",
|
||||
"delete": "Удалить прокси",
|
||||
"import": "Импорт",
|
||||
"export": "Экспорт",
|
||||
"noProxies": "Прокси не настроены",
|
||||
"noProxiesDescription": "Добавьте прокси для маршрутизации трафика браузера через него.",
|
||||
"form": {
|
||||
"name": "Название",
|
||||
"namePlaceholder": "Введите название прокси",
|
||||
"type": "Тип",
|
||||
"host": "Хост",
|
||||
"hostPlaceholder": "proxy.example.com",
|
||||
"port": "Порт",
|
||||
"portPlaceholder": "8080",
|
||||
"username": "Имя пользователя",
|
||||
"usernamePlaceholder": "Необязательно",
|
||||
"password": "Пароль",
|
||||
"passwordPlaceholder": "Необязательно"
|
||||
},
|
||||
"types": {
|
||||
"http": "HTTP",
|
||||
"https": "HTTPS",
|
||||
"socks4": "SOCKS4",
|
||||
"socks5": "SOCKS5"
|
||||
},
|
||||
"check": {
|
||||
"checking": "Проверка прокси...",
|
||||
"valid": "Прокси действителен",
|
||||
"invalid": "Прокси недействителен",
|
||||
"lastChecked": "Последняя проверка: {{time}}"
|
||||
},
|
||||
"sync": {
|
||||
"enabled": "Синхронизация включена",
|
||||
"disabled": "Синхронизация выключена"
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"title": "Группы",
|
||||
"management": "Управление группами",
|
||||
"add": "Добавить группу",
|
||||
"edit": "Редактировать группу",
|
||||
"delete": "Удалить группу",
|
||||
"noGroups": "Группы не созданы",
|
||||
"noGroupsDescription": "Создайте группу для организации профилей.",
|
||||
"form": {
|
||||
"name": "Название",
|
||||
"namePlaceholder": "Введите название группы"
|
||||
},
|
||||
"profileCount": "{{count}} профиль",
|
||||
"profileCount_plural": "{{count}} профилей",
|
||||
"assignProfiles": "Назначить профили",
|
||||
"sync": {
|
||||
"enabled": "Синхронизация включена",
|
||||
"disabled": "Синхронизация выключена"
|
||||
}
|
||||
},
|
||||
"sync": {
|
||||
"title": "Служба синхронизации",
|
||||
"config": "Настройка синхронизации",
|
||||
"serverUrl": "URL сервера",
|
||||
"serverUrlPlaceholder": "https://sync.example.com",
|
||||
"token": "Токен синхронизации",
|
||||
"tokenPlaceholder": "Введите токен синхронизации",
|
||||
"status": {
|
||||
"connected": "Подключено",
|
||||
"disconnected": "Отключено",
|
||||
"syncing": "Синхронизация...",
|
||||
"error": "Ошибка синхронизации"
|
||||
},
|
||||
"description": "Подключитесь к серверу синхронизации для синхронизации профилей, прокси и групп между устройствами."
|
||||
},
|
||||
"integrations": {
|
||||
"title": "Интеграции",
|
||||
"api": {
|
||||
"title": "Локальный API",
|
||||
"description": "Включите локальный API-сервер для внешних интеграций.",
|
||||
"enabled": "API включён",
|
||||
"disabled": "API выключен",
|
||||
"port": "Порт",
|
||||
"token": "API токен",
|
||||
"copyToken": "Копировать токен",
|
||||
"regenerateToken": "Перегенерировать токен"
|
||||
},
|
||||
"mcp": {
|
||||
"title": "MCP сервер",
|
||||
"description": "Включите MCP (Model Context Protocol) сервер для интеграции с AI-ассистентами.",
|
||||
"enabled": "MCP включён",
|
||||
"disabled": "MCP выключен",
|
||||
"port": "Порт",
|
||||
"token": "MCP токен",
|
||||
"config": "Конфигурация MCP",
|
||||
"copyConfig": "Копировать конфигурацию"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"title": "Импорт профиля",
|
||||
"description": "Импортируйте существующий профиль браузера из вашей системы.",
|
||||
"selectProfile": "Выберите профиль для импорта",
|
||||
"noProfiles": "Профили не обнаружены",
|
||||
"noProfilesDescription": "В вашей системе не обнаружены профили браузеров.",
|
||||
"importing": "Импорт профиля...",
|
||||
"success": "Профиль успешно импортирован",
|
||||
"error": "Ошибка импорта профиля"
|
||||
},
|
||||
"config": {
|
||||
"camoufox": {
|
||||
"title": "Настройки Camoufox",
|
||||
"fingerprint": {
|
||||
"title": "Отпечаток",
|
||||
"randomize": "Случайный при запуске",
|
||||
"randomizeDescription": "Генерировать новый отпечаток при каждом запуске браузера."
|
||||
},
|
||||
"os": {
|
||||
"title": "Операционная система",
|
||||
"description": "Операционная система для эмуляции при генерации отпечатка.",
|
||||
"windows": "Windows",
|
||||
"macos": "macOS",
|
||||
"linux": "Linux"
|
||||
},
|
||||
"screen": {
|
||||
"title": "Размер экрана",
|
||||
"minWidth": "Мин. ширина",
|
||||
"maxWidth": "Макс. ширина",
|
||||
"minHeight": "Мин. высота",
|
||||
"maxHeight": "Макс. высота"
|
||||
},
|
||||
"geoip": {
|
||||
"title": "GeoIP",
|
||||
"auto": "Автоматически (на основе прокси)",
|
||||
"manual": "Вручную",
|
||||
"disabled": "Выключено"
|
||||
},
|
||||
"blocking": {
|
||||
"title": "Блокировка",
|
||||
"images": "Блокировать изображения",
|
||||
"webrtc": "Блокировать WebRTC",
|
||||
"webgl": "Блокировать WebGL"
|
||||
}
|
||||
},
|
||||
"wayfern": {
|
||||
"title": "Настройки Wayfern",
|
||||
"fingerprint": {
|
||||
"title": "Отпечаток",
|
||||
"randomize": "Случайный при запуске",
|
||||
"randomizeDescription": "Генерировать новый отпечаток при каждом запуске браузера."
|
||||
},
|
||||
"os": {
|
||||
"title": "Операционная система",
|
||||
"description": "Операционная система для эмуляции при генерации отпечатка.",
|
||||
"windows": "Windows",
|
||||
"macos": "macOS",
|
||||
"linux": "Linux",
|
||||
"android": "Android",
|
||||
"ios": "iOS"
|
||||
},
|
||||
"screen": {
|
||||
"title": "Размер экрана",
|
||||
"minWidth": "Мин. ширина",
|
||||
"maxWidth": "Макс. ширина",
|
||||
"minHeight": "Мин. высота",
|
||||
"maxHeight": "Макс. высота"
|
||||
},
|
||||
"blocking": {
|
||||
"title": "Блокировка",
|
||||
"webrtc": "Блокировать WebRTC",
|
||||
"webgl": "Блокировать WebGL"
|
||||
}
|
||||
}
|
||||
},
|
||||
"cookies": {
|
||||
"title": "Cookie",
|
||||
"copy": {
|
||||
"title": "Копировать Cookie",
|
||||
"description": "Выберите cookie для копирования в другие профили.",
|
||||
"selectSource": "Выберите исходный профиль",
|
||||
"selectTarget": "Выберите целевые профили",
|
||||
"selectCookies": "Выберите Cookie",
|
||||
"allDomains": "Все домены",
|
||||
"selectedCount": "Выбрано {{count}} cookie",
|
||||
"selectedCount_plural": "Выбрано {{count}} cookie"
|
||||
},
|
||||
"success": "Cookie успешно скопированы",
|
||||
"error": "Ошибка копирования cookie"
|
||||
},
|
||||
"toasts": {
|
||||
"success": {
|
||||
"profileCreated": "Профиль успешно создан",
|
||||
"profileDeleted": "Профиль успешно удалён",
|
||||
"profileUpdated": "Профиль успешно обновлён",
|
||||
"profileLaunched": "Профиль успешно запущен",
|
||||
"proxyCreated": "Прокси успешно создан",
|
||||
"proxyDeleted": "Прокси успешно удалён",
|
||||
"proxyUpdated": "Прокси успешно обновлён",
|
||||
"groupCreated": "Группа успешно создана",
|
||||
"groupDeleted": "Группа успешно удалена",
|
||||
"groupUpdated": "Группа успешно обновлена",
|
||||
"settingsSaved": "Настройки успешно сохранены",
|
||||
"copied": "Скопировано в буфер обмена",
|
||||
"permissionRequested": "Запрошен доступ к {{permission}}",
|
||||
"downloadComplete": "{{browser}} {{version}} успешно загружен!",
|
||||
"importSuccess": "Успешно импортировано {{count}} элементов",
|
||||
"exportSuccess": "Успешно экспортировано {{count}} элементов",
|
||||
"syncSuccess": "Синхронизация успешно завершена",
|
||||
"cacheCleared": "Кэш успешно очищен"
|
||||
},
|
||||
"error": {
|
||||
"profileCreateFailed": "Ошибка создания профиля",
|
||||
"profileDeleteFailed": "Ошибка удаления профиля",
|
||||
"profileUpdateFailed": "Ошибка обновления профиля",
|
||||
"profileLaunchFailed": "Ошибка запуска профиля",
|
||||
"proxyCreateFailed": "Ошибка создания прокси",
|
||||
"proxyDeleteFailed": "Ошибка удаления прокси",
|
||||
"proxyUpdateFailed": "Ошибка обновления прокси",
|
||||
"groupCreateFailed": "Ошибка создания группы",
|
||||
"groupDeleteFailed": "Ошибка удаления группы",
|
||||
"groupUpdateFailed": "Ошибка обновления группы",
|
||||
"settingsSaveFailed": "Ошибка сохранения настроек",
|
||||
"copyFailed": "Ошибка копирования в буфер обмена",
|
||||
"downloadFailed": "Ошибка загрузки {{browser}}",
|
||||
"importFailed": "Ошибка импорта",
|
||||
"exportFailed": "Ошибка экспорта",
|
||||
"syncFailed": "Ошибка синхронизации",
|
||||
"cacheClearFailed": "Ошибка очистки кэша",
|
||||
"unknown": "Произошла неизвестная ошибка"
|
||||
},
|
||||
"loading": {
|
||||
"downloading": "Загрузка {{browser}} {{version}}",
|
||||
"extracting": "Распаковка {{browser}} {{version}}",
|
||||
"verifying": "Проверка {{browser}} {{version}}",
|
||||
"syncing": "Синхронизация...",
|
||||
"updatingVersions": "Обновление версий браузеров..."
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"required": "Это поле обязательно",
|
||||
"invalidUrl": "Введите корректный URL",
|
||||
"invalidPort": "Введите корректный номер порта (1-65535)",
|
||||
"invalidEmail": "Введите корректный адрес электронной почты",
|
||||
"minLength": "Минимум {{min}} символов",
|
||||
"maxLength": "Максимум {{max}} символов",
|
||||
"networkError": "Сетевая ошибка. Проверьте подключение.",
|
||||
"serverError": "Ошибка сервера. Попробуйте позже.",
|
||||
"unknownError": "Произошла неизвестная ошибка. Попробуйте снова."
|
||||
},
|
||||
"browser": {
|
||||
"firefox": "Firefox",
|
||||
"firefoxDeveloper": "Firefox Developer Edition",
|
||||
"chromium": "Chromium",
|
||||
"brave": "Brave",
|
||||
"zen": "Zen Browser",
|
||||
"camoufox": "Camoufox",
|
||||
"wayfern": "Wayfern"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
{
|
||||
"common": {
|
||||
"buttons": {
|
||||
"save": "保存",
|
||||
"cancel": "取消",
|
||||
"close": "关闭",
|
||||
"delete": "删除",
|
||||
"create": "创建",
|
||||
"back": "返回",
|
||||
"retry": "重试",
|
||||
"download": "下载",
|
||||
"confirm": "确认",
|
||||
"apply": "应用",
|
||||
"reset": "重置",
|
||||
"add": "添加",
|
||||
"edit": "编辑",
|
||||
"copy": "复制",
|
||||
"clear": "清除",
|
||||
"search": "搜索",
|
||||
"select": "选择",
|
||||
"grant": "授权",
|
||||
"start": "启动",
|
||||
"stop": "停止",
|
||||
"enable": "启用",
|
||||
"disable": "禁用",
|
||||
"import": "导入",
|
||||
"export": "导出",
|
||||
"refresh": "刷新",
|
||||
"loading": "加载中...",
|
||||
"saveSettings": "保存设置"
|
||||
},
|
||||
"status": {
|
||||
"active": "活跃",
|
||||
"inactive": "未活跃",
|
||||
"running": "运行中",
|
||||
"stopped": "已停止",
|
||||
"enabled": "已启用",
|
||||
"disabled": "已禁用",
|
||||
"granted": "已授权",
|
||||
"notGranted": "未授权",
|
||||
"connected": "已连接",
|
||||
"disconnected": "已断开",
|
||||
"synced": "已同步",
|
||||
"syncing": "同步中",
|
||||
"pending": "待处理",
|
||||
"error": "错误"
|
||||
},
|
||||
"labels": {
|
||||
"name": "名称",
|
||||
"type": "类型",
|
||||
"status": "状态",
|
||||
"actions": "操作",
|
||||
"description": "描述",
|
||||
"none": "无",
|
||||
"default": "默认",
|
||||
"custom": "自定义",
|
||||
"optional": "可选",
|
||||
"required": "必填"
|
||||
},
|
||||
"time": {
|
||||
"days": "天",
|
||||
"hours": "小时",
|
||||
"minutes": "分钟",
|
||||
"seconds": "秒",
|
||||
"remaining": "剩余"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "设置",
|
||||
"appearance": {
|
||||
"title": "外观",
|
||||
"theme": "主题",
|
||||
"themeDescription": "选择您喜欢的主题或跟随系统设置。自定义主题更改仅在保存时应用。",
|
||||
"themePreset": "主题预设",
|
||||
"customColors": "自定义颜色",
|
||||
"selectTheme": "选择主题",
|
||||
"selectThemePreset": "选择主题预设",
|
||||
"yourOwn": "自定义",
|
||||
"light": "浅色",
|
||||
"dark": "深色",
|
||||
"system": "跟随系统"
|
||||
},
|
||||
"language": {
|
||||
"title": "语言",
|
||||
"description": "选择应用程序界面的首选语言。",
|
||||
"systemDefault": "系统默认",
|
||||
"selectLanguage": "选择语言"
|
||||
},
|
||||
"defaultBrowser": {
|
||||
"title": "默认浏览器",
|
||||
"setAsDefault": "设为默认浏览器",
|
||||
"alreadyDefault": "已是默认浏览器",
|
||||
"description": "设为默认后,Donut Browser 将处理网页链接并允许您选择使用哪个配置文件。"
|
||||
},
|
||||
"permissions": {
|
||||
"title": "系统权限",
|
||||
"loading": "加载权限中...",
|
||||
"description": "这些权限允许从 Donut Browser 启动的浏览器访问系统资源。每个网站仍会单独请求您的权限。",
|
||||
"microphone": "麦克风",
|
||||
"microphoneDescription": "浏览器应用程序的麦克风访问权限",
|
||||
"camera": "摄像头",
|
||||
"cameraDescription": "浏览器应用程序的摄像头访问权限"
|
||||
},
|
||||
"integrations": {
|
||||
"title": "集成",
|
||||
"description": "配置本地 API 和 MCP(模型上下文协议)以与外部工具和 AI 助手集成。",
|
||||
"openSettings": "打开集成设置"
|
||||
},
|
||||
"commercial": {
|
||||
"title": "商业许可",
|
||||
"trialActive": "试用期:剩余 {{days}} 天 {{hours}} 小时",
|
||||
"trialActiveDescription": "试用期内商业使用免费",
|
||||
"trialExpired": "试用期已过期",
|
||||
"trialExpiredDescription": "个人使用仍然免费。商业使用需要许可证。"
|
||||
},
|
||||
"advanced": {
|
||||
"title": "高级",
|
||||
"clearCache": "清除所有版本缓存",
|
||||
"clearCacheDescription": "清除所有缓存的浏览器版本数据并从源刷新所有浏览器版本。这将强制重新下载所有浏览器的版本信息。"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"searchPlaceholder": "搜索配置文件...",
|
||||
"clearSearch": "清除搜索",
|
||||
"moreActions": "更多操作",
|
||||
"createProfile": "创建新配置文件",
|
||||
"menu": {
|
||||
"settings": "设置",
|
||||
"proxies": "代理",
|
||||
"groups": "分组",
|
||||
"syncService": "同步服务",
|
||||
"integrations": "集成",
|
||||
"importProfile": "导入配置文件"
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"title": "配置文件",
|
||||
"empty": "暂无配置文件",
|
||||
"emptyDescription": "创建您的第一个浏览器配置文件以开始使用。",
|
||||
"createFirst": "创建配置文件",
|
||||
"noResults": "未找到配置文件",
|
||||
"noResultsDescription": "没有配置文件匹配您的搜索条件。",
|
||||
"table": {
|
||||
"name": "名称",
|
||||
"browser": "浏览器",
|
||||
"status": "状态",
|
||||
"actions": "操作",
|
||||
"note": "备注",
|
||||
"group": "分组",
|
||||
"proxy": "代理",
|
||||
"lastLaunch": "最后启动"
|
||||
},
|
||||
"actions": {
|
||||
"launch": "启动",
|
||||
"stop": "停止",
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"copyCookies": "复制 Cookies",
|
||||
"configure": "配置"
|
||||
}
|
||||
},
|
||||
"createProfile": {
|
||||
"title": "创建新配置文件",
|
||||
"configureTitle": "配置配置文件",
|
||||
"antiDetect": {
|
||||
"title": "防检测浏览器",
|
||||
"description": "选择具有防检测功能的浏览器",
|
||||
"chromium": "Chromium (Wayfern)",
|
||||
"firefox": "Firefox (Camoufox)",
|
||||
"badge": "防检测浏览器"
|
||||
},
|
||||
"regular": {
|
||||
"title": "常规浏览器",
|
||||
"description": "从支持的常规浏览器中选择",
|
||||
"badge": "常规浏览器"
|
||||
},
|
||||
"profileName": "配置文件名称",
|
||||
"profileNamePlaceholder": "输入配置文件名称",
|
||||
"proxy": {
|
||||
"title": "代理",
|
||||
"addProxy": "添加代理",
|
||||
"noProxy": "无代理",
|
||||
"noProxiesAvailable": "没有可用的代理。添加一个代理来路由此配置文件的流量。"
|
||||
},
|
||||
"version": {
|
||||
"fetching": "正在获取可用版本...",
|
||||
"fetchError": "获取浏览器版本失败。请检查您的网络连接并重试。",
|
||||
"needsDownload": "{{browser}} 版本 ({{version}}) 需要下载",
|
||||
"available": "{{browser}} 版本 ({{version}}) 可用",
|
||||
"downloading": "正在下载 {{browser}} 版本 ({{version}})...",
|
||||
"latestNeedsDownload": "最新版本 ({{version}}) 需要下载",
|
||||
"latestAvailable": "最新版本 ({{version}}) 可用",
|
||||
"latestDownloading": "正在下载版本 ({{version}})..."
|
||||
}
|
||||
},
|
||||
"deleteDialog": {
|
||||
"title": "删除配置文件",
|
||||
"description": "您确定要删除此配置文件吗?此操作无法撤消。",
|
||||
"profilesTitle": "删除配置文件",
|
||||
"profilesDescription": "您确定要删除选中的配置文件吗?此操作无法撤消。",
|
||||
"profilesToDelete": "将被删除的配置文件:"
|
||||
},
|
||||
"proxies": {
|
||||
"title": "代理",
|
||||
"management": "代理管理",
|
||||
"add": "添加代理",
|
||||
"edit": "编辑代理",
|
||||
"delete": "删除代理",
|
||||
"import": "导入",
|
||||
"export": "导出",
|
||||
"noProxies": "未配置代理",
|
||||
"noProxiesDescription": "添加代理以通过它路由浏览器流量。",
|
||||
"form": {
|
||||
"name": "名称",
|
||||
"namePlaceholder": "输入代理名称",
|
||||
"type": "类型",
|
||||
"host": "主机",
|
||||
"hostPlaceholder": "proxy.example.com",
|
||||
"port": "端口",
|
||||
"portPlaceholder": "8080",
|
||||
"username": "用户名",
|
||||
"usernamePlaceholder": "可选",
|
||||
"password": "密码",
|
||||
"passwordPlaceholder": "可选"
|
||||
},
|
||||
"types": {
|
||||
"http": "HTTP",
|
||||
"https": "HTTPS",
|
||||
"socks4": "SOCKS4",
|
||||
"socks5": "SOCKS5"
|
||||
},
|
||||
"check": {
|
||||
"checking": "检查代理中...",
|
||||
"valid": "代理有效",
|
||||
"invalid": "代理无效",
|
||||
"lastChecked": "最后检查:{{time}}"
|
||||
},
|
||||
"sync": {
|
||||
"enabled": "同步已启用",
|
||||
"disabled": "同步已禁用"
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"title": "分组",
|
||||
"management": "分组管理",
|
||||
"add": "添加分组",
|
||||
"edit": "编辑分组",
|
||||
"delete": "删除分组",
|
||||
"noGroups": "暂无分组",
|
||||
"noGroupsDescription": "创建分组来组织您的配置文件。",
|
||||
"form": {
|
||||
"name": "名称",
|
||||
"namePlaceholder": "输入分组名称"
|
||||
},
|
||||
"profileCount": "{{count}} 个配置文件",
|
||||
"profileCount_plural": "{{count}} 个配置文件",
|
||||
"assignProfiles": "分配配置文件",
|
||||
"sync": {
|
||||
"enabled": "同步已启用",
|
||||
"disabled": "同步已禁用"
|
||||
}
|
||||
},
|
||||
"sync": {
|
||||
"title": "同步服务",
|
||||
"config": "同步配置",
|
||||
"serverUrl": "服务器 URL",
|
||||
"serverUrlPlaceholder": "https://sync.example.com",
|
||||
"token": "同步令牌",
|
||||
"tokenPlaceholder": "输入您的同步令牌",
|
||||
"status": {
|
||||
"connected": "已连接",
|
||||
"disconnected": "已断开",
|
||||
"syncing": "同步中...",
|
||||
"error": "同步错误"
|
||||
},
|
||||
"description": "连接到同步服务器以在设备之间同步您的配置文件、代理和分组。"
|
||||
},
|
||||
"integrations": {
|
||||
"title": "集成",
|
||||
"api": {
|
||||
"title": "本地 API",
|
||||
"description": "启用本地 API 服务器以进行外部集成。",
|
||||
"enabled": "API 已启用",
|
||||
"disabled": "API 已禁用",
|
||||
"port": "端口",
|
||||
"token": "API 令牌",
|
||||
"copyToken": "复制令牌",
|
||||
"regenerateToken": "重新生成令牌"
|
||||
},
|
||||
"mcp": {
|
||||
"title": "MCP 服务器",
|
||||
"description": "启用 MCP(模型上下文协议)服务器以与 AI 助手集成。",
|
||||
"enabled": "MCP 已启用",
|
||||
"disabled": "MCP 已禁用",
|
||||
"port": "端口",
|
||||
"token": "MCP 令牌",
|
||||
"config": "MCP 配置",
|
||||
"copyConfig": "复制配置"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"title": "导入配置文件",
|
||||
"description": "从您的系统导入现有的浏览器配置文件。",
|
||||
"selectProfile": "选择要导入的配置文件",
|
||||
"noProfiles": "未检测到配置文件",
|
||||
"noProfilesDescription": "您的系统上未检测到浏览器配置文件。",
|
||||
"importing": "正在导入配置文件...",
|
||||
"success": "配置文件导入成功",
|
||||
"error": "导入配置文件失败"
|
||||
},
|
||||
"config": {
|
||||
"camoufox": {
|
||||
"title": "Camoufox 配置",
|
||||
"fingerprint": {
|
||||
"title": "指纹",
|
||||
"randomize": "启动时随机化",
|
||||
"randomizeDescription": "每次启动浏览器时生成新的指纹。"
|
||||
},
|
||||
"os": {
|
||||
"title": "操作系统",
|
||||
"description": "用于生成指纹的模拟操作系统。",
|
||||
"windows": "Windows",
|
||||
"macos": "macOS",
|
||||
"linux": "Linux"
|
||||
},
|
||||
"screen": {
|
||||
"title": "屏幕尺寸",
|
||||
"minWidth": "最小宽度",
|
||||
"maxWidth": "最大宽度",
|
||||
"minHeight": "最小高度",
|
||||
"maxHeight": "最大高度"
|
||||
},
|
||||
"geoip": {
|
||||
"title": "GeoIP",
|
||||
"auto": "自动(基于代理)",
|
||||
"manual": "手动",
|
||||
"disabled": "禁用"
|
||||
},
|
||||
"blocking": {
|
||||
"title": "阻止",
|
||||
"images": "阻止图片",
|
||||
"webrtc": "阻止 WebRTC",
|
||||
"webgl": "阻止 WebGL"
|
||||
}
|
||||
},
|
||||
"wayfern": {
|
||||
"title": "Wayfern 配置",
|
||||
"fingerprint": {
|
||||
"title": "指纹",
|
||||
"randomize": "启动时随机化",
|
||||
"randomizeDescription": "每次启动浏览器时生成新的指纹。"
|
||||
},
|
||||
"os": {
|
||||
"title": "操作系统",
|
||||
"description": "用于生成指纹的模拟操作系统。",
|
||||
"windows": "Windows",
|
||||
"macos": "macOS",
|
||||
"linux": "Linux",
|
||||
"android": "Android",
|
||||
"ios": "iOS"
|
||||
},
|
||||
"screen": {
|
||||
"title": "屏幕尺寸",
|
||||
"minWidth": "最小宽度",
|
||||
"maxWidth": "最大宽度",
|
||||
"minHeight": "最小高度",
|
||||
"maxHeight": "最大高度"
|
||||
},
|
||||
"blocking": {
|
||||
"title": "阻止",
|
||||
"webrtc": "阻止 WebRTC",
|
||||
"webgl": "阻止 WebGL"
|
||||
}
|
||||
}
|
||||
},
|
||||
"cookies": {
|
||||
"title": "Cookies",
|
||||
"copy": {
|
||||
"title": "复制 Cookies",
|
||||
"description": "选择要复制到其他配置文件的 cookies。",
|
||||
"selectSource": "选择源配置文件",
|
||||
"selectTarget": "选择目标配置文件",
|
||||
"selectCookies": "选择 Cookies",
|
||||
"allDomains": "所有域名",
|
||||
"selectedCount": "已选择 {{count}} 个 cookie",
|
||||
"selectedCount_plural": "已选择 {{count}} 个 cookies"
|
||||
},
|
||||
"success": "Cookies 复制成功",
|
||||
"error": "复制 cookies 失败"
|
||||
},
|
||||
"toasts": {
|
||||
"success": {
|
||||
"profileCreated": "配置文件创建成功",
|
||||
"profileDeleted": "配置文件删除成功",
|
||||
"profileUpdated": "配置文件更新成功",
|
||||
"profileLaunched": "配置文件启动成功",
|
||||
"proxyCreated": "代理创建成功",
|
||||
"proxyDeleted": "代理删除成功",
|
||||
"proxyUpdated": "代理更新成功",
|
||||
"groupCreated": "分组创建成功",
|
||||
"groupDeleted": "分组删除成功",
|
||||
"groupUpdated": "分组更新成功",
|
||||
"settingsSaved": "设置保存成功",
|
||||
"copied": "已复制到剪贴板",
|
||||
"permissionRequested": "已请求 {{permission}} 访问权限",
|
||||
"downloadComplete": "{{browser}} {{version}} 下载成功!",
|
||||
"importSuccess": "成功导入 {{count}} 个项目",
|
||||
"exportSuccess": "成功导出 {{count}} 个项目",
|
||||
"syncSuccess": "同步成功完成",
|
||||
"cacheCleared": "缓存清除成功"
|
||||
},
|
||||
"error": {
|
||||
"profileCreateFailed": "创建配置文件失败",
|
||||
"profileDeleteFailed": "删除配置文件失败",
|
||||
"profileUpdateFailed": "更新配置文件失败",
|
||||
"profileLaunchFailed": "启动配置文件失败",
|
||||
"proxyCreateFailed": "创建代理失败",
|
||||
"proxyDeleteFailed": "删除代理失败",
|
||||
"proxyUpdateFailed": "更新代理失败",
|
||||
"groupCreateFailed": "创建分组失败",
|
||||
"groupDeleteFailed": "删除分组失败",
|
||||
"groupUpdateFailed": "更新分组失败",
|
||||
"settingsSaveFailed": "保存设置失败",
|
||||
"copyFailed": "复制到剪贴板失败",
|
||||
"downloadFailed": "下载 {{browser}} 失败",
|
||||
"importFailed": "导入失败",
|
||||
"exportFailed": "导出失败",
|
||||
"syncFailed": "同步失败",
|
||||
"cacheClearFailed": "清除缓存失败",
|
||||
"unknown": "发生未知错误"
|
||||
},
|
||||
"loading": {
|
||||
"downloading": "正在下载 {{browser}} {{version}}",
|
||||
"extracting": "正在解压 {{browser}} {{version}}",
|
||||
"verifying": "正在验证 {{browser}} {{version}}",
|
||||
"syncing": "同步中...",
|
||||
"updatingVersions": "正在更新浏览器版本..."
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"required": "此字段为必填项",
|
||||
"invalidUrl": "请输入有效的 URL",
|
||||
"invalidPort": "请输入有效的端口号(1-65535)",
|
||||
"invalidEmail": "请输入有效的电子邮件地址",
|
||||
"minLength": "至少需要 {{min}} 个字符",
|
||||
"maxLength": "最多 {{max}} 个字符",
|
||||
"networkError": "网络错误。请检查您的连接。",
|
||||
"serverError": "服务器错误。请稍后重试。",
|
||||
"unknownError": "发生未知错误。请重试。"
|
||||
},
|
||||
"browser": {
|
||||
"firefox": "Firefox",
|
||||
"firefoxDeveloper": "Firefox 开发者版",
|
||||
"chromium": "Chromium",
|
||||
"brave": "Brave",
|
||||
"zen": "Zen Browser",
|
||||
"camoufox": "Camoufox",
|
||||
"wayfern": "Wayfern"
|
||||
}
|
||||
}
|
||||
@@ -526,3 +526,70 @@ export interface CookieCopyResult {
|
||||
cookies_replaced: number;
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
// Proxy import/export types
|
||||
export interface ProxyExportData {
|
||||
version: string;
|
||||
proxies: ExportedProxy[];
|
||||
exported_at: string;
|
||||
source: string;
|
||||
}
|
||||
|
||||
export interface ExportedProxy {
|
||||
name: string;
|
||||
type: string;
|
||||
host: string;
|
||||
port: number;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export interface ProxyImportResult {
|
||||
imported_count: number;
|
||||
skipped_count: number;
|
||||
errors: string[];
|
||||
proxies: StoredProxy[];
|
||||
}
|
||||
|
||||
export interface ParsedProxyLine {
|
||||
proxy_type: string;
|
||||
host: string;
|
||||
port: number;
|
||||
username?: string;
|
||||
password?: string;
|
||||
original_line: string;
|
||||
}
|
||||
|
||||
export type ProxyParseResult =
|
||||
| ({ status: "parsed" } & ParsedProxyLine)
|
||||
| { status: "ambiguous"; line: string; possible_formats: string[] }
|
||||
| { status: "invalid"; line: string; reason: string };
|
||||
|
||||
// VPN types
|
||||
export type VpnType = "WireGuard" | "OpenVPN";
|
||||
|
||||
export interface VpnConfig {
|
||||
id: string;
|
||||
name: string;
|
||||
vpn_type: VpnType;
|
||||
config_data: string; // Raw config content (may be empty in list view)
|
||||
created_at: number;
|
||||
last_used?: number;
|
||||
}
|
||||
|
||||
export interface VpnImportResult {
|
||||
success: boolean;
|
||||
vpn_id?: string;
|
||||
vpn_type?: VpnType;
|
||||
name: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface VpnStatus {
|
||||
connected: boolean;
|
||||
vpn_id: string;
|
||||
connected_at?: number;
|
||||
bytes_sent?: number;
|
||||
bytes_received?: number;
|
||||
last_handshake?: number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user