refactor: cleanup

This commit is contained in:
zhom
2026-06-07 17:45:47 +04:00
parent 6b31c937ea
commit 15f3aa03f7
31 changed files with 762 additions and 252 deletions
+20 -5
View File
@@ -1168,11 +1168,14 @@ export default function Home() {
profileId: profile.id,
syncMode: enabling ? "Regular" : "Disabled",
});
showSuccessToast(enabling ? "Sync enabled" : "Sync disabled", {
description: enabling
? "Profile sync has been enabled"
: "Profile sync has been disabled",
});
showSuccessToast(
t(enabling ? "sync.enabledToast" : "sync.disabledToast"),
{
description: t(
enabling ? "sync.enabledDescription" : "sync.disabledDescription",
),
},
);
} catch (error) {
console.error("Failed to toggle sync:", error);
showErrorToast(t("errors.updateSyncSettingsFailed"));
@@ -1325,6 +1328,7 @@ export default function Home() {
let unlistenStarted: (() => void) | undefined;
let unlistenProgress: (() => void) | undefined;
let unlistenCompleted: (() => void) | undefined;
let unlistenWayfernBlocked: (() => void) | undefined;
void (async () => {
unlistenRequired = await listen(
@@ -1386,6 +1390,16 @@ export default function Home() {
duration: 5000,
});
});
unlistenWayfernBlocked = await listen("wayfern-paid-blocked", () => {
showToast({
id: "wayfern-paid-blocked",
type: "error",
title: t("wayfernBlocked.title"),
description: t("wayfernBlocked.description"),
duration: 15000,
});
});
})();
return () => {
@@ -1393,6 +1407,7 @@ export default function Home() {
unlistenStarted?.();
unlistenProgress?.();
unlistenCompleted?.();
unlistenWayfernBlocked?.();
};
}, [t]);
+18 -50
View File
@@ -83,12 +83,7 @@ interface ErrorToastProps extends BaseToastProps {
interface DownloadToastProps extends BaseToastProps {
type: "download";
stage?:
| "downloading"
| "extracting"
| "verifying"
| "completed"
| "downloading (twilight rolling release)";
stage?: "downloading" | "extracting" | "verifying" | "completed";
progress?: {
percentage: number;
speed?: string;
@@ -111,12 +106,6 @@ interface FetchingToastProps extends BaseToastProps {
browserName?: string;
}
interface TwilightUpdateToastProps extends BaseToastProps {
type: "twilight-update";
browserName?: string;
hasUpdate?: boolean;
}
interface SyncProgressToastProps extends BaseToastProps {
type: "sync-progress";
progress?: {
@@ -138,7 +127,6 @@ type ToastProps =
| DownloadToastProps
| VersionUpdateToastProps
| FetchingToastProps
| TwilightUpdateToastProps
| SyncProgressToastProps;
function formatBytesCompact(bytes: number): string {
@@ -191,10 +179,6 @@ function getToastIcon(type: ToastProps["type"], stage?: string) {
return (
<LuRefreshCw className="shrink-0 size-4 animate-spin text-foreground" />
);
case "twilight-update":
return (
<LuRefreshCw className="shrink-0 size-4 animate-spin text-foreground" />
);
case "sync-progress":
return (
<LuRefreshCw className="shrink-0 size-4 animate-spin text-foreground" />
@@ -246,7 +230,8 @@ export function UnifiedToast(props: ToastProps) {
<p className="flex-1 min-w-0 text-xs text-muted-foreground">
{progress.percentage.toFixed(1)}%
{progress.speed && `${progress.speed} MB/s`}
{progress.eta && `${progress.eta} remaining`}
{progress.eta &&
`${t("toasts.progress.remaining", { time: progress.eta })}`}
</p>
</div>
<div className="w-full bg-muted rounded-full h-1.5">
@@ -264,9 +249,10 @@ export function UnifiedToast(props: ToastProps) {
"current_browser" in progress && (
<div className="mt-2 space-y-1">
<p className="text-xs text-muted-foreground">
{progress.current_browser && (
<>Looking for updates for {progress.current_browser}</>
)}
{progress.current_browser &&
t("versionUpdater.toast.lookingForUpdates", {
browser: progress.current_browser,
})}
</p>
<div className="flex items-center gap-x-2">
<div className="flex-1 bg-muted rounded-full h-1.5 min-w-0">
@@ -293,7 +279,10 @@ export function UnifiedToast(props: ToastProps) {
{progress.phase === "uploading"
? t("appUpdate.toast.uploading")
: t("appUpdate.toast.downloading")}{" "}
{progress.completed_files}/{progress.total_files} files
{t("toasts.progress.filesProgress", {
completed: progress.completed_files,
total: progress.total_files,
})}
{" \u2022 "}
{formatBytesCompact(progress.completed_bytes)} /{" "}
{formatBytesCompact(progress.total_bytes)}
@@ -304,37 +293,21 @@ export function UnifiedToast(props: ToastProps) {
</>
)}
{progress.eta_seconds > 0 &&
progress.completed_files < progress.total_files && (
<>
{" \u2022 ~"}
{formatEtaCompact(progress.eta_seconds)} remaining
</>
)}
progress.completed_files < progress.total_files &&
` \u2022 ${t("toasts.progress.remaining", {
time: `~${formatEtaCompact(progress.eta_seconds)}`,
})}`}
</p>
{progress.failed_count > 0 && (
<p className="text-xs text-destructive mt-0.5">
{progress.failed_count} file(s) failed
{t("toasts.progress.filesFailed", {
count: progress.failed_count,
})}
</p>
)}
</div>
)}
{/* Twilight update progress */}
{type === "twilight-update" && (
<div className="mt-2">
<p className="text-xs text-muted-foreground">
{"hasUpdate" in props && props.hasUpdate
? "New twilight build available for download"
: "Checking for twilight updates..."}
</p>
{props.browserName && (
<p className="mt-1 text-xs text-muted-foreground">
{props.browserName} Rolling Release
</p>
)}
</div>
)}
{/* Description */}
{description && (
<p className="mt-1 text-xs leading-tight text-muted-foreground">
@@ -355,11 +328,6 @@ export function UnifiedToast(props: ToastProps) {
{t("browserDownload.toast.verifying")}
</p>
)}
{stage === "downloading (twilight rolling release)" && (
<p className="mt-1 text-xs text-muted-foreground">
{t("browserDownload.toast.downloadingRolling")}
</p>
)}
</>
)}
{action &&
+4 -4
View File
@@ -2039,12 +2039,12 @@ export function ProfilesDataTable({
if (isDisabled) {
const tooltipMessage = isRunning
? "Can't modify running profile"
? t("profiles.table.cantModifyRunning")
: isLaunching
? "Can't modify profile while launching"
? t("profiles.table.cantModifyLaunching")
: isStopping
? "Can't modify profile while stopping"
: "Can't modify profile while browser is updating";
? t("profiles.table.cantModifyStopping")
: t("profiles.table.cantModifyUpdating");
return (
<Tooltip>
+33 -17
View File
@@ -263,9 +263,9 @@ export function ProfileInfoDialog({
? vpnConfigs.find((v) => v.id === profile.vpn_id)?.name
: null;
const networkLabel = vpnName
? `VPN: ${vpnName}`
? t("profileInfo.network.vpnLabel", { name: vpnName })
: proxyName
? `Proxy: ${proxyName}`
? t("profileInfo.network.proxyLabel", { name: proxyName })
: t("profileInfo.values.none");
const syncStatus = syncStatuses[profile.id];
@@ -299,6 +299,10 @@ export function ProfileInfoDialog({
// `ProfileDnsBlocklistDialog` for the pattern). The settings tab is purely
// a navigation hub.
interface ActionItem {
// Stable, language-independent key used to map sidebar sections to actions.
// The sidebar must NOT match on `label` — labels are translated, so English
// substring matching hides sections for every non-English user.
id?: string;
icon: React.ReactNode;
label: string;
onClick: () => void;
@@ -311,6 +315,7 @@ export function ProfileInfoDialog({
const actions: ActionItem[] = [
{
id: "network",
icon: <LuGlobe className="size-4" />,
label: t("profiles.actions.viewNetwork"),
onClick: () => {
@@ -319,6 +324,7 @@ export function ProfileInfoDialog({
disabled: isCrossOs,
},
{
id: "sync",
icon: <LuRefreshCw className="size-4" />,
label: t("profiles.actions.syncSettings"),
onClick: () => {
@@ -337,6 +343,7 @@ export function ProfileInfoDialog({
runningBadge: isRunning,
},
{
id: "fingerprint",
icon: <LuFingerprint className="size-4" />,
label: t("profiles.actions.changeFingerprint"),
onClick: () => {
@@ -359,6 +366,7 @@ export function ProfileInfoDialog({
hidden: profile.browser !== "wayfern" || !onLaunchWithSync,
},
{
id: "cookiesCopy",
icon: <LuCopy className="size-4" />,
label: t("profiles.actions.copyCookiesToProfile"),
onClick: () => {
@@ -372,6 +380,7 @@ export function ProfileInfoDialog({
!onCopyCookiesToProfile,
},
{
id: "cookiesManage",
icon: <LuCookie className="size-4" />,
label: t("profileInfo.actions.manageCookies"),
onClick: () => {
@@ -395,6 +404,7 @@ export function ProfileInfoDialog({
hidden: profile.ephemeral === true,
},
{
id: "extension",
icon: <LuPuzzle className="size-4" />,
label: t("profileInfo.actions.assignExtensionGroup"),
onClick: () => {
@@ -419,6 +429,7 @@ export function ProfileInfoDialog({
},
},
{
id: "hook",
icon: <LuLink className="size-4" />,
label: t("profiles.actions.launchHook"),
onClick: () => {
@@ -461,6 +472,7 @@ export function ProfileInfoDialog({
destructive: true,
},
{
id: "delete",
icon: <LuTrash2 className="size-4" />,
label: t("profiles.actions.delete"),
onClick: () => {
@@ -534,6 +546,7 @@ interface ProfileInfoLayoutProps {
onCloneProfile?: (profile: BrowserProfile) => void;
onKillProfile?: (profile: BrowserProfile) => void;
visibleActions: {
id?: string;
icon: React.ReactNode;
label: string;
onClick: () => void;
@@ -579,22 +592,23 @@ function ProfileInfoLayout({
}: ProfileInfoLayoutProps) {
const [section, setSection] = React.useState<ProfileSection>("overview");
// Map sidebar items to existing action labels, so clicking a section
// simply triggers the existing dialog handler.
// Map sidebar items to existing actions by their stable, language-independent
// `id`, so clicking a section triggers the existing dialog handler. Matching
// on `label` would break for every non-English locale (the labels are
// translated) and hide whole sections.
const findAction = React.useCallback(
(substr: string) =>
visibleActions.find((a) => a.label.toLowerCase().includes(substr)),
(id: string) => visibleActions.find((a) => a.id === id),
[visibleActions],
);
const deleteAction = findAction("delete");
const fingerprintAction = findAction("fingerprint");
const cookiesManageAction = findAction("manage cookies");
const cookiesCopyAction = findAction("copy cookies");
const cookiesManageAction = findAction("cookiesManage");
const cookiesCopyAction = findAction("cookiesCopy");
const cookiesAction = cookiesManageAction ?? cookiesCopyAction;
const extensionAction = findAction("extension");
const syncAction = findAction("sync");
const _launchHookAction = findAction("hook") ?? findAction("launch hook");
const _launchHookAction = findAction("hook");
const _networkAction = findAction("network");
// Password actions are no longer routed via the legacy action handlers —
// SecuritySectionInline writes directly to the backend instead.
@@ -1149,7 +1163,7 @@ function SyncSectionInline({
syncMode: mode,
});
} catch (e) {
setError(String(e));
setError(translateBackendError(t as never, e));
} finally {
setIsSaving(false);
}
@@ -1192,7 +1206,9 @@ function SyncSectionInline({
<p className="text-[10px] uppercase tracking-wide text-muted-foreground">
{t("profileInfo.fields.syncStatus")}
</p>
<p className="text-sm mt-0.5">{syncStatus.status}</p>
<p className="text-sm mt-0.5">
{t(`profileInfo.syncStatusValue.${syncStatus.status}`)}
</p>
{syncStatus.error && (
<p className="text-xs text-destructive mt-1">{syncStatus.error}</p>
)}
@@ -1246,7 +1262,7 @@ function NetworkSectionInline({
setProxyId(nextId);
if (nextId !== null) setVpnId(null);
} catch (e) {
setError(String(e));
setError(translateBackendError(t as never, e));
} finally {
setIsSaving(false);
}
@@ -1264,7 +1280,7 @@ function NetworkSectionInline({
setVpnId(nextId);
if (nextId !== null) setProxyId(null);
} catch (e) {
setError(String(e));
setError(translateBackendError(t as never, e));
} finally {
setIsSaving(false);
}
@@ -1370,7 +1386,7 @@ function ExtensionsSectionInline({
);
if (mounted) setGroups(data);
} catch (e) {
if (mounted) setError(String(e));
if (mounted) setError(translateBackendError(t as never, e));
}
};
void load();
@@ -1384,7 +1400,7 @@ function ExtensionsSectionInline({
mounted = false;
unlisten?.();
};
}, []);
}, [t]);
const onChange = async (value: string) => {
const next = value === "__none__" ? null : value;
@@ -1397,7 +1413,7 @@ function ExtensionsSectionInline({
});
setGroupId(next);
} catch (e) {
setError(String(e));
setError(translateBackendError(t as never, e));
} finally {
setIsSaving(false);
}
@@ -1684,7 +1700,7 @@ function FingerprintSectionInline({
// Close the dialog once the fingerprint is saved.
onSaved();
} catch (e) {
setError(String(e));
setError(translateBackendError(t as never, e));
} finally {
setIsSaving(false);
}
+1 -1
View File
@@ -203,7 +203,7 @@ export function ProfilePasswordDialog({
<div className="flex flex-col gap-3">
{(mode === "set" || mode === "change") && (
<div className="rounded-md border border-warning/50 bg-warning/10 p-3 text-sm">
<p className="font-medium text-warning-foreground">
<p className="font-medium text-warning">
{t("profilePassword.warnings.forgetWarningTitle")}
</p>
<p className="mt-1 text-xs text-muted-foreground">
+30 -4
View File
@@ -239,7 +239,11 @@
"extDefault": "Default",
"dnsLevel": "DNS blocklist: {{level}}",
"extSearch": "Search groups…",
"extEmpty": "No extension groups"
"extEmpty": "No extension groups",
"cantModifyRunning": "Can't modify running profile",
"cantModifyLaunching": "Can't modify profile while launching",
"cantModifyStopping": "Can't modify profile while stopping",
"cantModifyUpdating": "Can't modify profile while browser is updating"
},
"actions": {
"launch": "Launch",
@@ -639,7 +643,11 @@
"profileSynced": "Profile '{{name}}' synced successfully",
"profileSyncFailed": "Failed to sync profile '{{name}}'",
"profileSyncFailedWithError": "Failed to sync profile '{{name}}': {{error}}"
}
},
"enabledToast": "Sync enabled",
"disabledToast": "Sync disabled",
"enabledDescription": "Profile sync has been enabled",
"disabledDescription": "Profile sync has been disabled"
},
"integrations": {
"title": "Integrations",
@@ -918,6 +926,11 @@
"syncingProfile": "Syncing profile '{{name}}'...",
"syncingProfileWithProgress": "{{count}} files ({{size}})",
"updatingVersions": "Updating browser versions..."
},
"progress": {
"remaining": "{{time}} remaining",
"filesProgress": "{{completed}}/{{total}} files",
"filesFailed": "{{count}} file(s) failed"
}
},
"errors": {
@@ -1136,7 +1149,9 @@
"addRule": "Add Rule",
"rulePlaceholder": "e.g. example.com, 192.168.1.*, .*\\.local",
"noRules": "No bypass rules configured.",
"ruleTypes": "Supports hostnames, IP addresses, and regex patterns."
"ruleTypes": "Supports hostnames, IP addresses, and regex patterns.",
"vpnLabel": "VPN: {{name}}",
"proxyLabel": "Proxy: {{name}}"
},
"launchHook": {
"title": "Launch Hook URL",
@@ -1198,6 +1213,12 @@
"notSupported": "Fingerprint editing is only available for Camoufox and Wayfern profiles.",
"lockedTitle": "Viewing & editing the fingerprint is a Pro feature",
"lockedDescription": "Fingerprint protection is included on every plan. Viewing and editing a profile's fingerprint values is what requires an active paid plan."
},
"syncStatusValue": {
"waiting": "Waiting",
"syncing": "Syncing",
"synced": "Synced",
"error": "Error"
}
},
"extensions": {
@@ -1718,7 +1739,8 @@
"updateStartedDescription": "Version {{version}} download will begin shortly. Browser launch is disabled until update completes.",
"downloadStarting": "Starting {{browser}} {{version}} download",
"downloadProgressBelow": "Download progress will be shown below...",
"autoDownloadStarted": "Downloading {{browser}} {{version}} automatically. Progress will be shown below."
"autoDownloadStarted": "Downloading {{browser}} {{version}} automatically. Progress will be shown below.",
"lookingForUpdates": "Looking for updates for {{browser}}"
}
},
"profilePassword": {
@@ -2017,5 +2039,9 @@
"trialBadge": "2 weeks free",
"commercialDesc": "Free for a 2-week evaluation. After that, a paid plan keeps the project maintained and thriving."
}
},
"wayfernBlocked": {
"title": "Browser automation paused",
"description": "Your account was temporarily restricted from Pro browser features, usually from signing in on multiple devices at once. Sign out of other devices, then relaunch the profile to restore it."
}
}
+30 -4
View File
@@ -239,7 +239,11 @@
"extDefault": "Predet.",
"dnsLevel": "Lista DNS: {{level}}",
"extSearch": "Buscar grupos…",
"extEmpty": "Sin grupos de extensiones"
"extEmpty": "Sin grupos de extensiones",
"cantModifyRunning": "No se puede modificar un perfil en ejecución",
"cantModifyLaunching": "No se puede modificar el perfil mientras se inicia",
"cantModifyStopping": "No se puede modificar el perfil mientras se detiene",
"cantModifyUpdating": "No se puede modificar el perfil mientras se actualiza el navegador"
},
"actions": {
"launch": "Iniciar",
@@ -639,7 +643,11 @@
"profileSynced": "Perfil '{{name}}' sincronizado correctamente",
"profileSyncFailed": "Error al sincronizar el perfil '{{name}}'",
"profileSyncFailedWithError": "Error al sincronizar el perfil '{{name}}': {{error}}"
}
},
"enabledToast": "Sincronización activada",
"disabledToast": "Sincronización desactivada",
"enabledDescription": "Se ha activado la sincronización del perfil",
"disabledDescription": "Se ha desactivado la sincronización del perfil"
},
"integrations": {
"title": "Integraciones",
@@ -918,6 +926,11 @@
"syncingProfile": "Sincronizando perfil '{{name}}'...",
"syncingProfileWithProgress": "{{count}} archivos ({{size}})",
"updatingVersions": "Actualizando versiones de navegadores..."
},
"progress": {
"remaining": "{{time}} restante",
"filesProgress": "{{completed}}/{{total}} archivos",
"filesFailed": "{{count}} archivo(s) con error"
}
},
"errors": {
@@ -1136,7 +1149,9 @@
"addRule": "Agregar Regla",
"rulePlaceholder": "ej. example.com, 192.168.1.*, .*\\.local",
"noRules": "No hay reglas de omisión configuradas.",
"ruleTypes": "Soporta nombres de host, direcciones IP y patrones regex."
"ruleTypes": "Soporta nombres de host, direcciones IP y patrones regex.",
"vpnLabel": "VPN: {{name}}",
"proxyLabel": "Proxy: {{name}}"
},
"launchHook": {
"title": "URL del hook de inicio",
@@ -1198,6 +1213,12 @@
"notSupported": "La edición de huellas digitales solo está disponible para perfiles Camoufox y Wayfern.",
"lockedTitle": "Ver y editar la huella digital es una función Pro",
"lockedDescription": "La protección de huella digital está incluida en todos los planes. Ver y editar los valores de la huella digital de un perfil es lo que requiere un plan de pago activo."
},
"syncStatusValue": {
"waiting": "Esperando",
"syncing": "Sincronizando",
"synced": "Sincronizado",
"error": "Error"
}
},
"extensions": {
@@ -1718,7 +1739,8 @@
"updateStartedDescription": "La descarga de la versión {{version}} comenzará en breve. El inicio del navegador está deshabilitado hasta que finalice la actualización.",
"downloadStarting": "Iniciando la descarga de {{browser}} {{version}}",
"downloadProgressBelow": "El progreso de la descarga se mostrará a continuación...",
"autoDownloadStarted": "Descargando {{browser}} {{version}} automáticamente. El progreso se mostrará a continuación."
"autoDownloadStarted": "Descargando {{browser}} {{version}} automáticamente. El progreso se mostrará a continuación.",
"lookingForUpdates": "Buscando actualizaciones de {{browser}}"
}
},
"profilePassword": {
@@ -2017,5 +2039,9 @@
"trialBadge": "2 semanas gratis",
"commercialDesc": "Gratis durante una evaluación de 2 semanas. Después, un plan de pago mantiene el proyecto en buen estado y próspero."
}
},
"wayfernBlocked": {
"title": "Automatización del navegador en pausa",
"description": "Tu cuenta fue restringida temporalmente de las funciones Pro del navegador, normalmente por iniciar sesión en varios dispositivos a la vez. Cierra sesión en los demás dispositivos y vuelve a iniciar el perfil para restaurarla."
}
}
+30 -4
View File
@@ -239,7 +239,11 @@
"extDefault": "Défaut",
"dnsLevel": "Liste DNS : {{level}}",
"extSearch": "Rechercher des groupes…",
"extEmpty": "Aucun groupe dextensions"
"extEmpty": "Aucun groupe dextensions",
"cantModifyRunning": "Impossible de modifier un profil en cours d'exécution",
"cantModifyLaunching": "Impossible de modifier le profil pendant le lancement",
"cantModifyStopping": "Impossible de modifier le profil pendant l'arrêt",
"cantModifyUpdating": "Impossible de modifier le profil pendant la mise à jour du navigateur"
},
"actions": {
"launch": "Lancer",
@@ -639,7 +643,11 @@
"profileSynced": "Profil '{{name}}' synchronisé avec succès",
"profileSyncFailed": "Échec de la synchronisation du profil '{{name}}'",
"profileSyncFailedWithError": "Échec de la synchronisation du profil '{{name}}' : {{error}}"
}
},
"enabledToast": "Synchronisation activée",
"disabledToast": "Synchronisation désactivée",
"enabledDescription": "La synchronisation du profil a été activée",
"disabledDescription": "La synchronisation du profil a été désactivée"
},
"integrations": {
"title": "Intégrations",
@@ -918,6 +926,11 @@
"syncingProfile": "Synchronisation du profil '{{name}}'...",
"syncingProfileWithProgress": "{{count}} fichiers ({{size}})",
"updatingVersions": "Mise à jour des versions de navigateurs..."
},
"progress": {
"remaining": "{{time}} restant",
"filesProgress": "{{completed}}/{{total}} fichiers",
"filesFailed": "Échec de {{count}} fichier(s)"
}
},
"errors": {
@@ -1136,7 +1149,9 @@
"addRule": "Ajouter une Règle",
"rulePlaceholder": "ex. example.com, 192.168.1.*, .*\\.local",
"noRules": "Aucune règle de contournement configurée.",
"ruleTypes": "Prend en charge les noms d'hôte, les adresses IP et les expressions régulières."
"ruleTypes": "Prend en charge les noms d'hôte, les adresses IP et les expressions régulières.",
"vpnLabel": "VPN : {{name}}",
"proxyLabel": "Proxy : {{name}}"
},
"launchHook": {
"title": "URL du hook de lancement",
@@ -1198,6 +1213,12 @@
"notSupported": "L’édition des empreintes nest disponible que pour les profils Camoufox et Wayfern.",
"lockedTitle": "Afficher et modifier l'empreinte est une fonctionnalité Pro",
"lockedDescription": "La protection contre le fingerprinting est incluse dans tous les forfaits. C'est l'affichage et la modification des valeurs de l'empreinte d'un profil qui nécessitent un forfait payant actif."
},
"syncStatusValue": {
"waiting": "En attente",
"syncing": "Synchronisation",
"synced": "Synchronisé",
"error": "Erreur"
}
},
"extensions": {
@@ -1718,7 +1739,8 @@
"updateStartedDescription": "Le téléchargement de la version {{version}} va bientôt commencer. Le lancement du navigateur est désactivé jusqu'à la fin de la mise à jour.",
"downloadStarting": "Démarrage du téléchargement de {{browser}} {{version}}",
"downloadProgressBelow": "La progression du téléchargement sera affichée ci-dessous...",
"autoDownloadStarted": "Téléchargement automatique de {{browser}} {{version}}. La progression sera affichée ci-dessous."
"autoDownloadStarted": "Téléchargement automatique de {{browser}} {{version}}. La progression sera affichée ci-dessous.",
"lookingForUpdates": "Recherche de mises à jour pour {{browser}}"
}
},
"profilePassword": {
@@ -2017,5 +2039,9 @@
"trialBadge": "2 semaines gratuites",
"commercialDesc": "Gratuit pendant une évaluation de 2 semaines. Ensuite, un forfait payant permet de maintenir et de faire prospérer le projet."
}
},
"wayfernBlocked": {
"title": "Automatisation du navigateur en pause",
"description": "Votre compte a été temporairement privé des fonctionnalités Pro du navigateur, généralement à cause d'une connexion sur plusieurs appareils à la fois. Déconnectez-vous des autres appareils, puis relancez le profil pour la rétablir."
}
}
+30 -4
View File
@@ -239,7 +239,11 @@
"extDefault": "既定",
"dnsLevel": "DNS ブロックリスト: {{level}}",
"extSearch": "グループを検索…",
"extEmpty": "拡張機能グループがありません"
"extEmpty": "拡張機能グループがありません",
"cantModifyRunning": "実行中のプロファイルは変更できません",
"cantModifyLaunching": "起動中はプロファイルを変更できません",
"cantModifyStopping": "停止中はプロファイルを変更できません",
"cantModifyUpdating": "ブラウザの更新中はプロファイルを変更できません"
},
"actions": {
"launch": "起動",
@@ -639,7 +643,11 @@
"profileSynced": "プロファイル '{{name}}' を同期しました",
"profileSyncFailed": "プロファイル '{{name}}' の同期に失敗しました",
"profileSyncFailedWithError": "プロファイル '{{name}}' の同期に失敗しました: {{error}}"
}
},
"enabledToast": "同期を有効化しました",
"disabledToast": "同期を無効化しました",
"enabledDescription": "プロファイルの同期が有効になりました",
"disabledDescription": "プロファイルの同期が無効になりました"
},
"integrations": {
"title": "統合",
@@ -918,6 +926,11 @@
"syncingProfile": "プロファイル '{{name}}' を同期中...",
"syncingProfileWithProgress": "{{count}} ファイル ({{size}})",
"updatingVersions": "ブラウザバージョンを更新中..."
},
"progress": {
"remaining": "残り {{time}}",
"filesProgress": "{{completed}}/{{total}} ファイル",
"filesFailed": "{{count}} 件のファイルが失敗しました"
}
},
"errors": {
@@ -1136,7 +1149,9 @@
"addRule": "ルールを追加",
"rulePlaceholder": "例: example.com, 192.168.1.*, .*\\.local",
"noRules": "バイパスルールは設定されていません。",
"ruleTypes": "ホスト名、IPアドレス、正規表現パターンをサポートしています。"
"ruleTypes": "ホスト名、IPアドレス、正規表現パターンをサポートしています。",
"vpnLabel": "VPN: {{name}}",
"proxyLabel": "Proxy: {{name}}"
},
"launchHook": {
"title": "起動フックURL",
@@ -1198,6 +1213,12 @@
"notSupported": "フィンガープリント編集は Camoufox / Wayfern プロファイルでのみ利用できます。",
"lockedTitle": "フィンガープリントの表示と編集は Pro 機能です",
"lockedDescription": "フィンガープリント保護はすべてのプランに含まれています。プロファイルのフィンガープリントの値を表示・編集するには、有効な有料プランが必要です。"
},
"syncStatusValue": {
"waiting": "待機中",
"syncing": "同期中",
"synced": "同期済み",
"error": "エラー"
}
},
"extensions": {
@@ -1718,7 +1739,8 @@
"updateStartedDescription": "バージョン {{version}} のダウンロードがまもなく開始されます。更新が完了するまでブラウザの起動は無効になります。",
"downloadStarting": "{{browser}} {{version}} のダウンロードを開始しています",
"downloadProgressBelow": "ダウンロードの進行状況は下に表示されます...",
"autoDownloadStarted": "{{browser}} {{version}} を自動的にダウンロードしています。進行状況は下に表示されます。"
"autoDownloadStarted": "{{browser}} {{version}} を自動的にダウンロードしています。進行状況は下に表示されます。",
"lookingForUpdates": "{{browser}} の更新を確認しています"
}
},
"profilePassword": {
@@ -2017,5 +2039,9 @@
"trialBadge": "2週間無料",
"commercialDesc": "2週間の評価期間は無料です。その後は有料プランが必要で、これによりプロジェクトの維持と発展が支えられます。"
}
},
"wayfernBlocked": {
"title": "ブラウザの自動化が一時停止しました",
"description": "通常は複数のデバイスで同時にサインインしたことが原因で、アカウントのProブラウザ機能が一時的に制限されました。他のデバイスからサインアウトし、プロファイルを再起動すると復元されます。"
}
}
+47 -21
View File
@@ -131,12 +131,12 @@
"title": "기본 브라우저",
"setAsDefault": "기본 브라우저로 설정",
"alreadyDefault": "이미 기본 브라우저입니다",
"description": "기본 브라우저로 설정하면 도넛 브라우저가 웹 링크를 처리하고 사용할 프로필을 선택할 수 있습니다."
"description": "기본 브라우저로 설정하면 Donut Browser가 웹 링크를 처리하고 사용할 프로필을 선택할 수 있습니다."
},
"permissions": {
"title": "시스템 권한",
"loading": "권한 불러오는 중...",
"description": "이 권한은 도넛 브라우저에서 실행된 브라우저가 시스템 리소스에 액세스할 수 있도록 합니다. 각 웹사이트는 여전히 개별적으로 권한을 요청합니다.",
"description": "이 권한은 Donut Browser에서 실행된 브라우저가 시스템 리소스에 액세스할 수 있도록 합니다. 각 웹사이트는 여전히 개별적으로 권한을 요청합니다.",
"microphone": "마이크",
"microphoneDescription": "브라우저 애플리케이션의 마이크 액세스",
"camera": "카메라",
@@ -180,7 +180,7 @@
"trialExpired": "체험판이 만료되었습니다",
"trialExpiredDescription": "개인 사용은 무료로 유지됩니다. 상업적 사용에는 라이선스가 필요합니다.",
"subscriptionActive": "구독 중 — {{plan}} 플랜",
"subscriptionActiveDescription": "도넛 브라우저 구독이 활성 상태입니다. 플랜 기간 동안 상업적 사용이 라이선스됩니다."
"subscriptionActiveDescription": "Donut Browser 구독이 활성 상태입니다. 플랜 기간 동안 상업적 사용이 라이선스됩니다."
},
"advanced": {
"title": "고급",
@@ -193,7 +193,7 @@
"copyLogsDescription": "최신 로그 파일(최대 5MB)을 클립보드에 묶어 버그 보고서에서 공유할 수 있도록 합니다."
},
"disableAutoUpdates": "앱 자동 업데이트 사용 안 함",
"disableAutoUpdatesDescription": "도넛 브라우저 업데이트를 앱이 자동으로 확인하고 설치하지 않도록 합니다. 브라우저 업데이트는 영향을 받지 않습니다.",
"disableAutoUpdatesDescription": "Donut Browser 업데이트를 앱이 자동으로 확인하고 설치하지 않도록 합니다. 브라우저 업데이트는 영향을 받지 않습니다.",
"keepDecryptedProfilesInRam": "복호화된 프로필을 RAM에 유지",
"keepDecryptedProfilesInRamDescription": "비밀번호로 보호된 프로필의 복호화된 RAM 사본을 실행 사이에 유지하여 시작 속도를 높입니다. 디스크의 사본은 그대로 암호화된 상태로 유지됩니다."
},
@@ -212,7 +212,7 @@
"extensions": "확장 프로그램"
},
"newProfile": "새로 만들기",
"donutLogo": "도넛 브라우저 로고",
"donutLogo": "Donut Browser 로고",
"scrollGroupsLeft": "그룹 왼쪽으로 스크롤",
"scrollGroupsRight": "그룹 오른쪽으로 스크롤"
},
@@ -239,7 +239,11 @@
"extDefault": "기본값",
"dnsLevel": "DNS 차단 목록: {{level}}",
"extSearch": "그룹 검색…",
"extEmpty": "확장 프로그램 그룹이 없습니다"
"extEmpty": "확장 프로그램 그룹이 없습니다",
"cantModifyRunning": "실행 중인 프로필은 수정할 수 없습니다",
"cantModifyLaunching": "실행하는 동안 프로필을 수정할 수 없습니다",
"cantModifyStopping": "중지하는 동안 프로필을 수정할 수 없습니다",
"cantModifyUpdating": "브라우저 업데이트 중에는 프로필을 수정할 수 없습니다"
},
"actions": {
"launch": "실행",
@@ -639,7 +643,11 @@
"profileSynced": "프로필 '{{name}}'이(가) 동기화되었습니다",
"profileSyncFailed": "프로필 '{{name}}' 동기화 실패",
"profileSyncFailedWithError": "프로필 '{{name}}' 동기화 실패: {{error}}"
}
},
"enabledToast": "동기화 사용",
"disabledToast": "동기화 사용 안 함",
"enabledDescription": "프로필 동기화가 활성화되었습니다",
"disabledDescription": "프로필 동기화가 비활성화되었습니다"
},
"integrations": {
"title": "통합",
@@ -918,6 +926,11 @@
"syncingProfile": "프로필 '{{name}}' 동기화 중...",
"syncingProfileWithProgress": "{{count}}개 파일 ({{size}})",
"updatingVersions": "브라우저 버전 업데이트 중..."
},
"progress": {
"remaining": "{{time}} 남음",
"filesProgress": "{{completed}}/{{total}} 파일",
"filesFailed": "{{count}}개 파일 실패"
}
},
"errors": {
@@ -1136,12 +1149,14 @@
"addRule": "규칙 추가",
"rulePlaceholder": "예: example.com, 192.168.1.*, .*\\.local",
"noRules": "구성된 우회 규칙이 없습니다.",
"ruleTypes": "호스트 이름, IP 주소 및 정규식 패턴을 지원합니다."
"ruleTypes": "호스트 이름, IP 주소 및 정규식 패턴을 지원합니다.",
"vpnLabel": "VPN: {{name}}",
"proxyLabel": "Proxy: {{name}}"
},
"launchHook": {
"title": "실행 후크 URL",
"label": "실행 후크 URL",
"description": "도넛 브라우저는 프로필이 실행될 때마다 이 URL로 GET 요청을 보냅니다.",
"description": "Donut Browser는 프로필이 실행될 때마다 이 URL로 GET 요청을 보냅니다.",
"placeholder": "https://example.com/hooks/profile-launch",
"invalidUrlHint": "유효한 http:// 또는 https:// URL을 입력하세요."
},
@@ -1198,6 +1213,12 @@
"notSupported": "핑거프린트 편집은 Camoufox 및 Wayfern 프로필에서만 사용할 수 있습니다.",
"lockedTitle": "핑거프린트 보기 및 편집은 Pro 기능입니다",
"lockedDescription": "핑거프린트 보호는 모든 요금제에 포함되어 있습니다. 프로필의 핑거프린트 값을 보고 편집하려면 활성 유료 요금제가 필요합니다."
},
"syncStatusValue": {
"waiting": "대기 중",
"syncing": "동기화 중",
"synced": "동기화됨",
"error": "오류"
}
},
"extensions": {
@@ -1535,7 +1556,7 @@
},
"wayfernTerms": {
"title": "Wayfern 이용 약관",
"description": "도넛 브라우저를 사용하기 전에 Wayfern의 이용 약관을 읽고 동의해야 합니다.",
"description": "Donut Browser를 사용하기 전에 Wayfern의 이용 약관을 읽고 동의해야 합니다.",
"reviewLabel": "다음 위치에서 이용 약관을 검토하세요:",
"agreeNotice": "\"동의함\"을 클릭하면 이 약관에 동의하는 것입니다.",
"acceptButton": "동의함",
@@ -1546,7 +1567,7 @@
"commercialTrial": {
"title": "상업용 체험판 만료됨",
"description": "2주 상업용 체험판 기간이 종료되었습니다.",
"body": "도넛 브라우저를 비즈니스 용도로 사용하는 경우 계속 사용하려면 상업용 라이선스를 구매해야 합니다. 개인 용도로는 계속 무료로 사용할 수 있습니다.",
"body": "Donut Browser를 비즈니스 용도로 사용하는 경우 계속 사용하려면 상업용 라이선스를 구매해야 합니다. 개인 용도로는 계속 무료로 사용할 수 있습니다.",
"understandButton": "이해했습니다",
"failed": "확인 저장 실패",
"tryAgain": "다시 시도하세요"
@@ -1554,10 +1575,10 @@
"permissionDialog": {
"titleMicrophone": "마이크 액세스가 필요합니다",
"titleCamera": "카메라 액세스가 필요합니다",
"descMicrophone": "도넛 브라우저는 웹 브라우저에서 마이크 기능을 활성화하기 위해 마이크에 액세스해야 합니다. 마이크를 사용하려는 각 웹사이트는 여전히 개별적으로 권한을 요청합니다.",
"descCamera": "도넛 브라우저는 웹 브라우저에서 카메라 기능을 활성화하기 위해 카메라에 액세스해야 합니다. 카메라를 사용하려는 각 웹사이트는 여전히 개별적으로 권한을 요청합니다.",
"grantedMicrophone": "권한이 허용되었습니다! 이제 도넛 브라우저에서 실행된 브라우저가 마이크에 액세스할 수 있습니다.",
"grantedCamera": "권한이 허용되었습니다! 이제 도넛 브라우저에서 실행된 브라우저가 카메라에 액세스할 수 있습니다.",
"descMicrophone": "Donut Browser는 웹 브라우저에서 마이크 기능을 활성화하기 위해 마이크에 액세스해야 합니다. 마이크를 사용하려는 각 웹사이트는 여전히 개별적으로 권한을 요청합니다.",
"descCamera": "Donut Browser는 웹 브라우저에서 카메라 기능을 활성화하기 위해 카메라에 액세스해야 합니다. 카메라를 사용하려는 각 웹사이트는 여전히 개별적으로 권한을 요청합니다.",
"grantedMicrophone": "권한이 허용되었습니다! 이제 Donut Browser에서 실행된 브라우저가 마이크에 액세스할 수 있습니다.",
"grantedCamera": "권한이 허용되었습니다! 이제 Donut Browser에서 실행된 브라우저가 카메라에 액세스할 수 있습니다.",
"notGrantedMicrophone": "권한이 허용되지 않았습니다. 아래 버튼을 클릭하여 마이크에 대한 액세스를 요청하세요.",
"notGrantedCamera": "권한이 허용되지 않았습니다. 아래 버튼을 클릭하여 카메라에 대한 액세스를 요청하세요.",
"doneButton": "완료",
@@ -1670,7 +1691,7 @@
},
"appUpdate": {
"toast": {
"updateFailed": "도넛 브라우저 업데이트 실패",
"updateFailed": "Donut Browser 업데이트 실패",
"restartFailed": "재시작 실패",
"updateReady": "업데이트 준비됨, 적용하려면 재시작하세요",
"manualDownloadRequired": "수동 다운로드 필요",
@@ -1718,7 +1739,8 @@
"updateStartedDescription": "버전 {{version}} 다운로드가 곧 시작됩니다. 업데이트가 완료될 때까지 브라우저 실행이 비활성화됩니다.",
"downloadStarting": "{{browser}} {{version}} 다운로드를 시작하는 중",
"downloadProgressBelow": "다운로드 진행 상황이 아래에 표시됩니다...",
"autoDownloadStarted": "{{browser}} {{version}}을(를) 자동으로 다운로드하는 중입니다. 진행 상황이 아래에 표시됩니다."
"autoDownloadStarted": "{{browser}} {{version}}을(를) 자동으로 다운로드하는 중입니다. 진행 상황이 아래에 표시됩니다.",
"lookingForUpdates": "{{browser}} 업데이트를 확인하는 중"
}
},
"profilePassword": {
@@ -1762,7 +1784,7 @@
},
"warnings": {
"forgetWarningTitle": "중요: 이 비밀번호는 복구할 수 없습니다",
"forgetWarningBody": "도넛 브라우저는 이 비밀번호를 재설정, 복구 또는 우회할 수 없습니다. 잊어버리면 이 프로필의 데이터에 영구적으로 액세스할 수 없게 됩니다."
"forgetWarningBody": "Donut Browser는 이 비밀번호를 재설정, 복구 또는 우회할 수 없습니다. 잊어버리면 이 프로필의 데이터에 영구적으로 액세스할 수 없게 됩니다."
},
"modes": {
"set": "설정",
@@ -1806,7 +1828,7 @@
"invalidLaunchHookUrl": "잘못된 실행 후크 URL입니다. 전체 http:// 또는 https:// URL을 사용하세요.",
"cookieDbLocked": "쿠키를 읽을 수 없습니다 — 데이터베이스가 잠겨 있습니다. 브라우저를 닫고 다시 시도하세요.",
"cookieDbUnavailable": "쿠키를 읽을 수 없습니다 — 쿠키 저장소를 사용할 수 없습니다.",
"selfHostedRequiresLogout": "자체 호스팅 서버를 구성하기 전에 도넛 계정에서 로그아웃하세요.",
"selfHostedRequiresLogout": "자체 호스팅 서버를 구성하기 전에 Donut 계정에서 로그아웃하세요.",
"fingerprintRequiresPro": "핑거프린트를 보거나 편집하려면 활성 유료 요금제가 필요합니다. 보호 기능은 모든 요금제에 포함되어 있습니다.",
"proxyNotWorking": "선택한 프록시가 작동하지 않아 프로필이 생성되지 않았습니다.",
"proxyPaymentRequired": "선택한 프록시는 결제가 필요합니다(402). 구독이 만료되었을 수 있어 프로필이 생성되지 않았습니다.",
@@ -1885,8 +1907,8 @@
},
"selfHosted": {
"title": "자체 호스팅 동기화 서버",
"description": "호스팅 클라우드를 사용하지 않고 프로필, 프록시, 그룹 및 확장 프로그램을 동기화하려면 도넛을 자체 donut-sync 서버로 연결하세요.",
"disabledWhileLoggedIn": "도넛 계정에 로그인되어 있는 동안에는 자체 호스팅 동기화를 사용할 수 없습니다. 사용자 지정 서버를 사용하려면 로그아웃하세요.",
"description": "호스팅 클라우드를 사용하지 않고 프로필, 프록시, 그룹 및 확장 프로그램을 동기화하려면 Donut을 자체 donut-sync 서버로 연결하세요.",
"disabledWhileLoggedIn": "Donut 계정에 로그인되어 있는 동안에는 자체 호스팅 동기화를 사용할 수 없습니다. 사용자 지정 서버를 사용하려면 로그아웃하세요.",
"connectionStatus": "연결:",
"statusUnknown": "테스트 안 됨",
"testConnection": "연결 테스트",
@@ -2017,5 +2039,9 @@
"trialBadge": "2주 무료",
"commercialDesc": "2주간의 평가 기간 동안 무료입니다. 이후에는 유료 요금제가 필요하며, 이를 통해 프로젝트가 유지되고 발전할 수 있습니다."
}
},
"wayfernBlocked": {
"title": "브라우저 자동화가 일시 중지됨",
"description": "보통 여러 기기에서 동시에 로그인하여 계정의 Pro 브라우저 기능이 일시적으로 제한되었습니다. 다른 기기에서 로그아웃한 후 프로필을 다시 실행하면 복원됩니다."
}
}
+30 -4
View File
@@ -239,7 +239,11 @@
"extDefault": "Padrão",
"dnsLevel": "Lista DNS: {{level}}",
"extSearch": "Pesquisar grupos…",
"extEmpty": "Sem grupos de extensões"
"extEmpty": "Sem grupos de extensões",
"cantModifyRunning": "Não é possível modificar um perfil em execução",
"cantModifyLaunching": "Não é possível modificar o perfil durante a inicialização",
"cantModifyStopping": "Não é possível modificar o perfil durante a parada",
"cantModifyUpdating": "Não é possível modificar o perfil enquanto o navegador é atualizado"
},
"actions": {
"launch": "Iniciar",
@@ -639,7 +643,11 @@
"profileSynced": "Perfil '{{name}}' sincronizado com sucesso",
"profileSyncFailed": "Falha ao sincronizar o perfil '{{name}}'",
"profileSyncFailedWithError": "Falha ao sincronizar o perfil '{{name}}': {{error}}"
}
},
"enabledToast": "Sincronização ativada",
"disabledToast": "Sincronização desativada",
"enabledDescription": "A sincronização do perfil foi ativada",
"disabledDescription": "A sincronização do perfil foi desativada"
},
"integrations": {
"title": "Integrações",
@@ -918,6 +926,11 @@
"syncingProfile": "Sincronizando perfil '{{name}}'...",
"syncingProfileWithProgress": "{{count}} arquivos ({{size}})",
"updatingVersions": "Atualizando versões de navegadores..."
},
"progress": {
"remaining": "{{time}} restante",
"filesProgress": "{{completed}}/{{total}} arquivos",
"filesFailed": "{{count}} arquivo(s) com falha"
}
},
"errors": {
@@ -1136,7 +1149,9 @@
"addRule": "Adicionar Regra",
"rulePlaceholder": "ex. example.com, 192.168.1.*, .*\\.local",
"noRules": "Nenhuma regra de bypass configurada.",
"ruleTypes": "Suporta nomes de host, endereços IP e padrões regex."
"ruleTypes": "Suporta nomes de host, endereços IP e padrões regex.",
"vpnLabel": "VPN: {{name}}",
"proxyLabel": "Proxy: {{name}}"
},
"launchHook": {
"title": "URL do hook de inicialização",
@@ -1198,6 +1213,12 @@
"notSupported": "A edição de impressão digital só está disponível para perfis Camoufox e Wayfern.",
"lockedTitle": "Visualizar e editar a impressão digital é um recurso Pro",
"lockedDescription": "A proteção contra fingerprint está incluída em todos os planos. Visualizar e editar os valores da impressão digital de um perfil é o que requer um plano pago ativo."
},
"syncStatusValue": {
"waiting": "Aguardando",
"syncing": "Sincronizando",
"synced": "Sincronizado",
"error": "Erro"
}
},
"extensions": {
@@ -1718,7 +1739,8 @@
"updateStartedDescription": "O download da versão {{version}} começará em breve. O início do navegador está desativado até a atualização ser concluída.",
"downloadStarting": "Iniciando o download do {{browser}} {{version}}",
"downloadProgressBelow": "O progresso do download será mostrado abaixo...",
"autoDownloadStarted": "Baixando {{browser}} {{version}} automaticamente. O progresso será mostrado abaixo."
"autoDownloadStarted": "Baixando {{browser}} {{version}} automaticamente. O progresso será mostrado abaixo.",
"lookingForUpdates": "Procurando atualizações para {{browser}}"
}
},
"profilePassword": {
@@ -2017,5 +2039,9 @@
"trialBadge": "2 semanas grátis",
"commercialDesc": "Gratuito durante uma avaliação de 2 semanas. Depois, um plano pago mantém o projeto ativo e próspero."
}
},
"wayfernBlocked": {
"title": "Automação do navegador pausada",
"description": "Sua conta foi temporariamente restringida dos recursos Pro do navegador, geralmente por entrar em vários dispositivos ao mesmo tempo. Saia dos outros dispositivos e reinicie o perfil para restaurá-la."
}
}
+30 -4
View File
@@ -239,7 +239,11 @@
"extDefault": "По умолч.",
"dnsLevel": "DNS-блок-лист: {{level}}",
"extSearch": "Поиск групп…",
"extEmpty": "Нет групп расширений"
"extEmpty": "Нет групп расширений",
"cantModifyRunning": "Нельзя изменить запущенный профиль",
"cantModifyLaunching": "Нельзя изменить профиль во время запуска",
"cantModifyStopping": "Нельзя изменить профиль во время остановки",
"cantModifyUpdating": "Нельзя изменить профиль во время обновления браузера"
},
"actions": {
"launch": "Запустить",
@@ -639,7 +643,11 @@
"profileSynced": "Профиль '{{name}}' успешно синхронизирован",
"profileSyncFailed": "Не удалось синхронизировать профиль '{{name}}'",
"profileSyncFailedWithError": "Не удалось синхронизировать профиль '{{name}}': {{error}}"
}
},
"enabledToast": "Синхронизация включена",
"disabledToast": "Синхронизация отключена",
"enabledDescription": "Синхронизация профиля включена",
"disabledDescription": "Синхронизация профиля отключена"
},
"integrations": {
"title": "Интеграции",
@@ -918,6 +926,11 @@
"syncingProfile": "Синхронизация профиля '{{name}}'...",
"syncingProfileWithProgress": "{{count}} файлов ({{size}})",
"updatingVersions": "Обновление версий браузеров..."
},
"progress": {
"remaining": "осталось {{time}}",
"filesProgress": "{{completed}}/{{total}} файлов",
"filesFailed": "Ошибка в {{count}} файле(ах)"
}
},
"errors": {
@@ -1136,7 +1149,9 @@
"addRule": "Добавить правило",
"rulePlaceholder": "напр. example.com, 192.168.1.*, .*\\.local",
"noRules": "Правила обхода не настроены.",
"ruleTypes": "Поддерживает имена хостов, IP-адреса и шаблоны регулярных выражений."
"ruleTypes": "Поддерживает имена хостов, IP-адреса и шаблоны регулярных выражений.",
"vpnLabel": "VPN: {{name}}",
"proxyLabel": "Proxy: {{name}}"
},
"launchHook": {
"title": "URL хука запуска",
@@ -1198,6 +1213,12 @@
"notSupported": "Редактирование отпечатков доступно только для профилей Camoufox и Wayfern.",
"lockedTitle": "Просмотр и редактирование отпечатка — функция Pro",
"lockedDescription": "Защита от отпечатков включена во все планы. Активный платный план требуется именно для просмотра и редактирования значений отпечатка профиля."
},
"syncStatusValue": {
"waiting": "Ожидание",
"syncing": "Синхронизация",
"synced": "Синхронизировано",
"error": "Ошибка"
}
},
"extensions": {
@@ -1718,7 +1739,8 @@
"updateStartedDescription": "Загрузка версии {{version}} скоро начнётся. Запуск браузера отключён до завершения обновления.",
"downloadStarting": "Запуск загрузки {{browser}} {{version}}",
"downloadProgressBelow": "Прогресс загрузки будет показан ниже...",
"autoDownloadStarted": "Автоматическая загрузка {{browser}} {{version}}. Прогресс будет показан ниже."
"autoDownloadStarted": "Автоматическая загрузка {{browser}} {{version}}. Прогресс будет показан ниже.",
"lookingForUpdates": "Поиск обновлений для {{browser}}"
}
},
"profilePassword": {
@@ -2017,5 +2039,9 @@
"trialBadge": "2 недели бесплатно",
"commercialDesc": "Бесплатно в течение 2-недельного ознакомительного периода. После этого требуется платный план, что помогает поддерживать и развивать проект."
}
},
"wayfernBlocked": {
"title": "Автоматизация браузера приостановлена",
"description": "Доступ вашей учётной записи к Pro-функциям браузера временно ограничен — обычно из-за входа сразу на нескольких устройствах. Выйдите из аккаунта на других устройствах и перезапустите профиль, чтобы восстановить доступ."
}
}
+30 -4
View File
@@ -239,7 +239,11 @@
"extDefault": "Mặc định",
"dnsLevel": "Danh sách chặn DNS: {{level}}",
"extSearch": "Tìm kiếm nhóm…",
"extEmpty": "Không có nhóm tiện ích"
"extEmpty": "Không có nhóm tiện ích",
"cantModifyRunning": "Không thể chỉnh sửa profile đang chạy",
"cantModifyLaunching": "Không thể chỉnh sửa profile khi đang khởi chạy",
"cantModifyStopping": "Không thể chỉnh sửa profile khi đang dừng",
"cantModifyUpdating": "Không thể chỉnh sửa profile khi trình duyệt đang cập nhật"
},
"actions": {
"launch": "Khởi chạy",
@@ -639,7 +643,11 @@
"profileSynced": "Đã đồng bộ profile '{{name}}' thành công",
"profileSyncFailed": "Đồng bộ profile '{{name}}' thất bại",
"profileSyncFailedWithError": "Đồng bộ profile '{{name}}' thất bại: {{error}}"
}
},
"enabledToast": "Đã bật đồng bộ",
"disabledToast": "Đã tắt đồng bộ",
"enabledDescription": "Đồng bộ profile đã được bật",
"disabledDescription": "Đồng bộ profile đã được tắt"
},
"integrations": {
"title": "Tích hợp",
@@ -918,6 +926,11 @@
"syncingProfile": "Đang đồng bộ profile '{{name}}'...",
"syncingProfileWithProgress": "{{count}} tệp ({{size}})",
"updatingVersions": "Đang cập nhật phiên bản trình duyệt..."
},
"progress": {
"remaining": "còn {{time}}",
"filesProgress": "{{completed}}/{{total}} tệp",
"filesFailed": "{{count}} tệp thất bại"
}
},
"errors": {
@@ -1136,7 +1149,9 @@
"addRule": "Thêm quy tắc",
"rulePlaceholder": "ví dụ: example.com, 192.168.1.*, .*\\.local",
"noRules": "Chưa cấu hình quy tắc bỏ qua nào.",
"ruleTypes": "Hỗ trợ tên máy chủ, địa chỉ IP và biểu thức chính quy."
"ruleTypes": "Hỗ trợ tên máy chủ, địa chỉ IP và biểu thức chính quy.",
"vpnLabel": "VPN: {{name}}",
"proxyLabel": "Proxy: {{name}}"
},
"launchHook": {
"title": "URL hook khởi chạy",
@@ -1198,6 +1213,12 @@
"notSupported": "Chỉnh sửa vân tay chỉ khả dụng cho profile Camoufox và Wayfern.",
"lockedTitle": "Xem và chỉnh sửa vân tay là tính năng Pro",
"lockedDescription": "Bảo vệ vân tay được bao gồm trong mọi gói. Việc xem và chỉnh sửa các giá trị vân tay của profile mới là phần yêu cầu gói trả phí đang hoạt động."
},
"syncStatusValue": {
"waiting": "Đang chờ",
"syncing": "Đang đồng bộ",
"synced": "Đã đồng bộ",
"error": "Lỗi"
}
},
"extensions": {
@@ -1718,7 +1739,8 @@
"updateStartedDescription": "Quá trình tải phiên bản {{version}} sẽ sớm bắt đầu. Việc khởi chạy trình duyệt bị tắt cho đến khi cập nhật hoàn tất.",
"downloadStarting": "Đang bắt đầu tải {{browser}} {{version}}",
"downloadProgressBelow": "Tiến trình tải sẽ hiển thị bên dưới...",
"autoDownloadStarted": "Đang tự động tải {{browser}} {{version}}. Tiến trình sẽ hiển thị bên dưới."
"autoDownloadStarted": "Đang tự động tải {{browser}} {{version}}. Tiến trình sẽ hiển thị bên dưới.",
"lookingForUpdates": "Đang tìm bản cập nhật cho {{browser}}"
}
},
"profilePassword": {
@@ -2017,5 +2039,9 @@
"trialBadge": "2 tuần miễn phí",
"commercialDesc": "Miễn phí dùng thử 2 tuần. Sau đó, gói trả phí giúp dự án được duy trì và phát triển."
}
},
"wayfernBlocked": {
"title": "Tự động hóa trình duyệt đã tạm dừng",
"description": "Tài khoản của bạn tạm thời bị hạn chế các tính năng Pro của trình duyệt, thường do đăng nhập trên nhiều thiết bị cùng lúc. Hãy đăng xuất khỏi các thiết bị khác rồi khởi chạy lại profile để khôi phục."
}
}
+30 -4
View File
@@ -239,7 +239,11 @@
"extDefault": "默认",
"dnsLevel": "DNS 屏蔽列表: {{level}}",
"extSearch": "搜索分组…",
"extEmpty": "没有扩展组"
"extEmpty": "没有扩展组",
"cantModifyRunning": "无法修改正在运行的配置文件",
"cantModifyLaunching": "启动期间无法修改配置文件",
"cantModifyStopping": "停止期间无法修改配置文件",
"cantModifyUpdating": "浏览器更新期间无法修改配置文件"
},
"actions": {
"launch": "启动",
@@ -639,7 +643,11 @@
"profileSynced": "配置文件 '{{name}}' 同步成功",
"profileSyncFailed": "同步配置文件 '{{name}}' 失败",
"profileSyncFailedWithError": "同步配置文件 '{{name}}' 失败: {{error}}"
}
},
"enabledToast": "已启用同步",
"disabledToast": "已禁用同步",
"enabledDescription": "已启用配置文件同步",
"disabledDescription": "已禁用配置文件同步"
},
"integrations": {
"title": "集成",
@@ -918,6 +926,11 @@
"syncingProfile": "正在同步配置文件 '{{name}}'...",
"syncingProfileWithProgress": "{{count}} 个文件 ({{size}})",
"updatingVersions": "正在更新浏览器版本..."
},
"progress": {
"remaining": "剩余 {{time}}",
"filesProgress": "{{completed}}/{{total}} 个文件",
"filesFailed": "{{count}} 个文件失败"
}
},
"errors": {
@@ -1136,7 +1149,9 @@
"addRule": "添加规则",
"rulePlaceholder": "例如 example.com, 192.168.1.*, .*\\.local",
"noRules": "未配置绕过规则。",
"ruleTypes": "支持主机名、IP地址和正则表达式模式。"
"ruleTypes": "支持主机名、IP地址和正则表达式模式。",
"vpnLabel": "VPN{{name}}",
"proxyLabel": "代理:{{name}}"
},
"launchHook": {
"title": "启动钩子 URL",
@@ -1198,6 +1213,12 @@
"notSupported": "指纹编辑仅适用于 Camoufox 和 Wayfern 配置文件。",
"lockedTitle": "查看和编辑指纹是 Pro 功能",
"lockedDescription": "所有方案都包含指纹保护。查看和编辑配置文件的指纹数值才需要有效的付费方案。"
},
"syncStatusValue": {
"waiting": "等待中",
"syncing": "同步中",
"synced": "已同步",
"error": "错误"
}
},
"extensions": {
@@ -1718,7 +1739,8 @@
"updateStartedDescription": "版本 {{version}} 即将开始下载。更新完成前浏览器启动将被禁用。",
"downloadStarting": "正在开始下载 {{browser}} {{version}}",
"downloadProgressBelow": "下载进度将显示在下方...",
"autoDownloadStarted": "正在自动下载 {{browser}} {{version}}。进度将显示在下方。"
"autoDownloadStarted": "正在自动下载 {{browser}} {{version}}。进度将显示在下方。",
"lookingForUpdates": "正在检查 {{browser}} 的更新"
}
},
"profilePassword": {
@@ -2017,5 +2039,9 @@
"trialBadge": "2 周免费",
"commercialDesc": "在 2 周评估期内免费。之后需要付费方案,这有助于本项目的持续维护与发展。"
}
},
"wayfernBlocked": {
"title": "浏览器自动化已暂停",
"description": "您的账户暂时被限制使用 Pro 浏览器功能,通常是因为同时在多台设备上登录。请退出其他设备的登录,然后重新启动配置文件即可恢复。"
}
}
+17 -24
View File
@@ -2,6 +2,7 @@ import { invoke } from "@tauri-apps/api/core";
import React from "react";
import { type ExternalToast, toast as sonnerToast } from "sonner";
import { UnifiedToast } from "@/components/custom-toast";
import i18n from "@/i18n";
interface BaseToastProps {
id?: string;
@@ -29,12 +30,7 @@ interface ErrorToastProps extends BaseToastProps {
interface DownloadToastProps extends BaseToastProps {
type: "download";
stage?:
| "downloading"
| "extracting"
| "verifying"
| "completed"
| "downloading (twilight rolling release)";
stage?: "downloading" | "extracting" | "verifying" | "completed";
progress?: {
percentage: number;
speed?: string;
@@ -159,25 +155,19 @@ export function showToast(props: ToastProps & { id?: string }) {
export function showDownloadToast(
browserName: string,
version: string,
stage:
| "downloading"
| "extracting"
| "verifying"
| "completed"
| "downloading (twilight rolling release)",
stage: "downloading" | "extracting" | "verifying" | "completed",
progress?: { percentage: number; speed?: string; eta?: string },
options?: { suppressCompletionToast?: boolean; onCancel?: () => void },
) {
const tParams = { browser: browserName, version };
const title =
stage === "completed"
? `${browserName} ${version} downloaded successfully!`
? i18n.t("toasts.success.downloadComplete", tParams)
: stage === "downloading"
? `Downloading ${browserName} ${version}`
? i18n.t("toasts.loading.downloading", tParams)
: stage === "extracting"
? `Extracting ${browserName} ${version}`
: stage === "downloading (twilight rolling release)"
? `Downloading ${browserName} ${version}`
: `Verifying ${browserName} ${version}`;
? i18n.t("toasts.loading.extracting", tParams)
: i18n.t("toasts.loading.verifying", tParams);
// Don't show completion toast if suppressed (for auto-update scenarios)
if (stage === "completed" && options?.suppressCompletionToast) {
@@ -186,9 +176,7 @@ export function showDownloadToast(
}
// Only show cancel button during active downloading, not for completed/extracting/verifying
const showCancel =
stage === "downloading" ||
stage === "downloading (twilight rolling release)";
const showCancel = stage === "downloading";
return showToast({
type: "download",
@@ -241,10 +229,15 @@ export function showAutoUpdateToast(
) {
return showToast({
type: "loading",
title: `${browserName} update started`,
title: i18n.t("versionUpdater.toast.updateStarted", {
browser: browserName,
}),
description:
options?.description ??
`Automatically downloading ${browserName} ${version}. Progress will be shown in download notifications.`,
i18n.t("versionUpdater.toast.autoDownloadStarted", {
browser: browserName,
version,
}),
id: options?.id ?? `auto-update-${browserName.toLowerCase()}-${version}`,
duration: options?.duration ?? 4000,
});
@@ -270,7 +263,7 @@ export function showSyncProgressToast(
) {
return showToast({
type: "sync-progress",
title: `Syncing profile '${profileName}'...`,
title: i18n.t("toasts.loading.syncingProfile", { name: profileName }),
progress,
id: options?.id,
duration: Number.POSITIVE_INFINITY,