chore: linting

This commit is contained in:
zhom
2026-03-28 23:31:20 +04:00
parent ba750a3401
commit 612c6610ce
36 changed files with 284 additions and 285 deletions
+5
View File
@@ -197,6 +197,7 @@ These are frequently overlooked issues that make UI look unprofessional:
Before delivering UI code, verify these items:
### Visual Quality
- [ ] No emojis used as icons (use SVG instead)
- [ ] All icons from consistent icon set (Heroicons/Lucide)
- [ ] Brand logos are correct (verified from Simple Icons)
@@ -204,24 +205,28 @@ Before delivering UI code, verify these items:
- [ ] Use theme colors directly (bg-primary) not var() wrapper
### Interaction
- [ ] All clickable elements have `cursor-pointer`
- [ ] Hover states provide clear visual feedback
- [ ] Transitions are smooth (150-300ms)
- [ ] Focus states visible for keyboard navigation
### Light/Dark Mode
- [ ] Light mode text has sufficient contrast (4.5:1 minimum)
- [ ] Glass/transparent elements visible in light mode
- [ ] Borders visible in both modes
- [ ] Test both modes before delivery
### Layout
- [ ] Floating elements have proper spacing from edges
- [ ] No content hidden behind fixed navbars
- [ ] Responsive at 320px, 768px, 1024px, 1440px
- [ ] No horizontal scroll on mobile
### Accessibility
- [ ] All images have alt text
- [ ] Form inputs have labels
- [ ] Color is not the only indicator
+2
View File
@@ -27,6 +27,7 @@ Or enter the dev shell: `nix develop`
### Manual Setup
Requirements:
- Node.js (see `.node-version`)
- pnpm
- Rust + Cargo (latest stable)
@@ -47,6 +48,7 @@ pnpm format && pnpm lint && pnpm test
```
This runs:
- **Biome** — JS/TS linting and formatting
- **Clippy + rustfmt** — Rust linting and formatting
- **typos** — Spellcheck (allowlist in `_typos.toml`)
+9 -11
View File
@@ -2,8 +2,6 @@
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
@@ -28,33 +26,33 @@
## Project setup
```bash
$ pnpm install
pnpm install
```
## Compile and run the project
```bash
# development
$ pnpm run start
pnpm run start
# watch mode
$ pnpm run start:dev
pnpm run start:dev
# production mode
$ pnpm run start:prod
pnpm run start:prod
```
## Run tests
```bash
# unit tests
$ pnpm run test
pnpm run test
# e2e tests
$ pnpm run test:e2e
pnpm run test:e2e
# test coverage
$ pnpm run test:cov
pnpm run test:cov
```
## Deployment
@@ -64,8 +62,8 @@ When you're ready to deploy your NestJS application to production, there are som
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
```bash
$ pnpm install -g @nestjs/mau
$ mau deploy
pnpm install -g @nestjs/mau
mau deploy
```
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
+2 -2
View File
@@ -38,7 +38,7 @@ export class AuthGuard implements CanActivate {
// Try SYNC_TOKEN first (self-hosted mode)
const expectedToken = this.configService.get<string>("SYNC_TOKEN");
if (expectedToken && token === expectedToken) {
(request as any).user = {
(request as unknown as Record<string, unknown>).user = {
mode: "self-hosted",
prefix: "",
teamPrefix: null,
@@ -55,7 +55,7 @@ export class AuthGuard implements CanActivate {
algorithms: ["RS256"],
}) as jwt.JwtPayload;
(request as any).user = {
(request as unknown as Record<string, unknown>).user = {
mode: "cloud",
prefix: decoded.prefix || `users/${decoded.sub}/`,
teamPrefix: decoded.teamPrefix || null,
+1 -1
View File
@@ -39,7 +39,7 @@ export class SyncController {
constructor(private readonly syncService: SyncService) {}
private getUserContext(req: Request): UserContext {
return (req as any).user as UserContext;
return (req as unknown as Record<string, unknown>).user as UserContext;
}
@Post("stat")
+5 -5
View File
@@ -324,7 +324,7 @@ export default function Home() {
const currentUrl = await getCurrent();
if (currentUrl && currentUrl.length > 0) {
console.log("Startup URL detected:", currentUrl[0]);
void handleUrlOpen(currentUrl[0]);
handleUrlOpen(currentUrl[0]);
}
} catch (error) {
console.error("Failed to check current URL:", error);
@@ -413,13 +413,13 @@ export default function Home() {
// Listen for URL open events from the deep link handler (when app is already running)
await listen<string>("url-open-request", (event) => {
console.log("Received URL open request:", event.payload);
void handleUrlOpen(event.payload);
handleUrlOpen(event.payload);
});
// Listen for show profile selector events
await listen<string>("show-profile-selector", (event) => {
console.log("Received show profile selector request:", event.payload);
void handleUrlOpen(event.payload);
handleUrlOpen(event.payload);
});
// Listen for show create profile dialog events
@@ -437,7 +437,7 @@ export default function Home() {
// Listen for custom logo click events
const handleLogoUrlEvent = (event: CustomEvent) => {
console.log("Received logo URL event:", event.detail);
void handleUrlOpen(event.detail);
handleUrlOpen(event.detail);
};
window.addEventListener(
@@ -995,7 +995,7 @@ export default function Home() {
// Check permissions when they are initialized
useEffect(() => {
if (isInitialized) {
void checkAllPermissions();
checkAllPermissions();
}
}, [isInitialized, checkAllPermissions]);
+10 -10
View File
@@ -50,12 +50,13 @@ interface CookieCopyDialogProps {
onCopyComplete?: () => void;
}
interface SelectionState {
[domain: string]: {
type SelectionState = Record<
string,
{
allSelected: boolean;
cookies: Set<string>;
};
}
}
>;
export function CookieCopyDialog({
isOpen,
@@ -148,7 +149,7 @@ export function CookieCopyDialog({
(domain: string, cookies: UnifiedCookie[]) => {
setSelection((prev) => {
const current = prev[domain];
const allSelected = current.allSelected || false;
const allSelected = current.allSelected;
if (allSelected) {
const newSelection = { ...prev };
@@ -171,7 +172,7 @@ export function CookieCopyDialog({
const toggleCookie = useCallback(
(domain: string, cookieName: string, totalCookies: number) => {
setSelection((prev) => {
const current = prev[domain] || {
const current = prev[domain] ?? {
allSelected: false,
cookies: new Set<string>(),
};
@@ -503,8 +504,8 @@ function DomainRow({
onToggleExpand,
}: DomainRowProps) {
const domainSelection = selection[domain.domain];
const isAllSelected = domainSelection.allSelected || false;
const selectedCount = domainSelection.cookies.size || 0;
const isAllSelected = domainSelection.allSelected;
const selectedCount = domainSelection.cookies.size;
const isPartial =
selectedCount > 0 && selectedCount < domain.cookie_count && !isAllSelected;
@@ -539,8 +540,7 @@ function DomainRow({
{isExpanded && (
<div className="ml-8 pl-2 border-l space-y-1">
{domain.cookies.map((cookie) => {
const isSelected =
domainSelection.cookies.has(cookie.name) || false;
const isSelected = domainSelection.cookies.has(cookie.name);
return (
<div
key={`${domain.domain}-${cookie.name}`}
+9 -9
View File
@@ -45,12 +45,13 @@ interface CookieManagementDialogProps {
initialTab?: "import" | "export";
}
interface SelectionState {
[domain: string]: {
type SelectionState = Record<
string,
{
allSelected: boolean;
cookies: Set<string>;
};
}
}
>;
const countCookies = (content: string): number => {
const trimmed = content.trim();
@@ -329,7 +330,7 @@ export function CookieManagementDialog({
const toggleCookie = useCallback(
(domain: string, cookieName: string, totalCookies: number) => {
setExportSelection((prev) => {
const current = prev[domain] || {
const current = prev[domain] ?? {
allSelected: false,
cookies: new Set<string>(),
};
@@ -591,8 +592,8 @@ function ExportDomainRow({
onToggleExpand,
}: ExportDomainRowProps) {
const domainSelection = selection[domain.domain];
const isAllSelected = domainSelection.allSelected || false;
const selectedCount = domainSelection.cookies.size || 0;
const isAllSelected = domainSelection.allSelected;
const selectedCount = domainSelection.cookies.size;
const isPartial =
selectedCount > 0 && selectedCount < domain.cookie_count && !isAllSelected;
@@ -627,8 +628,7 @@ function ExportDomainRow({
{isExpanded && (
<div className="ml-7 pl-2 border-l space-y-0.5">
{domain.cookies.map((cookie) => {
const isSelected =
domainSelection.cookies.has(cookie.name) || false;
const isSelected = domainSelection.cookies.has(cookie.name);
return (
<div
key={`${domain.domain}-${cookie.name}`}
+15 -11
View File
@@ -672,7 +672,7 @@ export function CreateProfileDialog({
!isCreateDisabled &&
!isCreating
) {
handleCreate();
void handleCreate();
}
}}
placeholder="Enter profile name"
@@ -754,7 +754,9 @@ export function CreateProfileDialog({
})()}
</p>
<LoadingButton
onClick={() => handleDownload("wayfern")}
onClick={() => {
void handleDownload("wayfern");
}}
isLoading={isBrowserCurrentlyDownloading(
"wayfern",
)}
@@ -856,7 +858,9 @@ export function CreateProfileDialog({
})()}
</p>
<LoadingButton
onClick={() => handleDownload("camoufox")}
onClick={() => {
void handleDownload("camoufox");
}}
isLoading={isBrowserCurrentlyDownloading(
"camoufox",
)}
@@ -963,9 +967,9 @@ export function CreateProfileDialog({
})()}
</p>
<LoadingButton
onClick={() =>
handleDownload(selectedBrowser)
}
onClick={() => {
void handleDownload(selectedBrowser);
}}
isLoading={isBrowserCurrentlyDownloading(
selectedBrowser,
)}
@@ -1163,7 +1167,7 @@ export function CreateProfileDialog({
<div className="space-y-2">
<Label>{t("extensions.extensionGroup")}</Label>
<Select
value={selectedExtensionGroupId || "none"}
value={selectedExtensionGroupId ?? "none"}
onValueChange={(val) => {
setSelectedExtensionGroupId(
val === "none" ? undefined : val,
@@ -1209,7 +1213,7 @@ export function CreateProfileDialog({
!isCreateDisabled &&
!isCreating
) {
handleCreate();
void handleCreate();
}
}}
placeholder="Enter profile name"
@@ -1263,9 +1267,9 @@ export function CreateProfileDialog({
})()}
</p>
<LoadingButton
onClick={() =>
handleDownload(selectedBrowser)
}
onClick={() => {
void handleDownload(selectedBrowser);
}}
isLoading={isBrowserCurrentlyDownloading(
selectedBrowser,
)}
+6 -4
View File
@@ -363,15 +363,17 @@ export function UnifiedToast(props: ToastProps) {
</>
)}
{action &&
"onClick" in (action as any) &&
"label" in (action as any) && (
"onClick" in (action as { onClick?: () => void; label?: string }) &&
"label" in (action as { onClick?: () => void; label?: string }) && (
<div className="mt-2 w-full">
<RippleButton
size="sm"
className="ml-auto"
onClick={(action as any).onClick}
onClick={
(action as { onClick: () => void; label: string }).onClick
}
>
{(action as any).label}
{(action as { onClick: () => void; label: string }).label}
</RippleButton>
</div>
)}
+1 -1
View File
@@ -46,7 +46,7 @@ function DataTableActionBar<TData>({
}, [table]);
const portalContainer =
portalContainerProp ?? (mounted ? globalThis.document?.body : null);
portalContainerProp ?? (mounted ? globalThis.document.body : null);
if (!portalContainer) return null;
+1 -1
View File
@@ -157,7 +157,7 @@ export function GroupAssignmentDialog({
</div>
) : (
<Select
value={selectedGroupId || "default"}
value={selectedGroupId ?? "default"}
onValueChange={(value) => {
setSelectedGroupId(value === "default" ? null : value);
}}
+2 -4
View File
@@ -19,9 +19,7 @@ export interface Option {
/** Group the options by providing key. */
[key: string]: string | boolean | undefined;
}
interface GroupOption {
[key: string]: Option[];
}
type GroupOption = Record<string, Option[]>;
interface MultipleSelectorProps {
value?: Option[];
@@ -259,7 +257,7 @@ const MultipleSelector = React.forwardRef<
if (!arrayOptions || onSearch) {
return;
}
const newOption = transToGroupOption(arrayOptions || [], groupBy);
const newOption = transToGroupOption(arrayOptions, groupBy);
if (JSON.stringify(newOption) !== JSON.stringify(options)) {
setOptions(newOption);
}
+13 -5
View File
@@ -218,12 +218,12 @@ interface TableMeta {
onLaunchWithSync: (profile: BrowserProfile) => void;
}
type SyncStatusDot = {
interface SyncStatusDot {
color: string;
tooltip: string;
animate: boolean;
encrypted: boolean;
};
}
function getProfileSyncStatusDot(
profile: BrowserProfile,
@@ -1215,7 +1215,7 @@ export function ProfilesDataTable({
React.useEffect(() => {
if (!browserState.isClient) return;
let unlisten: (() => void) | undefined;
(async () => {
void (async () => {
try {
unlisten = await listen<{ id: string; is_running: boolean }>(
"profile-running-changed",
@@ -1540,7 +1540,11 @@ export function ProfilesDataTable({
// Overflow actions
onAssignProfilesToGroup,
onCloneProfile,
onCloneProfile: onCloneProfile
? (profile: BrowserProfile) => {
void onCloneProfile(profile);
}
: undefined,
onConfigureCamoufox,
onCopyCookiesToProfile,
onOpenCookieManagement,
@@ -1572,7 +1576,11 @@ export function ProfilesDataTable({
// Synchronizer
getProfileSyncInfo: getProfileSyncInfo ?? (() => undefined),
onLaunchWithSync: onLaunchWithSync ?? (() => {}),
onLaunchWithSync:
onLaunchWithSync ??
(() => {
/* empty */
}),
}),
[
t,
+1 -1
View File
@@ -147,7 +147,7 @@ export function ProfileInfoDialog({
setExtensionGroupName(null);
return;
}
(async () => {
void (async () => {
try {
const group = await invoke<{ name: string } | null>(
"get_extension_group_for_profile",
+10 -13
View File
@@ -242,9 +242,9 @@ export function SettingsDialog({
const clearCustomTheme = useCallback(() => {
const root = document.documentElement;
THEME_VARIABLES.forEach(({ key }) =>
root.style.removeProperty(key as string),
);
THEME_VARIABLES.forEach(({ key }) => {
root.style.removeProperty(key as string);
});
}, []);
const loadPermissions = useCallback(() => {
@@ -378,16 +378,13 @@ export function SettingsDialog({
// Apply or clear custom variables only on Save
if (settings.theme === "custom") {
if (
customThemeState.colors &&
Object.keys(customThemeState.colors).length > 0
) {
if (Object.keys(customThemeState.colors).length > 0) {
try {
const root = document.documentElement;
// Clear any previous custom vars first
THEME_VARIABLES.forEach(({ key }) =>
root.style.removeProperty(key as string),
);
THEME_VARIABLES.forEach(({ key }) => {
root.style.removeProperty(key as string);
});
Object.entries(customThemeState.colors).forEach(([k, v]) => {
root.style.setProperty(k, v, "important");
});
@@ -398,9 +395,9 @@ export function SettingsDialog({
} else {
try {
const root = document.documentElement;
THEME_VARIABLES.forEach(({ key }) =>
root.style.removeProperty(key as string),
);
THEME_VARIABLES.forEach(({ key }) => {
root.style.removeProperty(key as string);
});
} catch {
/* empty */
}
@@ -77,7 +77,7 @@ function ObjectEditor({
const [jsonString, setJsonString] = useState("");
useEffect(() => {
setJsonString(JSON.stringify(value || {}, null, 2));
setJsonString(JSON.stringify(value ?? {}, null, 2));
}, [value]);
const handleChange = (newValue: string) => {
@@ -144,7 +144,7 @@ export function SharedCamoufoxConfigForm({
const handleGenerateFingerprint = async () => {
if (!profileVersion) return;
const browser = profileBrowser || browserType || "camoufox";
const browser = profileBrowser ?? browserType ?? "camoufox";
setIsGeneratingFingerprint(true);
try {
const configJson = JSON.stringify(config);
@@ -917,7 +917,7 @@ export function SharedCamoufoxConfigForm({
(fingerprintConfig["webGl:parameters"] as Record<
string,
unknown
>) || {}
>) ?? {}
}
onChange={(value) => {
updateFingerprintConfig("webGl:parameters", value);
@@ -934,7 +934,7 @@ export function SharedCamoufoxConfigForm({
(fingerprintConfig["webGl2:parameters"] as Record<
string,
unknown
>) || {}
>) ?? {}
}
onChange={(value) => {
updateFingerprintConfig("webGl2:parameters", value);
@@ -951,7 +951,7 @@ export function SharedCamoufoxConfigForm({
(fingerprintConfig["webGl:shaderPrecisionFormats"] as Record<
string,
unknown
>) || {}
>) ?? {}
}
onChange={(value) => {
updateFingerprintConfig("webGl:shaderPrecisionFormats", value);
@@ -968,7 +968,7 @@ export function SharedCamoufoxConfigForm({
(fingerprintConfig["webGl2:shaderPrecisionFormats"] as Record<
string,
unknown
>) || {}
>) ?? {}
}
onChange={(value) => {
updateFingerprintConfig("webGl2:shaderPrecisionFormats", value);
+3 -1
View File
@@ -162,7 +162,9 @@ export function SyncFollowerDialog({
!selectedIds.has(profile.id),
);
}}
onKeyDown={() => {}}
onKeyDown={() => {
/* empty */
}}
role="button"
tabIndex={0}
>
+3 -1
View File
@@ -173,7 +173,9 @@ export function TrafficDetailsDialog({
};
void fetchStats();
const interval = setInterval(fetchStats, 2000);
const interval = setInterval(() => {
void fetchStats();
}, 2000);
return () => {
clearInterval(interval);
+1 -1
View File
@@ -363,7 +363,7 @@ export type ColorPickerOutputProps = ComponentProps<typeof SelectTrigger>;
const formats = ["hex", "rgb", "css", "hsl"];
export const ColorPickerOutput = ({
className,
className: _className,
...props
}: ColorPickerOutputProps) => {
const { mode, setMode } = useColorPicker();
+2 -2
View File
@@ -43,7 +43,7 @@ interface HighlightContextType<T extends string> {
}
const HighlightContext = React.createContext<
HighlightContextType<any> | undefined
HighlightContextType<string> | undefined
>(undefined);
function useHighlight<T extends string>(): HighlightContextType<T> {
@@ -419,7 +419,7 @@ function HighlightItem<T extends React.ElementType>({
const Component = as ?? "div";
const element = children as React.ReactElement<ExtendedChildProps>;
const childValue =
id ?? value ?? element.props?.["data-value"] ?? element.props?.id ?? itemId;
id ?? value ?? element.props["data-value"] ?? element.props.id ?? itemId;
const isActive = activeValue === childValue;
const isDisabled = disabled === undefined ? contextDisabled : disabled;
const itemTransition = transition ?? contextTransition;
+1 -1
View File
@@ -84,7 +84,7 @@ export function WayfernConfigForm({
try {
const configJson = JSON.stringify(config);
const result = await invoke<string>("generate_sample_fingerprint", {
browser: profileBrowser || "wayfern",
browser: profileBrowser ?? "wayfern",
version: profileVersion,
configJson,
});
+6 -13
View File
@@ -22,18 +22,15 @@ export function useAutoHeight<T extends HTMLElement = HTMLDivElement>(
const el = ref.current;
if (!el) return 0;
const base = el.getBoundingClientRect().height ?? 0;
const base = el.getBoundingClientRect().height;
let extra = 0;
if (options.includeParentBox && el.parentElement) {
const cs = getComputedStyle(el.parentElement);
const paddingY =
(parseFloat(cs.paddingTop ?? "0") ?? 0) +
(parseFloat(cs.paddingBottom ?? "0") ?? 0);
const paddingY = parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom);
const borderY =
(parseFloat(cs.borderTopWidth ?? "0") ?? 0) +
(parseFloat(cs.borderBottomWidth ?? "0") ?? 0);
parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth);
const isBorderBox = cs.boxSizing === "border-box";
if (isBorderBox) {
extra += paddingY + borderY;
@@ -42,20 +39,16 @@ export function useAutoHeight<T extends HTMLElement = HTMLDivElement>(
if (options.includeSelfBox) {
const cs = getComputedStyle(el);
const paddingY =
(parseFloat(cs.paddingTop ?? "0") ?? 0) +
(parseFloat(cs.paddingBottom ?? "0") ?? 0);
const paddingY = parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom);
const borderY =
(parseFloat(cs.borderTopWidth ?? "0") ?? 0) +
(parseFloat(cs.borderBottomWidth ?? "0") ?? 0);
parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth);
const isBorderBox = cs.boxSizing === "border-box";
if (isBorderBox) {
extra += paddingY + borderY;
}
}
const dpr =
typeof window !== "undefined" ? (window.devicePixelRatio ?? 1) : 1;
const dpr = typeof window !== "undefined" ? window.devicePixelRatio : 1;
const total = Math.ceil((base + extra) * dpr) / dpr;
return total;
+3 -1
View File
@@ -367,7 +367,9 @@ export function useBrowserDownload() {
? loadDownloadedVersions("camoufox")
: Promise.resolve([]),
]);
} catch {}
} catch {
/* empty */
}
showDownloadToast(browserName, progress.version, "completed");
setDownloadProgress(null);
}
+1 -1
View File
@@ -50,7 +50,7 @@ export function useCloudAuth(): UseCloudAuthReturn {
};
}, [loadUser]);
const requestOtp = useCallback(async (email: string): Promise<string> => {
const requestOtp = useCallback((email: string): Promise<string> => {
return invoke<string>("cloud_request_otp", { email });
}, []);
+1 -1
View File
@@ -5,7 +5,7 @@ interface CommonControlledStateProps<T> {
defaultValue?: T;
}
export function useControlledState<T, Rest extends any[] = []>(
export function useControlledState<T, Rest extends unknown[] = []>(
props: CommonControlledStateProps<T> & {
onChange?: (value: T, ...args: Rest) => void;
},
+1 -1
View File
@@ -17,7 +17,7 @@ export function useProxyEvents() {
// Load proxy usage (how many profiles are using each proxy)
const loadProxyUsage = useCallback(async () => {
try {
const profiles = await invoke<Array<{ proxy_id?: string }>>(
const profiles = await invoke<{ proxy_id?: string }[]>(
"list_browser_profiles",
);
const counts: Record<string, number> = {};
+1 -1
View File
@@ -16,7 +16,7 @@ export function useVpnEvents() {
const loadVpnUsage = useCallback(async () => {
try {
const profiles = await invoke<Array<{ vpn_id?: string }>>(
const profiles = await invoke<{ vpn_id?: string }[]>(
"list_browser_profiles",
);
const counts: Record<string, number> = {};
+23 -25
View File
@@ -524,7 +524,29 @@
"selectedCount_plural": "{{count}} cookies selected"
},
"success": "Cookies copied successfully",
"error": "Failed to copy cookies"
"error": "Failed to copy cookies",
"management": {
"title": "Cookie Management",
"menuItem": "Cookie Management"
},
"import": {
"title": "Import Cookies",
"description": "Import cookies from a Netscape or JSON format file.",
"selectFile": "Choose File",
"preview": "{{count}} cookies found",
"success": "Successfully imported {{imported}} cookies ({{replaced}} replaced)",
"error": "Failed to import cookies",
"proFeature": "Cookie import is a Pro feature"
},
"export": {
"title": "Export Cookies",
"description": "Export cookies from this profile.",
"formatLabel": "Format",
"netscape": "Netscape TXT",
"json": "JSON",
"success": "Cookies exported successfully",
"error": "Failed to export cookies"
}
},
"toasts": {
"success": {
@@ -724,30 +746,6 @@
"cannotLaunch": "This profile was created on {{os}} and is not supported on this system",
"cannotModify": "Cannot modify sync settings for a cross-OS profile"
},
"cookies": {
"management": {
"title": "Cookie Management",
"menuItem": "Cookie Management"
},
"import": {
"title": "Import Cookies",
"description": "Import cookies from a Netscape or JSON format file.",
"selectFile": "Choose File",
"preview": "{{count}} cookies found",
"success": "Successfully imported {{imported}} cookies ({{replaced}} replaced)",
"error": "Failed to import cookies",
"proFeature": "Cookie import is a Pro feature"
},
"export": {
"title": "Export Cookies",
"description": "Export cookies from this profile.",
"formatLabel": "Format",
"netscape": "Netscape TXT",
"json": "JSON",
"success": "Cookies exported successfully",
"error": "Failed to export cookies"
}
},
"profileInfo": {
"title": "Profile Details",
"tabs": {
+23 -25
View File
@@ -524,7 +524,29 @@
"selectedCount_plural": "{{count}} cookies seleccionadas"
},
"success": "Cookies copiadas exitosamente",
"error": "Error al copiar cookies"
"error": "Error al copiar cookies",
"management": {
"title": "Gestión de Cookies",
"menuItem": "Gestión de Cookies"
},
"import": {
"title": "Importar Cookies",
"description": "Importar cookies desde un archivo en formato Netscape o JSON.",
"selectFile": "Elegir Archivo",
"preview": "{{count}} cookies encontradas",
"success": "Se importaron {{imported}} cookies exitosamente ({{replaced}} reemplazadas)",
"error": "Error al importar cookies",
"proFeature": "La importación de cookies es una función Pro"
},
"export": {
"title": "Exportar Cookies",
"description": "Exportar cookies de este perfil.",
"formatLabel": "Formato",
"netscape": "Netscape TXT",
"json": "JSON",
"success": "Cookies exportadas exitosamente",
"error": "Error al exportar cookies"
}
},
"toasts": {
"success": {
@@ -724,30 +746,6 @@
"cannotLaunch": "Este perfil fue creado en {{os}} y no es compatible con este sistema",
"cannotModify": "No se pueden modificar los ajustes de sincronización de un perfil de otro sistema operativo"
},
"cookies": {
"management": {
"title": "Gestión de Cookies",
"menuItem": "Gestión de Cookies"
},
"import": {
"title": "Importar Cookies",
"description": "Importar cookies desde un archivo en formato Netscape o JSON.",
"selectFile": "Elegir Archivo",
"preview": "{{count}} cookies encontradas",
"success": "Se importaron {{imported}} cookies exitosamente ({{replaced}} reemplazadas)",
"error": "Error al importar cookies",
"proFeature": "La importación de cookies es una función Pro"
},
"export": {
"title": "Exportar Cookies",
"description": "Exportar cookies de este perfil.",
"formatLabel": "Formato",
"netscape": "Netscape TXT",
"json": "JSON",
"success": "Cookies exportadas exitosamente",
"error": "Error al exportar cookies"
}
},
"profileInfo": {
"title": "Detalles del Perfil",
"tabs": {
+23 -25
View File
@@ -524,7 +524,29 @@
"selectedCount_plural": "{{count}} cookies sélectionnés"
},
"success": "Cookies copiés avec succès",
"error": "Échec de la copie des cookies"
"error": "Échec de la copie des cookies",
"management": {
"title": "Gestion des Cookies",
"menuItem": "Gestion des Cookies"
},
"import": {
"title": "Importer des Cookies",
"description": "Importer des cookies depuis un fichier au format Netscape ou JSON.",
"selectFile": "Choisir un Fichier",
"preview": "{{count}} cookies trouvés",
"success": "{{imported}} cookies importés avec succès ({{replaced}} remplacés)",
"error": "Échec de l'importation des cookies",
"proFeature": "L'importation de cookies est une fonctionnalité Pro"
},
"export": {
"title": "Exporter les Cookies",
"description": "Exporter les cookies de ce profil.",
"formatLabel": "Format",
"netscape": "Netscape TXT",
"json": "JSON",
"success": "Cookies exportés avec succès",
"error": "Échec de l'exportation des cookies"
}
},
"toasts": {
"success": {
@@ -724,30 +746,6 @@
"cannotLaunch": "Ce profil a été créé sur {{os}} et n'est pas pris en charge sur ce système",
"cannotModify": "Impossible de modifier les paramètres de synchronisation d'un profil d'un autre système d'exploitation"
},
"cookies": {
"management": {
"title": "Gestion des Cookies",
"menuItem": "Gestion des Cookies"
},
"import": {
"title": "Importer des Cookies",
"description": "Importer des cookies depuis un fichier au format Netscape ou JSON.",
"selectFile": "Choisir un Fichier",
"preview": "{{count}} cookies trouvés",
"success": "{{imported}} cookies importés avec succès ({{replaced}} remplacés)",
"error": "Échec de l'importation des cookies",
"proFeature": "L'importation de cookies est une fonctionnalité Pro"
},
"export": {
"title": "Exporter les Cookies",
"description": "Exporter les cookies de ce profil.",
"formatLabel": "Format",
"netscape": "Netscape TXT",
"json": "JSON",
"success": "Cookies exportés avec succès",
"error": "Échec de l'exportation des cookies"
}
},
"profileInfo": {
"title": "Détails du Profil",
"tabs": {
+23 -25
View File
@@ -524,7 +524,29 @@
"selectedCount_plural": "{{count}} 個のCookieを選択"
},
"success": "Cookieが正常にコピーされました",
"error": "Cookieのコピーに失敗しました"
"error": "Cookieのコピーに失敗しました",
"management": {
"title": "Cookie管理",
"menuItem": "Cookie管理"
},
"import": {
"title": "Cookieのインポート",
"description": "NetscapeまたはJSON形式のファイルからCookieをインポートします。",
"selectFile": "ファイルを選択",
"preview": "{{count}}件のCookieが見つかりました",
"success": "{{imported}}件のCookieをインポートしました({{replaced}}件を置換)",
"error": "Cookieのインポートに失敗しました",
"proFeature": "Cookieのインポートはプロ機能です"
},
"export": {
"title": "Cookieのエクスポート",
"description": "このプロファイルからCookieをエクスポートします。",
"formatLabel": "形式",
"netscape": "Netscape TXT",
"json": "JSON",
"success": "Cookieのエクスポートに成功しました",
"error": "Cookieのエクスポートに失敗しました"
}
},
"toasts": {
"success": {
@@ -724,30 +746,6 @@
"cannotLaunch": "このプロファイルは{{os}}で作成されたもので、このシステムではサポートされていません",
"cannotModify": "他のOSのプロファイルの同期設定は変更できません"
},
"cookies": {
"management": {
"title": "Cookie管理",
"menuItem": "Cookie管理"
},
"import": {
"title": "Cookieのインポート",
"description": "NetscapeまたはJSON形式のファイルからCookieをインポートします。",
"selectFile": "ファイルを選択",
"preview": "{{count}}件のCookieが見つかりました",
"success": "{{imported}}件のCookieをインポートしました({{replaced}}件を置換)",
"error": "Cookieのインポートに失敗しました",
"proFeature": "Cookieのインポートはプロ機能です"
},
"export": {
"title": "Cookieのエクスポート",
"description": "このプロファイルからCookieをエクスポートします。",
"formatLabel": "形式",
"netscape": "Netscape TXT",
"json": "JSON",
"success": "Cookieのエクスポートに成功しました",
"error": "Cookieのエクスポートに失敗しました"
}
},
"profileInfo": {
"title": "プロフィール詳細",
"tabs": {
+23 -25
View File
@@ -524,7 +524,29 @@
"selectedCount_plural": "{{count}} cookies selecionados"
},
"success": "Cookies copiados com sucesso",
"error": "Falha ao copiar cookies"
"error": "Falha ao copiar cookies",
"management": {
"title": "Gerenciamento de Cookies",
"menuItem": "Gerenciamento de Cookies"
},
"import": {
"title": "Importar Cookies",
"description": "Importar cookies de um arquivo no formato Netscape ou JSON.",
"selectFile": "Escolher Arquivo",
"preview": "{{count}} cookies encontrados",
"success": "{{imported}} cookies importados com sucesso ({{replaced}} substituídos)",
"error": "Falha ao importar cookies",
"proFeature": "A importação de cookies é um recurso Pro"
},
"export": {
"title": "Exportar Cookies",
"description": "Exportar cookies deste perfil.",
"formatLabel": "Formato",
"netscape": "Netscape TXT",
"json": "JSON",
"success": "Cookies exportados com sucesso",
"error": "Falha ao exportar cookies"
}
},
"toasts": {
"success": {
@@ -724,30 +746,6 @@
"cannotLaunch": "Este perfil foi criado em {{os}} e não é compatível com este sistema",
"cannotModify": "Não é possível modificar as configurações de sincronização de um perfil de outro sistema operacional"
},
"cookies": {
"management": {
"title": "Gerenciamento de Cookies",
"menuItem": "Gerenciamento de Cookies"
},
"import": {
"title": "Importar Cookies",
"description": "Importar cookies de um arquivo no formato Netscape ou JSON.",
"selectFile": "Escolher Arquivo",
"preview": "{{count}} cookies encontrados",
"success": "{{imported}} cookies importados com sucesso ({{replaced}} substituídos)",
"error": "Falha ao importar cookies",
"proFeature": "A importação de cookies é um recurso Pro"
},
"export": {
"title": "Exportar Cookies",
"description": "Exportar cookies deste perfil.",
"formatLabel": "Formato",
"netscape": "Netscape TXT",
"json": "JSON",
"success": "Cookies exportados com sucesso",
"error": "Falha ao exportar cookies"
}
},
"profileInfo": {
"title": "Detalhes do Perfil",
"tabs": {
+23 -25
View File
@@ -524,7 +524,29 @@
"selectedCount_plural": "Выбрано {{count}} cookie"
},
"success": "Cookie успешно скопированы",
"error": "Ошибка копирования cookie"
"error": "Ошибка копирования cookie",
"management": {
"title": "Управление Cookies",
"menuItem": "Управление Cookies"
},
"import": {
"title": "Импорт Cookies",
"description": "Импорт cookies из файла в формате Netscape или JSON.",
"selectFile": "Выбрать файл",
"preview": "Найдено {{count}} cookies",
"success": "Успешно импортировано {{imported}} cookies ({{replaced}} заменено)",
"error": "Ошибка импорта cookies",
"proFeature": "Импорт cookies — функция Pro"
},
"export": {
"title": "Экспорт Cookies",
"description": "Экспорт cookies из этого профиля.",
"formatLabel": "Формат",
"netscape": "Netscape TXT",
"json": "JSON",
"success": "Cookies успешно экспортированы",
"error": "Ошибка экспорта cookies"
}
},
"toasts": {
"success": {
@@ -724,30 +746,6 @@
"cannotLaunch": "Этот профиль был создан на {{os}} и не поддерживается в этой системе",
"cannotModify": "Невозможно изменить настройки синхронизации профиля другой ОС"
},
"cookies": {
"management": {
"title": "Управление Cookies",
"menuItem": "Управление Cookies"
},
"import": {
"title": "Импорт Cookies",
"description": "Импорт cookies из файла в формате Netscape или JSON.",
"selectFile": "Выбрать файл",
"preview": "Найдено {{count}} cookies",
"success": "Успешно импортировано {{imported}} cookies ({{replaced}} заменено)",
"error": "Ошибка импорта cookies",
"proFeature": "Импорт cookies — функция Pro"
},
"export": {
"title": "Экспорт Cookies",
"description": "Экспорт cookies из этого профиля.",
"formatLabel": "Формат",
"netscape": "Netscape TXT",
"json": "JSON",
"success": "Cookies успешно экспортированы",
"error": "Ошибка экспорта cookies"
}
},
"profileInfo": {
"title": "Детали профиля",
"tabs": {
+23 -25
View File
@@ -524,7 +524,29 @@
"selectedCount_plural": "已选择 {{count}} 个 cookies"
},
"success": "Cookies 复制成功",
"error": "复制 cookies 失败"
"error": "复制 cookies 失败",
"management": {
"title": "Cookie 管理",
"menuItem": "Cookie 管理"
},
"import": {
"title": "导入 Cookies",
"description": "从 Netscape 或 JSON 格式文件导入 Cookies。",
"selectFile": "选择文件",
"preview": "找到 {{count}} 个 Cookies",
"success": "成功导入 {{imported}} 个 Cookies(替换了 {{replaced}} 个)",
"error": "导入 Cookies 失败",
"proFeature": "导入 Cookies 是 Pro 功能"
},
"export": {
"title": "导出 Cookies",
"description": "从此配置文件导出 Cookies。",
"formatLabel": "格式",
"netscape": "Netscape TXT",
"json": "JSON",
"success": "Cookies 导出成功",
"error": "导出 Cookies 失败"
}
},
"toasts": {
"success": {
@@ -724,30 +746,6 @@
"cannotLaunch": "此配置文件在 {{os}} 上创建,不受此系统支持",
"cannotModify": "无法修改跨操作系统配置文件的同步设置"
},
"cookies": {
"management": {
"title": "Cookie 管理",
"menuItem": "Cookie 管理"
},
"import": {
"title": "导入 Cookies",
"description": "从 Netscape 或 JSON 格式文件导入 Cookies。",
"selectFile": "选择文件",
"preview": "找到 {{count}} 个 Cookies",
"success": "成功导入 {{imported}} 个 Cookies(替换了 {{replaced}} 个)",
"error": "导入 Cookies 失败",
"proFeature": "导入 Cookies 是 Pro 功能"
},
"export": {
"title": "导出 Cookies",
"description": "从此配置文件导出 Cookies。",
"formatLabel": "格式",
"netscape": "Netscape TXT",
"json": "JSON",
"success": "Cookies 导出成功",
"error": "导出 Cookies 失败"
}
},
"profileInfo": {
"title": "配置文件详情",
"tabs": {
+2 -2
View File
@@ -341,13 +341,13 @@ export interface CamoufoxFingerprintConfig {
"canvas:aaCapOffset"?: boolean;
// Voices
voices?: Array<{
voices?: {
isLocalService?: boolean;
isDefault?: boolean;
voiceURI?: string;
name?: string;
lang?: string;
}>;
}[];
"voices:blockIfNotDefined"?: boolean;
"voices:fakeCompletion"?: boolean;
"voices:fakeCompletion:charsPerSecond"?: number;